diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 000000000..9771255f3 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,151 @@ +version: '{build}' +branches: + only: + - master + +image: Visual Studio 2017 + +environment: + global: + # Appveyor doesn't have Qt 12 yet + LatestQtVersion: 5.13 + matrix: +# 32 bit builds +# MSVC 2015 builds +# Dynamic Library builds + # LTS version of Qt, dll, 32bit, MSVC 2015, qmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: x86 + use_mingw: "false" + use_static: "false" + use_cmake: "false" + # LTS version of Qt, dll, 32bit, MSVC 2015, cmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: x86 + use_mingw: "false" + use_static: "false" + use_cmake: "true" +# end Dynamic Library builds +# Static Library builds + # LTS version of Qt, static, 32bit, MSVC 2015, qmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: x86 + use_mingw: "false" + use_static: "true" + use_cmake: "false" + # LTS version of Qt, static, 32bit, MSVC 2015, cmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: x86 + use_mingw: "false" + use_static: "true" + use_cmake: "true" +# end Static Library builds +# end MSVC 2015 builds +# MinGW builds +# Dynamic Library builds + # LTS version of Qt, dll, 32bit, MinGW, qmake + - QT5: C:\Qt\%LatestQtVersion%\mingw73_32 + COMPILER: C:\Qt\Tools\mingw730_32 + targetPlatform: x86 + use_mingw: "true" + use_static: "false" + use_cmake: "false" + # LTS version of Qt, dll, 32bit, MinGW, cmake + - QT5: C:\Qt\%LatestQtVersion%\mingw73_32 + COMPILER: C:\Qt\Tools\mingw730_32 + targetPlatform: x86 + use_mingw: "true" + use_static: "false" + use_cmake: "true" +# end Dynamic Library builds +# Static Library builds + # LTS version of Qt, static, 32bit, MinGW, qmake + - QT5: C:\Qt\%LatestQtVersion%\mingw73_32 + COMPILER: C:\Qt\Tools\mingw730_32 + targetPlatform: x86 + use_mingw: "true" + use_static: "true" + use_cmake: "false" + # LTS version of Qt, static, 32bit, MinGW, cmake + - QT5: C:\Qt\%LatestQtVersion%\mingw73_32 + COMPILER: C:\Qt\Tools\mingw730_32 + targetPlatform: x86 + use_mingw: "true" + use_static: "true" + use_cmake: "true" +# end Static Library builds +# end MinGW builds +# end 32 bit builds +# 64 bit builds +# MSVC 2015 builds +# Dynamic Library builds + # LTS version of Qt, dll, 64bit, MSVC 2015, qmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017_64 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: amd64 + use_mingw: "false" + use_static: "false" + use_cmake: "false" + # LTS version of Qt, dll, 64bit, MSVC 2015, cmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017_64 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: amd64 + use_mingw: "false" + use_static: "false" + use_cmake: "true" +# end Dynamic Library builds +# Static Library builds + # LTS version of Qt, static, 64bit, MSVC 2015, qmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017_64 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: amd64 + use_mingw: "false" + use_static: "true" + use_cmake: "false" + # LTS version of Qt, static, 64bit, MSVC 2015, cmake + - QT5: C:\Qt\%LatestQtVersion%\msvc2017_64 + COMPILER: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build + targetPlatform: amd64 + use_mingw: "false" + use_static: "true" + use_cmake: "true" +# end Static Library builds +# end MSVC 2015 builds +# end 64 bit builds + +matrix: + fast_finish: true + +before_build: +- set originalWD=%CD% +- call "%QT5%\bin\qtenv2.bat" +- cd /D %originalWD% +- if %use_mingw%==false call "%COMPILER%\vcvarsall.bat" %targetPlatform% +- if %use_static%==true (set USESTATIC=ON) else (set USESTATIC=OFF) +- if %use_mingw%==true (set CMAKEGENERATOR="MinGW Makefiles") else (set CMAKEGENERATOR="NMake Makefiles") +- if %use_mingw%==true (set MAKEENGINE=mingw32-make) else (set MAKEENGINE=nmake) +- if %use_mingw%==true set PATH=%PATH:C:\Program Files\Git\usr\bin;=% + +build_script: +- if %use_cmake%==true mkdir build +- if %use_cmake%==true cd build +- if %use_cmake%==true cmake --version +- if %use_cmake%==true cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DBUILD_STATIC=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" ../ +- if %use_cmake%==true cmake --build . +- if %use_cmake%==true cmake --build . --target install +- if %use_cmake%==true cmake -G %CMAKEGENERATOR% -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=%USESTATIC% -DCMAKE_INSTALL_PREFIX="./installed" ../ +- if %use_cmake%==true cmake --build . +- if %use_cmake%==true cmake --build . --target install +- if %use_cmake%==false if %use_static%==true qmake "CONFIG+=adsBuildStatic" +- if %use_cmake%==false if %use_static%==false qmake +- if %use_cmake%==false %MAKEENGINE% debug +- if %use_cmake%==false %MAKEENGINE% install +- if %use_cmake%==false %MAKEENGINE% release +- if %use_cmake%==false %MAKEENGINE% install + +after_build: +- if %use_mingw%==true set PATH=C:\Program Files\Git\usr\bin;%PATH% diff --git a/.astylerc b/.astylerc deleted file mode 100644 index 6fb629c0c..000000000 --- a/.astylerc +++ /dev/null @@ -1,14 +0,0 @@ ---style=allman - ---indent=force-tab=4 - ---align-pointer=type ---align-reference=type - ---pad-oper ---pad-header ---unpad-paren - ---remove-comment-prefix - ---mode=c diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..f044fe5d7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,146 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true + +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: false + +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes + +BinPackArguments: true +BinPackParameters: true + +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: AfterColon +BreakStringLiterals: true + +ColumnLimit: 82 +CommentPragmas: '^(!.*|@c)' + +CompactNamespaces: false + +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true + +FixNamespaceComments: true + +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' # windows headers + Priority: -1 + - Regex: '^' # Qt headers + Priority: 3 + - Regex: '^' # other headers + Priority: 5 + - Regex: '<[[:alnum:]._/]+>' # system headers + Priority: 6 + - Regex: '.*' + Priority: 7 + +IncludeIsMainRegex: '(_p)?$' + +IndentCaseBlocks: false +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false + +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 + +NamespaceIndentation: None + +PenaltyBreakAssignment: 20 +PenaltyBreakBeforeFirstCallParameter: 15 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 5 +# PenaltyBreakOpenParenthesis: 30 +PenaltyBreakString: 150 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 100 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 150 + +PointerAlignment: Left +ReflowComments: true + +SortIncludes: CaseSensitive +SortUsingDeclarations: true + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false + +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false + +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +Standard: c++17 + +StatementMacros: [ 'Q_UNUSED', 'PIMPL_D', 'PIMPL_Q', 'OD_ENTRY', 'OD_ENTRY_PROCIMG' ] + +TabWidth: 4 +UseTab: Never diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..f75bc5b72 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +PyQtAds/_version.py export-subst diff --git a/.github/workflows/linux-builds.yml b/.github/workflows/linux-builds.yml new file mode 100644 index 000000000..9c0ef5cb5 --- /dev/null +++ b/.github/workflows/linux-builds.yml @@ -0,0 +1,59 @@ +name: linux-builds + +on: [push] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-20.04] + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v1 + - name: install qt + run: | + sudo apt-get update --fix-missing + sudo apt-get install qt5-default + sudo apt-get install qtbase5-private-dev qtdeclarative5-dev + - name: qmake + run: qmake + - name: make + run: make -j4 + + build_ubuntu_2204: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + - name: Cache Qt + id: cache-qt-6-4 + uses: actions/cache@v1 # not v2! + with: + path: ../Qt/6.4.2 + key: ${{ runner.os }}-QtCache-Qt6-4 + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '6.4.2' + host: 'linux' + target: 'desktop' + install-deps: true + cached: ${{ steps.cache-qt-6-4.outputs.cache-hit }} + setup-python: true + tools: '' + tools-only: false + + - name: Install needed xkbcommon symlink + run: sudo apt-get install libxkbcommon-dev -y + + - name: Ubuntu and Qt version + run: | + cat /etc/issue + echo number of processors: $(nproc) + qmake -v + - name: qmake + run: qmake + - name: make + run: make -j$(nproc) diff --git a/.gitignore b/.gitignore index 75c107bcc..2a7ed562d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,389 @@ -*.pro.user +*.pro.user* +/build +*.o +*.dylib +*.app +qrc_* +moc_* +ui_* +Makefile +*.dll +*.a +build* + +# IDEs +.idea + +# Python +.eggs +*.pyc +*.pyd +__pycache__ +PyQtAds/rc.py +/.cproject + + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*[.json, .xml, .info] + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd +/ build +/Settings.ini +.vscode/settings.json +/.settings diff --git a/.project b/.project new file mode 100644 index 000000000..9c7157bd5 --- /dev/null +++ b/.project @@ -0,0 +1,27 @@ + + + QtAdvancedDockingSystem + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/.travis.yml b/.travis.yml index 51eec4ef9..38db61fc9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,233 @@ -language: - - cpp +language: cpp +# gcc is clang on mac +compiler: gcc -compiler: - - g++ - -addons: - apt: +matrix: + fast_finish: true + include: + - name: Ubuntu qmake Qt5.5.1 + os: linux + dist: trusty + group: stable + addons: + apt: sources: - - ubuntu-sdk-team + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt551-trusty' + update: true packages: - - qt5-qmake - - qtbase5-dev - - qtdeclarative5-dev - - libqt5webkit5-dev - - libsqlite3-dev - -script: - - qmake -qt=qt5 -v - - qmake -qt=qt5 -r build.pro - - make + - qt55base + - qt55tools + - gcc-9 + - g++-9 + script: + - PATH="/opt/qt55/bin:$PATH" + - CXX="g++-9" + - CC="gcc-9" + - qt55-env.sh + - qmake + - make + - make install + - name: Ubuntu qmake dll + os: linux + dist: bionic + group: stable + services: + - xvfb + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic' + update: true + packages: + - qt514base + - qt514tools + - gcc-9 + - g++-9 + - libc6-i386 + - libgl-dev + - libgl1-mesa-dev + - mesa-common-dev + script: + - PATH="/opt/qt514/bin:$PATH" + - CXX="g++-9" + - CC="gcc-9" + - qt514-env.sh + - qmake + - make + - make install + - name: Ubuntu qmake static + os: linux + dist: bionic + group: stable + services: + - xvfb + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic' + update: true + packages: + - qt514base + - qt514tools + - gcc-9 + - g++-9 + - libc6-i386 + - libgl-dev + - libgl1-mesa-dev + - mesa-common-dev + script: + - PATH="/opt/qt514/bin:$PATH" + - CXX="g++-9" + - CC="gcc-9" + - qt514-env.sh + - qmake "CONFIG+=adsBuildStatic" + - make + - make install + - name: Ubuntu CMake dll + os: linux + dist: bionic + group: stable + services: + - xvfb + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic' + update: true + packages: + - qt514base + - qt514tools + - gcc-9 + - g++-9 + - libc6-i386 + - libgl-dev + - libgl1-mesa-dev + - mesa-common-dev + script: + - PATH="/opt/qt514/bin:$PATH" + - CXX="g++-9" + - CC="gcc-9" + - qt514-env.sh + - mkdir ./build + - cd ./build + - cmake --version + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_STATIC=OFF -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_STATIC=OFF -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - name: Ubuntu CMake Static + os: linux + dist: bionic + group: stable + services: + - xvfb + compiler: gcc + addons: + apt: + sources: + - ubuntu-toolchain-r-test + - sourceline: 'ppa:beineri/opt-qt-5.14.2-bionic' + update: true + packages: + - qt514base + - qt514tools + - gcc-9 + - g++-9 + - libc6-i386 + - libgl-dev + - libgl1-mesa-dev + - mesa-common-dev + script: + - PATH="/opt/qt514/bin:$PATH" + - CXX="g++-9" + - CC="gcc-9" + - qt514-env.sh + - mkdir ./build + - cd ./build + - cmake --version + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_STATIC=ON -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DBUILD_STATIC=ON -DBUILD_EXAMPLES=ON -DCMAKE_DEBUG_POSTFIX=d -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - name: macOS CMake dll + os: osx + osx_image: xcode11.3 + compiler: clang + addons: + homebrew: + packages: + - qt + update: true + script: + - PATH="/usr/local/opt/qt5/bin:$PATH" + - mkdir -p build + - cd build + - cmake --version + - cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=_debug -DBUILD_EXAMPLES=ON -DBUILD_STATIC=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=OFF -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - name: macOS CMake static + os: osx + osx_image: xcode11.3 + compiler: clang + addons: + homebrew: + packages: + - qt + update: true + script: + - PATH="/usr/local/opt/qt5/bin:$PATH" + - mkdir -p build + - cd build + - cmake --version + - cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_DEBUG_POSTFIX=_debug -DBUILD_EXAMPLES=ON -DBUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=RELEASE -DBUILD_EXAMPLES=ON -DBUILD_STATIC=ON -DCMAKE_INSTALL_PREFIX="./installed" ../ + - cmake --build . + - cmake --build . --target install + - name: macOS qmake dll + os: osx + osx_image: xcode11.3 + compiler: clang + addons: + homebrew: + packages: + - qt + update: true + script: + - PATH="/usr/local/opt/qt5/bin:$PATH" + - qmake + - make + - make install + - name: macOS qmake static + os: osx + osx_image: xcode11.3 + compiler: clang + addons: + homebrew: + packages: + - qt + update: true + script: + - PATH="/usr/local/opt/qt5/bin:$PATH" + - qmake "CONFIG+=adsBuildStatic" + - make + - make install +notifications: + email: false -#- sudo apt-get install -qq qtbase5-dev qtdeclarative5-dev libqt5webkit5-dev libsqlite3-dev -#- sudo apt-get install -qq qt5-default qttools5-dev-tools diff --git a/AdvancedDockingSystem/AdvancedDockingSystem.pri b/AdvancedDockingSystem/AdvancedDockingSystem.pri deleted file mode 100644 index 76e9016af..000000000 --- a/AdvancedDockingSystem/AdvancedDockingSystem.pri +++ /dev/null @@ -1,24 +0,0 @@ - -SOURCES += \ - $$PWD/src/API.cpp \ - $$PWD/src/ContainerWidget.cpp \ - $$PWD/src/SectionWidget.cpp \ - $$PWD/src/SectionContent.cpp \ - $$PWD/src/SectionTitleWidget.cpp \ - $$PWD/src/SectionContentWidget.cpp \ - $$PWD/src/DropOverlay.cpp \ - $$PWD/src/FloatingWidget.cpp \ - $$PWD/src/Internal.cpp \ - $$PWD/src/Serialization.cpp - -HEADERS += \ - $$PWD/include/ads/API.h \ - $$PWD/include/ads/ContainerWidget.h \ - $$PWD/include/ads/SectionWidget.h \ - $$PWD/include/ads/SectionContent.h \ - $$PWD/include/ads/SectionTitleWidget.h \ - $$PWD/include/ads/SectionContentWidget.h \ - $$PWD/include/ads/DropOverlay.h \ - $$PWD/include/ads/FloatingWidget.h \ - $$PWD/include/ads/Internal.h \ - $$PWD/include/ads/Serialization.h diff --git a/AdvancedDockingSystem/AdvancedDockingSystem.pro b/AdvancedDockingSystem/AdvancedDockingSystem.pro deleted file mode 100644 index 8c5c12ca8..000000000 --- a/AdvancedDockingSystem/AdvancedDockingSystem.pro +++ /dev/null @@ -1,35 +0,0 @@ -TARGET = AdvancedDockingSystem -TEMPLATE = lib -VERSION = 1.0.0 - -CONFIG += adsBuildShared - -QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -greaterThan(QT_MAJOR_VERSION, 4): DEFINES += ADS_NAMESPACE_ENABLED - -adsBuildShared { - CONFIG += shared - DEFINES += ADS_EXPORT -} -!adsBuildShared { - CONFIG += staticlib -} - -INCLUDEPATH += $$PWD/include - -windows { - # MinGW - *-g++* { - QMAKE_CXXFLAGS += -std=c++11 - QMAKE_CXXFLAGS += -Wall -Wextra -pedantic - } - # MSVC - *-msvc* { - } -} - -RESOURCES += \ - res/ads.qrc - -include(AdvancedDockingSystem.pri) diff --git a/AdvancedDockingSystem/include/ads/API.h b/AdvancedDockingSystem/include/ads/API.h deleted file mode 100644 index e290c9043..000000000 --- a/AdvancedDockingSystem/include/ads/API.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef ADS_API_H -#define ADS_API_H - -#include -class QWidget; -class QSplitter; - -// DLL Export API -#ifdef _WIN32 - #if defined(ADS_IMPORT) - #define ADS_EXPORT_API - #elif defined(ADS_EXPORT) - #define ADS_EXPORT_API __declspec(dllexport) - #else - #define ADS_EXPORT_API __declspec(dllimport) - #endif -#else - #define ADS_EXPORT_API -#endif - -// Use namespace -// Disabled with Qt4, it makes problems with signals and slots. -#ifdef ADS_NAMESPACE_ENABLED - #define ADS_NAMESPACE_BEGIN namespace ads { - #define ADS_NAMESPACE_END } - #define ADS_NS ::ads -#else - #define ADS_NAMESPACE_BEGIN - #define ADS_NAMESPACE_END - #define ADS_NS -#endif - -// Always enable "serialization" namespace. -// It is not required for signals and slots. -#define ADS_NAMESPACE_SER_BEGIN namespace ads { namespace serialization { -#define ADS_NAMESPACE_SER_END }} -#define ADS_NS_SER ::ads::serialization - -// Width of the native window frame border (based on OS). -#define ADS_WINDOW_FRAME_BORDER_WIDTH 7 - -// Beautiful C++ stuff. -#define ADS_Expects(cond) -#define ADS_Ensures(cond) - -// Indicates whether ADS should include animations. -//#define ADS_ANIMATIONS_ENABLED 1 -//#define ADS_ANIMATION_DURATION 150 - -ADS_NAMESPACE_BEGIN -class ContainerWidget; -class SectionWidget; - -enum DropArea -{ - InvalidDropArea = 0, - TopDropArea = 1, - RightDropArea = 2, - BottomDropArea = 4, - LeftDropArea = 8, - CenterDropArea = 16, - - AllAreas = TopDropArea | RightDropArea | BottomDropArea | LeftDropArea | CenterDropArea -}; -Q_DECLARE_FLAGS(DropAreas, DropArea) - -void deleteEmptySplitter(ContainerWidget* container); -ContainerWidget* findParentContainerWidget(QWidget* w); -SectionWidget* findParentSectionWidget(QWidget* w); -QSplitter* findParentSplitter(QWidget* w); -QSplitter* findImmediateSplitter(QWidget* w); - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/ContainerWidget.h b/AdvancedDockingSystem/include/ads/ContainerWidget.h deleted file mode 100644 index a32bb724e..000000000 --- a/AdvancedDockingSystem/include/ads/ContainerWidget.h +++ /dev/null @@ -1,191 +0,0 @@ -#ifndef ADS_CONTAINERWIDGET_H -#define ADS_CONTAINERWIDGET_H - -#include -#include -#include -#include -class QPoint; -class QSplitter; -class QMenu; -class QGridLayout; - -#include "ads/API.h" -#include "ads/Internal.h" -#include "ads/SectionContent.h" -#include "ads/FloatingWidget.h" -#include "ads/Serialization.h" - -ADS_NAMESPACE_BEGIN -class SectionWidget; -class DropOverlay; -class InternalContentData; - - -/*! - * ContainerWidget is the main container to provide the docking - * functionality. It manages multiple sections with all possible areas. - */ -class ADS_EXPORT_API ContainerWidget : public QFrame -{ - Q_OBJECT - - friend class SectionContent; - friend class SectionWidget; - friend class FloatingWidget; - friend class SectionTitleWidget; - friend class SectionContentWidget; - -public: - explicit ContainerWidget(QWidget *parent = NULL); - virtual ~ContainerWidget(); - - // - // Public API - // - - /*! - * Adds the section-content sc to this container-widget into the section-widget sw. - * If sw is not NULL, the area is used to indicate how the content should be arranged. - * Returns a pointer to the SectionWidget of the added SectionContent. Do not use it for anything else than adding more - * SectionContent elements with this method. - */ - SectionWidget* addSectionContent(const SectionContent::RefPtr& sc, SectionWidget* sw = NULL, DropArea area = CenterDropArea); - - /*! - * Completely removes the sc from this ContainerWidget. - * This container will no longer hold a reference to the content. - * The content can be safely deleted. - */ - bool removeSectionContent(const SectionContent::RefPtr& sc); - - /*! - * Shows the specific SectionContent in UI. - * Independed of the current state, whether it is used inside a section or is floating. - */ - bool showSectionContent(const SectionContent::RefPtr& sc); - - /*! - * Closes the specified SectionContent from UI. - * Independed of the current state, whether it is used inside a section or is floating. - */ - bool hideSectionContent(const SectionContent::RefPtr& sc); - - /*! - * Selects the specific SectionContent as current, if it is part of a SectionWidget. - * If SC is floating, it does nothing (or should we show it?) - */ - bool raiseSectionContent(const SectionContent::RefPtr& sc); - - /*! - * Indicates whether the SectionContent sc is visible. - */ - bool isSectionContentVisible(const SectionContent::RefPtr& sc); - - /*! - * Creates a QMenu based on available SectionContents. - * The caller is responsible to delete the menu. - */ - QMenu* createContextMenu() const; - - /*! - * Serializes the current state of contents and returns it as a plain byte array. - * \see restoreState(const QByteArray&) - */ - QByteArray saveState() const; - - /*! - * Deserilizes the state of contents from data, which was written with saveState(). - * \see saveState() - */ - bool restoreState(const QByteArray& data); - - // - // Advanced Public API - // You usually should not need access to this methods - // - - // Outer DropAreas - QRect outerTopDropRect() const; - QRect outerRightDropRect() const; - QRect outerBottomDropRect() const; - QRect outerLeftDropRect() const; - - /*! - * \brief contents - * \return List of known SectionContent for this ContainerWidget. - */ - QList contents() const; - - QPointer dropOverlay() const; - -private: - // - // Internal Stuff Begins Here - // - - SectionWidget* newSectionWidget(); - SectionWidget* dropContent(const InternalContentData& data, SectionWidget* targetSection, DropArea area, bool autoActive = true); - void addSection(SectionWidget* section); - SectionWidget* sectionAt(const QPoint& pos) const; - SectionWidget* dropContentOuterHelper(QLayout* l, const InternalContentData& data, Qt::Orientation orientation, bool append); - - // Serialization - QByteArray saveHierarchy() const; - void saveFloatingWidgets(QDataStream& out) const; - void saveSectionWidgets(QDataStream& out, QWidget* widget) const; - - bool saveSectionIndex(ADS_NS_SER::SectionIndexData &sid) const; - - bool restoreHierarchy(const QByteArray& data); - bool restoreFloatingWidgets(QDataStream& in, int version, QList& floatings); - bool restoreSectionWidgets(QDataStream& in, int version, QSplitter* currentSplitter, QList& sections, QList& contentsToHide); - - bool takeContent(const SectionContent::RefPtr& sc, InternalContentData& data); - -private slots: - void onActiveTabChanged(); - void onActionToggleSectionContentVisibility(bool visible); - -signals: - void orientationChanged(); - - /*! - * Emits whenever the "isActiveTab" state of a SectionContent changes. - * Whenever the users sets another tab as active, this signal gets invoked - * for the old tab and the new active tab (the order is unspecified). - */ - void activeTabChanged(const SectionContent::RefPtr& sc, bool active); - - /*! - * Emits whenever the visibility of a SectionContent changes. - * \see showSectionContent(), hideSectionContent() - * \since 0.2 - */ - void sectionContentVisibilityChanged(const SectionContent::RefPtr& sc, bool visible); - -private: - // Elements inside container. - QList _sections; - QList _floatings; - QHash _hiddenSectionContents; - - - // Helper lookup maps, restricted to this container. - QHash _scLookupMapById; - QHash _scLookupMapByName; - QHash _swLookupMapById; - - - // Layout stuff - QGridLayout* _mainLayout; - Qt::Orientation _orientation; - QPointer _splitter; // $mfreiholz: I'd like to remove this variable entirely, - // because it changes during user interaction anyway. - - // Drop overlay stuff. - QPointer _dropOverlay; -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/DropOverlay.h b/AdvancedDockingSystem/include/ads/DropOverlay.h deleted file mode 100644 index 9e3964459..000000000 --- a/AdvancedDockingSystem/include/ads/DropOverlay.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef DROP_OVERLAY_H -#define DROP_OVERLAY_H - -#include -#include -#include -#include -class QGridLayout; - -#include "ads/API.h" - -ADS_NAMESPACE_BEGIN -class DropOverlayCross; - -/*! - * DropOverlay paints a translucent rectangle over another widget. The geometry - * of the rectangle is based on the mouse location. - */ -class ADS_EXPORT_API DropOverlay : public QFrame -{ - Q_OBJECT - friend class DropOverlayCross; - -public: - DropOverlay(QWidget* parent); - virtual ~DropOverlay(); - - void setAllowedAreas(DropAreas areas); - DropAreas allowedAreas() const; - - void setAreaWidgets(const QHash& widgets); - - DropArea cursorLocation() const; - - DropArea showDropOverlay(QWidget* target); - void showDropOverlay(QWidget* target, const QRect& targetAreaRect); - void hideDropOverlay(); - -protected: - virtual void paintEvent(QPaintEvent *e); - virtual void showEvent(QShowEvent* e); - virtual void hideEvent(QHideEvent* e); - virtual void resizeEvent(QResizeEvent* e); - virtual void moveEvent(QMoveEvent* e); - -private: - DropAreas _allowedAreas; - DropOverlayCross* _cross; - - bool _fullAreaDrop; - QPointer _target; - QRect _targetRect; - DropArea _lastLocation; -}; - -/*! - * DropOverlayCross shows a cross with 5 different drop area possibilities. - * I could have handled everything inside DropOverlay, but because of some - * styling issues it's better to have a separate class for the cross. - */ -class DropOverlayCross : public QWidget -{ - Q_OBJECT - friend class DropOverlay; - -public: - DropOverlayCross(DropOverlay* overlay); - virtual ~DropOverlayCross(); - - void setAreaWidgets(const QHash& widgets); - DropArea cursorLocation() const; - -protected: - virtual void showEvent(QShowEvent* e); - -private: - void reset(); - -private: - DropOverlay* _overlay; - QHash _widgets; - QGridLayout* _grid; -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/FloatingWidget.h b/AdvancedDockingSystem/include/ads/FloatingWidget.h deleted file mode 100644 index c67a3e228..000000000 --- a/AdvancedDockingSystem/include/ads/FloatingWidget.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef FLOATINGWIDGET_H -#define FLOATINGWIDGET_H - -#include -class QBoxLayout; - -#include "ads/API.h" -#include "ads/SectionContent.h" - -ADS_NAMESPACE_BEGIN -class ContainerWidget; -class SectionTitleWidget; -class SectionContentWidget; -class InternalContentData; - -// FloatingWidget holds and displays SectionContent as a floating window. -// It can be resized, moved and dropped back into a SectionWidget. -class FloatingWidget : public QWidget -{ - Q_OBJECT - - friend class ContainerWidget; - -public: - FloatingWidget(ContainerWidget* container, SectionContent::RefPtr sc, SectionTitleWidget* titleWidget, SectionContentWidget* contentWidget, QWidget* parent = NULL); - virtual ~FloatingWidget(); - - SectionContent::RefPtr content() const { return _content; } - -public://private: - bool takeContent(InternalContentData& data); - -private slots: - void onCloseButtonClicked(); - -private: - ContainerWidget* _container; - SectionContent::RefPtr _content; - SectionTitleWidget* _titleWidget; - SectionContentWidget* _contentWidget; - - QBoxLayout* _titleLayout; -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/Internal.h b/AdvancedDockingSystem/include/ads/Internal.h deleted file mode 100644 index 7a1acf1ae..000000000 --- a/AdvancedDockingSystem/include/ads/Internal.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef ADS_INTERNAL_HEADER -#define ADS_INTERNAL_HEADER - -#include -#include - -#include "ads/API.h" - -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) -#else -#include "ads/SectionContent.h" -#endif - -#define SCLookupMapById(X) X->_scLookupMapById -#define SCLookupMapByName(X) X->_scLookupMapByName -#define SWLookupMapById(X) X->_swLookupMapById - -ADS_NAMESPACE_BEGIN -class SectionContent; -class SectionTitleWidget; -class SectionContentWidget; - - -class InternalContentData -{ -public: - typedef QSharedPointer RefPtr; - typedef QWeakPointer WeakPtr; - - InternalContentData(); - ~InternalContentData(); - - QSharedPointer content; - SectionTitleWidget* titleWidget; - SectionContentWidget* contentWidget; -}; - - -class HiddenSectionItem -{ -public: - HiddenSectionItem() : - preferredSectionId(-1), - preferredSectionIndex(-1) - {} - - int preferredSectionId; - int preferredSectionIndex; - InternalContentData data; -}; - - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/SectionContent.h b/AdvancedDockingSystem/include/ads/SectionContent.h deleted file mode 100644 index 716179249..000000000 --- a/AdvancedDockingSystem/include/ads/SectionContent.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef SECTIONCONTENT_H -#define SECTIONCONTENT_H - -#include -#include -#include -class QWidget; - -#include "ads/API.h" - -ADS_NAMESPACE_BEGIN -class ContainerWidget; - -class ADS_EXPORT_API SectionContent -{ - friend class ContainerWidget; - -private: - SectionContent(); - SectionContent(const SectionContent&); - SectionContent& operator=(const SectionContent&); - -public: - typedef QSharedPointer RefPtr; - typedef QWeakPointer WeakPtr; - - enum Flag - { - None = 0, - Closeable = 1, - AllFlags = Closeable - }; - Q_DECLARE_FLAGS(Flags, Flag) - - /*! - * Creates new content, associates it to container and takes ownership of - * title- and content- widgets. - * \param uniqueName An unique identifier across the entire process. - * \param container The parent ContainerWidget in which this content will be active. - * \param title The widget to use as title. - * \param content The widget to use as content. - * \return May return a invalid ref-pointer in case of invalid parameters. - */ - static RefPtr newSectionContent(const QString& uniqueName, ContainerWidget* container, QWidget* title, QWidget* content); - - virtual ~SectionContent(); - int uid() const; - QString uniqueName() const; - ContainerWidget* containerWidget() const; - QWidget* titleWidget() const; - QWidget* contentWidget() const; - Flags flags() const; - - QString visibleTitle() const; - QString title() const; - void setTitle(const QString& title); - void setFlags(const Flags f); - -private: - const int _uid; - QString _uniqueName; - - QPointer _containerWidget; - QPointer _titleWidget; - QPointer _contentWidget; - - // Optional attributes - QString _title; - Flags _flags; - - /* Note: This method could be a problem in static build environment - * since it may begin with 0 for every module which uses ADS. - */ - static int GetNextUid(); -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/SectionContentWidget.h b/AdvancedDockingSystem/include/ads/SectionContentWidget.h deleted file mode 100644 index fa59b8b49..000000000 --- a/AdvancedDockingSystem/include/ads/SectionContentWidget.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SECTION_CONTENT_WIDGET_H -#define SECTION_CONTENT_WIDGET_H - -#include - -#include "ads/API.h" -#include "ads/SectionContent.h" - -ADS_NAMESPACE_BEGIN -class ContainerWidget; -class SectionWidget; - -class SectionContentWidget : public QFrame -{ - Q_OBJECT - - friend class ContainerWidget; - -public: - SectionContentWidget(SectionContent::RefPtr c, QWidget* parent = 0); - virtual ~SectionContentWidget(); - -private: - SectionContent::RefPtr _content; -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/SectionTitleWidget.h b/AdvancedDockingSystem/include/ads/SectionTitleWidget.h deleted file mode 100644 index 31d98f863..000000000 --- a/AdvancedDockingSystem/include/ads/SectionTitleWidget.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SECTION_TITLE_WIDGET_H -#define SECTION_TITLE_WIDGET_H - -#include -#include -#include - -#include "ads/API.h" -#include "ads/SectionContent.h" - -ADS_NAMESPACE_BEGIN -class ContainerWidget; -class SectionWidget; -class FloatingWidget; - -class SectionTitleWidget : public QFrame -{ - Q_OBJECT - Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) - - friend class ContainerWidget; - friend class SectionWidget; - - SectionContent::RefPtr _content; - - // Drag & Drop (Floating) - QPointer _fw; - QPoint _dragStartPos; - - // Drag & Drop (Title/Tabs) - bool _tabMoving; - - // Property values - bool _activeTab; - -public: - SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent); - virtual ~SectionTitleWidget(); - - bool isActiveTab() const; - void setActiveTab(bool active); - -protected: - virtual void mousePressEvent(QMouseEvent* ev); - virtual void mouseReleaseEvent(QMouseEvent* ev); - virtual void mouseMoveEvent(QMouseEvent* ev); - -signals: - void activeTabChanged(); - void clicked(); -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/SectionWidget.h b/AdvancedDockingSystem/include/ads/SectionWidget.h deleted file mode 100644 index 2bdecd5e5..000000000 --- a/AdvancedDockingSystem/include/ads/SectionWidget.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef SECTION_WIDGET_H -#define SECTION_WIDGET_H - -#include -#include -#include -#include -#include -class QBoxLayout; -class QStackedLayout; -class QPushButton; -class QMenu; - -#include "ads/API.h" -#include "ads/Internal.h" -#include "ads/SectionContent.h" - -ADS_NAMESPACE_BEGIN -class ContainerWidget; -class SectionTitleWidget; -class SectionContentWidget; - -// SectionWidget manages multiple instances of SectionContent. -// It displays a title TAB, which is clickable and will switch to -// the contents associated to the title when clicked. -class ADS_EXPORT_API SectionWidget : public QFrame -{ - Q_OBJECT - friend class ContainerWidget; - - explicit SectionWidget(ContainerWidget* parent); - -public: - virtual ~SectionWidget(); - - int uid() const; - ContainerWidget* containerWidget() const; - - QRect titleAreaGeometry() const; - QRect contentAreaGeometry() const; - - const QList& contents() const { return _contents; } - void addContent(const SectionContent::RefPtr& c); - void addContent(const InternalContentData& data, bool autoActivate); - bool takeContent(int uid, InternalContentData& data); - int indexOfContent(const SectionContent::RefPtr& c) const; - int indexOfContentByUid(int uid) const; - int indexOfContentByTitlePos(const QPoint& pos, QWidget* exclude = NULL) const; - - int currentIndex() const; - void moveContent(int from, int to); - -protected: - virtual void showEvent(QShowEvent*); - -public slots: - void setCurrentIndex(int index); - -private slots: - void onSectionTitleClicked(); - void onCloseButtonClicked(); - void onTabsMenuActionTriggered(bool); - void updateTabsMenu(); - - -private: - const int _uid; - - QPointer _container; - QList _contents; - QList _sectionTitles; - QList _sectionContents; - - QBoxLayout* _topLayout; - QScrollArea* _tabsScrollArea; - QWidget* _tabsContainerWidget; - QBoxLayout* _tabsLayout; - QPushButton* _tabsMenuButton; - QPushButton* _closeButton; - int _tabsLayoutInitCount; // used for calculations on _tabsLayout modification calls. - - QStackedLayout *_contentsLayout; - - QPoint _mousePressPoint; - SectionContent::RefPtr _mousePressContent; - SectionTitleWidget* _mousePressTitleWidget; - - static int GetNextUid(); -}; - -/* Custom scrollable implementation for tabs */ -class SectionWidgetTabsScrollArea : public QScrollArea -{ -public: - SectionWidgetTabsScrollArea(SectionWidget* sectionWidget, QWidget* parent = NULL); - virtual ~SectionWidgetTabsScrollArea(); - -protected: - virtual void wheelEvent(QWheelEvent*); -}; - -ADS_NAMESPACE_END -#endif diff --git a/AdvancedDockingSystem/include/ads/Serialization.h b/AdvancedDockingSystem/include/ads/Serialization.h deleted file mode 100644 index 7cbb4b18d..000000000 --- a/AdvancedDockingSystem/include/ads/Serialization.h +++ /dev/null @@ -1,165 +0,0 @@ -#ifndef ADS_SERIALIZATION_H -#define ADS_SERIALIZATION_H - -#include -#include -#include -#include -#include - -#include "ads/API.h" - -ADS_NAMESPACE_SER_BEGIN - -enum EntryType -{ - ET_Unknown = 0x00000000, - ET_Hierarchy = 0x00000001, - ET_SectionIndex = 0x00000002, - - // Begin of custom entry types (e.g. CustomType + 42) - ET_Custom = 0x0000ffff -}; - -class ADS_EXPORT_API HeaderEntity -{ -public: - static qint32 MAGIC; - static qint32 MAJOR_VERSION; - static qint32 MINOR_VERSION; - - HeaderEntity(); - qint32 magic; - qint32 majorVersion; - qint32 minorVersion; -}; -QDataStream& operator<<(QDataStream& out, const HeaderEntity& data); -QDataStream& operator>>(QDataStream& in, HeaderEntity& data); - - -class ADS_EXPORT_API OffsetsHeaderEntity -{ -public: - OffsetsHeaderEntity(); - - qint64 entriesCount; - QList entries; -}; -QDataStream& operator<<(QDataStream& out, const OffsetsHeaderEntity& data); -QDataStream& operator>>(QDataStream& in, OffsetsHeaderEntity& data); - - -class ADS_EXPORT_API OffsetsHeaderEntryEntity -{ -public: - OffsetsHeaderEntryEntity(); - qint32 type; - qint64 offset; - qint64 contentSize; -}; -QDataStream& operator<<(QDataStream& out, const OffsetsHeaderEntryEntity& data); -QDataStream& operator>>(QDataStream& in, OffsetsHeaderEntryEntity& data); - - -class ADS_EXPORT_API SectionEntity -{ -public: - SectionEntity(); - qint32 x; - qint32 y; - qint32 width; - qint32 height; - qint32 currentIndex; - qint32 sectionContentsCount; - QList sectionContents; -}; -QDataStream& operator<<(QDataStream& out, const SectionEntity& data); -QDataStream& operator>>(QDataStream& in, SectionEntity& data); - - -class ADS_EXPORT_API SectionContentEntity -{ -public: - SectionContentEntity(); - QString uniqueName; - bool visible; - qint32 preferredIndex; -}; -QDataStream& operator<<(QDataStream& out, const SectionContentEntity& data); -QDataStream& operator>>(QDataStream& in, SectionContentEntity& data); - - -class ADS_EXPORT_API FloatingContentEntity -{ -public: - FloatingContentEntity(); - QString uniqueName; - qint32 xpos; - qint32 ypos; - qint32 width; - qint32 height; - bool visible; -}; -QDataStream& operator<<(QDataStream& out, const FloatingContentEntity& data); -QDataStream& operator>>(QDataStream& in, FloatingContentEntity& data); - - -// Type: OffsetHeaderEntry::Hierarchy -class ADS_EXPORT_API HierarchyData -{ -public: - HierarchyData(); - QByteArray data; -}; -QDataStream& operator<<(QDataStream& out, const HierarchyData& data); -QDataStream& operator>>(QDataStream& in, HierarchyData& data); - - -// Type: OffsetHeaderEntry::SectionIndex -class ADS_EXPORT_API SectionIndexData -{ -public: - SectionIndexData(); - qint32 sectionsCount; - QList sections; -}; -QDataStream& operator<<(QDataStream& out, const SectionIndexData& data); -QDataStream& operator>>(QDataStream& in, SectionIndexData& data); - - -/*! - * \brief The InMemoryWriter class writes into a QByteArray. - */ -class ADS_EXPORT_API InMemoryWriter -{ -public: - InMemoryWriter(); - bool write(qint32 entryType, const QByteArray& data); - bool write(const SectionIndexData& data); - QByteArray toByteArray() const; - qint32 offsetsCount() const { return _offsetsHeader.entriesCount; } - -private: - QBuffer _contentBuffer; - OffsetsHeaderEntity _offsetsHeader; -}; - -/*! - * \brief The InMemoryReader class - */ -class ADS_EXPORT_API InMemoryReader -{ -public: - InMemoryReader(const QByteArray& data); - bool initReadHeader(); - bool read(qint32 entryType, QByteArray &data); - bool read(SectionIndexData& sid); - qint32 offsetsCount() const { return _offsetsHeader.entriesCount; } - -private: - QByteArray _data; - OffsetsHeaderEntity _offsetsHeader; -}; - -ADS_NAMESPACE_SER_END -#endif diff --git a/AdvancedDockingSystem/res/ads.qrc b/AdvancedDockingSystem/res/ads.qrc deleted file mode 100644 index ce6401610..000000000 --- a/AdvancedDockingSystem/res/ads.qrc +++ /dev/null @@ -1,7 +0,0 @@ - - - stylesheets/default-windows.css - stylesheets/vendor-partsolutions.css - stylesheets/modern-windows.css - - diff --git a/AdvancedDockingSystem/res/stylesheets/default-windows.css b/AdvancedDockingSystem/res/stylesheets/default-windows.css deleted file mode 100644 index a0e00ba4d..000000000 --- a/AdvancedDockingSystem/res/stylesheets/default-windows.css +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Default style sheet on Windows Platforms - * Note: Always use CSS-classes with and without "ads--" namespace to support Qt4 & Qt5 - */ - -ads--ContainerWidget, -ContainerWidget -{ - background: palette(dark); -} - -ads--ContainerWidget QSplitter::handle, -ContainerWidget QSplitter::handle -{ - background: palette(dark); -} - -ads--SectionWidget, -SectionWidget -{ - background: palette(window); - border: 1px solid palette(light); -} - -ads--SectionWidget #tabsMenuButton::menu-indicator, -SectionWidget #tabsMenuButton::menu-indicator -{ - image: none; -} - -ads--SectionTitleWidget, -SectionTitleWidget -{ - background: palette(window); - border-color: palette(light); - border-style: solid; - border-width: 0 1px 0 0; - padding: 0 9px; -} - -ads--SectionTitleWidget[activeTab="true"], -SectionTitleWidget[activeTab="true"] -{ -/* background: palette(light);*/ -/* background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(255, 255, 255, 255), stop:1 rgba(240, 240, 240, 255));*/ - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:0.5, stop:0 palette(window), stop:1 palette(light)); -} - -ads--SectionContentWidget, -SectionContentWidget -{ - background: palette(light); - border-color: palette(light); - border-style: solid; - border-width: 1px 0 0 0; -} - -/* Special: QLabels inside SectionTitleWidget -*/ -ads--SectionTitleWidget QLabel, -SectionTitleWidget QLabel -{ - color: palette(dark); -} -ads--SectionTitleWidget[activeTab="true"] QLabel, -SectionTitleWidget[activeTab="true"] QLabel -{ - color: palette(foreground); -} - -/* Special: QLabels inside SectionTitleWidget, which is floating -*/ -ads--FloatingWidget ads--SectionTitleWidget QLabel, -FloatingWidget SectionTitleWidget QLabel -{ - color: palette(foreground); -} diff --git a/AdvancedDockingSystem/res/stylesheets/modern-windows.css b/AdvancedDockingSystem/res/stylesheets/modern-windows.css deleted file mode 100644 index 7bb577345..000000000 --- a/AdvancedDockingSystem/res/stylesheets/modern-windows.css +++ /dev/null @@ -1,35 +0,0 @@ -QSplitter::handle { - background: palette(light); -} - -ads--ContainerWidget, ContainerWidget { - background: palette(light); -} - -ads--SectionWidget, SectionWidget { - background: palette(light); -} - -ads--SectionTitleWidget, SectionTitleWidget { - background: #ffffff; -} -ads--SectionTitleWidget QLabel, SectionTitleWidget QLabel { - color: #000000; -} - -ads--SectionTitleWidget[activeTab="true"], SectionTitleWidget[activeTab="true"] { - background: #000000; - border-right: 1px solid #000000; - padding: 9px; -} -ads--SectionTitleWidget[activeTab="true"] QLabel, SectionTitleWidget[activeTab="true"] QLabel { - color: #ffffff; -} - -ads--SectionContentWidget, SectionContentWidget { - border: 1px solid #000000; -} - -QAbstractItemView { - border: 0; -} \ No newline at end of file diff --git a/AdvancedDockingSystem/res/stylesheets/vendor-partsolutions.css b/AdvancedDockingSystem/res/stylesheets/vendor-partsolutions.css deleted file mode 100644 index ee7ed2a86..000000000 --- a/AdvancedDockingSystem/res/stylesheets/vendor-partsolutions.css +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Style sheet used by CADENAS PARTsolutions product line - * Requires Qt4 compatibility - */ - -QSplitter::handle:vertical { - image: url(:/img/splitter-horizontal.png); -} - -QSplitter::handle:horizontal { - image: url(:/img/splitter-vertical.png); -} - -ads--ContainerWidget, ContainerWidget { - background: #9ab6ca; - border: 0; -} - -ads--SectionWidget, SectionWidget { - background: #7c9eb3; - border-color: #ffffff; - border-style: solid; - border-width: 1px; -} - -ads--SectionTitleWidget, SectionTitleWidget { - background: #7c9eb3; - border-right: 1px solid #E7F3F8; - padding: 6px 6px; -} - -ads--SectionTitleWidget[activeTab="true"], SectionTitleWidget[activeTab="true"] { - background: #E7F3F8; - border: 1px solid #E7F3F8; -} - -ads--SectionWidget QPushButton#closeButton, SectionWidget QPushButton#closeButton, -ads--FloatingWidget QPushButton#closeButton, FloatingWidget QPushButton#closeButton { - background: #ff0000; - border: 1px solid red; -} - -ads--SectionContentWidget, SectionContentWidget { - background: #ffffff; - border: 0px solid #E7F3F8; -} - -/* Special */ - -IconTitleWidget { - padding: 0; - margin: 0; -} - -ads--SectionTitleWidget QLabel, SectionTitleWidget QLabel { - color: #ffffff; - background: #7c9eb3; -} - -ads--SectionTitleWidget[activeTab="true"] QLabel, SectionTitleWidget[activeTab="true"] QLabel { - color: #000000; - background: #E7F3F8; -} \ No newline at end of file diff --git a/AdvancedDockingSystem/src/API.cpp b/AdvancedDockingSystem/src/API.cpp deleted file mode 100644 index 1cf8a784b..000000000 --- a/AdvancedDockingSystem/src/API.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "ads/API.h" - -#include -#include -#include -#include - -#include "ads/ContainerWidget.h" -#include "ads/SectionWidget.h" - -ADS_NAMESPACE_BEGIN - -static bool splitterContainsSectionWidget(QSplitter* splitter) -{ - for (int i = 0; i < splitter->count(); ++i) - { - QWidget* w = splitter->widget(i); - QSplitter* sp = qobject_cast(w); - SectionWidget* sw = NULL; - if (sp && splitterContainsSectionWidget(sp)) - return true; - else if ((sw = qobject_cast(w)) != NULL) - return true; - } - return false; -} - -void deleteEmptySplitter(ContainerWidget* container) -{ - bool doAgain = false; - do - { - doAgain = false; - QList splitters = container->findChildren(); - for (int i = 0; i < splitters.count(); ++i) - { - QSplitter* sp = splitters.at(i); - if (!sp->property("ads-splitter").toBool()) - continue; - if (sp->count() > 0 && splitterContainsSectionWidget(sp)) - continue; - delete splitters[i]; - doAgain = true; - break; - } - } - while (doAgain); -} - -ContainerWidget* findParentContainerWidget(QWidget* w) -{ - ContainerWidget* cw = 0; - QWidget* next = w; - do - { - if ((cw = dynamic_cast(next)) != 0) - { - break; - } - next = next->parentWidget(); - } - while (next); - return cw; -} - -SectionWidget* findParentSectionWidget(class QWidget* w) -{ - SectionWidget* cw = 0; - QWidget* next = w; - do - { - if ((cw = dynamic_cast(next)) != 0) - { - break; - } - next = next->parentWidget(); - } - while (next); - return cw; -} - -QSplitter* findParentSplitter(class QWidget* w) -{ - QSplitter* cw = 0; - QWidget* next = w; - do - { - if ((cw = dynamic_cast(next)) != 0) - { - break; - } - next = next->parentWidget(); - } - while (next); - return cw; -} - -QSplitter* findImmediateSplitter(class QWidget* w) -{ - QSplitter* sp = NULL; - QLayout* l = w->layout(); - if (!l || l->count() <= 0) - return sp; - for (int i = 0; i < l->count(); ++i) - { - QLayoutItem* li = l->itemAt(0); - if (!li->widget()) - continue; - if ((sp = dynamic_cast(li->widget())) != NULL) - break; - } - return sp; -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/ContainerWidget.cpp b/AdvancedDockingSystem/src/ContainerWidget.cpp deleted file mode 100644 index 9036c77d3..000000000 --- a/AdvancedDockingSystem/src/ContainerWidget.cpp +++ /dev/null @@ -1,1274 +0,0 @@ -#include "ads/ContainerWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ads/Internal.h" -#include "ads/SectionWidget.h" -#include "ads/SectionTitleWidget.h" -#include "ads/SectionContentWidget.h" -#include "ads/DropOverlay.h" -#include "ads/Serialization.h" - -ADS_NAMESPACE_BEGIN - -// Static Helper ////////////////////////////////////////////////////// - -static QSplitter* newSplitter(Qt::Orientation orientation = Qt::Horizontal, QWidget* parent = 0) -{ - QSplitter* s = new QSplitter(orientation, parent); - s->setProperty("ads-splitter", QVariant(true)); - s->setChildrenCollapsible(false); - s->setOpaqueResize(false); - return s; -} - -/////////////////////////////////////////////////////////////////////// - -ContainerWidget::ContainerWidget(QWidget *parent) : - QFrame(parent), - _mainLayout(NULL), - _orientation(Qt::Horizontal), - _splitter(NULL), - _dropOverlay(new DropOverlay(this)) -{ - _mainLayout = new QGridLayout(); - _mainLayout->setContentsMargins(9, 9, 9, 9); - _mainLayout->setSpacing(0); - setLayout(_mainLayout); -} - -ContainerWidget::~ContainerWidget() -{ - // Note: It's required to delete in 2 steps - // Remove from list, and then delete. - // Because the destrcutor of objects wants to modfiy the current - // iterating list as well... :-/ - while (!_sections.isEmpty()) - { - SectionWidget* sw = _sections.takeLast(); - delete sw; - } - while (!_floatings.isEmpty()) - { - FloatingWidget* fw = _floatings.takeLast(); - delete fw; - } - _scLookupMapById.clear(); - _scLookupMapByName.clear(); - _swLookupMapById.clear(); -} - -SectionWidget* ContainerWidget::addSectionContent(const SectionContent::RefPtr& sc, SectionWidget* sw, DropArea area) -{ - ADS_Expects(!sc.isNull()); - - // Drop it based on "area" - InternalContentData data; - data.content = sc; - data.titleWidget = new SectionTitleWidget(sc, NULL); - data.contentWidget = new SectionContentWidget(sc, NULL); - -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(data.titleWidget, &SectionTitleWidget::activeTabChanged, this, &ContainerWidget::onActiveTabChanged); -#else - QObject::connect(data.titleWidget, SIGNAL(activeTabChanged()), this, SLOT(onActiveTabChanged())); -#endif - - return dropContent(data, sw, area, false); -} - -bool ContainerWidget::removeSectionContent(const SectionContent::RefPtr& sc) -{ - ADS_Expects(!sc.isNull()); - - // Hide the content. - // The hideSectionContent() automatically deletes no longer required SectionWidget objects. - if (!hideSectionContent(sc)) - return false; - - // Begin of ugly work arround. - // TODO The hideSectionContent() method should take care of deleting FloatingWidgets and SectionWidgets, - // but only cares about SectionWidgets right now. So we need to check whether it was a FloatingWidget - // and delete it. - bool found = false; - for (int i = 0; i < _floatings.count(); ++i) - { - FloatingWidget* fw = _floatings.at(i); - InternalContentData data; - if (!(found = fw->takeContent(data))) - continue; - _floatings.removeAll(fw); - delete fw; - delete data.titleWidget; - delete data.contentWidget; - break; - } // End of ugly work arround. - - // Get from hidden contents and delete associated internal stuff. - if (!_hiddenSectionContents.contains(sc->uid())) - { - qFatal("Something went wrong... The content should have been there :-/"); - return false; - } - - // Delete internal objects. - HiddenSectionItem hsi = _hiddenSectionContents.take(sc->uid()); - delete hsi.data.titleWidget; - delete hsi.data.contentWidget; - - // Hide the custom widgets of SectionContent. - // ... should we? ... - - return true; -} - -bool ContainerWidget::showSectionContent(const SectionContent::RefPtr& sc) -{ - ADS_Expects(!sc.isNull()); - - // Search SC in floatings - for (int i = 0; i < _floatings.count(); ++i) - { - FloatingWidget* fw = _floatings.at(i); - const bool found = fw->content()->uid() == sc->uid(); - if (!found) - continue; - fw->setVisible(true); - fw->_titleWidget->setVisible(true); - fw->_contentWidget->setVisible(true); - emit sectionContentVisibilityChanged(sc, true); - return true; - } - - // Search SC in hidden sections - // Try to show them in the last position, otherwise simply append - // it to the first section (or create a new section?) - if (_hiddenSectionContents.contains(sc->uid())) - { - const HiddenSectionItem hsi = _hiddenSectionContents.take(sc->uid()); - hsi.data.titleWidget->setVisible(true); - hsi.data.contentWidget->setVisible(true); - SectionWidget* sw = NULL; - if (hsi.preferredSectionId > 0 && (sw = SWLookupMapById(this).value(hsi.preferredSectionId)) != NULL) - { - sw->addContent(hsi.data, true); - emit sectionContentVisibilityChanged(sc, true); - return true; - } - else if (_sections.size() > 0 && (sw = _sections.first()) != NULL) - { - sw->addContent(hsi.data, true); - emit sectionContentVisibilityChanged(sc, true); - return true; - } - else - { - sw = newSectionWidget(); - addSection(sw); - sw->addContent(hsi.data, true); - emit sectionContentVisibilityChanged(sc, true); - return true; - } - } - - // Already visible? - // TODO - qWarning("Unable to show SectionContent, don't know where 8-/ (already visible?)"); - return false; -} - -bool ContainerWidget::hideSectionContent(const SectionContent::RefPtr& sc) -{ - ADS_Expects(!sc.isNull()); - - // Search SC in floatings - // We can simply hide floatings, nothing else required. - for (int i = 0; i < _floatings.count(); ++i) - { - const bool found = _floatings.at(i)->content()->uid() == sc->uid(); - if (!found) - continue; - _floatings.at(i)->setVisible(false); - emit sectionContentVisibilityChanged(sc, false); - return true; - } - - // Search SC in sections - // It's required to remove the SC from SW completely and hold it in a - // separate list as long as a "showSectionContent" gets called for the SC again. - // In case that the SW does not have any other SCs, we need to delete it. - for (int i = 0; i < _sections.count(); ++i) - { - SectionWidget* sw = _sections.at(i); - const bool found = sw->indexOfContent(sc) >= 0; - if (!found) - continue; - - HiddenSectionItem hsi; - hsi.preferredSectionId = sw->uid(); - hsi.preferredSectionIndex = sw->indexOfContent(sc); - if (!sw->takeContent(sc->uid(), hsi.data)) - return false; - - hsi.data.titleWidget->setVisible(false); - hsi.data.contentWidget->setVisible(false); - _hiddenSectionContents.insert(sc->uid(), hsi); - - if (sw->contents().isEmpty()) - { - delete sw; - sw = NULL; - deleteEmptySplitter(this); - } - emit sectionContentVisibilityChanged(sc, false); - return true; - } - - // Search SC in hidden elements - // The content may already be hidden - if (_hiddenSectionContents.contains(sc->uid())) - return true; - - qFatal("Unable to hide SectionContent, don't know this one 8-/"); - return false; -} - -bool ContainerWidget::raiseSectionContent(const SectionContent::RefPtr& sc) -{ - ADS_Expects(!sc.isNull()); - - // Search SC in sections - for (int i = 0; i < _sections.count(); ++i) - { - SectionWidget* sw = _sections.at(i); - int index = sw->indexOfContent(sc); - if (index < 0) - continue; - sw->setCurrentIndex(index); - return true; - } - - // Search SC in floatings - for (int i = 0; i < _floatings.size(); ++i) - { - FloatingWidget* fw = _floatings.at(i); - if (fw->content()->uid() != sc->uid()) - continue; - fw->setVisible(true); - fw->raise(); - return true; - } - - // Search SC in hidden - if (_hiddenSectionContents.contains(sc->uid())) - return showSectionContent(sc); - - qFatal("Unable to hide SectionContent, don't know this one 8-/"); - return false; -} - -bool ContainerWidget::isSectionContentVisible(const SectionContent::RefPtr& sc) -{ - ADS_Expects(!sc.isNull()); - - // Search SC in floatings - for (int i = 0; i < _floatings.count(); ++i) - { - const bool found = _floatings.at(i)->content()->uid() == sc->uid(); - if (!found) - continue; - return _floatings.at(i)->isVisible(); - } - - // Search SC in sections - for (int i = 0; i < _sections.count(); ++i) - { - SectionWidget* sw = _sections.at(i); - const int index = sw->indexOfContent(sc); - if (index < 0) - continue; - return true; - } - - // Search SC in hidden - if (_hiddenSectionContents.contains(sc->uid())) - return false; - - qWarning() << "SectionContent is not a part of this ContainerWidget:" << sc->uniqueName(); - return false; -} - -QMenu* ContainerWidget::createContextMenu() const -{ - // Fill map with actions (sorted by key!) - QMap actions; - - // Visible contents of sections - for (int i = 0; i < _sections.size(); ++i) - { - const SectionWidget* sw = _sections.at(i); - const QList& contents = sw->contents(); - foreach (const SectionContent::RefPtr& sc, contents) - { - QAction* a = new QAction(QIcon(), sc->visibleTitle(), NULL); - a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid()))); - a->setProperty("uid", sc->uid()); - a->setProperty("type", "section"); - a->setCheckable(true); - a->setChecked(true); - a->setEnabled(sc->flags().testFlag(SectionContent::Closeable)); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(a, &QAction::toggled, this, &ContainerWidget::onActionToggleSectionContentVisibility); -#else - QObject::connect(a, SIGNAL(toggled(bool)), this, SLOT(onActionToggleSectionContentVisibility(bool))); -#endif - actions.insert(a->text(), a); - } - } - - // Hidden contents of sections - QHashIterator hiddenIter(_hiddenSectionContents); - while (hiddenIter.hasNext()) - { - hiddenIter.next(); - const SectionContent::RefPtr sc = hiddenIter.value().data.content; - - QAction* a = new QAction(QIcon(), sc->visibleTitle(), NULL); - a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid()))); - a->setProperty("uid", sc->uid()); - a->setProperty("type", "section"); - a->setCheckable(true); - a->setChecked(false); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(a, &QAction::toggled, this, &ContainerWidget::onActionToggleSectionContentVisibility); -#else - QObject::connect(a, SIGNAL(toggled(bool)), this, SLOT(onActionToggleSectionContentVisibility(bool))); -#endif - actions.insert(a->text(), a); - } - - // Floating contents - for (int i = 0; i < _floatings.size(); ++i) - { - const FloatingWidget* fw = _floatings.at(i); - const SectionContent::RefPtr sc = fw->content(); - - QAction* a = new QAction(QIcon(), sc->visibleTitle(), NULL); - a->setObjectName(QString("ads-action-sc-%1").arg(QString::number(sc->uid()))); - a->setProperty("uid", sc->uid()); - a->setProperty("type", "floating"); - a->setCheckable(true); - a->setChecked(fw->isVisible()); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(a, &QAction::toggled, this, &ContainerWidget::onActionToggleSectionContentVisibility); -#else - QObject::connect(a, SIGNAL(toggled(bool)), this, SLOT(onActionToggleSectionContentVisibility(bool))); -#endif - actions.insert(a->text(), a); - } - - // Create menu from "actions" - QMenu* m = new QMenu(NULL); - m->addActions(actions.values()); - return m; -} - -QByteArray ContainerWidget::saveState() const -{ - ADS_NS_SER::InMemoryWriter writer; - - // Hierarchy data. - const QByteArray hierarchyData = saveHierarchy(); - if (!hierarchyData.isEmpty()) - { - writer.write(ADS_NS_SER::ET_Hierarchy, hierarchyData); - } - - // SectionIndex data. - ADS_NS_SER::SectionIndexData sid; - if (saveSectionIndex(sid)) - { - writer.write(sid); - } - - if (writer.offsetsCount() == 0) - return QByteArray(); - return writer.toByteArray(); -} - -bool ContainerWidget::restoreState(const QByteArray& data) -{ - if (data.isEmpty()) - return false; - - ADS_NS_SER::InMemoryReader reader(data); - if (!reader.initReadHeader()) - return false; - - // Basic hierarchy data. - QByteArray hierarchyData; - if (reader.read(ADS_NS_SER::ET_Hierarchy, hierarchyData)) - { - restoreHierarchy(hierarchyData); - } - return true; -} - -QRect ContainerWidget::outerTopDropRect() const -{ - QRect r = rect(); - int h = r.height() / 100 * 5; - return QRect(r.left(), r.top(), r.width(), h); -} - -QRect ContainerWidget::outerRightDropRect() const -{ - QRect r = rect(); - int w = r.width() / 100 * 5; - return QRect(r.right() - w, r.top(), w, r.height()); -} - -QRect ContainerWidget::outerBottomDropRect() const -{ - QRect r = rect(); - int h = r.height() / 100 * 5; - return QRect(r.left(), r.bottom() - h, r.width(), h); -} - -QRect ContainerWidget::outerLeftDropRect() const -{ - QRect r = rect(); - int w = r.width() / 100 * 5; - return QRect(r.left(), r.top(), w, r.height()); -} - -QList ContainerWidget::contents() const -{ - QList wl = _scLookupMapById.values(); - QList sl; - for (int i = 0; i < wl.count(); ++i) - { - const SectionContent::RefPtr sc = wl.at(i).toStrongRef(); - if (sc) - sl.append(sc); - } - return sl; -} - -QPointer ContainerWidget::dropOverlay() const -{ - return _dropOverlay; -} - -/////////////////////////////////////////////////////////////////////// -// PRIVATE API BEGINS HERE -/////////////////////////////////////////////////////////////////////// - -SectionWidget* ContainerWidget::newSectionWidget() -{ - SectionWidget* sw = new SectionWidget(this); - _sections.append(sw); - return sw; -} - -SectionWidget* ContainerWidget::dropContent(const InternalContentData& data, SectionWidget* targetSection, DropArea area, bool autoActive) -{ - ADS_Expects(targetSection != NULL); - - SectionWidget* ret = NULL; - - // If no sections exists yet, create a default one and always drop into it. - if (_sections.count() <= 0) - { - targetSection = newSectionWidget(); - addSection(targetSection); - area = CenterDropArea; - } - - // Drop on outer area - if (!targetSection) - { - switch (area) - { - case TopDropArea: - ret = dropContentOuterHelper(_mainLayout, data, Qt::Vertical, false); - break; - case RightDropArea: - ret = dropContentOuterHelper(_mainLayout, data, Qt::Horizontal, true); - break; - case CenterDropArea: - case BottomDropArea: - ret = dropContentOuterHelper(_mainLayout, data, Qt::Vertical, true); - break; - case LeftDropArea: - ret = dropContentOuterHelper(_mainLayout, data, Qt::Horizontal, false); - break; - default: - return NULL; - } - return ret; - } - - QSplitter* targetSectionSplitter = findParentSplitter(targetSection); - - // Drop logic based on area. - switch (area) - { - case TopDropArea: - { - SectionWidget* sw = newSectionWidget(); - sw->addContent(data, true); - if (targetSectionSplitter->orientation() == Qt::Vertical) - { - const int index = targetSectionSplitter->indexOf(targetSection); - targetSectionSplitter->insertWidget(index, sw); - } - else - { - const int index = targetSectionSplitter->indexOf(targetSection); - QSplitter* s = newSplitter(Qt::Vertical); - s->addWidget(sw); - s->addWidget(targetSection); - targetSectionSplitter->insertWidget(index, s); - } - ret = sw; - break; - } - case RightDropArea: - { - SectionWidget* sw = newSectionWidget(); - sw->addContent(data, true); - if (targetSectionSplitter->orientation() == Qt::Horizontal) - { - const int index = targetSectionSplitter->indexOf(targetSection); - targetSectionSplitter->insertWidget(index + 1, sw); - } - else - { - const int index = targetSectionSplitter->indexOf(targetSection); - QSplitter* s = newSplitter(Qt::Horizontal); - s->addWidget(targetSection); - s->addWidget(sw); - targetSectionSplitter->insertWidget(index, s); - } - ret = sw; - break; - } - case BottomDropArea: - { - SectionWidget* sw = newSectionWidget(); - sw->addContent(data, true); - if (targetSectionSplitter->orientation() == Qt::Vertical) - { - int index = targetSectionSplitter->indexOf(targetSection); - targetSectionSplitter->insertWidget(index + 1, sw); - } - else - { - int index = targetSectionSplitter->indexOf(targetSection); - QSplitter* s = newSplitter(Qt::Vertical); - s->addWidget(targetSection); - s->addWidget(sw); - targetSectionSplitter->insertWidget(index, s); - } - ret = sw; - break; - } - case LeftDropArea: - { - SectionWidget* sw = newSectionWidget(); - sw->addContent(data, true); - if (targetSectionSplitter->orientation() == Qt::Horizontal) - { - int index = targetSectionSplitter->indexOf(targetSection); - targetSectionSplitter->insertWidget(index, sw); - } - else - { - QSplitter* s = newSplitter(Qt::Horizontal); - s->addWidget(sw); - int index = targetSectionSplitter->indexOf(targetSection); - targetSectionSplitter->insertWidget(index, s); - s->addWidget(targetSection); - } - ret = sw; - break; - } - case CenterDropArea: - { - targetSection->addContent(data, autoActive); - ret = targetSection; - break; - } - default: - break; - } - return ret; -} - -void ContainerWidget::addSection(SectionWidget* section) -{ - ADS_Expects(section != NULL); - - // Create default splitter. - if (!_splitter) - { - _splitter = newSplitter(_orientation); - _mainLayout->addWidget(_splitter, 0, 0); - } - if (_splitter->indexOf(section) != -1) - { - qWarning() << Q_FUNC_INFO << QString("Section has already been added"); - return; - } - _splitter->addWidget(section); -} - -SectionWidget* ContainerWidget::sectionAt(const QPoint& pos) const -{ - const QPoint gpos = mapToGlobal(pos); - for (int i = 0; i < _sections.size(); ++i) - { - SectionWidget* sw = _sections[i]; - if (sw->rect().contains(sw->mapFromGlobal(gpos))) - { - return sw; - } - } - return 0; -} - -SectionWidget* ContainerWidget::dropContentOuterHelper(QLayout* l, const InternalContentData& data, Qt::Orientation orientation, bool append) -{ - ADS_Expects(l != NULL); - - SectionWidget* sw = newSectionWidget(); - sw->addContent(data, true); - - QSplitter* oldsp = findImmediateSplitter(this); - if (!oldsp) - { - QSplitter* sp = newSplitter(orientation); - if (l->count() > 0) - { - qWarning() << "Still items in layout. This should never happen."; - QLayoutItem* li = l->takeAt(0); - delete li; - } - l->addWidget(sp); - sp->addWidget(sw); - } - else if (oldsp->orientation() == orientation - || oldsp->count() == 1) - { - oldsp->setOrientation(orientation); - if (append) - oldsp->addWidget(sw); - else - oldsp->insertWidget(0, sw); - } - else - { - QSplitter* sp = newSplitter(orientation); - if (append) - { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - QLayoutItem* li = l->replaceWidget(oldsp, sp); - sp->addWidget(oldsp); - sp->addWidget(sw); - delete li; -#else - int index = l->indexOf(oldsp); - QLayoutItem* li = l->takeAt(index); - l->addWidget(sp); - sp->addWidget(oldsp); - sp->addWidget(sw); - delete li; -#endif - } - else - { -#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) - sp->addWidget(sw); - QLayoutItem* li = l->replaceWidget(oldsp, sp); - sp->addWidget(oldsp); - delete li; -#else - sp->addWidget(sw); - int index = l->indexOf(oldsp); - QLayoutItem* li = l->takeAt(index); - l->addWidget(sp); - sp->addWidget(oldsp); - delete li; -#endif - } - } - return sw; -} - -QByteArray ContainerWidget::saveHierarchy() const -{ - /* - # Data Format - - quint32 Magic - quint32 Version - - int Number of floating widgets - LOOP Floating widgets - QString Unique name of content - QByteArray Geometry of floating widget - bool Visibility - - int Number of layout items (Valid values: 0, 1) - IF 0 - int Number of hidden contents - LOOP Contents - QString Unique name of content - ELSEIF 1 - ... todo ... - ENDIF - */ - QByteArray ba; - QDataStream out(&ba, QIODevice::WriteOnly); - out.setVersion(QDataStream::Qt_4_5); - out << (quint32) 0x00001337; // Magic - out << (quint32) 1; // Version - - // Save state of floating contents - saveFloatingWidgets(out); - - // Save state of sections and contents - if (_mainLayout->count() <= 0 || _sections.isEmpty()) - { - // Looks like the user has hidden all contents and no more sections - // are available. We can simply write a list of all hidden contents. - out << 0; // Mode - - out << _hiddenSectionContents.count(); - QHashIterator iter(_hiddenSectionContents); - while (iter.hasNext()) - { - iter.next(); - out << iter.value().data.content->uniqueName(); - } - } - else if (_mainLayout->count() == 1) - { - out << 1; // Mode - - // There should only be one! - QLayoutItem* li = _mainLayout->itemAt(0); - if (!li->widget()) - qFatal("Not a widget in _mainLayout, this shouldn't happen."); - - // Save sections beginning with the first QSplitter (li->widget()). - saveSectionWidgets(out, li->widget()); - - // Safe state of hidden contents, which doesn't have an section association - // or the section association points to a no longer existing section. - QHashIterator iter(_hiddenSectionContents); - int cnt = 0; - while (iter.hasNext()) - { - iter.next(); - if (iter.value().preferredSectionId <= 0 || !SWLookupMapById(this).contains(iter.value().preferredSectionId)) - cnt++; - } - out << cnt; - iter.toFront(); - while (iter.hasNext()) - { - iter.next(); - if (iter.value().preferredSectionId <= 0 || !SWLookupMapById(this).contains(iter.value().preferredSectionId)) - out << iter.value().data.content->uniqueName(); - } - } - else - { - // More? Oh oh.. something is wrong :-/ - out << -1; - qWarning() << "Oh noooz.. Something went wrong. There are too many items in _mainLayout."; - } - return ba; -} - -void ContainerWidget::saveFloatingWidgets(QDataStream& out) const -{ - out << _floatings.count(); - for (int i = 0; i < _floatings.count(); ++i) - { - FloatingWidget* fw = _floatings.at(i); - out << fw->content()->uniqueName(); - out << fw->saveGeometry(); - out << fw->isVisible(); - } -} - -void ContainerWidget::saveSectionWidgets(QDataStream& out, QWidget* widget) const -{ - QSplitter* sp = NULL; - SectionWidget* sw = NULL; - - if (!widget) - { - out << 0; - } - else if ((sp = dynamic_cast(widget)) != NULL) - { - out << 1; // Type = QSplitter - out << ((sp->orientation() == Qt::Horizontal) ? (int) 1 : (int) 2); - out << sp->count(); - out << sp->sizes(); - for (int i = 0; i < sp->count(); ++i) - { - saveSectionWidgets(out, sp->widget(i)); - } - } - else if ((sw = dynamic_cast(widget)) != NULL) - { - // Format (version 1) - // int Object type (SectionWidget=2) - // int Current active index - // int Number of contents (visible + hidden) - // LOOP Contents of section (last int) - // QString Unique name of SectionContent - // bool Visibility - // int Preferred index - - const QList& contents = sw->contents(); - QList hiddenContents; - - QHashIterator iter(_hiddenSectionContents); - while (iter.hasNext()) - { - iter.next(); - const HiddenSectionItem& hsi = iter.value(); - if (hsi.preferredSectionId != sw->uid()) - continue; - hiddenContents.append(hsi); - } - - out << 2; // Type = SectionWidget - out << sw->currentIndex(); - out << contents.count() + hiddenContents.count(); - - for (int i = 0; i < contents.count(); ++i) - { - out << contents[i]->uniqueName(); // Unique name - out << (bool) true; // Visiblity - out << i; // Index - } - for (int i = 0; i < hiddenContents.count(); ++i) - { - out << hiddenContents.at(i).data.content->uniqueName(); - out << (bool) false; - out << hiddenContents.at(i).preferredSectionIndex; - } - } -} - -bool ContainerWidget::saveSectionIndex(ADS_NS_SER::SectionIndexData& sid) const -{ - if (_sections.count() <= 0) - return false; - - sid.sectionsCount = _sections.count(); - for (int i = 0; i < sid.sectionsCount; ++i) - { - ADS_NS_SER::SectionEntity se; - se.x = mapFromGlobal(_sections[i]->parentWidget()->mapToGlobal(_sections[i]->pos())).x(); - se.y = mapFromGlobal(_sections[i]->parentWidget()->mapToGlobal(_sections[i]->pos())).y(); - se.width = _sections[i]->geometry().width(); - se.height = _sections[i]->geometry().height(); - se.currentIndex = _sections[i]->currentIndex(); - se.sectionContentsCount = _sections[i]->contents().count(); - foreach (const SectionContent::RefPtr& sc, _sections[i]->contents()) - { - ADS_NS_SER::SectionContentEntity sce; - sce.uniqueName = sc->uniqueName(); - sce.visible = true; - sce.preferredIndex = _sections[i]->indexOfContent(sc); - se.sectionContents.append(sce); // std::move()? - } - sid.sections.append(se); // std::move()? - } - return true; -} - -bool ContainerWidget::restoreHierarchy(const QByteArray& data) -{ - QDataStream in(data); - in.setVersion(QDataStream::Qt_4_5); - - quint32 magic = 0; - in >> magic; - if (magic != 0x00001337) - return false; - - quint32 version = 0; - in >> version; - if (version != 1) - return false; - - QList oldFloatings = _floatings; - QList oldSections = _sections; - - // Restore floating widgets - QList floatings; - bool success = restoreFloatingWidgets(in, version, floatings); - if (!success) - { - qWarning() << "Could not restore floatings completely"; - } - - // Restore splitters, sections and contents - QList sections; - QList contentsToHide; - - int mode = 0; - in >> mode; - if (mode == 0) - { - // List of hidden contents. There are no sections at all. - int cnt = 0; - in >> cnt; - - if(cnt > 0) - { - // Create dummy section, required to call hideSectionContent() later. - SectionWidget* sw = new SectionWidget(this); - sections.append(sw); - - for (int i = 0; i < cnt; ++i) - { - QString uname; - in >> uname; - - const SectionContent::RefPtr sc = SCLookupMapByName(this).value(uname); - if (!sc) - continue; - - InternalContentData data; - if (!takeContent(sc, data)) - qFatal("This should never happen!!!"); - - sw->addContent(data, false); - contentsToHide.append(sc); - } - } - } - else if (mode == 1) - { - success = restoreSectionWidgets(in, version, NULL, sections, contentsToHide); - if (!success) - qWarning() << "Could not restore sections completely"; - - // Restore lonely hidden contents - int cnt = 0; - in >> cnt; - for (int i = 0; i < cnt; ++i) - { - QString uname; - in >> uname; - const SectionContent::RefPtr sc = SCLookupMapByName(this).value(uname); - if (!sc) - continue; - - InternalContentData data; - if (!takeContent(sc, data)) - { - qWarning("This should never happen! Looks like a bug during serialization, since the content is already being used in SWs."); - continue; - } - - SectionWidget* sw = NULL; - if (sections.size() <= 0) - qFatal("This should never happen, because above a section should have been created."); - else - sw = sections.first(); - - sw->addContent(data, false); - contentsToHide.append(sc); - } - } - - // Handle SectionContent which is not mentioned by deserialized data. - // What shall we do with it? For now: Simply drop them into the first SectionWidget. - if (true) - { - QList leftContents; - - // Collect all contents which has been restored - QList contents; - for (int i = 0; i < floatings.count(); ++i) - contents.append(floatings.at(i)->content()); - for (int i = 0; i < sections.count(); ++i) - for (int j = 0; j < sections.at(i)->contents().count(); ++j) - contents.append(sections.at(i)->contents().at(j)); - for (int i = 0; i < contentsToHide.count(); ++i) - contents.append(contentsToHide.at(i)); - - // Compare restored contents with available contents - const QList allContents = SCLookupMapById(this).values(); - for (int i = 0; i < allContents.count(); ++i) - { - const SectionContent::RefPtr sc = allContents.at(i).toStrongRef(); - if (sc.isNull() || sc->containerWidget() != this) - continue; - if (contents.contains(sc)) - continue; - leftContents.append(sc); - } - - // What should we do with a drunken sailor.. what should.. erm.. - // What should we do with the left-contents? - // Lets add them to the first found SW or create one, if no SW is available. - for (int i = 0; i < leftContents.count(); ++i) - { - const SectionContent::RefPtr sc = leftContents.at(i); - SectionWidget* sw = NULL; - - if (sections.isEmpty()) - { - sw = new SectionWidget(this); - sections.append(sw); - addSection(sw); - } - else - sw = sections.first(); - - InternalContentData data; - if (!takeContent(sc, data)) - sw->addContent(sc); - else - sw->addContent(data, false); - } - } - - _floatings = floatings; - _sections = sections; - - // Delete old objects - QLayoutItem* old = _mainLayout->takeAt(0); - _mainLayout->addWidget(_splitter); - delete old; - qDeleteAll(oldFloatings); - qDeleteAll(oldSections); - - // Hide all as "hidden" marked contents - for (int i = 0; i < contentsToHide.count(); ++i) - hideSectionContent(contentsToHide.at(i)); - - deleteEmptySplitter(this); - return success; -} - -bool ContainerWidget::restoreFloatingWidgets(QDataStream& in, int version, QList& floatings) -{ - Q_UNUSED(version) - - int fwCount = 0; - in >> fwCount; - if (fwCount <= 0) - return true; - - for (int i = 0; i < fwCount; ++i) - { - QString uname; - in >> uname; - QByteArray geom; - in >> geom; - bool visible = false; - in >> visible; - - const SectionContent::RefPtr sc = SCLookupMapByName(this).value(uname).toStrongRef(); - if (!sc) - { - qWarning() << "Can not find SectionContent:" << uname; - continue; - } - - InternalContentData data; - if (!this->takeContent(sc, data)) - continue; - - FloatingWidget* fw = new FloatingWidget(this, sc, data.titleWidget, data.contentWidget, this); - fw->restoreGeometry(geom); - fw->setVisible(visible); - if (visible) - { - fw->_titleWidget->setVisible(visible); - fw->_contentWidget->setVisible(visible); - } - floatings.append(fw); - data.titleWidget->_fw = fw; // $mfreiholz: Don't look at it :-< It's more than ugly... - } - return true; -} - -bool ContainerWidget::restoreSectionWidgets(QDataStream& in, int version, QSplitter* currentSplitter, QList& sections, QList& contentsToHide) -{ - if (in.atEnd()) - return true; - - int type; - in >> type; - - // Splitter - if (type == 1) - { - int orientation, count; - QList sizes; - in >> orientation >> count >> sizes; - - QSplitter* sp = newSplitter((Qt::Orientation) orientation); - for (int i = 0; i < count; ++i) - { - if (!restoreSectionWidgets(in, version, sp, sections, contentsToHide)) - return false; - } - if (sp->count() <= 0) - { - delete sp; - sp = NULL; - } - else if (sp) - { - sp->setSizes(sizes); - - if (!currentSplitter) - _splitter = sp; - else - currentSplitter->addWidget(sp); - } - } - // Section - else if (type == 2) - { - if (!currentSplitter) - { - qWarning() << "Missing splitter object for section"; - return false; - } - - int currentIndex, count; - in >> currentIndex >> count; - - SectionWidget* sw = new SectionWidget(this); - for (int i = 0; i < count; ++i) - { - QString uname; - in >> uname; - bool visible = false; - in >> visible; - int preferredIndex = -1; - in >> preferredIndex; - - const SectionContent::RefPtr sc = SCLookupMapByName(this).value(uname).toStrongRef(); - if (!sc) - { - qWarning() << "Can not find SectionContent:" << uname; - continue; - } - - InternalContentData data; - if (!takeContent(sc, data)) - { - qCritical() << "Can not find InternalContentData of SC, this should never happen!" << sc->uid() << sc->uniqueName(); - sw->addContent(sc); - } - else - sw->addContent(data, false); - - if (!visible) - contentsToHide.append(sc); - } - if (sw->contents().isEmpty()) - { - delete sw; - sw = NULL; - } - else if (sw) - { - sw->setCurrentIndex(currentIndex); - currentSplitter->addWidget(sw); - sections.append(sw); - } - } - // Unknown - else - { - qWarning() << "Unknown object type during restore"; - } - - return true; -} - -bool ContainerWidget::takeContent(const SectionContent::RefPtr& sc, InternalContentData& data) -{ - ADS_Expects(!sc.isNull()); - - // Search in sections - bool found = false; - for (int i = 0; i < _sections.count() && !found; ++i) - { - found = _sections.at(i)->takeContent(sc->uid(), data); - } - - // Search in floating widgets - for (int i = 0; i < _floatings.count() && !found; ++i) - { - found = _floatings.at(i)->content()->uid() == sc->uid(); - if (found) - _floatings.at(i)->takeContent(data); - } - - // Search in hidden items - if (!found && _hiddenSectionContents.contains(sc->uid())) - { - const HiddenSectionItem hsi = _hiddenSectionContents.take(sc->uid()); - data = hsi.data; - found = true; - } - - return found; -} - -void ContainerWidget::onActiveTabChanged() -{ - SectionTitleWidget* stw = qobject_cast(sender()); - if (stw) - { - emit activeTabChanged(stw->_content, stw->isActiveTab()); - } -} - -void ContainerWidget::onActionToggleSectionContentVisibility(bool visible) -{ - QAction* a = qobject_cast(sender()); - if (!a) - return; - const int uid = a->property("uid").toInt(); - const SectionContent::RefPtr sc = SCLookupMapById(this).value(uid).toStrongRef(); - if (sc.isNull()) - { - qCritical() << "Can not find content by ID" << uid; - return; - } - if (visible) - showSectionContent(sc); - else - hideSectionContent(sc); -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/DropOverlay.cpp b/AdvancedDockingSystem/src/DropOverlay.cpp deleted file mode 100644 index 56094bef6..000000000 --- a/AdvancedDockingSystem/src/DropOverlay.cpp +++ /dev/null @@ -1,441 +0,0 @@ -#include "ads/DropOverlay.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -ADS_NAMESPACE_BEGIN - -// Helper ///////////////////////////////////////////////////////////// - -static QPixmap createDropIndicatorPixmap(const QPalette& pal, const QSizeF& size, DropArea dropArea) -{ - const QColor borderColor = pal.color(QPalette::Active, QPalette::Highlight); - const QColor backgroundColor = pal.color(QPalette::Active, QPalette::Base); - const QColor areaBackgroundColor = pal.color(QPalette::Active, QPalette::Highlight).lighter(150); - - QPixmap pm(size.width(), size.height()); - pm.fill(QColor(0, 0, 0, 0)); - - QPainter p(&pm); - QPen pen = p.pen(); - QRectF baseRect(pm.rect()); - - // Fill - p.fillRect(baseRect, backgroundColor); - - // Drop area rect. - if (true) - { - p.save(); - QRectF areaRect; - QLineF areaLine; - QLinearGradient gradient; - switch (dropArea) - { - case TopDropArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * .5f); - areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); - gradient.setStart(areaRect.topLeft()); - gradient.setFinalStop(areaRect.bottomLeft()); - gradient.setColorAt(0.f, areaBackgroundColor); - gradient.setColorAt(1.f, areaBackgroundColor.lighter(120)); - break; - case RightDropArea: - areaRect = QRectF(baseRect.width() * .5f, baseRect.y(), baseRect.width() * .5f, baseRect.height()); - areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); - gradient.setStart(areaRect.topLeft()); - gradient.setFinalStop(areaRect.topRight()); - gradient.setColorAt(0.f, areaBackgroundColor.lighter(120)); - gradient.setColorAt(1.f, areaBackgroundColor); - break; - case BottomDropArea: - areaRect = QRectF(baseRect.x(), baseRect.height() * .5f, baseRect.width(), baseRect.height() * .5f); - areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); - gradient.setStart(areaRect.topLeft()); - gradient.setFinalStop(areaRect.bottomLeft()); - gradient.setColorAt(0.f, areaBackgroundColor.lighter(120)); - gradient.setColorAt(1.f, areaBackgroundColor); - break; - case LeftDropArea: - areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * .5f, baseRect.height()); - areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); - gradient.setStart(areaRect.topLeft()); - gradient.setFinalStop(areaRect.topRight()); - gradient.setColorAt(0.f, areaBackgroundColor); - gradient.setColorAt(1.f, areaBackgroundColor.lighter(120)); - break; - default: - break; - } - if (areaRect.isValid()) - { - p.fillRect(areaRect, gradient); - - pen = p.pen(); - pen.setColor(borderColor); - pen.setStyle(Qt::DashLine); - p.setPen(pen); - p.drawLine(areaLine); - } - p.restore(); - } - - // Border - if (true) - { - p.save(); - pen = p.pen(); - pen.setColor(borderColor); - pen.setWidth(1); - - p.setPen(pen); - p.drawRect(baseRect.adjusted(0, 0, -pen.width(), -pen.width())); - p.restore(); - } - return pm; -} - -static QWidget* createDropIndicatorWidget(DropArea dropArea) -{ - QLabel* l = new QLabel(); - l->setObjectName("DropAreaLabel"); - - const qreal metric = static_cast(l->fontMetrics().height()) * 2.f; - const QSizeF size(metric, metric); - - l->setPixmap(createDropIndicatorPixmap(l->palette(), size, dropArea)); - return l; -} - -/////////////////////////////////////////////////////////////////////// - -DropOverlay::DropOverlay(QWidget* parent) : - QFrame(parent), - _allowedAreas(InvalidDropArea), - _cross(new DropOverlayCross(this)), - _fullAreaDrop(false), - _lastLocation(InvalidDropArea) -{ - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - setWindowOpacity(0.2); - setWindowTitle("DropOverlay"); - - QBoxLayout* l = new QBoxLayout(QBoxLayout::TopToBottom); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - setLayout(l); - - // Cross with default drop area widgets. - QHash areaWidgets; - areaWidgets.insert(ADS_NS::TopDropArea, createDropIndicatorWidget(TopDropArea)); //createDropWidget(":/img/split-top.png")); - areaWidgets.insert(ADS_NS::RightDropArea, createDropIndicatorWidget(RightDropArea));//createDropWidget(":/img/split-right.png")); - areaWidgets.insert(ADS_NS::BottomDropArea, createDropIndicatorWidget(BottomDropArea));//createDropWidget(":/img/split-bottom.png")); - areaWidgets.insert(ADS_NS::LeftDropArea, createDropIndicatorWidget(LeftDropArea));//createDropWidget(":/img/split-left.png")); - areaWidgets.insert(ADS_NS::CenterDropArea, createDropIndicatorWidget(CenterDropArea));//createDropWidget(":/img/dock-center.png")); - _cross->setAreaWidgets(areaWidgets); - - _cross->setVisible(false); - setVisible(false); -} - -DropOverlay::~DropOverlay() -{ -} - -void DropOverlay::setAllowedAreas(DropAreas areas) -{ - if (areas == _allowedAreas) - return; - _allowedAreas = areas; - - _cross->reset(); -} - -DropAreas DropOverlay::allowedAreas() const -{ - return _allowedAreas; -} - -void DropOverlay::setAreaWidgets(const QHash& widgets) -{ - _cross->setAreaWidgets(widgets); -} - -DropArea DropOverlay::cursorLocation() const -{ - return _cross->cursorLocation(); -} - -DropArea DropOverlay::showDropOverlay(QWidget* target) -{ - if (_target == target) - { - // Hint: We could update geometry of overlay here. - DropArea da = cursorLocation(); - if (da != _lastLocation) - { - repaint(); - _lastLocation = da; - } - return da; - } - - hideDropOverlay(); - _fullAreaDrop = false; - _target = target; - _targetRect = QRect(); - _lastLocation = InvalidDropArea; - - // Move it over the target. - resize(target->size()); - move(target->mapToGlobal(target->rect().topLeft())); - - show(); - - return cursorLocation(); -} - -void DropOverlay::showDropOverlay(QWidget* target, const QRect& targetAreaRect) -{ - if (_target == target && _targetRect == targetAreaRect) - { - return; - } - - hideDropOverlay(); - _fullAreaDrop = true; - _target = target; - _targetRect = targetAreaRect; - _lastLocation = InvalidDropArea; - - // Move it over the target's area. - resize(targetAreaRect.size()); - move(target->mapToGlobal(QPoint(targetAreaRect.x(), targetAreaRect.y()))); - - show(); - - return; -} - -void DropOverlay::hideDropOverlay() -{ - hide(); - _fullAreaDrop = false; -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - _target.clear(); -#else - _target = 0; -#endif - _targetRect = QRect(); - _lastLocation = InvalidDropArea; -} - -void DropOverlay::paintEvent(QPaintEvent*) -{ - QPainter p(this); - const QColor areaColor = palette().color(QPalette::Active, QPalette::Highlight);//QColor(0, 100, 255) - - // Always draw drop-rect over the entire rect() - if (_fullAreaDrop) - { - QRect r = rect(); - p.fillRect(r, QBrush(areaColor, Qt::Dense4Pattern)); - p.setBrush(QBrush(areaColor)); - p.drawRect(r); - return; - } - - // Draw rect based on location - QRect r = rect(); - const DropArea da = cursorLocation(); - switch (da) - { - case ADS_NS::TopDropArea: - r.setHeight(r.height() / 2); - break; - case ADS_NS::RightDropArea: - r.setX(r.width() / 2); - break; - case ADS_NS::BottomDropArea: - r.setY(r.height() / 2); - break; - case ADS_NS::LeftDropArea: - r.setWidth(r.width() / 2); - break; - case ADS_NS::CenterDropArea: - r = rect(); - break; - default: - r = QRect(); - } - if (!r.isNull()) - { - p.fillRect(r, QBrush(areaColor, Qt::Dense4Pattern)); - p.setBrush(QBrush(areaColor)); - p.drawRect(r); - } - - // Draw rect over the entire size + border. -// auto r = rect(); -// r.setWidth(r.width() - 1); -// r.setHeight(r.height() - 1); - -// p.fillRect(r, QBrush(QColor(0, 100, 255), Qt::Dense4Pattern)); -// p.setBrush(QBrush(QColor(0, 100, 255))); - // p.drawRect(r); -} - -void DropOverlay::showEvent(QShowEvent*) -{ - _cross->show(); -} - -void DropOverlay::hideEvent(QHideEvent*) -{ - _cross->hide(); -} - -void DropOverlay::resizeEvent(QResizeEvent* e) -{ - _cross->resize(e->size()); -} - -void DropOverlay::moveEvent(QMoveEvent* e) -{ - _cross->move(e->pos()); -} - -/////////////////////////////////////////////////////////////////////// - -static QPair gridPosForArea(const DropArea area) -{ - switch (area) - { - case ADS_NS::TopDropArea: - return qMakePair(QPoint(0, 1), (int) Qt::AlignHCenter | Qt::AlignBottom); - case ADS_NS::RightDropArea: - return qMakePair(QPoint(1, 2), (int) Qt::AlignLeft | Qt::AlignVCenter); - case ADS_NS::BottomDropArea: - return qMakePair(QPoint(2, 1), (int) Qt::AlignHCenter | Qt::AlignTop); - case ADS_NS::LeftDropArea: - return qMakePair(QPoint(1, 0), (int) Qt::AlignRight | Qt::AlignVCenter); - case ADS_NS::CenterDropArea: - return qMakePair(QPoint(1, 1), (int) Qt::AlignCenter); - default: - return QPair(); - } -} - -DropOverlayCross::DropOverlayCross(DropOverlay* overlay) : - QWidget(overlay->parentWidget()), - _overlay(overlay), - _widgets() -{ - setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); - setWindowTitle("DropOverlayCross"); - setAttribute(Qt::WA_TranslucentBackground); - - _grid = new QGridLayout(); - _grid->setContentsMargins(0, 0, 0, 0); - _grid->setSpacing(6); - - QBoxLayout* bl1 = new QBoxLayout(QBoxLayout::TopToBottom); - bl1->setContentsMargins(0, 0, 0, 0); - bl1->setSpacing(0); - setLayout(bl1); - - QBoxLayout* bl2 = new QBoxLayout(QBoxLayout::LeftToRight); - bl2->setContentsMargins(0, 0, 0, 0); - bl2->setSpacing(0); - - bl1->addStretch(1); - bl1->addLayout(bl2); - bl2->addStretch(1); - bl2->addLayout(_grid, 0); - bl2->addStretch(1); - bl1->addStretch(1); -} - -DropOverlayCross::~DropOverlayCross() -{ -} - -void DropOverlayCross::setAreaWidgets(const QHash& widgets) -{ - // Delete old widgets. - QMutableHashIterator i(_widgets); - while (i.hasNext()) - { - i.next(); - QWidget* widget = i.value(); - _grid->removeWidget(widget); - delete widget; - i.remove(); - } - - // Insert new widgets into grid. - _widgets = widgets; - QHashIterator i2(_widgets); - while (i2.hasNext()) - { - i2.next(); - const DropArea area = i2.key(); - QWidget* widget = i2.value(); - const QPair opts = gridPosForArea(area); - _grid->addWidget(widget, opts.first.x(), opts.first.y(), (Qt::Alignment) opts.second); - } - reset(); -} - -DropArea DropOverlayCross::cursorLocation() const -{ - const QPoint pos = mapFromGlobal(QCursor::pos()); - QHashIterator i(_widgets); - while (i.hasNext()) - { - i.next(); - if (_overlay->allowedAreas().testFlag(i.key()) - && i.value() - && i.value()->isVisible() - && i.value()->geometry().contains(pos)) - { - return i.key(); - } - } - return InvalidDropArea; -} - -void DropOverlayCross::showEvent(QShowEvent*) -{ - resize(_overlay->size()); - move(_overlay->pos()); -} - -void DropOverlayCross::reset() -{ - QList allAreas; - allAreas << ADS_NS::TopDropArea << ADS_NS::RightDropArea << ADS_NS::BottomDropArea << ADS_NS::LeftDropArea << ADS_NS::CenterDropArea; - const DropAreas allowedAreas = _overlay->allowedAreas(); - - // Update visibility of area widgets based on allowedAreas. - for (int i = 0; i < allAreas.count(); ++i) - { - const QPair opts = gridPosForArea(allAreas.at(i)); - - QLayoutItem* item = _grid->itemAtPosition(opts.first.x(), opts.first.y()); - QWidget* w = NULL; - if (item && (w = item->widget()) != NULL) - { - w->setVisible(allowedAreas.testFlag(allAreas.at(i))); - } - } -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/FloatingWidget.cpp b/AdvancedDockingSystem/src/FloatingWidget.cpp deleted file mode 100644 index d6a39a518..000000000 --- a/AdvancedDockingSystem/src/FloatingWidget.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "ads/FloatingWidget.h" - -#include -#include -#include -#include -#include - -#include "ads/ContainerWidget.h" -#include "ads/SectionTitleWidget.h" -#include "ads/SectionContentWidget.h" -#include "ads/Internal.h" - -ADS_NAMESPACE_BEGIN - -FloatingWidget::FloatingWidget(ContainerWidget* container, SectionContent::RefPtr sc, SectionTitleWidget* titleWidget, SectionContentWidget* contentWidget, QWidget* parent) : - QWidget(parent, Qt::CustomizeWindowHint | Qt::Tool), - _container(container), - _content(sc), - _titleWidget(titleWidget), - _contentWidget(contentWidget) -{ - QBoxLayout* l = new QBoxLayout(QBoxLayout::TopToBottom); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - setLayout(l); - - // Title + Controls - _titleLayout = new QBoxLayout(QBoxLayout::LeftToRight); - _titleLayout->addWidget(titleWidget, 1); - l->addLayout(_titleLayout, 0); - titleWidget->setActiveTab(false); - - if (sc->flags().testFlag(SectionContent::Closeable)) - { - QPushButton* closeButton = new QPushButton(); - closeButton->setObjectName("closeButton"); - closeButton->setFlat(true); - closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); - closeButton->setToolTip(tr("Close")); - closeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - _titleLayout->addWidget(closeButton); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(closeButton, &QPushButton::clicked, this, &FloatingWidget::onCloseButtonClicked); -#else - QObject::connect(closeButton, SIGNAL(clicked(bool)), this, SLOT(onCloseButtonClicked())); -#endif - } - - // Content - l->addWidget(contentWidget, 1); - contentWidget->show(); - -// _container->_floatingWidgets.append(this); -} - -FloatingWidget::~FloatingWidget() -{ - _container->_floatings.removeAll(this); // Note: I don't like this here, but we have to remove it from list... -} - -bool FloatingWidget::takeContent(InternalContentData& data) -{ - data.content = _content; - data.titleWidget = _titleWidget; - data.contentWidget = _contentWidget; - - _titleLayout->removeWidget(_titleWidget); - _titleWidget->setParent(_container); - _titleWidget = NULL; - - layout()->removeWidget(_contentWidget); - _contentWidget->setParent(_container); - _contentWidget = NULL; - - return true; -} - -void FloatingWidget::onCloseButtonClicked() -{ - _container->hideSectionContent(_content); -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/Internal.cpp b/AdvancedDockingSystem/src/Internal.cpp deleted file mode 100644 index 0113ae760..000000000 --- a/AdvancedDockingSystem/src/Internal.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "ads/Internal.h" - -ADS_NAMESPACE_BEGIN - -InternalContentData::InternalContentData() : - titleWidget(NULL), - contentWidget(NULL) -{ -} - -InternalContentData::~InternalContentData() -{ -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/SectionContent.cpp b/AdvancedDockingSystem/src/SectionContent.cpp deleted file mode 100644 index d7efb12d8..000000000 --- a/AdvancedDockingSystem/src/SectionContent.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "ads/SectionContent.h" - -#include -#include - -#include "ads/Internal.h" -#include "ads/ContainerWidget.h" - -ADS_NAMESPACE_BEGIN - -SectionContent::SectionContent() : - _uid(GetNextUid()), - _flags(AllFlags) -{ -} - -SectionContent::RefPtr SectionContent::newSectionContent(const QString& uniqueName, ContainerWidget* container, QWidget* title, QWidget* content) -{ - if (uniqueName.isEmpty()) - { - qFatal("Can not create SectionContent with empty uniqueName"); - return RefPtr(); - } - else if (SCLookupMapByName(container).contains(uniqueName)) - { - qFatal("Can not create SectionContent with already used uniqueName"); - return RefPtr(); - } - else if (!container || !title || !content) - { - qFatal("Can not create SectionContent with NULL values"); - return RefPtr(); - } - - QSharedPointer sc(new SectionContent()); - sc->_uniqueName = uniqueName; - sc->_containerWidget = container; - sc->_titleWidget = title; - sc->_contentWidget = content; - - SCLookupMapById(container).insert(sc->uid(), sc); - SCLookupMapByName(container).insert(sc->uniqueName(), sc); - return sc; -} - -SectionContent::~SectionContent() -{ - if (_containerWidget) - { - SCLookupMapById(_containerWidget).remove(_uid); - SCLookupMapByName(_containerWidget).remove(_uniqueName); - } - delete _titleWidget; - delete _contentWidget; -} - -int SectionContent::uid() const -{ - return _uid; -} - -QString SectionContent::uniqueName() const -{ - return _uniqueName; -} - -ContainerWidget* SectionContent::containerWidget() const -{ - return _containerWidget; -} - -QWidget* SectionContent::titleWidget() const -{ - return _titleWidget; -} - -QWidget* SectionContent::contentWidget() const -{ - return _contentWidget; -} - -SectionContent::Flags SectionContent::flags() const -{ - return _flags; -} - -QString SectionContent::visibleTitle() const -{ - if (_title.isEmpty()) - return _uniqueName; - return _title; -} - -QString SectionContent::title() const -{ - return _title; -} - -void SectionContent::setTitle(const QString& title) -{ - _title = title; -} - -void SectionContent::setFlags(const Flags f) -{ - _flags = f; -} - -int SectionContent::GetNextUid() -{ - static int NextUid = 0; - return ++NextUid; -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/SectionContentWidget.cpp b/AdvancedDockingSystem/src/SectionContentWidget.cpp deleted file mode 100644 index af90f4b22..000000000 --- a/AdvancedDockingSystem/src/SectionContentWidget.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "ads/SectionContentWidget.h" - -#include - -ADS_NAMESPACE_BEGIN - -SectionContentWidget::SectionContentWidget(SectionContent::RefPtr c, QWidget* parent) : - QFrame(parent), - _content(c) -{ - QBoxLayout* l = new QBoxLayout(QBoxLayout::TopToBottom); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - l->addWidget(_content->contentWidget()); - setLayout(l); -} - -SectionContentWidget::~SectionContentWidget() -{ - layout()->removeWidget(_content->contentWidget()); -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/SectionTitleWidget.cpp b/AdvancedDockingSystem/src/SectionTitleWidget.cpp deleted file mode 100644 index d74eb04af..000000000 --- a/AdvancedDockingSystem/src/SectionTitleWidget.cpp +++ /dev/null @@ -1,291 +0,0 @@ -#include "ads/SectionTitleWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef ADS_ANIMATIONS_ENABLED -#include -#include -#endif - -#include "ads/Internal.h" -#include "ads/DropOverlay.h" -#include "ads/SectionContent.h" -#include "ads/SectionWidget.h" -#include "ads/FloatingWidget.h" -#include "ads/ContainerWidget.h" - -ADS_NAMESPACE_BEGIN - -SectionTitleWidget::SectionTitleWidget(SectionContent::RefPtr content, QWidget* parent) : - QFrame(parent), - _content(content), - _tabMoving(false), - _activeTab(false) -{ - QBoxLayout* l = new QBoxLayout(QBoxLayout::LeftToRight); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - l->addWidget(content->titleWidget()); - setLayout(l); -} - -SectionTitleWidget::~SectionTitleWidget() -{ - layout()->removeWidget(_content->titleWidget()); -} - -bool SectionTitleWidget::isActiveTab() const -{ - return _activeTab; -} - -void SectionTitleWidget::setActiveTab(bool active) -{ - if (active != _activeTab) - { - _activeTab = active; - - style()->unpolish(this); - style()->polish(this); - update(); - - emit activeTabChanged(); - } -} - -void SectionTitleWidget::mousePressEvent(QMouseEvent* ev) -{ - if (ev->button() == Qt::LeftButton) - { - ev->accept(); - _dragStartPos = ev->pos(); - return; - } - QFrame::mousePressEvent(ev); -} - -void SectionTitleWidget::mouseReleaseEvent(QMouseEvent* ev) -{ - SectionWidget* section = NULL; - ContainerWidget* cw = findParentContainerWidget(this); - - // Drop contents of FloatingWidget into SectionWidget. - if (_fw) - { - SectionWidget* sw = cw->sectionAt(cw->mapFromGlobal(ev->globalPos())); - if (sw) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::AllAreas); - DropArea loc = cw->_dropOverlay->showDropOverlay(sw); - if (loc != InvalidDropArea) - { -#if !defined(ADS_ANIMATIONS_ENABLED) - InternalContentData data; - _fw->takeContent(data); - _fw->deleteLater(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - _fw.clear(); -#else - _fw = 0; -#endif - cw->dropContent(data, sw, loc, true); -#else - QPropertyAnimation* moveAnim = new QPropertyAnimation(_fw, "pos", this); - moveAnim->setStartValue(_fw->pos()); - moveAnim->setEndValue(sw->mapToGlobal(sw->rect().topLeft())); - moveAnim->setDuration(ADS_ANIMATION_DURATION); - - QPropertyAnimation* resizeAnim = new QPropertyAnimation(_fw, "size", this); - resizeAnim->setStartValue(_fw->size()); - resizeAnim->setEndValue(sw->size()); - resizeAnim->setDuration(ADS_ANIMATION_DURATION); - - QParallelAnimationGroup* animGroup = new QParallelAnimationGroup(this); - QObject::connect(animGroup, &QPropertyAnimation::finished, [this, data, sw, loc]() - { - InternalContentData data = _fw->takeContent(); - _fw->deleteLater(); - _fw.clear(); - cw->dropContent(data, sw, loc); - }); - animGroup->addAnimation(moveAnim); - animGroup->addAnimation(resizeAnim); - animGroup->start(QAbstractAnimation::DeleteWhenStopped); -#endif - } - } - // Mouse is over a outer-edge drop area - else - { - DropArea dropArea = ADS_NS::InvalidDropArea; - if (cw->outerTopDropRect().contains(cw->mapFromGlobal(ev->globalPos()))) - dropArea = ADS_NS::TopDropArea; - if (cw->outerRightDropRect().contains(cw->mapFromGlobal(ev->globalPos()))) - dropArea = ADS_NS::RightDropArea; - if (cw->outerBottomDropRect().contains(cw->mapFromGlobal(ev->globalPos()))) - dropArea = ADS_NS::BottomDropArea; - if (cw->outerLeftDropRect().contains(cw->mapFromGlobal(ev->globalPos()))) - dropArea = ADS_NS::LeftDropArea; - - if (dropArea != ADS_NS::InvalidDropArea) - { -#if !defined(ADS_ANIMATIONS_ENABLED) - InternalContentData data; - _fw->takeContent(data); - _fw->deleteLater(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - _fw.clear(); -#else - _fw = 0; -#endif - cw->dropContent(data, NULL, dropArea, true); -#else -#endif - } - } - } - // End of tab moving, change order now - else if (_tabMoving - && (section = findParentSectionWidget(this)) != NULL) - { - // Find tab under mouse - QPoint pos = ev->globalPos(); - pos = section->mapFromGlobal(pos); - const int fromIndex = section->indexOfContent(_content); - const int toIndex = section->indexOfContentByTitlePos(pos, this); - section->moveContent(fromIndex, toIndex); - } - - if (!_dragStartPos.isNull()) - emit clicked(); - - // Reset - _dragStartPos = QPoint(); - _tabMoving = false; - cw->_dropOverlay->hideDropOverlay(); - QFrame::mouseReleaseEvent(ev); -} - -void SectionTitleWidget::mouseMoveEvent(QMouseEvent* ev) -{ - ContainerWidget* cw = findParentContainerWidget(this); - SectionWidget* section = NULL; - - // Move already existing FloatingWidget - if (_fw && (ev->buttons() & Qt::LeftButton)) - { - ev->accept(); - - const QPoint moveToPos = ev->globalPos() - (_dragStartPos + QPoint(ADS_WINDOW_FRAME_BORDER_WIDTH, ADS_WINDOW_FRAME_BORDER_WIDTH)); - _fw->move(moveToPos); - - // Show drop indicator - if (true) - { - // Mouse is over a SectionWidget - section = cw->sectionAt(cw->mapFromGlobal(QCursor::pos())); - if (section) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::AllAreas); - cw->_dropOverlay->showDropOverlay(section); - } - // Mouse is at the edge of the ContainerWidget - // Top, Right, Bottom, Left - else if (cw->outerTopDropRect().contains(cw->mapFromGlobal(QCursor::pos()))) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::TopDropArea); - cw->_dropOverlay->showDropOverlay(cw, cw->outerTopDropRect()); - } - else if (cw->outerRightDropRect().contains(cw->mapFromGlobal(QCursor::pos()))) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::RightDropArea); - cw->_dropOverlay->showDropOverlay(cw, cw->outerRightDropRect()); - } - else if (cw->outerBottomDropRect().contains(cw->mapFromGlobal(QCursor::pos()))) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::BottomDropArea); - cw->_dropOverlay->showDropOverlay(cw, cw->outerBottomDropRect()); - } - else if (cw->outerLeftDropRect().contains(cw->mapFromGlobal(QCursor::pos()))) - { - cw->_dropOverlay->setAllowedAreas(ADS_NS::LeftDropArea); - cw->_dropOverlay->showDropOverlay(cw, cw->outerLeftDropRect()); - } - else - { - cw->_dropOverlay->hideDropOverlay(); - } - } - return; - } - // Begin to drag/float the SectionContent. - else if (!_fw && !_dragStartPos.isNull() && (ev->buttons() & Qt::LeftButton) - && (section = findParentSectionWidget(this)) != NULL - && !section->titleAreaGeometry().contains(section->mapFromGlobal(ev->globalPos()))) - { - ev->accept(); - - // Create floating widget. - InternalContentData data; - if (!section->takeContent(_content->uid(), data)) - { - qWarning() << "THIS SHOULD NOT HAPPEN!!" << _content->uid() << _content->uniqueName(); - return; - } - - _fw = new FloatingWidget(cw, data.content, data.titleWidget, data.contentWidget, cw); - _fw->resize(section->size()); - cw->_floatings.append(_fw); // Note: I don't like this... - - const QPoint moveToPos = ev->globalPos() - (_dragStartPos + QPoint(ADS_WINDOW_FRAME_BORDER_WIDTH, ADS_WINDOW_FRAME_BORDER_WIDTH)); - _fw->move(moveToPos); - _fw->show(); - - // Delete old section, if it is empty now. - if (section->contents().isEmpty()) - { - delete section; - section = NULL; - } - deleteEmptySplitter(cw); - return; - } - // Handle movement of this tab - else if (_tabMoving - && (section = findParentSectionWidget(this)) != NULL) - { - ev->accept(); - - int left, top, right, bottom; - getContentsMargins(&left, &top, &right, &bottom); - QPoint moveToPos = mapToParent(ev->pos()) - _dragStartPos; - moveToPos.setY(0/* + top*/); - move(moveToPos); - - return; - } - // Begin to drag title inside the title area to switch its position inside the SectionWidget. - else if (!_dragStartPos.isNull() && (ev->buttons() & Qt::LeftButton) - && (ev->pos() - _dragStartPos).manhattanLength() >= QApplication::startDragDistance() // Wait a few pixels before start moving - && (section = findParentSectionWidget(this)) != NULL - && section->titleAreaGeometry().contains(section->mapFromGlobal(ev->globalPos()))) - { - ev->accept(); - - _tabMoving = true; - raise(); // Raise current title-widget above other tabs - - return; - } - QFrame::mouseMoveEvent(ev); -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/SectionWidget.cpp b/AdvancedDockingSystem/src/SectionWidget.cpp deleted file mode 100644 index 270affde3..000000000 --- a/AdvancedDockingSystem/src/SectionWidget.cpp +++ /dev/null @@ -1,458 +0,0 @@ -#include "ads/SectionWidget.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(ADS_ANIMATIONS_ENABLED) -#include -#endif - -#include "ads/Internal.h" -#include "ads/DropOverlay.h" -#include "ads/SectionContent.h" -#include "ads/SectionTitleWidget.h" -#include "ads/SectionContentWidget.h" -#include "ads/FloatingWidget.h" -#include "ads/ContainerWidget.h" - -ADS_NAMESPACE_BEGIN - -SectionWidget::SectionWidget(ContainerWidget* parent) : - QFrame(parent), - _uid(GetNextUid()), - _container(parent), - _tabsLayout(NULL), - _tabsLayoutInitCount(0), - _contentsLayout(NULL), - _mousePressTitleWidget(NULL) -{ - QBoxLayout* l = new QBoxLayout(QBoxLayout::TopToBottom); - l->setContentsMargins(0, 0, 0, 0); - l->setSpacing(0); - setLayout(l); - - /* top area with tabs and close button */ - - _topLayout = new QBoxLayout(QBoxLayout::LeftToRight); - _topLayout->setContentsMargins(0, 0, 0, 0); - _topLayout->setSpacing(0); - l->addLayout(_topLayout); - - _tabsScrollArea = new SectionWidgetTabsScrollArea(this); - _topLayout->addWidget(_tabsScrollArea, 1); - - _tabsContainerWidget = new QWidget(); - _tabsContainerWidget->setObjectName("tabsContainerWidget"); - _tabsScrollArea->setWidget(_tabsContainerWidget); - - _tabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); - _tabsLayout->setContentsMargins(0, 0, 0, 0); - _tabsLayout->setSpacing(0); - _tabsLayout->addStretch(1); - _tabsContainerWidget->setLayout(_tabsLayout); - - _tabsMenuButton = new QPushButton(); - _tabsMenuButton->setObjectName("tabsMenuButton"); - _tabsMenuButton->setFlat(true); - _tabsMenuButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarUnshadeButton)); - _tabsMenuButton->setMaximumWidth(_tabsMenuButton->iconSize().width()); - _topLayout->addWidget(_tabsMenuButton, 0); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - //QObject::connect(_tabsMenuButton, &QPushButton::clicked, this, &SectionWidget::onTabsMenuButtonClicked); -#else - //QObject::connect(_tabsMenuButton, SIGNAL(clicked()), this, SLOT(onTabsMenuButtonClicked())); -#endif - - _closeButton = new QPushButton(); - _closeButton->setObjectName("closeButton"); - _closeButton->setFlat(true); - _closeButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); - _closeButton->setToolTip(tr("Close")); - _closeButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - _topLayout->addWidget(_closeButton, 0); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(_closeButton, &QPushButton::clicked, this, &SectionWidget::onCloseButtonClicked); -#else - QObject::connect(_closeButton, SIGNAL(clicked(bool)), this, SLOT(onCloseButtonClicked())); -#endif - - _tabsLayoutInitCount = _tabsLayout->count(); - - /* central area with contents */ - - _contentsLayout = new QStackedLayout(); - _contentsLayout->setContentsMargins(0, 0, 0, 0); - _contentsLayout->setSpacing(0); - l->addLayout(_contentsLayout, 1); - -#if defined(ADS_ANIMATIONS_ENABLED) - QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this); - shadow->setOffset(0, 0); - shadow->setBlurRadius(8); - setGraphicsEffect(shadow); -#endif - - SWLookupMapById(_container).insert(_uid, this); -} - -SectionWidget::~SectionWidget() -{ - if (_container) - { - SWLookupMapById(_container).remove(_uid); - _container->_sections.removeAll(this); // Note: I don't like this here, but we have to remove it from list... - } - - // Delete empty QSplitter. - QSplitter* splitter = findParentSplitter(this); - if (splitter && splitter->count() == 0) - { - splitter->deleteLater(); - } -} - -int SectionWidget::uid() const -{ - return _uid; -} - -ContainerWidget* SectionWidget::containerWidget() const -{ - return _container; -} - -QRect SectionWidget::titleAreaGeometry() const -{ - return _topLayout->geometry(); -} - -QRect SectionWidget::contentAreaGeometry() const -{ - return _contentsLayout->geometry(); -} - -void SectionWidget::addContent(const SectionContent::RefPtr& c) -{ - _contents.append(c); - - SectionTitleWidget* title = new SectionTitleWidget(c, NULL); - _sectionTitles.append(title); - _tabsLayout->insertWidget(_tabsLayout->count() - _tabsLayoutInitCount, title); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(title, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked); -#else - QObject::connect(title, SIGNAL(clicked()), this, SLOT(onSectionTitleClicked())); -#endif - - SectionContentWidget* content = new SectionContentWidget(c, NULL); - _sectionContents.append(content); - _contentsLayout->addWidget(content); - - // Active first TAB. - if (_contents.size() == 1) - setCurrentIndex(0); - // Switch to newest. -// else -// setCurrentIndex(_contentsLayout->count() - 1); - - updateTabsMenu(); -} - -void SectionWidget::addContent(const InternalContentData& data, bool autoActivate) -{ - _contents.append(data.content); - - // Add title-widget to tab-bar - // #FIX: Make it visible, since it is possible that it was hidden previously. - _sectionTitles.append(data.titleWidget); - _tabsLayout->insertWidget(_tabsLayout->count() - _tabsLayoutInitCount, data.titleWidget); - data.titleWidget->setVisible(true); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(data.titleWidget, &SectionTitleWidget::clicked, this, &SectionWidget::onSectionTitleClicked); -#else - QObject::connect(data.titleWidget, SIGNAL(clicked()), this, SLOT(onSectionTitleClicked())); -#endif - - // Add content-widget to stack. - // Visibility is managed by QStackedWidget. - _sectionContents.append(data.contentWidget); - _contentsLayout->addWidget(data.contentWidget); - - // Activate first TAB. - if (_contents.size() == 1) - setCurrentIndex(0); - // Switch to just added TAB. - else if (autoActivate) - setCurrentIndex(_contents.count() - 1); - // Mark it as inactive tab. - else - data.titleWidget->setActiveTab(false); // or: setCurrentIndex(currentIndex()) - - updateTabsMenu(); -} - -bool SectionWidget::takeContent(int uid, InternalContentData& data) -{ - // Find SectionContent. - SectionContent::RefPtr sc; - int index = -1; - for (int i = 0; i < _contents.count(); i++) - { - if (_contents[i]->uid() != uid) - continue; - index = i; - sc = _contents.takeAt(i); - break; - } - if (!sc) - return false; - - // Title wrapper widget (TAB) - SectionTitleWidget* title = _sectionTitles.takeAt(index); - if (title) - { - _tabsLayout->removeWidget(title); -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - title->setAttribute(Qt::WA_WState_Created, false); /* fix: floating rubberband #16 */ -#endif - title->disconnect(this); - title->setParent(_container); -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) - title->setAttribute(Qt::WA_WState_Created, true); /* fix: floating rubberband #16 */ -#endif - } - - // Content wrapper widget (CONTENT) - SectionContentWidget* content = _sectionContents.takeAt(index); - if (content) - { - _contentsLayout->removeWidget(content); - content->disconnect(this); - content->setParent(_container); - } - - // Select the previous tab as activeTab. - if (_contents.size() > 0 && title->isActiveTab()) - { - if (index > 0) - setCurrentIndex(index - 1); - else - setCurrentIndex(0); - } - - updateTabsMenu(); - - data.content = sc; - data.titleWidget = title; - data.contentWidget = content; - return !data.content.isNull(); -} - -int SectionWidget::indexOfContent(const SectionContent::RefPtr& c) const -{ - return _contents.indexOf(c); -} - -int SectionWidget::indexOfContentByUid(int uid) const -{ - for (int i = 0; i < _contents.count(); ++i) - { - if (_contents[i]->uid() == uid) - return i; - } - return -1; -} - -int SectionWidget::indexOfContentByTitlePos(const QPoint& p, QWidget* exclude) const -{ - int index = -1; - for (int i = 0; i < _sectionTitles.size(); ++i) - { - if (_sectionTitles[i]->geometry().contains(p) && (exclude == NULL || _sectionTitles[i] != exclude)) - { - index = i; - break; - } - } - return index; -} - -int SectionWidget::currentIndex() const -{ - return _contentsLayout->currentIndex(); -} - -void SectionWidget::moveContent(int from, int to) -{ - if (from >= _contents.size() || from < 0 || to >= _contents.size() || to < 0 || from == to) - { - qDebug() << "Invalid index for tab movement" << from << to; - _tabsLayout->update(); - return; - } - - _contents.move(from, to); - _sectionTitles.move(from, to); - _sectionContents.move(from, to); - - QLayoutItem* liFrom = NULL; - liFrom = _tabsLayout->takeAt(from); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - _tabsLayout->insertItem(to, liFrom); -#else - _tabsLayout->insertWidget(to, liFrom->widget()); - delete liFrom; - liFrom = NULL; -#endif - - liFrom = _contentsLayout->takeAt(from); - _contentsLayout->insertWidget(to, liFrom->widget()); - delete liFrom; - - updateTabsMenu(); -} - -void SectionWidget::showEvent(QShowEvent*) -{ - _tabsScrollArea->ensureWidgetVisible(_sectionTitles.at(currentIndex())); -} - -void SectionWidget::setCurrentIndex(int index) -{ - if (index < 0 || index > _contents.count() - 1) - { - qWarning() << Q_FUNC_INFO << "Invalid index" << index; - return; - } - - // Set active TAB - for (int i = 0; i < _tabsLayout->count(); ++i) - { - QLayoutItem* item = _tabsLayout->itemAt(i); - if (item->widget()) - { - SectionTitleWidget* stw = dynamic_cast(item->widget()); - if (stw) - { - if (i == index) - { - stw->setActiveTab(true); - _tabsScrollArea->ensureWidgetVisible(stw); - if (stw->_content->flags().testFlag(SectionContent::Closeable)) - _closeButton->setEnabled(true); - else - _closeButton->setEnabled(false); - } - else - stw->setActiveTab(false); - } - } - } - - // Set active CONTENT - _contentsLayout->setCurrentIndex(index); -} - -void SectionWidget::onSectionTitleClicked() -{ - SectionTitleWidget* stw = qobject_cast(sender()); - if (stw) - { - int index = _tabsLayout->indexOf(stw); - setCurrentIndex(index); - } -} - -void SectionWidget::onCloseButtonClicked() -{ - const int index = currentIndex(); - if (index < 0 || index > _contents.size() - 1) - return; - SectionContent::RefPtr sc = _contents.at(index); - if (sc.isNull()) - return; - _container->hideSectionContent(sc); -} - -void SectionWidget::onTabsMenuActionTriggered(bool) -{ - QAction* a = qobject_cast(sender()); - if (a) - { - const int uid = a->data().toInt(); - const int index = indexOfContentByUid(uid); - if (index >= 0) - setCurrentIndex(index); - } -} - -void SectionWidget::updateTabsMenu() -{ - QMenu* m = new QMenu(); - for (int i = 0; i < _contents.count(); ++i) - { - const SectionContent::RefPtr& sc = _contents.at(i); - QAction* a = m->addAction(QIcon(), sc->visibleTitle()); - a->setData(sc->uid()); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QObject::connect(a, &QAction::triggered, this, &SectionWidget::onTabsMenuActionTriggered); -#else - QObject::connect(a, SIGNAL(triggered(bool)), this, SLOT(onTabsMenuActionTriggered(bool))); -#endif - } - QMenu* old = _tabsMenuButton->menu(); - _tabsMenuButton->setMenu(m); - delete old; -} - -int SectionWidget::GetNextUid() -{ - static int NextUid = 0; - return ++NextUid; -} - -/*****************************************************************************/ - -SectionWidgetTabsScrollArea::SectionWidgetTabsScrollArea(SectionWidget*, - QWidget* parent) : - QScrollArea(parent) -{ - /* Important: QSizePolicy::Ignored makes the QScrollArea behaves - like a QLabel and automatically fits into the layout. */ - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); - setFrameStyle(QFrame::NoFrame); - setWidgetResizable(true); - setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); -} - -SectionWidgetTabsScrollArea::~SectionWidgetTabsScrollArea() -{ -} - -void SectionWidgetTabsScrollArea::wheelEvent(QWheelEvent* e) -{ - e->accept(); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - const int direction = e->angleDelta().y(); -#else - const int direction = e->delta(); -#endif - if (direction < 0) - horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); - else - horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); -} - -ADS_NAMESPACE_END diff --git a/AdvancedDockingSystem/src/Serialization.cpp b/AdvancedDockingSystem/src/Serialization.cpp deleted file mode 100644 index bf0859f19..000000000 --- a/AdvancedDockingSystem/src/Serialization.cpp +++ /dev/null @@ -1,440 +0,0 @@ -#include "ads/Serialization.h" - -#include - -ADS_NAMESPACE_SER_BEGIN - -/* - \namespace ads::serialization - - Serialization of ContainerWidget - -------------------------------- - - # Data Format Header - - quint32 Magic - quint32 Major Version - quint32 Minor Version - - # Offsets of available contents - - qint32 Number of offset headers - LOOP - qint32 Type (e.g. Hierachy, SectionIndex) - qint64 Offset - qint64 Length - - # Type: Hierachy - # Used to recreate the GUI geometry and state. - - int Number of floating widgets - LOOP Floating widgets - QString Unique name of content - QByteArray Geometry of floating widget - bool Visibility - - int Number of layout items (Valid values: 0, 1) - IF 0 - int Number of hidden contents - LOOP Contents - QString Unique name of content - ELSEIF 1 - ... todo ... - ENDIF - - # Type: SectionIndex - # Can be used for quick lookups on details for SectionWidgets. - # It includes sizes and its contents. - - qint32 Number of section-widgets - LOOP - qint32 Width - qint32 Height - qint32 Current active tab index - qint32 Number of contents - LOOP - QString Unique name of content - bool Visibility - qint32 Preferred tab index - -*/ - -/////////////////////////////////////////////////////////////////////////////// - -qint32 HeaderEntity::MAGIC = 0x00001337; -qint32 HeaderEntity::MAJOR_VERSION = 2; -qint32 HeaderEntity::MINOR_VERSION = 0; - -HeaderEntity::HeaderEntity() : - magic(0), majorVersion(0), minorVersion(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const HeaderEntity& data) -{ - out << data.magic; - out << data.majorVersion; - out << data.minorVersion; - return out; -} - -QDataStream& operator>>(QDataStream& in, HeaderEntity& data) -{ - in >> data.magic; - in >> data.majorVersion; - in >> data.minorVersion; - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -OffsetsHeaderEntity::OffsetsHeaderEntity() : - entriesCount(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const OffsetsHeaderEntity& data) -{ - out << data.entriesCount; - for (int i = 0; i < data.entriesCount; ++i) - { - out << data.entries.at(i); - } - return out; -} - -QDataStream& operator>>(QDataStream& in, OffsetsHeaderEntity& data) -{ - in >> data.entriesCount; - for (int i = 0; i < data.entriesCount; ++i) - { - OffsetsHeaderEntryEntity entry; - in >> entry; - data.entries.append(entry); - } - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -OffsetsHeaderEntryEntity::OffsetsHeaderEntryEntity() : - type(ET_Unknown), offset(0), contentSize(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const OffsetsHeaderEntryEntity& data) -{ - out << data.type; - out << data.offset; - out << data.contentSize; - return out; -} - -QDataStream& operator>>(QDataStream& in, OffsetsHeaderEntryEntity& data) -{ - in >> data.type; - in >> data.offset; - in >> data.contentSize; - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -SectionEntity::SectionEntity() : - x(0), y(0), width(0), height(0), currentIndex(0), sectionContentsCount(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const SectionEntity& data) -{ - out << data.x; - out << data.y; - out << data.width; - out << data.height; - out << data.currentIndex; - out << data.sectionContentsCount; - for (int i = 0; i < data.sectionContentsCount; ++i) - { - out << data.sectionContents.at(i); - } - return out; -} - -QDataStream& operator>>(QDataStream& in, SectionEntity& data) -{ - in >> data.x; - in >> data.y; - in >> data.width; - in >> data.height; - in >> data.currentIndex; - in >> data.sectionContentsCount; - for (int i = 0; i < data.sectionContentsCount; ++i) - { - SectionContentEntity sc; - in >> sc; - data.sectionContents.append(sc); - } - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -SectionContentEntity::SectionContentEntity() : - visible(false), preferredIndex(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const SectionContentEntity& data) -{ - out << data.uniqueName; - out << data.visible; - out << data.preferredIndex; - return out; -} - -QDataStream& operator>>(QDataStream& in, SectionContentEntity& data) -{ - in >> data.uniqueName; - in >> data.visible; - in >> data.preferredIndex; - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -FloatingContentEntity::FloatingContentEntity() : - xpos(0), ypos(0), width(0), height(0), visible(false) -{ -} - -QDataStream& operator<<(QDataStream& out, const FloatingContentEntity& data) -{ - out << data.uniqueName; - out << data.xpos; - out << data.ypos; - out << data.width; - out << data.height; - out << data.visible; - return out; -} - -QDataStream& operator>>(QDataStream& in, FloatingContentEntity& data) -{ - in >> data.uniqueName; - in >> data.xpos; - in >> data.ypos; - in >> data.width; - in >> data.height; - in >> data.visible; - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -HierarchyData::HierarchyData() -{ -} - -QDataStream& operator<<(QDataStream& out, const HierarchyData& data) -{ - out << data.data; - return out; -} - -QDataStream& operator>>(QDataStream& in, HierarchyData& data) -{ - in >> data.data; - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -SectionIndexData::SectionIndexData() : - sectionsCount(0) -{ -} - -QDataStream& operator<<(QDataStream& out, const SectionIndexData& data) -{ - out << data.sectionsCount; - for (int i = 0; i < data.sectionsCount; ++i) - { - out << data.sections.at(i); - } - return out; -} - -QDataStream& operator>>(QDataStream& in, SectionIndexData& data) -{ - in >> data.sectionsCount; - for (int i = 0; i < data.sectionsCount; ++i) - { - SectionEntity s; - in >> s; - data.sections.append(s); - } - return in; -} - -/////////////////////////////////////////////////////////////////////////////// - -InMemoryWriter::InMemoryWriter() -{ - _contentBuffer.open(QIODevice::ReadWrite); -} - -bool InMemoryWriter::write(qint32 entryType, const QByteArray& data) -{ - OffsetsHeaderEntryEntity entry; - entry.type = entryType; - entry.offset = _contentBuffer.pos(); // Relative offset! - entry.contentSize = data.size(); - - _contentBuffer.write(data); - - _offsetsHeader.entries.append(entry); - _offsetsHeader.entriesCount += 1; - - return true; -} - -bool InMemoryWriter::write(const SectionIndexData& data) -{ - OffsetsHeaderEntryEntity entry; - entry.type = ET_SectionIndex; - entry.offset = _contentBuffer.pos(); // Relative offset! - - QDataStream out(&_contentBuffer); - out.setVersion(QDataStream::Qt_4_5); - out << data; - - entry.contentSize = _contentBuffer.size() - entry.offset; - - _offsetsHeader.entries.append(entry); - _offsetsHeader.entriesCount += 1; - - return true; -} - -QByteArray InMemoryWriter::toByteArray() const -{ - QByteArray data; - QDataStream out(&data, QIODevice::ReadWrite); - out.setVersion(QDataStream::Qt_4_5); - - // Basic format header. - HeaderEntity header; - header.magic = HeaderEntity::MAGIC; - header.majorVersion = HeaderEntity::MAJOR_VERSION; - header.minorVersion = HeaderEntity::MINOR_VERSION; - out << header; - - // Offsets-Header - // - Save begin pos - // - Write OffsetsHeader - // - Convert relative- to absolute-offsets - // - Seek back to begin-pos and write OffsetsHeader again. - // Use a copy of OffsetsHeader to keep the _offsetsHeader relative. - const qint64 posOffsetHeaders = out.device()->pos(); - OffsetsHeaderEntity offsetsHeader = _offsetsHeader; - out << offsetsHeader; - - // Now we know the size of the entire header. - // We can update the relative- to absolute-offsets now. - const qint64 allHeaderSize = out.device()->pos(); - for (int i = 0; i < offsetsHeader.entriesCount; ++i) - { - offsetsHeader.entries[i].offset += allHeaderSize; // Absolute offset! - } - - // Seek back and write again with absolute offsets. - // TODO Thats not nice, but it works... - out.device()->seek(posOffsetHeaders); - out << offsetsHeader; - - // Write contents. - out.writeRawData(_contentBuffer.data().constData(), _contentBuffer.size()); - - return data; -} - -/////////////////////////////////////////////////////////////////////////////// - -InMemoryReader::InMemoryReader(const QByteArray& data) : - _data(data) -{ -} - -bool InMemoryReader::initReadHeader() -{ - QDataStream in(_data); - in.setVersion(QDataStream::Qt_4_5); - - // Basic format header. - HeaderEntity header; - in >> header; - if (header.magic != HeaderEntity::MAGIC) - { - qWarning() << QString("invalid format (magic=%1)").arg(header.magic); - return false; - } - if (header.majorVersion != HeaderEntity::MAJOR_VERSION) - { - qWarning() << QString("format is too new (major=%1; minor=%2)") - .arg(header.majorVersion).arg(header.minorVersion); - return false; - } - - // OffsetsHeader. - in >> _offsetsHeader; - - return !in.atEnd(); -} - -bool InMemoryReader::read(qint32 entryType, QByteArray& data) -{ - // Find offset for "type". - int index = -1; - for (int i = 0; i < _offsetsHeader.entriesCount; ++i) - { - if (_offsetsHeader.entries.at(i).type == entryType) - { - index = i; - break; - } - } - if (index < 0) - return false; - else if (_offsetsHeader.entries.at(index).offset == 0) - return false; - - const OffsetsHeaderEntryEntity& entry = _offsetsHeader.entries.at(index); - - QDataStream in(_data); - in.setVersion(QDataStream::Qt_4_5); - in.device()->seek(entry.offset); - - char* buff = new char[entry.contentSize]; - in.readRawData(buff, entry.contentSize); - data.append(buff, entry.contentSize); - delete[] buff; - - return true; -} - -bool InMemoryReader::read(SectionIndexData& sid) -{ - QByteArray sidData; - if (!read(ET_SectionIndex, sidData) || sidData.isEmpty()) - return false; - - QDataStream in(sidData); - in.setVersion(QDataStream::Qt_4_5); - in >> sid; - - return in.atEnd(); -} - -/////////////////////////////////////////////////////////////////////////////// - -ADS_NAMESPACE_SER_END diff --git a/AdvancedDockingSystemDemo/AdvancedDockingSystemDemo.pro b/AdvancedDockingSystemDemo/AdvancedDockingSystemDemo.pro deleted file mode 100644 index 34b2f7e2b..000000000 --- a/AdvancedDockingSystemDemo/AdvancedDockingSystemDemo.pro +++ /dev/null @@ -1,55 +0,0 @@ -TARGET = AdvancedDockingSystemDemo - -QT += core gui -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -greaterThan(QT_MAJOR_VERSION, 4): DEFINES += ADS_NAMESPACE_ENABLED - -windows { - # MinGW - *-g++* { - QMAKE_CXXFLAGS += -std=c++11 - } - # MSVC - *-msvc* { - } -} - -SOURCES += \ - src/main.cpp \ - src/mainwindow.cpp \ - src/icontitlewidget.cpp \ - src/dialogs/SectionContentListModel.cpp \ - src/dialogs/SectionContentListWidget.cpp - -HEADERS += \ - src/mainwindow.h \ - src/icontitlewidget.h \ - src/dialogs/SectionContentListModel.h \ - src/dialogs/SectionContentListWidget.h - -FORMS += \ - src/mainwindow.ui \ - src/dialogs/SectionContentListWidget.ui - - -# Dependency: AdvancedDockingSystem (staticlib) -#win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/release/ -lAdvancedDockingSystem -#else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/debug/ -lAdvancedDockingSystem -#else:unix: LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/ -lAdvancedDockingSystem - -#INCLUDEPATH += $$PWD/../AdvancedDockingSystem/include -#DEPENDPATH += $$PWD/../AdvancedDockingSystem/include - -#win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../AdvancedDockingSystem/release/libAdvancedDockingSystem.a -#else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../AdvancedDockingSystem/debug/libAdvancedDockingSystem.a -#else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../AdvancedDockingSystem/release/AdvancedDockingSystem.lib -#else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../AdvancedDockingSystem/debug/AdvancedDockingSystem.lib -#else:unix: PRE_TARGETDEPS += $$OUT_PWD/../AdvancedDockingSystem/libAdvancedDockingSystem.a - -# Dependency: AdvancedDockingSystem (shared) -win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/release/ -lAdvancedDockingSystem1 -else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/debug/ -lAdvancedDockingSystem1 -else:unix: LIBS += -L$$OUT_PWD/../AdvancedDockingSystem/ -lAdvancedDockingSystem - -INCLUDEPATH += $$PWD/../AdvancedDockingSystem/include -DEPENDPATH += $$PWD/../AdvancedDockingSystem/include diff --git a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.cpp b/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.cpp deleted file mode 100644 index 11885fb21..000000000 --- a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "SectionContentListModel.h" - -SectionContentListModel::SectionContentListModel(QObject* parent) : - QAbstractTableModel(parent) -{ - _headers.insert(UidColumn, "UID"); - _headers.insert(UniqueNameColumn, "Unique Name"); - _headers.insert(TitleColumn, "Title"); - _headers.insert(VisibleColumn, "Visible"); -} - -SectionContentListModel::~SectionContentListModel() -{ -} - -void SectionContentListModel::init(ADS_NS::ContainerWidget* cw) -{ -#if QT_VERSION >= 0x050000 - beginResetModel(); - _cw = cw; - _contents = _cw->contents(); - endResetModel(); -#else - _cw = cw; - _contents = _cw->contents(); - reset(); -#endif -} - -int SectionContentListModel::columnCount(const QModelIndex& parent) const -{ - Q_UNUSED(parent) - return _headers.count(); -} - -QVariant SectionContentListModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - if (orientation == Qt::Horizontal && role == Qt::DisplayRole) - return _headers.value(section); - return QVariant(); -} - -int SectionContentListModel::rowCount(const QModelIndex& parent) const -{ - Q_UNUSED(parent) - return _contents.count(); -} - -QVariant SectionContentListModel::data(const QModelIndex& index, int role) const -{ - if (!index.isValid() || index.row() > rowCount(index) - 1) - return QVariant(); - - const ADS_NS::SectionContent::RefPtr sc = _contents.at(index.row()); - if (sc.isNull()) - return QVariant(); - - switch (role) - { - case Qt::DisplayRole: - { - switch (index.column()) - { - case UidColumn: - return sc->uid(); - case UniqueNameColumn: - return sc->uniqueName(); - case TitleColumn: - return sc->title(); - case VisibleColumn: - return _cw->isSectionContentVisible(sc); - } - } - } - return QVariant(); -} - -bool SectionContentListModel::removeRows(int row, int count, const QModelIndex& parent) -{ - if (row > rowCount(parent) - 1) - return false; - - const int first = row; - const int last = row + count - 1; - beginRemoveRows(parent, first, last); - - for (int i = last; i >= first; --i) - { - const ADS_NS::SectionContent::RefPtr sc = _contents.at(i); - _cw->removeSectionContent(sc); - _contents.removeAt(i); - } - - endRemoveRows(); - return true; -} diff --git a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.h b/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.h deleted file mode 100644 index 2f0bcaf23..000000000 --- a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListModel.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ADS_SECTIONCONTENTMODEL_H -#define ADS_SECTIONCONTENTMODEL_H - -#include -#include -#include -#include - -#include "ads/API.h" -#include "ads/ContainerWidget.h" -#include "ads/SectionContent.h" -ADS_NAMESPACE_BEGIN -class ContainerWidget; -ADS_NAMESPACE_END - -class SectionContentListModel : public QAbstractTableModel -{ - Q_OBJECT - -public: - enum Column - { - UidColumn, - UniqueNameColumn, - TitleColumn, - VisibleColumn - }; - - SectionContentListModel(QObject* parent); - virtual ~SectionContentListModel(); - void init(ADS_NS::ContainerWidget* cw); - - virtual int columnCount(const QModelIndex &parent) const; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - - virtual int rowCount(const QModelIndex &parent) const; - virtual QVariant data(const QModelIndex &index, int role) const; - - virtual bool removeRows(int row, int count, const QModelIndex &parent); - -private: - QHash _headers; - - ADS_NS::ContainerWidget* _cw; - QList _contents; -}; - -#endif diff --git a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.cpp b/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.cpp deleted file mode 100644 index 26a3b465e..000000000 --- a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "SectionContentListWidget.h" -#include "SectionContentListModel.h" - - -SectionContentListWidget::SectionContentListWidget(QWidget* parent) : - QDialog(parent) -{ - _ui.setupUi(this); - connect(_ui.deleteButton, SIGNAL(clicked(bool)), this, SLOT(onDeleteButtonClicked())); -} - -void SectionContentListWidget::setValues(const SectionContentListWidget::Values& v) -{ - _v = v; - - // Reset - QAbstractItemModel* m = _ui.tableView->model(); - if (m) - { - _ui.tableView->setModel(NULL); - delete m; - m = NULL; - } - - // Fill. - SectionContentListModel* sclm = new SectionContentListModel(this); - sclm->init(_v.cw); - _ui.tableView->setModel(sclm); -} - -void SectionContentListWidget::onDeleteButtonClicked() -{ - const QModelIndex mi = _ui.tableView->currentIndex(); - if (!mi.isValid()) - return; - - _ui.tableView->model()->removeRows(mi.row(), 1, mi.parent()); -} diff --git a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.h b/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.h deleted file mode 100644 index 064aef6fc..000000000 --- a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SECTIONCONTENTLISTWIDGET -#define SECTIONCONTENTLISTWIDGET - -#include -#include "ui_SectionContentListWidget.h" - -#include "ads/API.h" -#include "ads/ContainerWidget.h" -#include "ads/SectionContent.h" - -class SectionContentListWidget : public QDialog -{ - Q_OBJECT - -public: - class Values - { - public: - ADS_NS::ContainerWidget* cw; - }; - - SectionContentListWidget(QWidget* parent); - void setValues(const Values& v); - -private slots: - void onDeleteButtonClicked(); - -private: - Ui::SectionContentListWidgetForm _ui; - Values _v; -}; - -#endif diff --git a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.ui b/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.ui deleted file mode 100644 index 1f25ed380..000000000 --- a/AdvancedDockingSystemDemo/src/dialogs/SectionContentListWidget.ui +++ /dev/null @@ -1,61 +0,0 @@ - - - SectionContentListWidgetForm - - - - 0 - 0 - 522 - 258 - - - - Form - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - - - - - - - Delete - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - diff --git a/AdvancedDockingSystemDemo/src/icontitlewidget.cpp b/AdvancedDockingSystemDemo/src/icontitlewidget.cpp deleted file mode 100644 index 9bf7b4583..000000000 --- a/AdvancedDockingSystemDemo/src/icontitlewidget.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "icontitlewidget.h" - -#include -#include -#include -#include -#include - -IconTitleWidget::IconTitleWidget(const QIcon& icon, const QString& title, QWidget *parent) : - QFrame(parent) -{ - QBoxLayout* l = new QBoxLayout(QBoxLayout::LeftToRight); - l->setContentsMargins(0, 0, 0, 0); - setLayout(l); - - _iconLabel = new QLabel(); - l->addWidget(_iconLabel); - - _titleLabel = new QLabel(); - l->addWidget(_titleLabel, 1); - - setIcon(icon); - setTitle(title); -} - -void IconTitleWidget::setIcon(const QIcon& icon) -{ - if (icon.isNull()) - { - _iconLabel->setPixmap(QPixmap()); - _iconLabel->setVisible(false); - } - else - { - _iconLabel->setPixmap(icon.pixmap(16, 16)); - _iconLabel->setVisible(true); - } -} - -void IconTitleWidget::setTitle(const QString& title) -{ - if (title.isEmpty()) - { - _titleLabel->setText(QString()); - _titleLabel->setVisible(false); - } - else - { - _titleLabel->setText(title); - _titleLabel->setVisible(true); - } -} - -void IconTitleWidget::polishUpdate() -{ - QList widgets; - widgets.append(_iconLabel); - widgets.append(_titleLabel); - foreach (QWidget* w, widgets) - { - w->style()->unpolish(w); - w->style()->polish(w); - w->update(); - } -} diff --git a/AdvancedDockingSystemDemo/src/icontitlewidget.h b/AdvancedDockingSystemDemo/src/icontitlewidget.h deleted file mode 100644 index 35bb98238..000000000 --- a/AdvancedDockingSystemDemo/src/icontitlewidget.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef ICONTITLEWIDGET_H -#define ICONTITLEWIDGET_H - -#include -class QIcon; -class QString; -class QLabel; - -class IconTitleWidget : public QFrame -{ - Q_OBJECT - -public: - explicit IconTitleWidget(const QIcon& icon, const QString& title, QWidget *parent = 0); - -public slots: - void setIcon(const QIcon& icon); - void setTitle(const QString& title); - void polishUpdate(); - -public: - QLabel* _iconLabel; - QLabel* _titleLabel; -}; - -#endif // ICONTITLEWIDGET_H diff --git a/AdvancedDockingSystemDemo/src/main.cpp b/AdvancedDockingSystemDemo/src/main.cpp deleted file mode 100644 index 620f65ec0..000000000 --- a/AdvancedDockingSystemDemo/src/main.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -#include "mainwindow.h" - -static void initStyleSheet(QApplication& a) -{ - //Q_INIT_RESOURCE(ads); // If static linked. - QFile f(":ads/stylesheets/default-windows.css"); - if (f.open(QFile::ReadOnly)) - { - const QByteArray ba = f.readAll(); - f.close(); - a.setStyleSheet(QString(ba)); - } -} - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - a.setQuitOnLastWindowClosed(true); - initStyleSheet(a); - - MainWindow mw; - mw.show(); - return a.exec(); -} diff --git a/AdvancedDockingSystemDemo/src/mainwindow.cpp b/AdvancedDockingSystemDemo/src/mainwindow.cpp deleted file mode 100644 index dce502c5e..000000000 --- a/AdvancedDockingSystemDemo/src/mainwindow.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ads/SectionWidget.h" -#include "ads/DropOverlay.h" - -#include "dialogs/SectionContentListWidget.h" - -#include "icontitlewidget.h" - -/////////////////////////////////////////////////////////////////////// - -static int CONTENT_COUNT = 0; - -static ADS_NS::SectionContent::RefPtr createLongTextLabelSC(ADS_NS::ContainerWidget* container) -{ - QWidget* w = new QWidget(); - QBoxLayout* bl = new QBoxLayout(QBoxLayout::TopToBottom); - w->setLayout(bl); - - QLabel* l = new QLabel(); - l->setWordWrap(true); - l->setAlignment(Qt::AlignTop | Qt::AlignLeft); - l->setText(QString("Lorem Ipsum ist ein einfacher Demo-Text für die Print- und Schriftindustrie. Lorem Ipsum ist in der Industrie bereits der Standard Demo-Text seit 1500, als ein unbekannter Schriftsteller eine Hand voll Wörter nahm und diese durcheinander warf um ein Musterbuch zu erstellen. Es hat nicht nur 5 Jahrhunderte überlebt, sondern auch in Spruch in die elektronische Schriftbearbeitung geschafft (bemerke, nahezu unverändert). Bekannt wurde es 1960, mit dem erscheinen von Letrase, welches Passagen von Lorem Ipsum enhielt, so wie Desktop Software wie Aldus PageMaker - ebenfalls mit Lorem Ipsum.")); - bl->addWidget(l); - - const int index = ++CONTENT_COUNT; - ADS_NS::SectionContent::RefPtr sc = ADS_NS::SectionContent::newSectionContent(QString("uname-%1").arg(index), container, new IconTitleWidget(QIcon(), QString("Label %1").arg(index)), w); - sc->setTitle("Ein Label " + QString::number(index)); - return sc; -} - -static ADS_NS::SectionContent::RefPtr createCalendarSC(ADS_NS::ContainerWidget* container) -{ - QCalendarWidget* w = new QCalendarWidget(); - - const int index = ++CONTENT_COUNT; - return ADS_NS::SectionContent::newSectionContent(QString("uname-%1").arg(index), container, new IconTitleWidget(QIcon(), QString("Calendar %1").arg(index)), w); -} - -static ADS_NS::SectionContent::RefPtr createFileSystemTreeSC(ADS_NS::ContainerWidget* container) -{ - QTreeView* w = new QTreeView(); - w->setFrameShape(QFrame::NoFrame); - // QFileSystemModel* m = new QFileSystemModel(w); - // m->setRootPath(QDir::currentPath()); - // w->setModel(m); - - const int index = ++CONTENT_COUNT; - return ADS_NS::SectionContent::newSectionContent(QString("uname-%1").arg(index), container, new IconTitleWidget(QIcon(), QString("Filesystem %1").arg(index)), w); -} - -static void storeDataHelper(const QString& fname, const QByteArray& ba) -{ - QFile f(fname + QString(".dat")); - if (f.open(QFile::WriteOnly)) - { - f.write(ba); - f.close(); - } -} - -static QByteArray loadDataHelper(const QString& fname) -{ - QFile f(fname + QString(".dat")); - if (f.open(QFile::ReadOnly)) - { - QByteArray ba = f.readAll(); - f.close(); - return ba; - } - return QByteArray(); -} - -/////////////////////////////////////////////////////////////////////// - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - ui->setupUi(this); - - // Setup actions. - QObject::connect(ui->actionContentList, SIGNAL(triggered()), this, SLOT(showSectionContentListDialog())); - - // ADS - Create main container (ContainerWidget). - _container = new ADS_NS::ContainerWidget(); -#if QT_VERSION >= 0x050000 - QObject::connect(_container, &ADS_NS::ContainerWidget::activeTabChanged, this, &MainWindow::onActiveTabChanged); - QObject::connect(_container, &ADS_NS::ContainerWidget::sectionContentVisibilityChanged, this, &MainWindow::onSectionContentVisibilityChanged); -#else - QObject::connect(_container, SIGNAL(activeTabChanged(const SectionContent::RefPtr&, bool)), this, SLOT(onActiveTabChanged(const SectionContent::RefPtr&, bool))); - QObject::connect(_container, SIGNAL(sectionContentVisibilityChanged(SectionContent::RefPtr,bool)), this, SLOT(onSectionContentVisibilityChanged(SectionContent::RefPtr,bool))); -#endif - setCentralWidget(_container); - - // Optional: Use custom drop area widgets. - if (false) - { - QHash areaWidgets; - areaWidgets.insert(ADS_NS::TopDropArea, new QPushButton("TOP")); - areaWidgets.insert(ADS_NS::RightDropArea, new QPushButton("RIGHT")); - areaWidgets.insert(ADS_NS::BottomDropArea, new QPushButton("BOTTOM")); - areaWidgets.insert(ADS_NS::LeftDropArea, new QPushButton("LEFT")); - areaWidgets.insert(ADS_NS::CenterDropArea, new QPushButton("CENTER")); - _container->dropOverlay()->setAreaWidgets(areaWidgets); - } - - // ADS - Adding some contents. - if (true) - { - // Test #1: Use high-level public API - ADS_NS::ContainerWidget* cw = _container; - ADS_NS::SectionWidget* sw = NULL; - - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::CenterDropArea); - sw = _container->addSectionContent(createCalendarSC(cw), sw, ADS_NS::RightDropArea); - sw = _container->addSectionContent(createFileSystemTreeSC(cw), sw, ADS_NS::CenterDropArea); - - _container->addSectionContent(createCalendarSC(_container)); - _container->addSectionContent(createLongTextLabelSC(_container)); - _container->addSectionContent(createLongTextLabelSC(_container)); - _container->addSectionContent(createLongTextLabelSC(_container)); - - ADS_NS::SectionContent::RefPtr sc = createLongTextLabelSC(cw); - sc->setFlags(ADS_NS::SectionContent::AllFlags ^ ADS_NS::SectionContent::Closeable); - _container->addSectionContent(sc); - } - else if (false) - { - // Issue #2: If the first drop is not into CenterDropArea, the application crashes. - ADS_NS::ContainerWidget* cw = _container; - ADS_NS::SectionWidget* sw = NULL; - - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::LeftDropArea); - sw = _container->addSectionContent(createCalendarSC(cw), sw, ADS_NS::LeftDropArea); - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::CenterDropArea); - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::CenterDropArea); - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::CenterDropArea); - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::RightDropArea); - sw = _container->addSectionContent(createLongTextLabelSC(cw), sw, ADS_NS::BottomDropArea); - } - - // Default window geometry - resize(800, 600); - restoreGeometry(loadDataHelper("MainWindow")); - - // ADS - Restore geometries and states of contents. - _container->restoreState(loadDataHelper("ContainerWidget")); -} - -MainWindow::~MainWindow() -{ - delete ui; -} - -void MainWindow::showSectionContentListDialog() -{ - SectionContentListWidget::Values v; - v.cw = _container; - - SectionContentListWidget w(this); - w.setValues(v); - w.exec(); -} - -void MainWindow::onActiveTabChanged(const ADS_NS::SectionContent::RefPtr& sc, bool active) -{ - Q_UNUSED(active); - IconTitleWidget* itw = dynamic_cast(sc->titleWidget()); - if (itw) - { - itw->polishUpdate(); - } -} - -void MainWindow::onSectionContentVisibilityChanged(const ADS_NS::SectionContent::RefPtr& sc, bool visible) -{ - qDebug() << Q_FUNC_INFO << sc->uniqueName() << visible; -} - -void MainWindow::onActionAddSectionContentTriggered() -{ - return; -} - -void MainWindow::contextMenuEvent(QContextMenuEvent* e) -{ - Q_UNUSED(e); - QMenu* m = _container->createContextMenu(); - m->exec(QCursor::pos()); - delete m; -} - -void MainWindow::closeEvent(QCloseEvent* e) -{ - Q_UNUSED(e); - storeDataHelper("MainWindow", saveGeometry()); - storeDataHelper("ContainerWidget", _container->saveState()); -} diff --git a/AdvancedDockingSystemDemo/src/mainwindow.h b/AdvancedDockingSystemDemo/src/mainwindow.h deleted file mode 100644 index 3548e8402..000000000 --- a/AdvancedDockingSystemDemo/src/mainwindow.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include -#include "ads/API.h" -#include "ads/ContainerWidget.h" -#include "ads/SectionContent.h" - -namespace Ui { -class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = 0); - virtual ~MainWindow(); - -public slots: - void showSectionContentListDialog(); - -private slots: -#if QT_VERSION >= 0x050000 - void onActiveTabChanged(const ADS_NS::SectionContent::RefPtr& sc, bool active); - void onSectionContentVisibilityChanged(const ADS_NS::SectionContent::RefPtr& sc, bool visible); -#else - void onActiveTabChanged(const SectionContent::RefPtr& sc, bool active); - void onSectionContentVisibilityChanged(const SectionContent::RefPtr& sc, bool visible); -#endif - void onActionAddSectionContentTriggered(); - -protected: - virtual void contextMenuEvent(QContextMenuEvent* e); - virtual void closeEvent(QCloseEvent* e); - -private: - Ui::MainWindow *ui; - ADS_NS::ContainerWidget* _container; -}; - -#endif // MAINWINDOW_H diff --git a/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pri b/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pri deleted file mode 100644 index 028a5deea..000000000 --- a/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pri +++ /dev/null @@ -1,7 +0,0 @@ - -HEADERS += \ - $$PWD/src/TestCore.h - -SOURCES += \ - $$PWD/src/main.cpp \ - $$PWD/src/TestCore.cpp diff --git a/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pro b/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pro deleted file mode 100644 index 7f682ced9..000000000 --- a/AdvancedDockingSystemUnitTests/AdvancedDockingSystemUnitTests.pro +++ /dev/null @@ -1,14 +0,0 @@ -TARGET = AdvancedDockingSystemUnitTests - -QT += core gui testlib -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets -greaterThan(QT_MAJOR_VERSION, 4): DEFINES += ADS_NAMESPACE_ENABLED -DEFINES += ADS_IMPORT - -INCLUDEPATH += $$PWD/src - -INCLUDEPATH += $$PWD/../AdvancedDockingSystem/include -DEPENDPATH += $$PWD/../AdvancedDockingSystem/include - -include(AdvancedDockingSystemUnitTests.pri) -include(../AdvancedDockingSystem/AdvancedDockingSystem.pri) \ No newline at end of file diff --git a/AdvancedDockingSystemUnitTests/src/TestCore.cpp b/AdvancedDockingSystemUnitTests/src/TestCore.cpp deleted file mode 100644 index 597615cc8..000000000 --- a/AdvancedDockingSystemUnitTests/src/TestCore.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "TestCore.h" - -#include "ads/API.h" -#include "ads/Serialization.h" - -void TestCore::serialization() -{ - QList datas; - datas.append(QByteArray("Custom Data Here!!!")); - datas.append(QByteArray("Even More...")); - datas.append(QByteArray("lalalaalalalalalalal").toBase64()); - - // WRITE some data. - ADS_NS_SER::InMemoryWriter writer; - for (int i = 0; i < datas.count(); ++i) - { - QVERIFY(writer.write(ADS_NS_SER::ET_Custom + i, datas.at(i))); - } - - // Type: SectionIndexData - ADS_NS_SER::SectionIndexData sid; - for (int i = 0; i < 1; ++i) - { - ADS_NS_SER::SectionEntity se; - se.x = i; - se.y = i; - se.width = 100 + i; - se.height = 100 + i; - se.currentIndex = i; - - for (int j = 0; j < 1; ++j) - { - ADS_NS_SER::SectionContentEntity sce; - sce.uniqueName = QString("uname-%1-%2").arg(i).arg(j); - sce.preferredIndex = 8; - sce.visible = true; - se.sectionContents.append(sce); - se.sectionContentsCount += 1; - } - - sid.sections.append(se); - sid.sectionsCount += 1; - } - QVERIFY(writer.write(sid)); - - QVERIFY(writer.offsetsCount() == datas.count() + 1); - const QByteArray writtenData = writer.toByteArray(); - QVERIFY(writtenData.size() > 0); - - // READ and validate written data. - ADS_NS_SER::InMemoryReader reader(writtenData); - QVERIFY(reader.initReadHeader()); - QVERIFY(reader.offsetsCount() == datas.count() + 1); - for (int i = 0; i < datas.count(); ++i) - { - QByteArray readData; - QVERIFY(reader.read(ADS_NS_SER::ET_Custom + i, readData)); - QVERIFY(readData == datas.at(i)); - } - - // Type: SectionIndexData - ADS_NS_SER::SectionIndexData sidRead; - QVERIFY(reader.read(sidRead)); - - // TODO compare sidRead with sid -} - -QTEST_MAIN(TestCore) \ No newline at end of file diff --git a/AdvancedDockingSystemUnitTests/src/TestCore.h b/AdvancedDockingSystemUnitTests/src/TestCore.h deleted file mode 100644 index fbe11d1b7..000000000 --- a/AdvancedDockingSystemUnitTests/src/TestCore.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef TEST_CORE_H -#define TEST_CORE_H - -#include - -class TestCore : public QObject -{ - Q_OBJECT - -private slots: - void serialization(); -}; - -#endif \ No newline at end of file diff --git a/AdvancedDockingSystemUnitTests/src/main.cpp b/AdvancedDockingSystemUnitTests/src/main.cpp deleted file mode 100644 index 815c5e856..000000000 --- a/AdvancedDockingSystemUnitTests/src/main.cpp +++ /dev/null @@ -1 +0,0 @@ -#include diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..b263c79b8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.5) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +if (POLICY CMP0091) + cmake_policy(SET CMP0091 NEW) +endif (POLICY CMP0091) + +# 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) +endif() + + +set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Installation Directory" FORCE) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..8000a6faa --- /dev/null +++ b/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..adc4e4d87 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include versioneer.py +include PyQtAds/_version.py diff --git a/README.md b/README.md index 8ed74846d..ca76dbaf6 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,717 @@ -# Advanced Docking System for Qt -[![Gitter](https://badges.gitter.im/mfreiholz/Qt-Advanced-Docking-System.svg)](https://gitter.im/mfreiholz/Qt-Advanced-Docking-System?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -Manages content widgets more like Visual Studio or similar programs. -I also try to get everything done with basic Qt functionality. -Basic usage of QWidgets an QLayouts and using basic styles as much as possible. - -![Layout of widgets](preview.png) -![Dropping widgets](preview-dragndrop.png) - -## Tested Compatible Environments -- Windows 7 / 8 / 8.1 / 10 -- Ubuntu 15.10 - -## Build -Open the `build.pro` with QtCreator and start the build, that's it. -You can run the demo project and test it yourself. - -## Release & Development -The `master` branch is not guaranteed to be stable or does not even build, since it is the main working branch. -If you want a version that builds, you should always use a release/beta tag. - -## Getting started / Example -The following example shows the minimum code required to use ADS. - -_MyWindow.h_ -```cpp -#include -#include "ads/API.h" -#include "ads/ContainerWidget.h" -#include "ads/SectionContent.h" -class MyWindow : public QMainWindow -{ - Q_OBJECT -public: - MyWindow(QWidget* parent); - -private: - // The main container for dockings. - ADS_NS::ContainerWidget* _container; - - // You always want to keep a reference of your content, - // in case you need to perform any action on it (show, hide, ...) - ADS_NS::SectionContent::RefPtr _sc1; -}; -``` - -_MyWindow.cpp_ -```cpp -#include "MyWindow.h" -#include -MyWindow::MyWindow(QWidget* parent) : QMainWindow(parent) -{ - _container = new ADS_NS::ContainerWidget(); - setCentralWidget(_container); - - _sc1 = ADS_NS::SectionContent::newSectionContent(QString("Unique-Internal-Name"), _container, new QLabel("Visible Title"), new QLabel("Content Widget")); - _container->addSectionContent(_sc1, NULL, ADS_NS::CenterDropArea); -} - -static void initStyleSheet(QApplication& a) -{ - //Q_INIT_RESOURCE(ads); // If static linked. - QFile f(":ads/stylesheets/default-windows.css"); - if (f.open(QFile::ReadOnly)) - { - const QByteArray ba = f.readAll(); - f.close(); - a.setStyleSheet(QString(ba)); - } -} - -int main(int argc, char *argv[]) -{ - QApplication a(argc, argv); - a.setQuitOnLastWindowClosed(true); - initStyleSheet(a); - - MainWindow mw; - mw.show(); - return a.exec(); -} -``` - -## Developers -[Manuel Freiholz](https://mfreiholz.de), Project Maintainer - -## License information -![WTFPL](license.png) - -This projects uses the [WTFPL license](http://www.wtfpl.net/) -(Do **W**hat **T**he **F**uck You Want To **P**ublic **L**icense) - -Using it? Let us know by creating a [new issue](https://github.com/mfreiholz/qt-docks/issues/new) (You don't have to, of course). +![ukraine](doc/ukraine.jpg) + +![logo](doc/ads_logo.svg) + +------------------ + +[![GitHub release (latest by date)](https://img.shields.io/github/v/release/githubuser0xFFFF/Qt-Advanced-Docking-System?color=%23ff9833)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest) +[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md) +[![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) +[![GitHub contributors](https://img.shields.io/github/contributors/githubuser0xFFFF/Qt-Advanced-Docking-System?color=ffdf00)](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/graphs/contributors) + +Qt Advanced Docking System lets you create customizable layouts using a full +featured window docking system similar to what is found in many popular +integrated development environments (IDEs) such as Visual Studio. + +- [What's new...](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest) +- [Documentation](doc/user-guide.md) +- Original Repository: https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System + +[![Video Advanced Docking](doc/advanced-docking_video.png)](https://www.youtube.com/watch?v=7pdNfafg3Qc) + +## New and Noteworthy + +Release [4.1](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest) significantly improves the Auto-Hide functionality and also brings improvements +for Drag and Drop of dock widgets into dock area tabs. These are the highlights of the new version: + +#### Drag & Drop to Auto-Hide + +Now you can easily drag any dock widget or any floating widget to the +borders of a window to pin it as a auto-hide tab in one of the 4 sidebars. +If you drag a dock widget close the one of the four window borders, special +drop overlays will be shown to indicate the drop area for auto-hide widgets: + +![Auo-Hide drag to Sidebar](doc/AutoHide_Drag_to_Sidebar.gif) + +Of course, this also works with dock areas: + +![Auo-Hide drag Dock Area](doc/AutoHide_Drag_DockArea.gif) + +If you drag a dock widget or dock area into a sidebar, then you even have +control over where tabs are inserted. Simply drag your mouse over a specific +auto-hide tab, and your dragged dock widget will be inserted before this tab. +Drag to the sidebar area behind the last tab, and the dragged widget will be +appended as last tab. In the following screen capture, the **Image Viewer 1** will +be inserted before the **Table 0** Auto-Hide tab and the **Image Viewer 2** +is appende behind the last tab: + +![Auo-Hide tab insert order](doc/AutoHide_Tab_Insert_Order.gif) + +#### Auto-Hide Tab Insertion Order + +It is also possible to drag Auto-Hide tabs to a new auto-hide position. +That means, you can drag them to a different border or sidebar: + +![Auto-Hide change sidebar](doc/AutoHide_Change_Sidebar.gif) + +#### Auto-Hide Tab Sorting + +You can drag Auto-Hide tabs to a new position in the current sidebar +to sort them: + +![Auo-Hide sort tabs](doc/AutoHide_Sort_Tabs.gif) + +#### Auto-Hide Drag to Float / Dock + +But that is not all. You can also simply move Auto-Hide tabs to another +floating widget or dock them via drag and drop: + +![Auo-Hide drag to float or dock](doc/AutoHide_Drag_to_Float_or_Dock.gif) + +#### Auto-Hide Context Menu + +All Auto-Hide tabs now have a context menu, that provides all the functionality +that you know from Dock widget tabs. With the **Pin To...** item from the +context menu it is very easy to move an Auto-Hide tab to a different Auto-Hide +sidebar: + +![Auo-Hide context menu](doc/AutoHide_Context_Menu.png) + +#### Dock Area Tab Insert Order + +And last but not least the new version also improves the docking of widgets +into the tabs of a Dock area. Just as with Auto-Hide tabs, you can now determine the position at which a tab is inserted by moving the mouse over an already existing tab (insertion before the tab) or behind the last tab +(appending): + +![Dock area tab insert order](doc/DockArea_Tab_Insertion_Order.gif) + +The [release 4.0](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/latest) +adds the following features: + +- Auto-Hide functionality ([read more...](#auto-hide-functionality)) + +![Auto Hide Functionality](doc/AutoHide_Animation.gif) + +- improved demo application with new image viewer dock widgets + +![Auto Hide Functionality](doc/Feature_ImageViewer.png) + +- Visual Studio like CSS theme in demo application + +The [release 3.8](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/3.8.3) +adds the following features: + +- option to close tabs with the middle mouse button +- `DeleteContentOnClose` flag for dynamic deletion and creation of dock widget + content +- improved focus highlighting functionality + +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.3) +adds some nice new features: + +- support for [central widget](doc/user-guide.md#central-widget) concept + +![Central Widget](doc/central_widget.gif) + +- support for [native floating widgets](doc/user-guide.md#floatingcontainerforcenativetitlebar-linux-only) on Linux + +![FloatingContainerForceNativeTitleBar true](doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png) + +Both features are contributions from ADS users. Read the [documentation](doc/user-guide.md) +to learn more about both new features. + +The [release 3.5](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/releases/tag/3.5.0) +adds the new [focus highlighting](doc/user-guide.md#focushighlighting) feature. +This optional feature enables highlighting of the focused dock widget like you +know it from Visual Studio. + +![FocusHighlighting](doc/cfg_flag_FocusHighlighting.gif) + + [learn more...](doc/user-guide.md#focushighlighting) + +## Features + +### Overview + +- [New and Noteworthy](#new-and-noteworthy) + - [Drag \& Drop to Auto-Hide](#drag--drop-to-auto-hide) + - [Auto-Hide Tab Insertion Order](#auto-hide-tab-insertion-order) + - [Auto-Hide Tab Sorting](#auto-hide-tab-sorting) + - [Auto-Hide Drag to Float / Dock](#auto-hide-drag-to-float--dock) + - [Auto-Hide Context Menu](#auto-hide-context-menu) + - [Dock Area Tab Insert Order](#dock-area-tab-insert-order) +- [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) + - [Cancelable docking process](#cancelable-docking-process) + - [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) + - [Auto-Hide Functionality](#auto-hide-functionality) +- [Python Bindings](#python-bindings) + - [PySide6](#pyside6) + - [PyQt5](#pyqt5) +- [Tested Compatible Environments](#tested-compatible-environments) + - [Supported Qt Versions](#supported-qt-versions) + - [Windows](#windows) + - [macOS](#macos) + - [Linux](#linux) +- [Build](#build) + - [Qt5 on Ubuntu 18.04 or 20.04](#qt5-on-ubuntu-1804-or-2004) + - [Qt5 on Ubuntu 22.04](#qt5-on-ubuntu-2204) + - [Qt6 on Ubuntu 22.04](#qt6-on-ubuntu-2204) +- [Getting started / Example](#getting-started--example) +- [License information](#license-information) +- [Donation](#donation) +- [Showcase](#showcase) + - [Qt Creator IDE](#qt-creator-ide) + - [Qt Design Studio](#qt-design-studio) + - [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) + - [RDE – Robox Development Environment](#rde--robox-development-environment) + - [ResInsight](#resinsight) + - [ADTF 3](#adtf-3) + - [DREAM.3D NX](#dream3d-nx) + - [LabPlot](#labplot) +- [Alternative Docking System Implementations](#alternative-docking-system-implementations) + - [KDDockWidgets](#kddockwidgets) + - [QtitanDocking](#qtitandocking) + - [DockingPanes](#dockingpanes) + +### Docking everywhere - no central widget + +There is no central widget like in the Qt docking system. You can dock on every +border of the main window or you can dock into each dock area - so you are +free to dock almost everywhere. + +![Dropping widgets](doc/preview-dragndrop.png) + +![Dropping widgets](doc/preview-dragndrop_dark.png) + +### Docking inside floating windows + +There is no difference between the main window and a floating window. Docking +into floating windows is supported. + +![Docking inside floating windows](doc/floating-widget-dragndrop.png) + +![Docking inside floating windows](doc/floating-widget-dragndrop_dark.png) + +### Grouped dragging + +When dragging the titlebar of a dock, all the tabs that are tabbed with it are +going to be dragged. So you can move complete groups of tabbed widgets into +a floating widget or from one dock area to another one. + +![Grouped dragging](doc/grouped-dragging.gif) + +![Grouped dragging](doc/grouped-dragging_dark.png) + +### Perspectives for fast switching of the complete main window layout + +A perspective defines the set and layout of dock windows in the main +window. You can save the current layout of the dockmanager into a named +perspective to make your own custom perspective. Later you can simply +select a perspective from the perspective list to quickly switch the complete +main window layout. + +![Perspective](doc/perspectives.gif) + +![Perspective](doc/perspectives_dark.png) + +### Opaque and non-opaque splitter resizing + +The advanced docking system uses standard QSplitters as resize separators and thus supports opaque and non-opaque resizing functionality of QSplitter. In some rare cases, for very complex widgets or on slow machines resizing via separator on the fly may cause flicking and glaring of rendered content inside a widget. The global dock manager flag `OpaqueSplitterResize` configures the resizing behaviour of the splitters. If this flag is set, then widgets are resized dynamically (opaquely) while interactively moving the splitters. + +![Opaque resizing](doc/opaque_resizing.gif) + +If this flag is cleared, the widget resizing is deferred until the mouse button is released - this is some kind of lazy resizing separator. + +![Non-opaque resizing](doc/non_opaque_resizing.gif) + +### Cancelable docking process + +In contrast to the standard Qt docking system, docking with the ADS works more like a drag & drop operation. That means, the dragged dock widget or dock area is not undocked immediately. Instead, a drag preview widget is created and dragged around to indicate the future position of the dock widget or dock area. The actual dock operation is only executed when the mouse button is released. That makes it possible, to cancel an active drag operation with the escape key. + +The drag preview widget can be configured by a number of global dock manager flags: +- `DragPreviewIsDynamic`: if this flag is enabled, the preview will be adjusted dynamically to the drop area +- `DragPreviewShowsContentPixmap`: the created drag preview window shows a static copy of the content of the dock widget / dock are that is dragged +- `DragPreviewHasWindowFrame`: this flag configures if the drag preview is frameless like a QRubberBand or looks like a real window + +### Tab-menu for easy handling of many tabbed dock widgets + +Tabs are a good way to quickly switch between dockwidgets in a dockarea. However, if the number of dockwidgets in a dockarea is too large, this may affect the usability of the tab bar. To keep track in this situation, you can use the tab menu. The menu allows you to quickly select the dockwidget you want to activate from a drop down menu. + +![Tab menu](doc/tab_menu.gif) + +### Many different ways to detach dock widgets + +You can detach dock widgets and also dock areas in the following ways: + +- by dragging the dock widget tab or the dock area title bar +- by double clicking the tab or title bar +- by using the detach menu entry from the tab and title bar drop down menu + +### 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. + +### Auto-Hide Functionality + +The 4.0 release of ADS added the new **Auto-Hide** feature. Thanks to the +[initial contribution](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/pull/452) by [Ahmad Syarifuddin](https://github.com/SyarifFakhri) it was +possible to close this long standing [feature request](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/147). The "Auto Hide" feature +allows to display more information using less screen space by hiding or showing +windows pinned to one of the four dock container borders. + +![Auto-Hide Movie](doc/AutoHide_Movie.gif) + +The Advanced Docking +System supports "Auto-Hide" functionality for **all** dock containers - that means, +for the main window and for each floating widget. Here is short list of all +auto hide features: + +- supported for the main window and all floating dock containers +- supports showing and hiding via mouse click or mouse hover +- respects opaque / non opaque splitter resizing flag +- context menu for pinning a dock widget or a complete dock area to a certain border +- configuration option to configure if the pin button should pin the current + dock widget tab or a complete dock area +- click the pin button holding the Ctrl key to pin a complete dock area +- fully CSS styleable +- backward compatible state file format - is is possible to load older dock manager + state files without auto hide support and older versions can load the new state + files with Auto-Hide state information + +More about the auto hide configuration options in the [online documentation...](doc/user-guide.md#auto-hide-configuration-flags) + +## Python Bindings + +![Python Logo](doc/python_logo.png) + +Thanks to the contribution of several users, the Advanced Docking System comes +with a complete Python integration. Python bindings are available for **PyQt5** and +**PySide6**. + +### PySide6 + +A PySide6 ADS package is available via PyPi and can be installed on Windows, +macOS, and Linux with: + +```bash +pip install PySide6-QtAds +``` + +Sample code is available [here](https://github.com/mborgerson/Qt-Advanced-Docking-System/tree/pyside6/examples). To run the samples, you'll also need to install latest qtpy +from source (pip install https://github.com/spyder-ide/qtpy/archive/refs/heads/master.zip). +The PySide6 bindings were contributed by: + +- [mborgerson](https://github.com/mborgerson) + +Please file PySide6-QtAds-specific issues on its [pyside6_qtads](https://github.com/mborgerson/pyside6_qtads) fork for tracking. For more information about the PySide6 bindings read [this](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/298) issue. + +### PyQt5 + +A package is available via [conda-forge](https://github.com/conda-forge/pyqtads-feedstock). +The python integration has been contributed to this project by the following people: + +- [n-elie](https://github.com/n-elie) +- [Hugo Slepicka](https://github.com/hhslepicka) +- [K Lauer](https://github.com/klauer) + +A Python integration is also available via PyPi. You can install the +[PyQtAds](https://pypi.org/project/PyQtAds/) package via pip. This feature has been +contributed to this project by: + +- [Mira Weller](https://github.com/luelista) + +## Tested Compatible Environments + +### Supported Qt Versions + +The library supports **Qt5** and **Qt6**. + +### Windows + +Windows 10 [![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) + +The library was developed on and for Windows. It is used in a commercial Windows application and is therefore constantly tested. + +### macOS + +macOS [![Build Status](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System.svg?branch=master)](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System) + +The application can be compiled for macOS. A user reported, that the library works on macOS. If have not tested it. + +![Advanced Docking on macOS](doc/macos.png) + +### Linux + +[![Build Status](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System.svg?branch=master)](https://travis-ci.org/githubuser0xFFFF/Qt-Advanced-Docking-System) +[![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) + +Unfortunately, there is no such thing as a Linux operating system. Linux is a heterogeneous environment with a variety of different distributions. So it is not possible to support "Linux" like it is possible for Windows. It is only possible to support and test a small subset of Linux distributions. The library can be compiled for and has been developed and tested with some Linux distributions. Depending on the used window manager or compositor, dock widgets +with native title bars are supported or not. If native title bars are not supported, +the library switches to `QWidget` based title bars. + +- **Kubuntu 18.04 and 19.10** - uses KWin - no native title bars +- **Ubuntu 18.04, 19.10 and 20.04** - native title bars are supported +- **Ubuntu 22.04** - uses Wayland -> no native title bars + +There are some requirements for the Linux distribution that have to be met: + +- an X server that supports ARGB visuals and a compositing window manager. This is required to display the translucent dock overlays ([https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows](https://doc.qt.io/qt-5/qwidget.html#creating-translucent-windows)). If your Linux distribution does not support this, or if you disable this feature, you will very likely see issue [#95](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/95). +- Wayland is not properly supported by Qt yet. If you use Wayland, then you should set the session type to x11: `XDG_SESSION_TYPE=x11 ./AdvancedDockingSystemDemo`. You will find more details about this in issue [#288](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/288). + +Screenshot Kubuntu: +![Advanced Docking on Kubuntu Linux](doc/linux_kubuntu_1804.png) + +Screenshot Ubuntu: +![Advanced Docking on Ubuntu Linux](doc/linux_ubuntu_1910.png) + +## Build + +The Linux build requires private header files. Make sure that they are installed. +The library uses SVG icons, so ensure that Qt SVG support is installed. The demo +application creates a `QQuickWidget` for testing, so ensure that the required +libraries are installed. + +### Qt5 on Ubuntu 18.04 or 20.04 + +```bash +sudo apt install qt5-default qtbase5-private-dev +``` + +### Qt5 on Ubuntu 22.04 + +```bash +sudo apt install qtbase5-dev qtbase5-private-dev qtbase5-dev-tools libqt5svg5 libqt5qml5 qtdeclarative5-dev +``` + +### Qt6 on Ubuntu 22.04 + +```bash +sudo apt install qt6-default qt6-base-dev qt6-base-private-dev qt6-tools-dev libqt6svg6 qt6-qtdeclarative +``` + +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 + +The following example shows the minimum code required to use the advanced Qt docking system. + +*MainWindow.h* + +```cpp +#include +#include "DockManager.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + + // The main container for docking + ads::CDockManager* m_DockManager; +}; +``` + +*MainWindow.cpp* + +```cpp +#include "MainWindow.h" +#include "ui_MainWindow.h" + +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // 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 + // 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()); + + // Add the dock widget to the top dock widget area + m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); +} + +MainWindow::~MainWindow() +{ + delete ui; +} +``` + +## License information + +[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL%20v2.1-blue.svg)](gnu-lgpl-v2.1.md) +This project uses the [LGPLv2.1 license](gnu-lgpl-v2.1.md) + +## Donation + +If this project help you reduce time to develop or if you just like it, you can give me a cup of coffee :coffee::wink:. + + + Donate with PayPal + + +## Showcase + +### [Qt Creator IDE](https://www.qt.io/development-tools) + +From version 4.12 on, Qt Creator uses the Advanced Docking Framework for its +Qt Quick Designer. This improves the usability when using multiple screens. + +![Qt Creator](doc/showcase_qtcreator.png) + +### [Qt Design Studio](https://www.qt.io/ui-design-tools) + +Taken from the [Qt Blog](https://www.qt.io/blog/qt-design-studio-1.5-beta-released): + +> The most obvious change in [Qt Design Studio 1.5](https://www.qt.io/blog/qt-design-studio-1.5-beta-released) is the integration of dock widgets using the Qt Advanced Docking System. This allows the user to fully customize the workspace and also to undock any view into its own top level window. This especially improves the usability when using multiple screens. + +[![Qt Design Studio](doc/showcase_qt_design_studio_video.png)](https://youtu.be/za9KBWcFXEw?t=84) + +### [CETONI Elements](https://cetoni.com/cetoni-elements/) + +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. + +[learn more...](https://cetoni.com/cetoni-elements/) + +[![CETONI_Elements](doc/showcase_qmix_elements.png)](https://www.youtube.com/watch?v=7pdNfafg3Qc) + +### [ezEditor](https://github.com/ezEngine/ezEngine) + +The ezEditor is a full blown graphical editor used for editing scenes and +importing and authoring assets for the [ezEngine](https://github.com/ezEngine/ezEngine) - +an open source C++ game engine in active development. + +![ezEditor](doc/showcase_ezEngine_editor.png) + +### [D-Tect X](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html) + +D-Tect X is a X-ray inspection software for industrial radiography. It is a state-of-the-art 64-bit application which supports GPU (Graphics Processing Unit) acceleration and takes full advantage of computers with multiple CPU cores. A large set of tools assist the user in image analysis and evaluation. Thanks to the Qt Advanced Docking System the flexible and intuitive user interface can be completely customized to each user’s preference. + +[learn more...](https://www.duerr-ndt.com/products/ndt-software/d-tect-xray-inspection-software.html) + +[![D-TectX](doc/showcase_d-tect-x.png)](https://youtu.be/mOor7GmmIJo?t=13) + +### [HiveWE](https://github.com/stijnherfst/HiveWE) + +HiveWE is a Warcraft III world editor. It focusses on speed and ease of use, +especially for large maps where the regular World Editor is often too slow and clunky. +It has a JASS editor with syntax highlighting, tabs, code completion and more. +The JASS editor uses the Qt Advanced Docking System for the management and layout +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) + +### [RDE – Robox Development Environment](https://www.robox.it/en/product/rde-robox-development-environment/) + +This software is a development environment for PAC (Programmable Automation Controllers) +from ROBOX. It offers a lot of tools to write, compile and debug machine control +and application software. The Advanced Docking System helps to organize all the tools and +windows (Project window, Shell window, Monitor windows, Oscilloscope window...) +on the screen to provide a easy to use, highly configurable and visual pleasing +development experience. + +[read more...](https://www.robox.it/en/product/rde-robox-development-environment/) + +![RDE](doc/showcase_robox_ide.png) + +### [ResInsight](https://www.ceetronsolutions.com/projects/resinsight) + +ResInsight as a software from Ceetron Solutions for visualization of oil and +gas reservoir simulation data. It allows reservoir models, simulation results, +and measurements to be visualized with very high performance. Optimized use of +graphics technology and simultaneous processing on multiple CPU cores have been +vital to enhance the performance and capacity of ResInsight for large data sets. +The Advanced Docking System has empowered Ceetron to build a much more intuitive +user interface for its ResInsight users. + +[read more...](https://resinsight.org/) + +[![ResInsight](doc/showcase_resinsight.png)](https://www.youtube.com/watch?v=HzLaQ1p6AUc) + +### [ADTF 3](https://www.digitalwerk.net/adtf/) + +The Automotive Data and Time-Triggered Framework was designed as a Rapid Prototyping Toolset, Simulation Framework and Test- and Measurement Tool. It is meant for: + +- Developing and testing ADAS and HAD components +- Recording of vehicle data for visualisation +- Simulation of complex scenarios in SIL/HIL test environments + +The software features time-based processing of multiple data streams and graphical editing of dynamic filter graphs. It also includes an SDK for custom plug-ins and reusable components, as well as components for data visualization in both 2D and 3D. This is was the +[manual](https://support.digitalwerk.net/adtf/v3/adtf_html/page_adtf_xsystem_plugin.html) +says about the switch to Qt Advanced Docking: + +> After several minor improvements the Qt5 ADTF XSystem uses the Advanced Docking System for Qt since ADTF 3.10.0 for more convenience and usability regarding layouting, docking and embedding several widgets. + +[read more...](https://support.digitalwerk.net/adtf/v3/adtf_html/index.html) + +![ADTF](doc/showcase_adtf.png) + +### [DREAM.3D NX](https://github.com/BlueQuartzSoftware/DREAM3D) + +DREAM.3D *(Digital Representation Environment for Analysis of Materials in 3D)* is an open source, cross-platform and modular, software suite that allows users to prepare, reconstruct, quantify, instantiate, and mesh, multidimensional, multimodal microstructural data, as well as many other applications. + +[BlueQuartz Software](http://www.bluequartz.net/) is currently completely rewriting the DREAM.3D application. For the upcoming version **[DREAM3D NX](http://www.dream3d.io/)** they improved the UI by using the Advanced Docking System. An [early version](http://www.dream3d.io/) of **DREAM3D NX** with ADS is already available to any user who would like to take the brand new version out for a spin. + +![DREAM.3D NX](doc/showcase_dream3d_nx.png) + +[read more...](http://dream3d.bluequartz.net/) + +### [LabPlot](https://labplot.kde.org/) + +KDE LabPlot is the ultimate free, open source and cross-platform tool for scientists, engineers, and students who need to analyze and visualize data. With its intuitive interface and powerful features, you can create stunning plots and diagrams with ease. Whether you're working with CSV, FITS, or HDF5 data, KDE LabPlot makes it simple to import and analyze your data. + +The LabPlot project recently switched to the Qt Advanced Docking System for their user interface. This switch represents a significant improvement to the LabPlot software, allowing users to create and manage complex data visualization layouts with ease. + +![LabPlot](doc/showcase_labplot.png) + +[read more...](https://labplot.kde.org/) + +## Alternative Docking System Implementations + +If this Qt Advanced Docking System does not fit to your needs you may consider some of the alternative docking system solutions for Qt. + +### KDDockWidgets + +This is an advanced docking framework for Qt from [KDAB](https://www.kdab.com/). The interesting thing is, that they separated GUI code from logic, so they can easily provide a QtQuick backend in the future. + +- [Blog post about KDDockWidgets](https://www.kdab.com/kddockwidgets/) +- [GitHub project](https://github.com/KDAB/KDDockWidgets) + +**License:** dual-licensed, available under both commercial and GPL license. + +### QtitanDocking + +This is a commercial component from [Developer Machines](https://www.devmachines.com/) for Qt Framework that allows to create a Microsoft like dockable user interface. They also offer a lot of other interesting and useful components for Qt. The library is available + +- [Product page](https://www.devmachines.com/qtitandocking-overview.html) + +**License:** Commercial license + +### DockingPanes + +DockingPanes is a library for Qt Widgets that implements docking windows that have the look and feel of Visual Studio. It provides a simple API which allows an application to make use of docking windows with a few calls. + +- [GitHub project](https://github.com/KestrelRadarSensors/dockingpanes) + +**License:** GPL diff --git a/ads.pri b/ads.pri new file mode 100644 index 000000000..ddfb8a1da --- /dev/null +++ b/ads.pri @@ -0,0 +1,28 @@ + +CONFIG(debug, debug|release){ + win32-g++ { + versionAtLeast(QT_VERSION, 5.15.0) { + LIBS += -lqtadvanceddocking + } + else { + LIBS += -lqtadvanceddockingd + } + } + else:msvc { + LIBS += -lqtadvanceddockingd + } + else:mac { + LIBS += -lqtadvanceddocking_debug + } + else { + LIBS += -lqtadvanceddocking + } +} +else{ + LIBS += -lqtadvanceddocking +} + + +unix:!macx { + LIBS += -lxcb +} diff --git a/ads.pro b/ads.pro new file mode 100644 index 000000000..c53240706 --- /dev/null +++ b/ads.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + src \ + demo \ + examples + +demo.depends = src +examples.depends = src diff --git a/adsConfig.cmake b/adsConfig.cmake new file mode 100644 index 000000000..542e5c9c4 --- /dev/null +++ b/adsConfig.cmake @@ -0,0 +1,5 @@ +include(CMakeFindDependencyMacro) +find_dependency(Qt5Core ${REQUIRED_QT_VERSION} REQUIRED) +find_dependency(Qt5Gui ${REQUIRED_QT_VERSION} REQUIRED) +find_dependency(Qt5Widgets ${REQUIRED_QT_VERSION} REQUIRED) +include("${CMAKE_CURRENT_LIST_DIR}/adsTargets.cmake") \ No newline at end of file diff --git a/build.pro b/build.pro deleted file mode 100644 index b83748aab..000000000 --- a/build.pro +++ /dev/null @@ -1,6 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS = \ - AdvancedDockingSystem \ - AdvancedDockingSystemDemo \ - AdvancedDockingSystemUnitTests diff --git a/cmake/modules/GetGitRevisionDescription.cmake b/cmake/modules/GetGitRevisionDescription.cmake new file mode 100644 index 000000000..a9ad77b7f --- /dev/null +++ b/cmake/modules/GetGitRevisionDescription.cmake @@ -0,0 +1,172 @@ +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# git_local_changes() +# +# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. +# Uses the return code of "git diff-index --quiet HEAD --". +# Does not regard untracked files. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + # check if this is a submodule + if(NOT IS_DIRECTORY ${GIT_DIR}) + file(READ ${GIT_DIR} submodule) + string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) + get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) + get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) + endif() + if(NOT IS_DIRECTORY "${GIT_DIR}") + file(READ ${GIT_DIR} worktree) + string(REGEX REPLACE "gitdir: (.*)worktrees(.*)\n$" "\\1" GIT_DIR ${worktree}) + endif() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_exact_tag _var) + git_describe(out --exact-match ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_local_changes _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + diff-index --quiet HEAD -- + WORKING_DIRECTORY + "${CMAKE_CURRENT_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(res EQUAL 0) + set(${_var} "CLEAN" PARENT_SCOPE) + else() + set(${_var} "DIRTY" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/modules/GetGitRevisionDescription.cmake.in b/cmake/modules/GetGitRevisionDescription.cmake.in new file mode 100644 index 000000000..6d8b708ef --- /dev/null +++ b/cmake/modules/GetGitRevisionDescription.cmake.in @@ -0,0 +1,41 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + else() + configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) + file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) + if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") + set(HEAD_HASH "${CMAKE_MATCH_1}") + endif() + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt new file mode 100644 index 000000000..f40bfc907 --- /dev/null +++ b/demo/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_demo VERSION ${VERSION_SHORT}) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} 5.5 COMPONENTS Core Gui Widgets Quick QuickWidgets REQUIRED) +if(WIN32 AND QT_VERSION_MAJOR LESS 6) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS AxContainer REQUIRED) +endif() +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(ads_demo_SRCS + main.cpp + MainWindow.cpp + mainwindow.ui + StatusDialog.cpp + StatusDialog.ui + ImageViewer.cpp + RenderWidget.cpp + demo.qrc +) +add_executable(AdvancedDockingSystemDemo WIN32 ${ads_demo_SRCS}) +target_include_directories(AdvancedDockingSystemDemo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src") +target_link_libraries(AdvancedDockingSystemDemo PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Quick + Qt${QT_VERSION_MAJOR}::QuickWidgets) +if(WIN32 AND QT_VERSION_MAJOR LESS 6) + target_link_libraries(AdvancedDockingSystemDemo PUBLIC Qt${QT_VERSION_MAJOR}::AxContainer) +endif() +target_link_libraries(AdvancedDockingSystemDemo PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +set_target_properties(AdvancedDockingSystemDemo 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 Demo" + 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" +) +#if(BUILD_STATIC) +# target_compile_definitions(AdvancedDockingSystemDemo PRIVATE ADS_STATIC) +#endif() + diff --git a/demo/ImageViewer.cpp b/demo/ImageViewer.cpp new file mode 100644 index 000000000..073be1f91 --- /dev/null +++ b/demo/ImageViewer.cpp @@ -0,0 +1,280 @@ +//============================================================================ +/// \file ImageViewer.cpp +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Implementation of CImageViewer +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ImageViewer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "RenderWidget.h" + +/** + * Private image viewer data + */ +struct ImageViewerPrivate +{ + CImageViewer* _this; + CRenderWidget* RenderWidget;///< renders the image to screen + bool AutoFit;///< automatically fit image to window size on resize events + QSize ImageSize;///< stores the image size to detect image size changes + QPoint MouseMoveStartPos;///< for calculation of mouse move vector + QLabel* ScalingLabel;///< label displays scaling factor + QList OverlayTools;///< list of tool widget to overlay + + ImageViewerPrivate(CImageViewer* _public) : _this(_public) {} +}; + + + +//============================================================================ +CImageViewer::CImageViewer(QWidget *parent) + : Super(parent), + d(new ImageViewerPrivate(this)) +{ + d->AutoFit = true; + d->RenderWidget = new CRenderWidget(this); + + this->setBackgroundRole(QPalette::Light); + this->setAlignment(Qt::AlignCenter); + this->setWidget(d->RenderWidget); + this->createActions(); + this->setMouseTracking(false); // only produce mouse move events if mouse button pressed +} + + +//============================================================================ +CImageViewer::~CImageViewer() +{ + delete d; +} + + +//============================================================================ +bool CImageViewer::loadFile(const QString& fileName) +{ + QImageReader reader(fileName); + reader.setAutoTransform(true); + const QImage newImage = reader.read(); + if (newImage.isNull()) + { + QMessageBox::information(this, QGuiApplication::applicationDisplayName(), + tr("Cannot load %1: %2") + .arg(QDir::toNativeSeparators(fileName), reader.errorString())); + return false; + } + + setImage(newImage); + setWindowFilePath(fileName); + return true; +} + + +//=========================================================================== +void CImageViewer::setImage(const QImage &newImage) +{ + d->RenderWidget->showImage(newImage); + this->adjustDisplaySize(newImage); +} + + +//============================================================================ +void CImageViewer::adjustDisplaySize(const QImage& Image) +{ + if (d->ImageSize == Image.size()) + { + return; + } + d->ImageSize = Image.size(); + if (d->AutoFit) + { + this->fitToWindow(); + } +} + + +//=========================================================================== +static void initializeImageFileDialog(QFileDialog &dialog, QFileDialog::AcceptMode acceptMode) +{ + static bool firstDialog = true; + + if (firstDialog) { + firstDialog = false; + const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + dialog.setDirectory(picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last()); + } + + QStringList mimeTypeFilters; + const QByteArrayList supportedMimeTypes = acceptMode == QFileDialog::AcceptOpen + ? QImageReader::supportedMimeTypes() : QImageWriter::supportedMimeTypes(); + for (const QByteArray &mimeTypeName : supportedMimeTypes) + mimeTypeFilters.append(mimeTypeName); + mimeTypeFilters.sort(); + dialog.setMimeTypeFilters(mimeTypeFilters); + dialog.selectMimeTypeFilter("image/jpeg"); + if (acceptMode == QFileDialog::AcceptSave) + dialog.setDefaultSuffix("jpg"); +} + + +//=========================================================================== +void CImageViewer::open() +{ + QFileDialog dialog(this, tr("Open File")); + initializeImageFileDialog(dialog, QFileDialog::AcceptOpen); + + while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {} +} + + +//=========================================================================== +void CImageViewer::createActions() +{ + QAction* a; + a = new QAction(tr("&Open...")); + a->setIcon(QIcon(":/adsdemo/images/perm_media.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::open); + a->setShortcut(QKeySequence::Open);; + this->addAction(a); + + a = new QAction(tr("Fit on Screen")); + a->setIcon(QIcon(":/adsdemo/images/zoom_out_map.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::fitToWindow); + this->addAction(a); + + a = new QAction(tr("Actual Pixels")); + a->setIcon(QIcon(":/adsdemo/images/find_in_page.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::normalSize); + this->addAction(a); + + a = new QAction(this); + a->setSeparator(true); + this->addAction(a); + + a = new QAction(tr("Zoom In (25%)")); + a->setIcon(QIcon(":/adsdemo/images/zoom_in.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::zoomIn); + this->addAction(a); + + a = new QAction(tr("Zoom Out (25%)")); + a->setIcon(QIcon(":/adsdemo/images/zoom_out.svg")); + connect(a, &QAction::triggered, this, &CImageViewer::zoomOut); + this->addAction(a); + + this->setContextMenuPolicy(Qt::ActionsContextMenu); +} + + +//=========================================================================== +void CImageViewer::zoomIn() +{ + d->AutoFit = false; + d->RenderWidget->zoomIn(); +} + + +//=========================================================================== +void CImageViewer::zoomOut() +{ + d->AutoFit = false; + d->RenderWidget->zoomOut(); +} + + +//=========================================================================== +void CImageViewer::normalSize() +{ + d->AutoFit = false; + d->RenderWidget->normalSize(); +} + + +//=========================================================================== +void CImageViewer::fitToWindow() +{ + d->AutoFit = true; + d->RenderWidget->scaleToSize(this->maximumViewportSize()); +} + + +//============================================================================ +void CImageViewer::resizeEvent(QResizeEvent* ResizeEvent) +{ + Super::resizeEvent(ResizeEvent); + if (d->AutoFit) + { + this->fitToWindow(); + } +} + + +//============================================================================ +void CImageViewer::mousePressEvent(QMouseEvent* Event) +{ + d->RenderWidget->setCursor(Qt::ClosedHandCursor); + d->MouseMoveStartPos = Event->pos(); + Super::mousePressEvent(Event); +} + + +//============================================================================ +void CImageViewer::mouseReleaseEvent(QMouseEvent* Event) +{ + d->RenderWidget->setCursor(Qt::OpenHandCursor); + Super::mouseReleaseEvent(Event); +} + + +//============================================================================ +void CImageViewer::mouseMoveEvent(QMouseEvent* Event) +{ + QPoint MoveVector = Event->pos() - d->MouseMoveStartPos; + d->MouseMoveStartPos = Event->pos(); + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + - MoveVector.x()); + verticalScrollBar()->setValue(verticalScrollBar()->value() - MoveVector.y()); +} + + +//============================================================================ +void CImageViewer::wheelEvent(QWheelEvent* Event) +{ + double numDegrees = Event->angleDelta().y() / 8; + double numSteps = numDegrees / 15; + d->AutoFit = false; + double Zoom; + if (numSteps < 0) + { + Zoom = pow(0.9, 0 - numSteps); + } + else + { + Zoom = pow(1.10, numSteps); + } + d->RenderWidget->zoomByValue(Zoom); +} + +#include "moc_ImageViewer.cpp" +//--------------------------------------------------------------------------- +// EOF ImageViewer.cpp diff --git a/demo/ImageViewer.h b/demo/ImageViewer.h new file mode 100644 index 000000000..37bc8cba7 --- /dev/null +++ b/demo/ImageViewer.h @@ -0,0 +1,88 @@ +#ifndef ImageViewerH +#define ImageViewerH +//============================================================================ +/// \file ImageViewer.h +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Declaration of CImageViewer +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +QT_BEGIN_NAMESPACE +class QLabel; +QT_END_NAMESPACE + + +struct ImageViewerPrivate; + +/** + * Tiny simple image viewer for showing images in demo + */ +class CImageViewer : public QScrollArea +{ + Q_OBJECT +public: + using Super = QScrollArea; + + explicit CImageViewer(QWidget *parent = nullptr); + virtual ~CImageViewer(); + + bool loadFile(const QString& Filename); + void setImage(const QImage &newImage); + +public Q_SLOTS: + void open(); + void zoomIn(); + void zoomOut(); + void normalSize(); + void fitToWindow(); + +protected: + /** + * @brief Reimplemented from QScrollArea to adjust image scaling if m_AutoFit is + * true. + */ + virtual void resizeEvent(QResizeEvent* ResizeEvent); + + /** + * @brief Handle mouse press events. + */ + virtual void mousePressEvent(QMouseEvent* Event); + + /** + * @brief Handles mouse release events. + */ + virtual void mouseReleaseEvent(QMouseEvent* Event); + + /** + * @brief Handle mouse move events. + */ + virtual void mouseMoveEvent(QMouseEvent* Event); + + /** + * @brief Use mouse wheel to change scaling of the image. + */ + virtual void wheelEvent(QWheelEvent* Event); + +private: + /** + * @brief Create the wiget actions. + */ + void createActions(); + + /** + * @brief Adjust size of render widget in case of image size change. + * @param[in] Image The new image that may have a different image size. + */ + void adjustDisplaySize(const QImage& Image); + + ImageViewerPrivate* d; + friend ImageViewerPrivate; +}; + +//--------------------------------------------------------------------------- +#endif // ImageViewerH diff --git a/demo/MainWindow.cpp b/demo/MainWindow.cpp new file mode 100644 index 000000000..55bb6ef3e --- /dev/null +++ b/demo/MainWindow.cpp @@ -0,0 +1,1056 @@ + +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file MainWindow.cpp +/// \author Uwe Kindler +/// \date 13.02.2018 +/// \brief Implementation of CMainWindow demo class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "ui_mainwindow.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) +#include +#endif + +#ifdef Q_OS_WIN +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#include +#endif +#endif + +#include "DockAreaTabBar.h" +#include "DockAreaTitleBar.h" +#include "DockAreaWidget.h" +#include "DockComponentsFactory.h" +#include "DockManager.h" +#include "DockSplitter.h" +#include "DockWidget.h" +#include "FloatingDockContainer.h" +#include "ImageViewer.h" +#include "MyDockAreaTitleBar.h" +#include "StatusDialog.h" + +/** + * Returns a random number from 0 to highest - 1 + */ +int randomNumberBounded(int highest) +{ +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + return QRandomGenerator::global()->bounded(highest); +#else + qsrand(QTime::currentTime().msec()); + return qrand() % highest; +#endif +} + + +/** + * Function returns a features string with closable (c), movable (m) and floatable (f) + * features. i.e. The following string is for a not closable but movable and floatable + * widget: c- m+ f+ + */ +static QString featuresString(ads::CDockWidget* DockWidget) +{ + auto f = DockWidget->features(); + return QString("c%1 m%2 f%3") + .arg(f.testFlag(ads::CDockWidget::DockWidgetClosable) ? "+" : "-") + .arg(f.testFlag(ads::CDockWidget::DockWidgetMovable) ? "+" : "-") + .arg(f.testFlag(ads::CDockWidget::DockWidgetFloatable) ? "+" : "-"); +} + + +/** + * Appends the string returned by featuresString() to the window title of + * the given DockWidget + */ +static void appendFeaturStringToWindowTitle(ads::CDockWidget* DockWidget) +{ + DockWidget->setWindowTitle(DockWidget->windowTitle() + + QString(" (%1)").arg(featuresString(DockWidget))); +} + +/** + * Helper function to create an SVG icon + */ +static QIcon svgIcon(const QString& File) +{ + // This is a workaround, because in item views SVG icons are not + // properly scaled and look blurry or pixelate + QIcon SvgIcon(File); + SvgIcon.addPixmap(SvgIcon.pixmap(92)); + return SvgIcon; +} + + +//============================================================================ +class CCustomComponentsFactory : public ads::CDockComponentsFactory +{ +public: + using Super = ads::CDockComponentsFactory; + ads::CDockAreaTitleBar* createDockAreaTitleBar(ads::CDockAreaWidget* DockArea) const override + { + auto TitleBar = new MyDockAreaTitleBar(DockArea); + auto CustomButton = new QToolButton(DockArea); + CustomButton->setToolTip(QObject::tr("Help")); + CustomButton->setIcon(svgIcon(":/adsdemo/images/help_outline.svg")); + CustomButton->setAutoRaise(true); + int Index = TitleBar->indexOf(TitleBar->button(ads::TitleBarButtonTabsMenu)); + TitleBar->insertWidget(Index + 1, CustomButton); + return TitleBar; + } +}; + + + +//=========================================================================== +/** + * Custom QTableWidget with a minimum size hint to test CDockWidget + * setMinimumSizeHintMode() function of CDockWidget + */ +class CMinSizeTableWidget : public QTableWidget +{ +public: + using QTableWidget::QTableWidget; + virtual QSize minimumSizeHint() const override + { + return QSize(300, 100); + } +}; + + + +//============================================================================ +/** + * Private data class pimpl + */ +struct MainWindowPrivate +{ + CMainWindow* _this; + Ui::MainWindow ui; + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + ads::CDockManager* DockManager = nullptr; + ads::CDockWidget* WindowTitleTestDockWidget = nullptr; + QPointer LastDockedEditor; + QPointer LastCreatedFloatingEditor; + + MainWindowPrivate(CMainWindow* _public) : _this(_public) {} + + /** + * Creates the toolbar actions + */ + void createActions(); + + /** + * Fill the dock manager with dock widgets + */ + void createContent(); + + /** + * Saves the dock manager state and the main window geometry + */ + void saveState(); + + /** + * Save the list of perspectives + */ + void savePerspectives(); + + /** + * Restores the dock manager state + */ + void restoreState(); + + /** + * Restore the perspective listo of the dock manager + */ + void restorePerspectives(); + + /** + * Creates a dock widget with a file system tree view + */ + ads::CDockWidget* createFileSystemTreeDockWidget() + { + static int FileSystemCount = 0; + QTreeView* w = new QTreeView(); + w->setFrameShape(QFrame::NoFrame); + QFileSystemModel* m = new QFileSystemModel(w); + m->setRootPath(QDir::currentPath()); + w->setModel(m); + w->setRootIndex(m->index(QDir::currentPath())); + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Filesystem %1") + .arg(FileSystemCount++)); + DockWidget->setWidget(w); + DockWidget->setIcon(svgIcon(":/adsdemo/images/folder_open.svg")); + ui.menuView->addAction(DockWidget->toggleViewAction()); + // We disable focus to test focus highlighting if the dock widget content + // does not support focus + w->setFocusPolicy(Qt::NoFocus); + auto ToolBar = DockWidget->createDefaultToolBar(); + ToolBar->addAction(ui.actionSaveState); + ToolBar->addAction(ui.actionRestoreState); + return DockWidget; + } + + /** + * Create a dock widget with a QCalendarWidget + */ + ads::CDockWidget* createCalendarDockWidget() + { + static int CalendarCount = 0; + QCalendarWidget* w = new QCalendarWidget(); + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Calendar %1").arg(CalendarCount++)); + // The following lines are for testing the setWidget() and takeWidget() + // functionality + DockWidget->setWidget(w); + DockWidget->setWidget(w); // what happens if we set a widget if a widget is already set + DockWidget->takeWidget(); // we remove the widget + DockWidget->setWidget(w); // and set the widget again - there should be no error + DockWidget->setToggleViewActionMode(ads::CDockWidget::ActionModeShow); + DockWidget->setIcon(svgIcon(":/adsdemo/images/date_range.svg")); + ui.menuView->addAction(DockWidget->toggleViewAction()); + auto ToolBar = DockWidget->createDefaultToolBar(); + ToolBar->addAction(ui.actionSaveState); + ToolBar->addAction(ui.actionRestoreState); + // For testing all calendar dock widgets have a the tool button style + // Qt::ToolButtonTextUnderIcon + DockWidget->setToolBarStyleSource(ads::CDockWidget::ToolBarStyleFromDockWidget); + DockWidget->setToolBarStyle(Qt::ToolButtonTextUnderIcon, ads::CDockWidget::StateFloating); + return DockWidget; + } + + + /** + * Create dock widget with a text label + */ + ads::CDockWidget* createLongTextLabelDockWidget() + { + static int LabelCount = 0; + QLabel* l = new QLabel(); + l->setWordWrap(true); + l->setAlignment(Qt::AlignTop | Qt::AlignLeft); + l->setText(QString("Label %1 %2 - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. " + "Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque " + "penatibus et magnis dis parturient montes, nascetur ridiculus mus. " + "Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. " + "Nulla consequat massa quis enim. Donec pede justo, fringilla vel, " + "aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, " + "imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede " + "mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum " + "semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, " + "porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, " + "dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla " + "ut metus varius laoreet.") + .arg(LabelCount) + .arg(QTime::currentTime().toString("hh:mm:ss:zzz"))); + + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Label %1").arg(LabelCount++)); + DockWidget->setWidget(l); + DockWidget->setIcon(svgIcon(":/adsdemo/images/font_download.svg")); + ui.menuView->addAction(DockWidget->toggleViewAction()); + return DockWidget; + } + + + /** + * Creates as imple editor widget + */ + ads::CDockWidget* createEditorWidget() + { + static int EditorCount = 0; + QPlainTextEdit* w = new QPlainTextEdit(); + w->setPlaceholderText("This is an editor. If you close the editor, it will be " + "deleted. Enter your text here."); + w->setStyleSheet("border: none"); + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Editor %1").arg(EditorCount++)); + DockWidget->setWidget(w); + DockWidget->setIcon(svgIcon(":/adsdemo/images/edit.svg")); + DockWidget->setFeature(ads::CDockWidget::CustomCloseHandling, true); + ui.menuView->addAction(DockWidget->toggleViewAction()); + + QMenu* OptionsMenu = new QMenu(DockWidget); + OptionsMenu->setTitle(QObject::tr("Options")); + OptionsMenu->setToolTip(OptionsMenu->title()); + OptionsMenu->setIcon(svgIcon(":/adsdemo/images/custom-menu-button.svg")); + auto MenuAction = OptionsMenu->menuAction(); + // The object name of the action will be set for the QToolButton that + // is created in the dock area title bar. You can use this name for CSS + // styling + MenuAction->setObjectName("optionsMenu"); + DockWidget->setTitleBarActions({OptionsMenu->menuAction()}); + auto a = OptionsMenu->addAction(QObject::tr("Clear Editor")); + w->connect(a, SIGNAL(triggered()), SLOT(clear())); + + return DockWidget; + } + + /** + * Creates a simply image viewr + */ + ads::CDockWidget* createImageViewer() + { + static int ImageViewerCount = 0; + auto w = new CImageViewer(); + auto ImageIndex = randomNumberBounded(4); + auto FileName = ":adsdemo/images/ads_logo.svg"; + + // Pick a random image from a number of images + switch (ImageIndex) + { + case 0: FileName = ":adsdemo/images/ads_tile_blue.svg"; break; + case 1: FileName = ":adsdemo/images/ads_tile_blue_light.svg"; break; + case 2: FileName = ":adsdemo/images/ads_tile_green.svg"; break; + case 3: FileName = ":adsdemo/images/ads_tile_orange.svg"; break; + } + + auto Result = w->loadFile(FileName); + qDebug() << "loadFile result: " << Result; + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Image Viewer %1").arg(ImageViewerCount++)); + DockWidget->setIcon(svgIcon(":/adsdemo/images/photo.svg")); + DockWidget->setWidget(w,ads:: CDockWidget::ForceNoScrollArea); + auto ToolBar = DockWidget->createDefaultToolBar(); + ToolBar->addActions(w->actions()); + return DockWidget; + } + + /** + * Create a table widget + */ + ads::CDockWidget* createTableWidget() + { + static int TableCount = 0; + auto w = new CMinSizeTableWidget(); + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Table %1").arg(TableCount++)); + static int colCount = 5; + static int rowCount = 30; + w->setColumnCount(colCount); + w->setRowCount(rowCount); + for (int col = 0; col < colCount; ++col) + { + w->setHorizontalHeaderItem(col, new QTableWidgetItem(QString("Col %1").arg(col+1))); + for (int row = 0; row < rowCount; ++row) + { + w->setItem(row, col, new QTableWidgetItem(QString("T %1-%2").arg(row + 1).arg(col+1))); + } + } + DockWidget->setWidget(w); + DockWidget->setIcon(svgIcon(":/adsdemo/images/grid_on.svg")); + DockWidget->setMinimumSizeHintMode(ads::CDockWidget::MinimumSizeHintFromContent); + auto ToolBar = DockWidget->createDefaultToolBar(); + auto Action = ToolBar->addAction(svgIcon(":/adsdemo/images/fullscreen.svg"), "Toggle Fullscreen"); + QObject::connect(Action, &QAction::triggered, [=]() + { + if (DockWidget->isFullScreen()) + { + DockWidget->showNormal(); + } + else + { + DockWidget->showFullScreen(); + } + }); + ui.menuView->addAction(DockWidget->toggleViewAction()); + return DockWidget; + } + + /** + * Create QQuickWidget for test for OpenGL and QQuick + */ + ads::CDockWidget *createQQuickWidget() + { + QQuickWidget *widget = new QQuickWidget(); + ads::CDockWidget *dockWidget = new ads::CDockWidget("Quick"); + dockWidget->setWidget(widget); + return dockWidget; + } + + +#ifdef Q_OS_WIN +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + /** + * Creates an ActiveX widget on windows + */ + ads::CDockWidget* createActiveXWidget(QWidget* parent = nullptr) + { + static int ActiveXCount = 0; + QAxWidget* w = new QAxWidget("{6bf52a52-394a-11d3-b153-00c04f79faa6}", parent); + ads::CDockWidget* DockWidget = new ads::CDockWidget(QString("Active X %1").arg(ActiveXCount++)); + DockWidget->setWidget(w); + ui.menuView->addAction(DockWidget->toggleViewAction()); + return DockWidget; + } +#endif +#endif +}; + +//============================================================================ +void MainWindowPrivate::createContent() +{ + // Test container docking + auto DockWidget = createCalendarDockWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetClosable, false); + auto SpecialDockArea = DockManager->addDockWidget(ads::LeftDockWidgetArea, DockWidget); + + // For this Special Dock Area we want to avoid dropping on the center of it (i.e. we don't want this widget to be ever tabbified): + { + //SpecialDockArea->setAllowedAreas(ads::OuterDockAreas); + SpecialDockArea->setAllowedAreas({ads::LeftDockWidgetArea, ads::RightDockWidgetArea, ads::TopDockWidgetArea}); // just for testing + } + + DockWidget = createLongTextLabelDockWidget(); + WindowTitleTestDockWidget = DockWidget; + DockWidget->setFeature(ads::CDockWidget::DockWidgetFocusable, false); + DockManager->addDockWidget(ads::LeftDockWidgetArea, DockWidget); + auto FileSystemWidget = createFileSystemTreeDockWidget(); + FileSystemWidget->setFeature(ads::CDockWidget::DockWidgetFloatable, false); + appendFeaturStringToWindowTitle(FileSystemWidget); + DockManager->addDockWidget(ads::BottomDockWidgetArea, FileSystemWidget); + + FileSystemWidget = createFileSystemTreeDockWidget(); + FileSystemWidget->setFeature(ads::CDockWidget::DockWidgetMovable, false); + FileSystemWidget->setFeature(ads::CDockWidget::DockWidgetFloatable, false); + appendFeaturStringToWindowTitle(FileSystemWidget); + + // Test custom factory - we inject a help button into the title bar + ads::CDockComponentsFactory::setFactory(new CCustomComponentsFactory()); + auto TopDockArea = DockManager->addDockWidget(ads::TopDockWidgetArea, FileSystemWidget); + // Uncomment the next line if you would like to test the + // HideSingleWidgetTitleBar functionality + // TopDockArea->setDockAreaFlag(ads::CDockAreaWidget::HideSingleWidgetTitleBar, true); + ads::CDockComponentsFactory::resetDefaultFactory(); + + // We create a calendar widget and clear all flags to prevent the dock area + // from closing + 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(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 + auto CustomButton = new QToolButton(DockArea); + CustomButton->setToolTip(QObject::tr("Create Editor")); + CustomButton->setIcon(svgIcon(":/adsdemo/images/plus.svg")); + CustomButton->setAutoRaise(true); + + auto TitleBar = DockArea->titleBar(); + int Index = TitleBar->indexOf(TitleBar->tabBar()); + TitleBar->insertWidget(Index + 1, CustomButton); + QObject::connect(CustomButton, &QToolButton::clicked, [=]() + { + auto DockWidget = createEditorWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetDeleteOnClose, true); + DockManager->addDockWidgetTabToArea(DockWidget, DockArea); + _this->connect(DockWidget, SIGNAL(closeRequested()), SLOT(onEditorCloseRequested())); + }); + + // Test dock area docking + auto RighDockArea = DockManager->addDockWidget(ads::RightDockWidgetArea, createLongTextLabelDockWidget(), TopDockArea); + DockWidget = createLongTextLabelDockWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetPinnable, false); + DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget, RighDockArea); + auto BottomDockArea = DockManager->addDockWidget(ads::BottomDockWidgetArea, createLongTextLabelDockWidget(), RighDockArea); + DockManager->addDockWidget(ads::CenterDockWidgetArea, createLongTextLabelDockWidget(), RighDockArea); + auto LabelDockWidget = createLongTextLabelDockWidget(); + DockManager->addDockWidget(ads::CenterDockWidgetArea, LabelDockWidget, BottomDockArea); + + // Tests CustomCloseHandling without DeleteOnClose + LabelDockWidget->setFeature(ads::CDockWidget::CustomCloseHandling, true); + LabelDockWidget->setWindowTitle(LabelDockWidget->windowTitle() + " [Custom Close]"); + 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)) + DockManager->addDockWidget(ads::CenterDockWidgetArea, createActiveXWidget(), RighDockArea); +#endif +#endif + + for (auto DockWidget : DockManager->dockWidgetsMap()) + { + _this->connect(DockWidget, SIGNAL(viewToggled(bool)), SLOT(onViewToggled(bool))); + _this->connect(DockWidget, SIGNAL(visibilityChanged(bool)), SLOT(onViewVisibilityChanged(bool))); + } + + // Create image viewer + DockWidget = createImageViewer(); + DockManager->addDockWidget(ads::LeftDockWidgetArea, DockWidget); + + // Create quick widget + DockWidget = createQQuickWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetClosable, true); + DockManager->addDockWidget(ads::LeftDockWidgetArea, DockWidget); +} + + +//============================================================================ +void MainWindowPrivate::createActions() +{ + ui.toolBar->addAction(ui.actionSaveState); + ui.toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + ui.actionSaveState->setIcon(svgIcon(":/adsdemo/images/save.svg")); + ui.toolBar->addAction(ui.actionRestoreState); + ui.actionRestoreState->setIcon(svgIcon(":/adsdemo/images/restore.svg")); + + ui.toolBar->addSeparator(); + + QAction* a = ui.toolBar->addAction("Lock Workspace"); + a->setIcon(svgIcon(":/adsdemo/images/lock_outline.svg")); + a->setCheckable(true); + a->setChecked(false); + QObject::connect(a, &QAction::triggered, _this, &CMainWindow::lockWorkspace); + + PerspectiveListAction = new QWidgetAction(_this); + PerspectiveComboBox = new QComboBox(_this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui.toolBar->addAction(PerspectiveListAction); + + a = SavePerspectiveAction = ui.toolBar->addAction("Create Perspective"); + a->setIcon(svgIcon(":/adsdemo/images/picture_in_picture.svg")); + QObject::connect(a, &QAction::triggered, _this, &CMainWindow::savePerspective); + ui.toolBar->addAction(SavePerspectiveAction); + + ui.toolBar->addSeparator(); + + a = ui.toolBar->addAction("Create Floating Editor"); + a->setProperty("Floating", true); + a->setToolTip("Creates floating dynamic dockable editor windows that are deleted on close"); + a->setIcon(svgIcon(":/adsdemo/images/note_add.svg")); + _this->connect(a, SIGNAL(triggered()), SLOT(createEditor())); + ui.menuTests->addAction(a); + + a = ui.toolBar->addAction("Create Docked Editor"); + a->setProperty("Floating", false); + a->setToolTip("Creates a docked editor windows that are deleted on close"); + a->setIcon(svgIcon(":/adsdemo/images/docked_editor.svg")); + _this->connect(a, SIGNAL(triggered()), SLOT(createEditor())); + ui.menuTests->addAction(a); + + a = ui.toolBar->addAction("Create Editor Tab"); + a->setProperty("Floating", false); + a->setToolTip("Creates a editor tab and inserts it as second tab into an area"); + a->setIcon(svgIcon(":/adsdemo/images/tab.svg")); + a->setProperty("Tabbed", true); + _this->connect(a, SIGNAL(triggered()), SLOT(createEditor())); + ui.menuTests->addAction(a); + + ui.toolBar->addSeparator(); + a = ui.toolBar->addAction("Create Floating Table"); + a->setToolTip("Creates floating dynamic dockable table with millions of entries"); + a->setIcon(svgIcon(":/adsdemo/images/grid_on.svg")); + _this->connect(a, SIGNAL(triggered()), SLOT(createTable())); + ui.menuTests->addAction(a); + + a = ui.toolBar->addAction("Create Image Viewer"); + auto ToolButton = qobject_cast(ui.toolBar->widgetForAction(a)); + ToolButton->setPopupMode(QToolButton::InstantPopup); + a->setToolTip("Creates floating, docked or pinned image viewer"); + a->setIcon(svgIcon(":/adsdemo/images/panorama.svg")); + ui.menuTests->addAction(a); + auto Menu = new QMenu(); + ToolButton->setMenu(Menu); + a = Menu->addAction("Floating Image Viewer"); + _this->connect(a, SIGNAL(triggered()), SLOT(createImageViewer())); + a = Menu->addAction("Docked Image Viewer"); + _this->connect(a, SIGNAL(triggered()), SLOT(createImageViewer())); + a = Menu->addAction("Pinned Image Viewer"); + _this->connect(a, SIGNAL(triggered()), SLOT(createImageViewer())); + + + ui.menuTests->addSeparator(); + a = ui.menuTests->addAction("Show Status Dialog"); + _this->connect(a, SIGNAL(triggered()), SLOT(showStatusDialog())); + + a = ui.menuTests->addAction("Toggle Label 0 Window Title"); + _this->connect(a, SIGNAL(triggered()), SLOT(toggleDockWidgetWindowTitle())); + ui.menuTests->addSeparator(); + + a = ui.toolBar->addAction("Apply VS Style"); + a->setToolTip("Applies a Visual Studio light style (visual_studio_light.css)." ); + a->setIcon(svgIcon(":/adsdemo/images/color_lens.svg")); + QObject::connect(a, &QAction::triggered, _this, &CMainWindow::applyVsStyle); + ui.menuTests->addAction(a); +} + + +//============================================================================ +void MainWindowPrivate::saveState() +{ + QSettings Settings("Settings.ini", QSettings::IniFormat); + Settings.setValue("mainWindow/Geometry", _this->saveGeometry()); + Settings.setValue("mainWindow/State", _this->saveState()); + Settings.setValue("mainWindow/DockingState", DockManager->saveState()); +} + + +//============================================================================ +void MainWindowPrivate::restoreState() +{ + QSettings Settings("Settings.ini", QSettings::IniFormat); + _this->restoreGeometry(Settings.value("mainWindow/Geometry").toByteArray()); + _this->restoreState(Settings.value("mainWindow/State").toByteArray()); + DockManager->restoreState(Settings.value("mainWindow/DockingState").toByteArray()); +} + + + +//============================================================================ +void MainWindowPrivate::savePerspectives() +{ + QSettings Settings("Settings.ini", QSettings::IniFormat); + DockManager->savePerspectives(Settings); +} + + + +//============================================================================ +void MainWindowPrivate::restorePerspectives() +{ + QSettings Settings("Settings.ini", QSettings::IniFormat); + DockManager->loadPerspectives(Settings); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); +} + + +//============================================================================ +CMainWindow::CMainWindow(QWidget *parent) : + QMainWindow(parent), + d(new MainWindowPrivate(this)) +{ + using namespace ads; + d->ui.setupUi(this); + setWindowTitle(QApplication::instance()->applicationName()); + d->createActions(); + + // uncomment the following line if the tab close button should be + // a QToolButton instead of a QPushButton + // CDockManager::setConfigFlags(CDockManager::configFlags() | CDockManager::TabCloseButtonIsToolButton); + + // uncomment the following line if you want to use opaque undocking and + // opaque splitter resizing + //CDockManager::setConfigFlags(CDockManager::DefaultOpaqueConfig); + + // uncomment the following line if you want a fixed tab width that does + // not change if the visibility of the close button changes + //CDockManager::setConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden, true); + + // uncomment the following line if you don't want close button on DockArea's title bar + //CDockManager::setConfigFlag(CDockManager::DockAreaHasCloseButton, false); + + // uncomment the following line if you don't want undock button on DockArea's title bar + //CDockManager::setConfigFlag(CDockManager::DockAreaHasUndockButton, false); + + // uncomment the following line if you don't want tabs menu button on DockArea's title bar + //CDockManager::setConfigFlag(CDockManager::DockAreaHasTabsMenuButton, false); + + // uncomment the following line if you don't want disabled buttons to appear on DockArea's title bar + //CDockManager::setConfigFlag(CDockManager::DockAreaHideDisabledButtons, true); + + // uncomment the following line if you want to show tabs menu button on DockArea's title bar only when there are more than one tab and at least of them has elided title + //CDockManager::setConfigFlag(CDockManager::DockAreaDynamicTabsMenuButtonVisibility, true); + + // uncomment the following line if you want floating container to always show application title instead of active dock widget's title + //CDockManager::setConfigFlag(CDockManager::FloatingContainerHasWidgetTitle, false); + + // uncomment the following line if you want floating container to show active dock widget's icon instead of always showing application icon + //CDockManager::setConfigFlag(CDockManager::FloatingContainerHasWidgetIcon, true); + + // uncomment the following line if you want a central widget in the main dock container (the dock manager) without a titlebar + // If you enable this code, you can test it in the demo with the Calendar 0 + // dock widget. + //CDockManager::setConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar, true); + + // uncomment the following line to enable focus highlighting of the dock + // widget that has the focus + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); + + // uncomment if you would like to enable dock widget auto hiding + CDockManager::setAutoHideConfigFlags({CDockManager::DefaultAutoHideConfig}); + + // 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); + d->DockManager->setDockWidgetToolBarStyle(Qt::ToolButtonIconOnly, ads::CDockWidget::StateFloating); + + #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + connect(d->PerspectiveComboBox, SIGNAL(activated(QString)), + d->DockManager, SLOT(openPerspective(QString))); + #else + connect(d->PerspectiveComboBox, SIGNAL(textActivated(QString)), + d->DockManager, SLOT(openPerspective(QString))); + #endif + + d->createContent(); + // Default window geometry - center on screen + resize(1280, 720); + setGeometry(QStyle::alignedRect( + Qt::LeftToRight, Qt::AlignCenter, frameSize(), + QGuiApplication::primaryScreen()->availableGeometry() + )); + + //d->restoreState(); + d->restorePerspectives(); +} + + +//============================================================================ +CMainWindow::~CMainWindow() +{ + delete d; +} + + +//============================================================================ +void CMainWindow::closeEvent(QCloseEvent* event) +{ + d->saveState(); + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + d->DockManager->deleteLater(); + QMainWindow::closeEvent(event); +} + + +//============================================================================ +void CMainWindow::on_actionSaveState_triggered(bool) +{ + qDebug() << "MainWindow::on_actionSaveState_triggered"; + d->saveState(); +} + + +//============================================================================ +void CMainWindow::on_actionRestoreState_triggered(bool) +{ + qDebug() << "MainWindow::on_actionRestoreState_triggered"; + d->restoreState(); +} + + +//============================================================================ +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + d->DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(d->PerspectiveComboBox); + d->PerspectiveComboBox->clear(); + d->PerspectiveComboBox->addItems(d->DockManager->perspectiveNames()); + d->PerspectiveComboBox->setCurrentText(PerspectiveName); + + d->savePerspectives(); +} + + +//============================================================================ +void CMainWindow::onViewToggled(bool Open) +{ + Q_UNUSED(Open); + auto DockWidget = qobject_cast(sender()); + if (!DockWidget) + { + return; + } + + //qDebug() << DockWidget->objectName() << " viewToggled(" << Open << ")"; +} + + +//============================================================================ +void CMainWindow::onViewVisibilityChanged(bool Visible) +{ + Q_UNUSED(Visible); + auto DockWidget = qobject_cast(sender()); + if (!DockWidget) + { + return; + } + + //qDebug() << DockWidget->objectName() << " visibilityChanged(" << Visible << ")"; +} + + +//============================================================================ +void CMainWindow::createEditor() +{ + QObject* Sender = sender(); + QVariant vFloating = Sender->property("Floating"); + bool Floating = vFloating.isValid() ? vFloating.toBool() : true; + QVariant vTabbed = Sender->property("Tabbed"); + bool Tabbed = vTabbed.isValid() ? vTabbed.toBool() : true; + auto DockWidget = d->createEditorWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetDeleteOnClose, true); + DockWidget->setFeature(ads::CDockWidget::DockWidgetForceCloseWithArea, true); + connect(DockWidget, SIGNAL(closeRequested()), SLOT(onEditorCloseRequested())); + + if (Floating) + { + auto FloatingWidget = d->DockManager->addDockWidgetFloating(DockWidget); + FloatingWidget->move(QPoint(20, 20)); + d->LastCreatedFloatingEditor = DockWidget; + d->LastDockedEditor.clear(); + return; + } + + + ads::CDockAreaWidget* EditorArea = d->LastDockedEditor ? d->LastDockedEditor->dockAreaWidget() : nullptr; + if (EditorArea) + { + if (Tabbed) + { + // Test inserting the dock widget tab at a given position instead + // of appending it. This function inserts the new dock widget as + // first tab + d->DockManager->addDockWidgetTabToArea(DockWidget, EditorArea, 0); + } + else + { + d->DockManager->setConfigFlag(ads::CDockManager::EqualSplitOnInsertion, true); + d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget, EditorArea); + } + } + else + { + if (d->LastCreatedFloatingEditor) + { + d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget, d->LastCreatedFloatingEditor->dockAreaWidget()); + } + else + { + d->DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); + } + } + d->LastDockedEditor = DockWidget; +} + + +//============================================================================ +void CMainWindow::onEditorCloseRequested() +{ + auto DockWidget = qobject_cast(sender()); + int Result = QMessageBox::question(this, "Close Editor", QString("Editor %1 " + "contains unsaved changes? Would you like to close it?") + .arg(DockWidget->windowTitle())); + if (QMessageBox::Yes == Result) + { + DockWidget->closeDockWidget(); + } +} + + +//============================================================================ +void CMainWindow::onImageViewerCloseRequested() +{ + auto DockWidget = qobject_cast(sender()); + int Result = QMessageBox::question(this, "Close Image Viewer", QString("%1 " + "contains unsaved changes? Would you like to close it?") + .arg(DockWidget->windowTitle())); + if (QMessageBox::Yes == Result) + { + DockWidget->closeDockWidget(); + } +} + + +//============================================================================ +void CMainWindow::createTable() +{ + auto DockWidget = d->createTableWidget(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetDeleteOnClose, true); + auto FloatingWidget = d->DockManager->addDockWidgetFloating(DockWidget); + FloatingWidget->move(QPoint(40, 40)); +} + + +//============================================================================ +void CMainWindow::showStatusDialog() +{ + CStatusDialog Dialog(d->DockManager); + Dialog.exec(); +} + + +//============================================================================ +void CMainWindow::toggleDockWidgetWindowTitle() +{ + QString Title = d->WindowTitleTestDockWidget->windowTitle(); + int i = Title.indexOf(" (Test)"); + if (-1 == i) + { + Title += " (Test)"; + } + else + { + Title = Title.left(i); + } + d->WindowTitleTestDockWidget->setWindowTitle(Title); +} + + +//============================================================================ +void CMainWindow::applyVsStyle() +{ + QFile StyleSheetFile(":adsdemo/res/visual_studio_light.css"); + StyleSheetFile.open(QIODevice::ReadOnly); + QTextStream StyleSheetStream(&StyleSheetFile); + auto Stylesheet = StyleSheetStream.readAll(); + StyleSheetFile.close(); + d->DockManager->setStyleSheet(Stylesheet); +} + + +//============================================================================ +void CMainWindow::createImageViewer() +{ + QAction* a = qobject_cast(sender()); + qDebug() << "createImageViewer " << a->text(); + + auto DockWidget = d->createImageViewer(); + DockWidget->setFeature(ads::CDockWidget::DockWidgetDeleteOnClose, true); + DockWidget->setFeature(ads::CDockWidget::DockWidgetForceCloseWithArea, true); + DockWidget->setFeature(ads::CDockWidget::CustomCloseHandling, true); + DockWidget->resize(QSize(640, 480)); + connect(DockWidget, &ads::CDockWidget::closeRequested, this, + &CMainWindow::onImageViewerCloseRequested); + + if (a->text().startsWith("Floating")) + { + auto FloatingWidget = d->DockManager->addDockWidgetFloating(DockWidget); + FloatingWidget->move(QPoint(20, 20)); + } + else if (a->text().startsWith("Docked")) + { + d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget); + } + else if (a->text().startsWith("Pinned")) + { + d->DockManager->addAutoHideDockWidget(ads::SideBarLeft, DockWidget); + } +} + + +//============================================================================ +void CMainWindow::lockWorkspace(bool Value) +{ + if (Value) + { + d->DockManager->lockDockWidgetFeaturesGlobally(); + } + else + { + d->DockManager->lockDockWidgetFeaturesGlobally(ads::CDockWidget::NoDockWidgetFeatures); + } +} + diff --git a/demo/MainWindow.h b/demo/MainWindow.h new file mode 100644 index 000000000..dd1236085 --- /dev/null +++ b/demo/MainWindow.h @@ -0,0 +1,74 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file MainWindow.h +/// \author Uwe Kindler +/// \date 13.02.2018 +/// \brief Declaration of CMainWindow class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include + + + +struct MainWindowPrivate; + + +/** + * Simple main window for demo + */ +class CMainWindow : public QMainWindow +{ + Q_OBJECT +private: + MainWindowPrivate* d;///< private data - pimpl + friend struct MainWindowPrivate; + +protected: + virtual void closeEvent(QCloseEvent* event) override; + +public: + explicit CMainWindow(QWidget *parent = 0); + virtual ~CMainWindow(); + +private slots: + void on_actionSaveState_triggered(bool); + void on_actionRestoreState_triggered(bool); + void savePerspective(); + void onViewToggled(bool Open); + void onViewVisibilityChanged(bool Visible); + void createEditor(); + void createTable(); + void onEditorCloseRequested(); + void onImageViewerCloseRequested(); + void showStatusDialog(); + void toggleDockWidgetWindowTitle(); + void applyVsStyle(); + void createImageViewer(); + void lockWorkspace(bool Value); +}; + +#endif // MAINWINDOW_H diff --git a/demo/MyDockAreaTitleBar.h b/demo/MyDockAreaTitleBar.h new file mode 100644 index 000000000..074cc795b --- /dev/null +++ b/demo/MyDockAreaTitleBar.h @@ -0,0 +1,34 @@ +// +// Created by fuga on 08 nov 2024. +// + +#ifndef QTADS_MYDOCKAREATITLEBAR_H +#define QTADS_MYDOCKAREATITLEBAR_H + +#include + +class MyDockAreaTitleBar : public ads::CDockAreaTitleBar { +public: + explicit MyDockAreaTitleBar(ads::CDockAreaWidget* parent) + : CDockAreaTitleBar(parent) + {} + + QMenu* buildContextMenu(QMenu*) override + { + auto menu = ads::CDockAreaTitleBar::buildContextMenu(nullptr); + menu->addSeparator(); + auto action = menu->addAction(tr("Format HardDrive")); + + connect(action, &QAction::triggered, this, [this](){ + QMessageBox msgBox; + msgBox.setText("No, just kidding"); + msgBox.setStandardButtons(QMessageBox::Abort); + msgBox.setDefaultButton(QMessageBox::Abort); + msgBox.exec(); + }); + + return menu; + } +}; + +#endif // QTADS_MYDOCKAREATITLEBAR_H diff --git a/demo/RenderWidget.cpp b/demo/RenderWidget.cpp new file mode 100644 index 000000000..c168e963a --- /dev/null +++ b/demo/RenderWidget.cpp @@ -0,0 +1,108 @@ +//============================================================================ +/// \file RenderWidget.cpp +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Implementation of CRenderWidget +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include "RenderWidget.h" + +#include +#include + + +//=========================================================================== +CRenderWidget::CRenderWidget(QWidget* Parent) : + QWidget(Parent), m_ScaleFactor(1) +{ + this->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + this->setCursor(Qt::OpenHandCursor); +} + +//=========================================================================== +CRenderWidget::~CRenderWidget() +{ + +} + +//=========================================================================== +void CRenderWidget::showImage(const QImage& Image) +{ + m_Image = QPixmap::fromImage(Image); + this->adjustWidgetSize(); + this->repaint(); +} + +//=========================================================================== +void CRenderWidget::paintEvent(QPaintEvent* Event) +{ + Q_UNUSED(Event); + QPainter Painter(this); + Painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + Painter.setRenderHint(QPainter::Antialiasing, true); + Painter.scale(m_ScaleFactor, m_ScaleFactor); + Painter.drawPixmap(QPoint(0, 0), m_Image); +} + +//============================================================================ +void CRenderWidget::zoomIn() +{ + scaleImage(1.25); +} + +//============================================================================ +void CRenderWidget::zoomOut() +{ + scaleImage(0.8); +} + +//============================================================================ +void CRenderWidget::zoomByValue(double ZoomValue) +{ + scaleImage(ZoomValue); +} + +//============================================================================ +void CRenderWidget::normalSize() +{ + m_ScaleFactor = 1; + this->adjustWidgetSize(); +} + +//============================================================================ +void CRenderWidget::scaleImage(double ScaleFactor) +{ + m_ScaleFactor *= ScaleFactor; + this->adjustWidgetSize(); +} + +//============================================================================ +void CRenderWidget::adjustWidgetSize() +{ + QSize ScaledImageSize = m_Image.size() * m_ScaleFactor; + if (ScaledImageSize != this->size()) + { + this->setFixedSize(ScaledImageSize); + } +} + +//============================================================================ +void CRenderWidget::scaleToSize(const QSize& TargetSize) +{ + if (m_Image.isNull()) + { + return; + } + double ScaleFactorH = (double) TargetSize.width() / m_Image.size().width(); + double ScaleFactorV = (double) TargetSize.height() + / m_Image.size().height(); + m_ScaleFactor = (ScaleFactorH < ScaleFactorV) ? ScaleFactorH : ScaleFactorV; + this->adjustWidgetSize(); +} + +//--------------------------------------------------------------------------- +// EOF RenderWidget.cpp diff --git a/demo/RenderWidget.h b/demo/RenderWidget.h new file mode 100644 index 000000000..81fedf145 --- /dev/null +++ b/demo/RenderWidget.h @@ -0,0 +1,111 @@ +#ifndef RenderWidgetH +#define RenderWidgetH +//============================================================================ +/// \file RenderWidget.h +/// \author Uwe Kindler +/// \date 04.11.2022 +/// \brief Declaration of CRenderWidget +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include + +//============================================================================ +// FORWARD DECLARATIONS +//============================================================================ +class QImage; + + +/** + * @brief Widget for fast display of images (i.e. for video capture devices) + */ +class CRenderWidget : public QWidget +{ + Q_OBJECT +private: + QPixmap m_Image; + double m_ScaleFactor; + +protected: + /** + * @brief Reimplemented paint event method showing actual image. + */ + void paintEvent(QPaintEvent* PaintEvent); + + /** + * @brief Change scale factor + */ + void scaleImage(double ScaleFactor); + + /** + * @brief Adjust widget size to size of image. + */ + void adjustWidgetSize(); + +public: + /** + * Constructor + * @param[in] Parent Parent widget. + */ + CRenderWidget(QWidget* Parent); + + /** + * Destructor + */ + virtual ~CRenderWidget(); + +signals: + /** + * @brief Signalize change of captured image size. + * @param ImageSize New image size. + */ + void imageSizeChanged(const QSize& ImageSize); + +public slots: + /** + * @brief Show new image in render widget. + */ + void showImage(const QImage& Image); + + /** + * @brief Zoom into the scene. + * This function decreases the scaling factor by setting it to the previous + * value in internal scaling list. + * @brief Steps The number of steps to zoom in. One step is 25%. + */ + void zoomIn(); + + /** + * @brief Zoom out of the scene. + * This function decreases the scaling factor by setting it to the next + * value in internal scaling list. + * @brief Steps The number of steps to zoom out. One step is 25%. + */ + void zoomOut(); + + /** + * @brief Change zoom by zoom value. + * @param[in] ZoomValue This is the zoom value to apply. A value of 1 + * means no change a value > 1 increases the image (i.e. 1.25 would increase + * the image by 25%) and a value of < 1 decreases the image size (i.e. + * a value of 0.8 would decrease the image size by 25%). + */ + void zoomByValue(double ZoomValue); + + /** + * @brief Resets the actual scaling to 1 and display the image with its + * actual pixel size. + */ + void normalSize(); + + /** + * @brief Scales the wiget and its content image to the given TargetSize + */ + void scaleToSize(const QSize& TargetSize); +}; // class CRenderWidget + +//--------------------------------------------------------------------------- +#endif // RenderWidgetH diff --git a/demo/StatusDialog.cpp b/demo/StatusDialog.cpp new file mode 100644 index 000000000..fe8b4f5ed --- /dev/null +++ b/demo/StatusDialog.cpp @@ -0,0 +1,88 @@ +//============================================================================ +/// \file StatusDialog.cpp +/// \author Uwe Kindler +/// \date 13.04.2020 +/// \brief Implementation of CStatusDialog class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "StatusDialog.h" + +#include + +#include "DockManager.h" +#include "DockWidget.h" +#include "ui_StatusDialog.h" + +/** + * Private data class of CStatusDialog class (pimpl) + */ +struct StatusDialogPrivate +{ + CStatusDialog *_this; + Ui::CStatusDialogClass ui; + ads::CDockManager* DockManager; + QMap DockWidgets; + + /** + * Private data constructor + */ + StatusDialogPrivate(CStatusDialog *_public); +}; +// struct StatusDialogPrivate + +//============================================================================ +StatusDialogPrivate::StatusDialogPrivate(CStatusDialog *_public) : + _this(_public) +{ + +} + +//============================================================================ +CStatusDialog::CStatusDialog(ads::CDockManager* DockManager) : + QDialog(DockManager), + d(new StatusDialogPrivate(this)) +{ + d->ui.setupUi(this); + d->DockManager = DockManager; + d->DockWidgets = DockManager->dockWidgetsMap(); + + for (auto it = d->DockWidgets.begin(); it != d->DockWidgets.end(); ++it) + { + QVariant vDockWidget = QVariant::fromValue(it.value()); + d->ui.dockWidgetsComboBox->addItem(it.key(), vDockWidget); + } +} + +//============================================================================ +CStatusDialog::~CStatusDialog() +{ + delete d; +} + + +//============================================================================ +void CStatusDialog::on_dockWidgetsComboBox_currentIndexChanged(int index) +{ + if (index < 0) + { + return; + } + + auto vDockWidget = d->ui.dockWidgetsComboBox->currentData(); + auto DockWidget = vDockWidget.value(); + d->ui.isClosedCheckBox->setChecked(DockWidget->isClosed()); + d->ui.isFloatingCheckBox->setChecked(DockWidget->isFloating()); + d->ui.tabbedCheckBox->setChecked(DockWidget->isTabbed()); + d->ui.isCurrentTabCheckBox->setChecked(DockWidget->isCurrentTab()); + d->ui.closableCheckBox->setChecked(DockWidget->features().testFlag(ads::CDockWidget::DockWidgetClosable)); + d->ui.movableCheckBox->setChecked(DockWidget->features().testFlag(ads::CDockWidget::DockWidgetMovable)); + d->ui.floatableCheckBox->setChecked(DockWidget->features().testFlag(ads::CDockWidget::DockWidgetFloatable)); + d->ui.deleteOnCloseCheckBox->setChecked(DockWidget->features().testFlag(ads::CDockWidget::DockWidgetDeleteOnClose)); + d->ui.customCloseHandlingCheckBox->setChecked(DockWidget->features().testFlag(ads::CDockWidget::CustomCloseHandling)); +} + +//--------------------------------------------------------------------------- +// EOF StatusDialog.cpp diff --git a/demo/StatusDialog.h b/demo/StatusDialog.h new file mode 100644 index 000000000..977fe829d --- /dev/null +++ b/demo/StatusDialog.h @@ -0,0 +1,47 @@ +#ifndef StatusDialogH +#define StatusDialogH +//============================================================================ +/// \file StatusDialog.h +/// \author Uwe Kindler +/// \date 13.04.2020 +/// \brief Declaration of CStatusDialog class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +namespace ads {class CDockManager;} +struct StatusDialogPrivate; + +/** + * Displays status info about dock widgets + */ +class CStatusDialog : public QDialog +{ + Q_OBJECT +private: + StatusDialogPrivate* d; ///< private data (pimpl) + friend struct StatusDialogPrivate; + +private slots: + void on_dockWidgetsComboBox_currentIndexChanged(int index); + +protected: +public: + using Super = QDialog; + /** + * Default Constructor + */ + CStatusDialog(ads::CDockManager* parent); + + /** + * Virtual Destructor + */ + virtual ~CStatusDialog(); +}; // class StatusDialog + + // namespace namespace_name +//----------------------------------------------------------------------------- +#endif // StatusDialogH diff --git a/demo/StatusDialog.ui b/demo/StatusDialog.ui new file mode 100644 index 000000000..9bd07a1c3 --- /dev/null +++ b/demo/StatusDialog.ui @@ -0,0 +1,146 @@ + + + CStatusDialogClass + + + + 0 + 0 + 357 + 331 + + + + + 0 + 0 + + + + Dock Widget Status + + + + QLayout::SetFixedSize + + + + + + 0 + 0 + + + + Dock Widget: + + + + + + + + 300 + 0 + + + + + + + + + 0 + 0 + + + + Status + + + + + + closed + + + + + + + floating + + + + + + + tabbed + + + + + + + is current tab + + + + + + + + + + + 0 + 0 + + + + Feature Flags + + + + + + DockWidgetClosable + + + + + + + DockWidgetMovable + + + + + + + DockWidgetFloatable + + + + + + + DockWidgetDeleteOnClose + + + + + + + CustomCloseHandling + + + + + + + + + + + diff --git a/demo/app.css b/demo/app.css new file mode 100644 index 000000000..b316b29ff --- /dev/null +++ b/demo/app.css @@ -0,0 +1,4 @@ +ads--CTitleBarButton::menu-indicator +{ + image: none; +} \ No newline at end of file diff --git a/demo/app.ico b/demo/app.ico new file mode 100644 index 000000000..3a57c647d Binary files /dev/null and b/demo/app.ico differ diff --git a/demo/app.rc b/demo/app.rc new file mode 100644 index 000000000..b8bb54af7 --- /dev/null +++ b/demo/app.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "app.ico" diff --git a/demo/demo.pro b/demo/demo.pro new file mode 100644 index 000000000..913b5be12 --- /dev/null +++ b/demo/demo.pro @@ -0,0 +1,49 @@ +ADS_OUT_ROOT = $${OUT_PWD}/.. + +TARGET = AdvancedDockingSystemDemo +DESTDIR = $${ADS_OUT_ROOT}/lib +QT += core gui widgets quick quickwidgets + +include(../ads.pri) + +lessThan(QT_MAJOR_VERSION, 6) { + win32 { + QT += axcontainer + } +} + +CONFIG += c++14 +CONFIG += debug_and_release +DEFINES += QT_DEPRECATED_WARNINGS +RC_FILE += app.rc + +adsBuildStatic { + DEFINES += ADS_STATIC +} + + +HEADERS += \ + MainWindow.h \ + StatusDialog.h \ + ImageViewer.h \ + RenderWidget.h + +SOURCES += \ + main.cpp \ + MainWindow.cpp \ + StatusDialog.cpp \ + ImageViewer.cpp \ + RenderWidget.cpp + +FORMS += \ + mainwindow.ui \ + StatusDialog.ui + +RESOURCES += demo.qrc + + +LIBS += -L$${ADS_OUT_ROOT}/lib + + +INCLUDEPATH += ../src +DEPENDPATH += ../src diff --git a/demo/demo.qrc b/demo/demo.qrc new file mode 100644 index 000000000..8b3529374 --- /dev/null +++ b/demo/demo.qrc @@ -0,0 +1,43 @@ + + + images/folder.svg + images/folder_open.svg + images/note_add.svg + images/picture_in_picture.svg + images/restore.svg + images/save.svg + images/date_range.svg + images/edit.svg + images/grid_on.svg + images/custom-menu-button.svg + app.css + images/plus.svg + images/help_outline.svg + images/fullscreen.svg + images/create_floating_editor.svg + images/create_floating_table.svg + images/docked_editor.svg + images/tab.svg + res/visual_studio_light.css + images/color_lens.svg + images/ads_icon.svg + images/ads_logo.svg + images/find_in_page.svg + images/perm_media.svg + images/zoom_in.svg + images/zoom_out.svg + images/zoom_out_map.svg + images/ads_tile_blue.svg + images/ads_tile_blue_light.svg + images/ads_tile_green.svg + images/ads_tile_orange.svg + images/photo.svg + images/crop_original.svg + images/panorama.svg + images/ads_icon2.svg + images/font_download.svg + images/lock_outline.svg + images/lock.svg + images/lock_open.svg + + diff --git a/demo/images/ads_icon.svg b/demo/images/ads_icon.svg new file mode 100644 index 000000000..ee0ac56cc --- /dev/null +++ b/demo/images/ads_icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/demo/images/ads_icon2.svg b/demo/images/ads_icon2.svg new file mode 100644 index 000000000..8ed092a33 --- /dev/null +++ b/demo/images/ads_icon2.svg @@ -0,0 +1,11 @@ + + + electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH + + + + + + + + diff --git a/demo/images/ads_logo.svg b/demo/images/ads_logo.svg new file mode 100644 index 000000000..da1b3b4de --- /dev/null +++ b/demo/images/ads_logo.svg @@ -0,0 +1,12 @@ + + + + Qt Advanced Docking + + + + + + + + diff --git a/demo/images/ads_tile_blue.svg b/demo/images/ads_tile_blue.svg new file mode 100644 index 000000000..91ec6dc1b --- /dev/null +++ b/demo/images/ads_tile_blue.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/images/ads_tile_blue_light.svg b/demo/images/ads_tile_blue_light.svg new file mode 100644 index 000000000..aa9bb833b --- /dev/null +++ b/demo/images/ads_tile_blue_light.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/images/ads_tile_green.svg b/demo/images/ads_tile_green.svg new file mode 100644 index 000000000..ce8075af4 --- /dev/null +++ b/demo/images/ads_tile_green.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/images/ads_tile_orange.svg b/demo/images/ads_tile_orange.svg new file mode 100644 index 000000000..bb343dddf --- /dev/null +++ b/demo/images/ads_tile_orange.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/images/color_lens.svg b/demo/images/color_lens.svg new file mode 100644 index 000000000..456049712 --- /dev/null +++ b/demo/images/color_lens.svg @@ -0,0 +1,6 @@ + + color_lens icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/create_floating_editor.svg b/demo/images/create_floating_editor.svg new file mode 100644 index 000000000..82f701bc5 --- /dev/null +++ b/demo/images/create_floating_editor.svg @@ -0,0 +1,80 @@ + + + + + + image/svg+xml + + + + + + + + note_add icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + + + + + diff --git a/demo/images/create_floating_table.svg b/demo/images/create_floating_table.svg new file mode 100644 index 000000000..8de57a1d0 --- /dev/null +++ b/demo/images/create_floating_table.svg @@ -0,0 +1,80 @@ + + + + + + image/svg+xml + + + + + + + + note_add icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + + + + + diff --git a/demo/images/crop_original.svg b/demo/images/crop_original.svg new file mode 100644 index 000000000..a4a6513a5 --- /dev/null +++ b/demo/images/crop_original.svg @@ -0,0 +1,6 @@ + + crop_original icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/custom-menu-button.svg b/demo/images/custom-menu-button.svg new file mode 100644 index 000000000..fde053506 --- /dev/null +++ b/demo/images/custom-menu-button.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + diff --git a/demo/images/date_range.svg b/demo/images/date_range.svg new file mode 100644 index 000000000..47087e063 --- /dev/null +++ b/demo/images/date_range.svg @@ -0,0 +1,6 @@ + + date_range icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/docked_editor.svg b/demo/images/docked_editor.svg new file mode 100644 index 000000000..562407479 --- /dev/null +++ b/demo/images/docked_editor.svg @@ -0,0 +1,6 @@ + + chrome_reader_mode icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/edit.svg b/demo/images/edit.svg new file mode 100644 index 000000000..064047b2b --- /dev/null +++ b/demo/images/edit.svg @@ -0,0 +1,6 @@ + + create icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/find_in_page.svg b/demo/images/find_in_page.svg new file mode 100644 index 000000000..ff4e34667 --- /dev/null +++ b/demo/images/find_in_page.svg @@ -0,0 +1,6 @@ + + find_in_page icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/folder.svg b/demo/images/folder.svg new file mode 100644 index 000000000..8d5eb6998 --- /dev/null +++ b/demo/images/folder.svg @@ -0,0 +1,6 @@ + + folder icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/folder_open.svg b/demo/images/folder_open.svg new file mode 100644 index 000000000..9201f6493 --- /dev/null +++ b/demo/images/folder_open.svg @@ -0,0 +1,6 @@ + + folder_open icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/font_download.svg b/demo/images/font_download.svg new file mode 100644 index 000000000..ecf5582e4 --- /dev/null +++ b/demo/images/font_download.svg @@ -0,0 +1,6 @@ + + font_download icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/fullscreen.svg b/demo/images/fullscreen.svg new file mode 100644 index 000000000..2cf89e386 --- /dev/null +++ b/demo/images/fullscreen.svg @@ -0,0 +1,6 @@ + + settings_overscan icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/grid_on.svg b/demo/images/grid_on.svg new file mode 100644 index 000000000..058d41524 --- /dev/null +++ b/demo/images/grid_on.svg @@ -0,0 +1,6 @@ + + grid_on icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/help_outline.svg b/demo/images/help_outline.svg new file mode 100644 index 000000000..66f85eea3 --- /dev/null +++ b/demo/images/help_outline.svg @@ -0,0 +1,6 @@ + + help_outline icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/lock.svg b/demo/images/lock.svg new file mode 100644 index 000000000..ec11129c0 --- /dev/null +++ b/demo/images/lock.svg @@ -0,0 +1,6 @@ + + lock icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/lock_open.svg b/demo/images/lock_open.svg new file mode 100644 index 000000000..a30effc04 --- /dev/null +++ b/demo/images/lock_open.svg @@ -0,0 +1,6 @@ + + lock_open icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/lock_outline.svg b/demo/images/lock_outline.svg new file mode 100644 index 000000000..613e430dc --- /dev/null +++ b/demo/images/lock_outline.svg @@ -0,0 +1,6 @@ + + lock_outline icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/material_icons_license.txt b/demo/images/material_icons_license.txt new file mode 100644 index 000000000..f464cddd7 --- /dev/null +++ b/demo/images/material_icons_license.txt @@ -0,0 +1,211 @@ + The Google Material icons and modifications can be used free of charge for + commerical and non-commerical projects according to the Apache License 2.0. + + An attribution to https://material.io/icons/ and/or https://iconfu.com on + your website or in your app's "about" screen would be wonderful. + + Please do not re-sell the icons. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/demo/images/note_add.svg b/demo/images/note_add.svg new file mode 100644 index 000000000..385cd392e --- /dev/null +++ b/demo/images/note_add.svg @@ -0,0 +1,6 @@ + + note_add icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/panorama.svg b/demo/images/panorama.svg new file mode 100644 index 000000000..5716c3dfe --- /dev/null +++ b/demo/images/panorama.svg @@ -0,0 +1,6 @@ + + panorama icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/perm_media.svg b/demo/images/perm_media.svg new file mode 100644 index 000000000..2dac12f97 --- /dev/null +++ b/demo/images/perm_media.svg @@ -0,0 +1,6 @@ + + perm_media icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/photo.svg b/demo/images/photo.svg new file mode 100644 index 000000000..6da023429 --- /dev/null +++ b/demo/images/photo.svg @@ -0,0 +1,6 @@ + + photo icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/picture_in_picture.svg b/demo/images/picture_in_picture.svg new file mode 100644 index 000000000..73e723919 --- /dev/null +++ b/demo/images/picture_in_picture.svg @@ -0,0 +1,6 @@ + + picture_in_picture icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/plus.svg b/demo/images/plus.svg new file mode 100644 index 000000000..33e9a27e8 --- /dev/null +++ b/demo/images/plus.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/demo/images/restore.svg b/demo/images/restore.svg new file mode 100644 index 000000000..508998328 --- /dev/null +++ b/demo/images/restore.svg @@ -0,0 +1,6 @@ + + restore icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/save.svg b/demo/images/save.svg new file mode 100644 index 000000000..351b8ed89 --- /dev/null +++ b/demo/images/save.svg @@ -0,0 +1,6 @@ + + save icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/tab.svg b/demo/images/tab.svg new file mode 100644 index 000000000..df5fd7c7e --- /dev/null +++ b/demo/images/tab.svg @@ -0,0 +1,6 @@ + + tab icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_in.svg b/demo/images/zoom_in.svg new file mode 100644 index 000000000..b65ca3c4f --- /dev/null +++ b/demo/images/zoom_in.svg @@ -0,0 +1,6 @@ + + zoom_in icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_out.svg b/demo/images/zoom_out.svg new file mode 100644 index 000000000..d2912384d --- /dev/null +++ b/demo/images/zoom_out.svg @@ -0,0 +1,6 @@ + + zoom_out icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/images/zoom_out_map.svg b/demo/images/zoom_out_map.svg new file mode 100644 index 000000000..dedc2d5f7 --- /dev/null +++ b/demo/images/zoom_out_map.svg @@ -0,0 +1,6 @@ + + zoom_out_map icon - Licensed under Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0) - Created with Iconfu.com - Derivative work of Material icons (Copyright Google Inc.) + + + + \ No newline at end of file diff --git a/demo/main.cpp b/demo/main.cpp new file mode 100644 index 000000000..750c983f0 --- /dev/null +++ b/demo/main.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include + + + +void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + switch (type) { + case QtDebugMsg: + fprintf(stdout, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); + break; + case QtInfoMsg: + fprintf(stdout, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); + break; + case QtWarningMsg: + fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); + break; + case QtCriticalMsg: + fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); + break; + case QtFatalMsg: + fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); + abort(); + } + + fflush(stderr); + fflush(stdout); +} + +int main(int argc, char *argv[]) +{ +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#if QT_VERSION >= 0x050600 + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif +#endif + std::shared_ptr b; + QApplication a(argc, argv); + a.setApplicationName("Advanced Docking System Demo"); + a.setQuitOnLastWindowClosed(true); + a.setWindowIcon(QIcon(":/adsdemo/images/ads_icon2.svg")); + + qInstallMessageHandler(myMessageOutput); + qDebug() << "Message handler test"; + + CMainWindow mw; + mw.show(); + + QFile StyleSheetFile(":/adsdemo/app.css"); + StyleSheetFile.open(QIODevice::ReadOnly); + QTextStream StyleSheetStream(&StyleSheetFile); + a.setStyleSheet(StyleSheetStream.readAll()); + StyleSheetFile.close(); + return a.exec(); +} diff --git a/demo/main.py b/demo/main.py new file mode 100644 index 000000000..c0de01729 --- /dev/null +++ b/demo/main.py @@ -0,0 +1,613 @@ +import datetime +import logging +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import (QCoreApplication, QDir, Qt, QSettings, QSignalBlocker, + QRect, QPoint, qDebug, qInstallMessageHandler, + QtDebugMsg, QtInfoMsg, QtWarningMsg, + QtCriticalMsg, QtFatalMsg, QSize) +from PyQt5.QtGui import (QGuiApplication, QIcon, QCloseEvent) +from PyQt5.QtWidgets import (QCalendarWidget, QFileSystemModel, QFrame, QLabel, + QMenu, QTreeView, QAction, QWidgetAction, + QComboBox, QStyle, QSizePolicy, QInputDialog, QMenu, + QToolButton, QWidget, QPlainTextEdit, + QTableWidget, QTableWidgetItem, QApplication, + QMessageBox) +try: + from PyQt5.QAxContainer import QAxWidget +except ImportError: + ACTIVEX_AVAILABLE = False +else: + ACTIVEX_AVAILABLE = True + +from PyQtAds import QtAds + +import rc # pyrcc5 demo.qrc -o rc.py +from status_dialog import CStatusDialog + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + + +class _State: + label_count = 0 + calendar_count = 0 + file_system_count = 0 + editor_count = 0 + table_count = 0 + activex_count = 0 + + +def features_string(dock_widget: QtAds.CDockWidget) -> str: + '''Function returns a features string with closable (c), movable (m) and floatable (f) + features. i.e. The following string is for a not closable but movable and floatable + widget: c- m+ f+''' + + f = dock_widget.features() + closable = f & QtAds.CDockWidget.DockWidgetClosable + movable = f & QtAds.CDockWidget.DockWidgetMovable + floatable = f & QtAds.CDockWidget.DockWidgetFloatable + + return "c{} m{} f{}".format("+" if closable else "-", + "+" if movable else "-", + "+" if floatable else "-") + + +def append_feature_string_to_window_title(dock_widget: QtAds.CDockWidget): + '''Appends the string returned by features_string() to the window title of + the given DockWidget''' + + dock_widget.setWindowTitle(dock_widget.windowTitle() + " ({})".format(features_string(dock_widget))) + + +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 CMinSizeTableWidget(QTableWidget): + """Custom QTableWidget with a minimum size hint to test CDockWidget + setMinimumSizeHintMode() function of CDockWidget""" + + def minimumSizeHint(self) -> QSize: + return QSize(300, 100) + + +class CCustomComponentsFactory(QtAds.CDockComponentsFactory): + + def createDockAreaTitleBar(self, dock_area: QtAds.CDockAreaWidget) -> QtAds.CDockAreaTitleBar: + title_bar = QtAds.CDockAreaTitleBar(dock_area) + custom_button = QToolButton(dock_area) + custom_button.setToolTip("Help") + custom_button.setIcon(svg_icon(":/adsdemo/images/help_outline.svg")) + custom_button.setAutoRaise(True) + index = title_bar.indexOf(title_bar.button(QtAds.TitleBarButtonTabsMenu)) + title_bar.insertWidget(index + 1, custom_button) + return title_bar + + +class MainWindow(MainWindowUI, MainWindowBase): + save_perspective_action: QAction + perspective_list_action: QWidgetAction + perspective_combo_box: QComboBox + dock_manager: QtAds.CDockManager + + def __init__(self, parent=None): + super().__init__(parent) + self.save_perspective_action = None + self.perspective_list_action = None + self.perspective_combo_box = None + self.dock_manager = None + self.window_title_test_dock_widget = None + self.last_docked_editor = None + + self.setupUi(self) + self.create_actions() + + # uncomment the following line if the tab close button should be + # a QToolButton instead of a QPushButton + # QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.configFlags() | QtAds.CDockManager.TabCloseButtonIsToolButton) + + # uncomment the following line if you want to use opaque undocking and + # opaque splitter resizing + #QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.DefaultOpaqueConfig) + + # uncomment the following line if you want a fixed tab width that does + # not change if the visibility of the close button changes + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.RetainTabSizeWhenCloseButtonHidden, True) + + # uncomment the following line if you don't want close button on DockArea's title bar + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.DockAreaHasCloseButton, False) + + # uncomment the following line if you don't want undock button on DockArea's title bar + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.DockAreaHasUndockButton, False) + + # uncomment the following line if you don't want tabs menu button on DockArea's title bar + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.DockAreaHasTabsMenuButton, False) + + # uncomment the following line if you don't want disabled buttons to appear on DockArea's title bar + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.DockAreaHideDisabledButtons, True) + + # uncomment the following line if you want to show tabs menu button on DockArea's title bar only when there are more than one tab and at least of them has elided title + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.DockAreaDynamicTabsMenuButtonVisibility, True) + + # uncomment the following line if you want floating container to always show application title instead of active dock widget's title + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FloatingContainerHasWidgetTitle, False) + + # uncomment the following line if you want floating container to show active dock widget's icon instead of always showing application icon + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FloatingContainerHasWidgetIcon, True) + + # uncomment the following line if you want a central widget in the main dock container (the dock manager) without a titlebar + # If you enable this code, you can test it in the demo with the Calendar 0 + # dock widget. + #QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.HideSingleCentralWidgetTitleBar, True) + + # uncomment the following line to enable focus highlighting of the dock + # widget that has the focus + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FocusHighlighting, True) + + # uncomment if you would like to enable an equal distribution of the + # available size of a splitter to all contained dock widgets + # QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.EqualSplitOnInsertion, True) + + # Now create the dock manager and its content + self.dock_manager = QtAds.CDockManager(self) + + # Uncomment the following line to have the old style where the dock + # area close button closes the active tab + # QtAds.CDockManager.setConfigFlags(QtAds.CDockManager.DockAreaHasCloseButton + # | QtAds.CDockManager.DockAreaCloseButtonClosesTab) + self.perspective_combo_box.activated[str].connect(self.dock_manager.openPerspective) + + self.create_content() + # Default window geometry - center on screen + self.resize(1280, 720) + self.setGeometry(QStyle.alignedRect( + Qt.LeftToRight, Qt.AlignCenter, self.frameSize(), + QGuiApplication.primaryScreen().availableGeometry())) + + # self.restore_state() + self.restore_perspectives() + + def create_content(self): + # Test container docking + dock_widget = self.create_calendar_dock_widget() + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetClosable, False) + special_dock_area = self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, dock_widget) + + # For this Special Dock Area we want to avoid dropping on the center of it (i.e. we don't want this widget to be ever tabbified): + special_dock_area.setAllowedAreas(QtAds.OuterDockAreas) + # special_dock_area.setAllowedAreas(QtAds.LeftDockWidgetArea | QtAds.RightDockWidgetArea) # just for testing + + dock_widget = self.create_long_text_label_dock_widget() + self.window_title_test_dock_widget = dock_widget + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetFocusable, False) + self.dock_manager.addDockWidget(QtAds.LeftDockWidgetArea, dock_widget) + file_system_widget = self.create_file_system_tree_dock_widget() + tool_bar = file_system_widget.createDefaultToolBar() + tool_bar.addAction(self.actionSaveState) + tool_bar.addAction(self.actionRestoreState) + file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) + append_feature_string_to_window_title(file_system_widget) + self.dock_manager.addDockWidget(QtAds.BottomDockWidgetArea, file_system_widget) + + file_system_widget = self.create_file_system_tree_dock_widget() + file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetMovable, False) + file_system_widget.setFeature(QtAds.CDockWidget.DockWidgetFloatable, False) + append_feature_string_to_window_title(file_system_widget) + + # Test custom factory - we inject a help button into the title bar + QtAds.CDockComponentsFactory.setFactory(CCustomComponentsFactory()) + top_dock_area = self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, file_system_widget) + # Uncomment the next line if you would like to test the + # HideSingleWidgetTitleBar functionality + # top_dock_area.setDockAreaFlag(QtAds.CDockAreaWidget.HideSingleWidgetTitleBar, True) + QtAds.CDockComponentsFactory.resetDefaultFactory() + + # We create a calendar widget and clear all flags to prevent the dock area + # from closing + 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 + custom_button = QToolButton(dock_area) + custom_button.setToolTip("Create Editor") + custom_button.setIcon(svg_icon(":/adsdemo/images/plus.svg")) + custom_button.setAutoRaise(True) + + title_bar = dock_area.titleBar() + index = title_bar.indexOf(title_bar.tabBar()) + title_bar.insertWidget(index + 1, custom_button) + def on_button_clicked(): + dock_widget = self.create_editor_widget() + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + self.dock_manager.addDockWidgetTabToArea(dock_widget, dock_area) + dock_widget.closeRequested.connect(self.on_editor_close_requested) + custom_button.clicked.connect(on_button_clicked) + + # Test dock area docking + right_dock_area = self.dock_manager.addDockWidget( + QtAds.RightDockWidgetArea, + self.create_long_text_label_dock_widget(), top_dock_area) + self.dock_manager.addDockWidget( + QtAds.TopDockWidgetArea, + self.create_long_text_label_dock_widget(), right_dock_area) + + bottom_dock_area = self.dock_manager.addDockWidget( + QtAds.BottomDockWidgetArea, + self.create_long_text_label_dock_widget(), right_dock_area) + + self.dock_manager.addDockWidget( + QtAds.CenterDockWidgetArea, + self.create_long_text_label_dock_widget(), right_dock_area) + self.dock_manager.addDockWidget( + QtAds.CenterDockWidgetArea, + self.create_long_text_label_dock_widget(), bottom_dock_area) + + + action = self.menuTests.addAction("Set {} Floating".format(dock_widget.windowTitle())) + action.triggered.connect(dock_widget.setFloating) + action = self.menuTests.addAction("Set {} As Current Tab".format(dock_widget.windowTitle())) + action.triggered.connect(dock_widget.setAsCurrentTab) + action = self.menuTests.addAction("Raise {}".format(dock_widget.windowTitle())) + action.triggered.connect(dock_widget.raise_) + + if ACTIVEX_AVAILABLE: + flags = self.dock_manager.configFlags() + if flags & QtAds.CDockManager.OpaqueUndocking: + self.dock_manager.addDockWidget(QtAds.CenterDockWidgetArea, + self.create_activex_widget(), right_dock_area) + + for dock_widget in self.dock_manager.dockWidgetsMap().values(): + dock_widget.viewToggled.connect(self.on_view_toggled) + dock_widget.visibilityChanged.connect(self.on_view_visibility_changed) + + def create_actions(self): + self.toolBar.addAction(self.actionSaveState) + self.toolBar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon) + self.actionSaveState.setIcon(svg_icon(":/adsdemo/images/save.svg")) + self.toolBar.addAction(self.actionRestoreState) + self.actionRestoreState.setIcon(svg_icon(":/adsdemo/images/restore.svg")) + + self.save_perspective_action = QAction("Create Perspective", self) + self.save_perspective_action.setIcon(svg_icon(":/adsdemo/images/picture_in_picture.svg")) + self.save_perspective_action.triggered.connect(self.save_perspective) + self.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_list_action.setDefaultWidget(self.perspective_combo_box) + self.toolBar.addSeparator() + self.toolBar.addAction(self.perspective_list_action) + self.toolBar.addAction(self.save_perspective_action) + + a = self.toolBar.addAction("Create Floating Editor") + a.setProperty("Floating", True) + a.setToolTip("Creates floating dynamic dockable editor windows that are deleted on close") + a.setIcon(svg_icon(":/adsdemo/images/note_add.svg")) + a.triggered.connect(self.create_editor) + self.menuTests.addAction(a) + + a = self.toolBar.addAction("Create Docked Editor") + a.setProperty("Floating", False) + a.setToolTip("Creates a docked editor windows that are deleted on close") + a.setIcon(svg_icon(":/adsdemo/images/docked_editor.svg")) + a.triggered.connect(self.create_editor) + self.menuTests.addAction(a) + + a = self.toolBar.addAction("Create Floating Table") + a.setToolTip("Creates floating dynamic dockable table with millions of entries") + a.setIcon(svg_icon(":/adsdemo/images/grid_on.svg")) + a.triggered.connect(self.create_table) + self.menuTests.addAction(a) + + self.menuTests.addSeparator() + a = self.menuTests.addAction("Show Status Dialog") + a.triggered.connect(self.show_status_dialog) + self.menuTests.addSeparator() + + def closeEvent(self, event: QCloseEvent): + self.save_state() + self.dock_manager.deleteLater() + super().closeEvent(event) + + def on_actionSaveState_triggered(self, state: bool): + qDebug("MainWindow::on_action_save_state_triggered") + self.save_state() + + def on_actionRestoreState_triggered(self, state: bool): + qDebug("MainWindow::on_action_restore_state_triggered") + self.restore_state() + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save perspective", + "Enter unique name:") + + if ok and perspective_name: + self.dock_manager.addPerspective(perspective_name) + _ = 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) + + self.save_perspectives() + + def on_view_toggled(self, open: bool): + dock_widget = self.sender() + if dock_widget is None: + return + + qDebug("{} view_toggled({})".format(dock_widget.objectName(), open)) + + def on_view_visibility_changed(self, visible: bool): + dock_widget = self.sender() + if dock_widget is None: + return + + # qDebug("{} visibility_changed({})".format(dock_widget.objectName(), visible)) + + def create_editor(self): + sender = self.sender() + floating = sender.property("Floating") + dock_widget = self.create_editor_widget() + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetForceCloseWithArea, True) + dock_widget.closeRequested.connect(self.on_editor_close_requested) + + if floating: + floating_widget = self.dock_manager.addDockWidgetFloating(dock_widget) + floating_widget.move(QPoint(20, 20)) + else: + editor_area = self.last_docked_editor.dockAreaWidget() if self.last_docked_editor is not None else None + if editor_area is not None: + self.dock_manager.setConfigFlag(QtAds.CDockManager.EqualSplitOnInsertion, True) + self.dock_manager.addDockWidget(QtAds.RightDockWidgetArea, dock_widget, editor_area) + else: + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + self.last_docked_editor = dock_widget + + def on_editor_close_requested(self): + dock_widget = self.sender() + result = QMessageBox.question(self, "Close Editor", + "Editor {} contains unsaved changes? Would you like to close it?".format(dock_widget.windowTitle())) + if result == QMessageBox.Yes: + dock_widget.closeDockWidget() + + def create_table(self): + dock_widget = self.create_table_widget() + dock_widget.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + floating_widget = self.dock_manager.addDockWidgetFloating(dock_widget) + floating_widget.move(QPoint(40, 40)) + + def show_status_dialog(self): + dialog = CStatusDialog(self.dock_manager) + dialog.exec_() + + + def toggle_dock_widget_window_title(self): + title = self.window_title_test_dock_widget.windowTitle() + i = title.find(" (Test) ") + if i == -1: + title += " (Test) " + else: + title = title[i] + self.window_title_test_dock_widget.setWindowTitle(title) + + def save_state(self): + ''' + Saves the dock manager state and the main window geometry + ''' + settings = QSettings("Settings.ini", QSettings.IniFormat) + settings.setValue("mainWindow/Geometry", self.saveGeometry()) + settings.setValue("mainWindow/State", self.saveState()) + settings.setValue("mainWindow/DockingState", self.dock_manager.saveState()) + + def restore_state(self): + ''' + Restores the dock manager state + ''' + settings = QSettings("Settings.ini", QSettings.IniFormat) + geom = settings.value("mainWindow/Geometry") + if geom is not None: + self.restoreGeometry(geom) + + state = settings.value("mainWindow/State") + if state is not None: + self.restoreState(state) + + state = settings.value("mainWindow/DockingState") + if state is not None: + self.dock_manager.restore_state(state) + + def save_perspectives(self): + ''' + Save the list of perspectives + ''' + settings = QSettings("Settings.ini", QSettings.IniFormat) + self.dock_manager.savePerspectives(settings) + + def restore_perspectives(self): + ''' + Restore the perspective listo of the dock manager + ''' + settings = QSettings("Settings.ini", QSettings.IniFormat) + self.dock_manager.loadPerspectives(settings) + self.perspective_combo_box.clear() + self.perspective_combo_box.addItems(self.dock_manager.perspectiveNames()) + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, 'Save perspective', 'Enter unique name:') + if ok and perspective_name: + self.dock_manager.addPerspective(perspective_name) + _ = 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) + self.save_perspectives() + + def create_long_text_label_dock_widget(self) -> QtAds.CDockWidget: + label = QLabel() + label.setWordWrap(True) + label.setAlignment(Qt.AlignTop | Qt.AlignLeft) + label.setText('''Label {} {} - Lorem ipsum dolor sit amet, consectetuer + adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum + sociis natoque penatibus et magnis dis parturient montes, nascetur + ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium + quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla + vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, + imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis + pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. + Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, + consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra + quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. + '''.format(_State.label_count, datetime.datetime.now().strftime("%H:%M:%S:%f"))) + dock_widget = QtAds.CDockWidget("Label {}".format(_State.label_count)) + _State.label_count += 1 + dock_widget.setWidget(label) + + self.menuView.addAction(dock_widget.toggleViewAction()) + return dock_widget + + def create_calendar_dock_widget(self) -> QtAds.CDockWidget: + widget = QCalendarWidget() + + dock_widget = QtAds.CDockWidget("Calendar {}".format(_State.calendar_count)) + _State.calendar_count += 1 + # The following lines are for testing the setWidget() and takeWidget() + # functionality + dock_widget.setWidget(widget) + dock_widget.setWidget(widget) # what happens if we set a widget if a widget is already set + dock_widget.takeWidget() # we remove the widget + dock_widget.setWidget(widget) # and set the widget again - there should be no error + dock_widget.setToggleViewActionMode(QtAds.CDockWidget.ActionModeShow) + dock_widget.setIcon(svg_icon(":/adsdemo/images/date_range.svg")) + self.menuView.addAction(dock_widget.toggleViewAction()) + return dock_widget + + def create_file_system_tree_dock_widget(self) -> QtAds.CDockWidget: + widget = QTreeView() + widget.setFrameShape(QFrame.NoFrame) + + m = QFileSystemModel(widget) + m.setRootPath(QDir.currentPath()) + widget.setModel(m) + + dock_widget = QtAds.CDockWidget("Filesystem {}".format(_State.file_system_count)) + _State.file_system_count += 1 + dock_widget.setWidget(widget) + self.menuView.addAction(dock_widget.toggleViewAction()) + return dock_widget + + def create_editor_widget(self) -> QtAds.CDockWidget: + widget = QPlainTextEdit() + widget.setPlaceholderText("This is an editor. If you close the editor, it will be " + "deleted. Enter your text here.") + widget.setStyleSheet("border: none") + dock_widget = QtAds.CDockWidget("Editor {}".format(_State.editor_count)) + _State.editor_count += 1 + dock_widget.setWidget(widget) + dock_widget.setIcon(svg_icon(":/adsdemo/images/edit.svg")) + dock_widget.setFeature(QtAds.CDockWidget.CustomCloseHandling, True) + self.menuView.addAction(dock_widget.toggleViewAction()) + + options_menu = QMenu(dock_widget) + options_menu.setTitle("Options") + options_menu.setToolTip(options_menu.title()) + options_menu.setIcon(svg_icon(":/adsdemo/images/custom-menu-button.svg")) + menu_action = options_menu.menuAction() + # The object name of the action will be set for the QToolButton that + # is created in the dock area title bar. You can use this name for CSS + # styling + menu_action.setObjectName("options_menu") + dock_widget.setTitleBarActions([options_menu.menuAction()]) + a = options_menu.addAction("Clear Editor") + a.triggered.connect(widget.clear) + + return dock_widget + + + def create_table_widget(self) -> QtAds.CDockWidget: + widget = CMinSizeTableWidget() + dock_widget = QtAds.CDockWidget("Table {}".format(_State.table_count)) + _State.table_count += 1 + COLCOUNT = 5 + ROWCOUNT = 30 + widget.setColumnCount(COLCOUNT) + widget.setRowCount(ROWCOUNT) + for col in range(ROWCOUNT): + widget.setHorizontalHeaderItem(col, QTableWidgetItem("Col {}".format(col + 1))) + for row in range(ROWCOUNT): + widget.setItem(row, col, QTableWidgetItem("T {:}-{:}".format(row + 1, col + 1))) + + dock_widget.setWidget(widget) + dock_widget.setIcon(svg_icon(":/adsdemo/images/grid_on.svg")) + dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromContent) + toolbar = dock_widget.createDefaultToolBar() + action = toolbar.addAction(svg_icon(":/adsdemo/images/fullscreen.svg"), "Toggle Fullscreen") + + def on_toggle_fullscreen(): + if dock_widget.isFullScreen(): + dock_widget.showNormal() + else: + dock_widget.showFullScreen() + + action.triggered.connect(on_toggle_fullscreen) + self.menuView.addAction(dock_widget.toggleViewAction()) + return dock_widget + + def create_activex_widget(self, parent: QWidget = None) -> QtAds.CDockWidget: + widget = QAxWidget("{6bf52a52-394a-11d3-b153-00c04f79faa6}", parent) + dock_widget = QtAds.CDockWidget("Active X {}".format(_State.activex_count)) + _State.activex_count += 1 + dock_widget.setWidget(widget) + self.menuView.addAction(dock_widget.toggleViewAction()) + return dock_widget + + +def my_message_output(type, context, msg): + if type == QtDebugMsg: + print("Debug: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtInfoMsg: + print("Info: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtWarningMsg: + print("Warning: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtCriticalMsg: + print("Critical: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + elif type == QtFatalMsg: + print("Fatal: {} ({}:{}, {})".format(msg, context.file, context.line, context.function)) + + +if __name__ == '__main__': + QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) + QGuiApplication.setAttribute(Qt.AA_EnableHighDpiScaling) + app = QApplication(sys.argv) + app.setQuitOnLastWindowClosed(True) + + with open(os.path.join(os.path.dirname(__file__), "app.css"), "r") as style_sheet_file: + app.setStyleSheet(style_sheet_file.read()) + + qInstallMessageHandler(my_message_output) + qDebug("Message handler test") + + mw = MainWindow() + mw.show() + app.exec_() diff --git a/AdvancedDockingSystemDemo/src/mainwindow.ui b/demo/mainwindow.ui similarity index 65% rename from AdvancedDockingSystemDemo/src/mainwindow.ui rename to demo/mainwindow.ui index cce9b4838..ea5c711d8 100644 --- a/AdvancedDockingSystemDemo/src/mainwindow.ui +++ b/demo/mainwindow.ui @@ -1,79 +1,87 @@ - - - MainWindow - - - - 0 - 0 - 400 - 300 - - - - MainWindow - - - - - - - 0 - 0 - 400 - 21 - - - - - File - - - - - - View - - - - - - - - About - - - - - - - - - Add SectionContent - - - - - Contents... - - - - - Demo 2 - - - - - Demo 3 - - - - - Exit - - - - - - - + + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + QMainWindow::AllowTabbedDocks + + + + + + + 0 + 0 + 400 + 21 + + + + + File + + + + + + + View + + + + + About + + + + + Tests + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + Exit + + + + + Save State + + + + + Restore State + + + + + + + diff --git a/demo/res/visual_studio_light.css b/demo/res/visual_studio_light.css new file mode 100644 index 000000000..056163add --- /dev/null +++ b/demo/res/visual_studio_light.css @@ -0,0 +1,379 @@ + +/* + * Visual Studio like light theme + */ + +/***************************************************************************** + * CDockManager + *****************************************************************************/ +ads--CDockManager +{ + background: palette(window); +} + + + +/***************************************************************************** + * CDockContainerWidget + *****************************************************************************/ +ads--CDockContainerWidget { + background: palette(window); + padding: 2px; +} + + +/***************************************************************************** + * CDockAreaWidget + *****************************************************************************/ +ads--CDockAreaWidget { + background: palette(window); + /*border: 1px solid palette(dark);*/ +} + + +ads--CDockAreaTitleBar { + background: transparent; + border-bottom: 2px solid rgb(204, 204, 204); + padding-bottom: 0px; +} + + +ads--CTitleBarButton { + padding: 0px 0px; + background: transparent; + border: none; +} + +ads--CTitleBarButton:hover { + background: rgba(0, 0, 0, 24); +} + +ads--CTitleBarButton:pressed { + background: rgba(0, 0, 0, 48); +} + +QScrollArea#dockWidgetScrollArea { + padding: 0px; + border: none; +} + +#tabsMenuButton::menu-indicator { + image: none; +} + + +#dockAreaCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#detachGroupButton { + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + +ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + border-bottom: 2px solid palette(highlight); +} + + +/***************************************************************************** + * CDockWidgetTab + *****************************************************************************/ +ads--CDockWidgetTab { + background: palette(window); + border: none; + padding: 0 0px; + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ +} + +ads--CDockWidgetTab[activeTab="true"] { + background: rgb(204, 204, 204); +} + +ads--CDockWidgetTab QLabel { + color: palette(foreground); +} + +ads--CDockWidgetTab[activeTab="true"] QLabel { + color: palette(foreground); +} + + +#tabCloseButton { + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#tabCloseButton:hover { + /*border: 1px solid rgba(0, 0, 0, 32);*/ + background: rgba(0, 0, 0, 24); +} + +#tabCloseButton:pressed { + background: rgba(0, 0, 0, 48); +} + + +/* Focus related styling */ +ads--CDockWidgetTab[focused="true"] { + background: palette(highlight); + border-color: palette(highlight); +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton { + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + +ads--CDockWidgetTab[focused="true"]>#tabCloseButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CDockWidgetTab[focused="true"]>#tabCloseButton:pressed { + background: rgba(255, 255, 255, 92); +} + +ads--CDockWidgetTab[focused="true"] QLabel { + color: palette(light); +} + + +/***************************************************************************** + * CDockWidget + *****************************************************************************/ +ads--CDockWidget { + background: palette(light); + border: 1px solid rgb(204, 204, 204); + border-top: none; +} + + + +/***************************************************************************** + * + * Styling of auto hide functionality + * + *****************************************************************************/ + + +/***************************************************************************** + * CAutoHideTab + *****************************************************************************/ +ads--CAutoHideTab { + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + + +ads--CAutoHideTab:hover +{ + color: palette(highlight); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/** + * Auto hide tabs with icon only + */ +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + +/** + * Auto hide tabs with icon only hover + */ +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + + +/***************************************************************************** + * CAutoHideSideBar + *****************************************************************************/ +ads--CAutoHideSideBar{ + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + + +ads--CAutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + + +/***************************************************************************** + * CAutoHideDockContainer + *****************************************************************************/ +ads--CAutoHideDockContainer { + background: palette(window); +} + + +ads--CAutoHideDockContainer ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +/* + * This is required because the ads--CDockAreaWidget[focused="true"] will + * overwrite the ads--CAutoHideDockContainer ads--CDockAreaTitleBar rule + */ +ads--CAutoHideDockContainer ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + + +/***************************************************************************** + * CAutoHideDockContainer titlebar buttons + *****************************************************************************/ +#dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button.svg); + qproperty-iconSize: 16px; +} + +ads--CAutoHideDockContainer #dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaCloseButton{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + + +ads--CAutoHideDockContainer ads--CTitleBarButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CAutoHideDockContainer ads--CTitleBarButton:pressed { + background: rgba(255, 255, 255, 96); +} + +/***************************************************************************** + * CAutoHideDockContainer Titlebar and Buttons + *****************************************************************************/ + + +/***************************************************************************** + * CResizeHandle + *****************************************************************************/ +ads--CResizeHandle { + background: palette(window); +} + + +ads--CAutoHideDockContainer[sideBarLocation="0"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="1"] ads--CResizeHandle { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="2"] ads--CResizeHandle { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="3"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} diff --git a/demo/status_dialog.py b/demo/status_dialog.py new file mode 100644 index 000000000..cf58a385d --- /dev/null +++ b/demo/status_dialog.py @@ -0,0 +1,38 @@ +import os +import sys + +from PyQt5 import uic +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'StatusDialog.ui') +StatusDialogUI, StatusDialogBase = uic.loadUiType(UI_FILE) + +class CStatusDialog(StatusDialogUI, StatusDialogBase): + + def __init__(self, dock_manager: QtAds.CDockManager, parent=None): + super().__init__(parent) + + self.setupUi(self) + + self.dock_manager = dock_manager + self.dock_widgets = self.dock_manager.dockWidgetsMap() + + for key, widget in self.dock_widgets.items(): + self.dockWidgetsComboBox.addItem(key, widget) + + def on_dockWidgetsComboBox_currentIndexChanged(self, index: int): + if not isinstance(index, int): + return + if index < 0: + return + + dock_widget = self.dockWidgetsComboBox.currentData() + self.isClosedCheckBox.setChecked(dock_widget.isClosed()) + self.isFloatingCheckBox.setChecked(dock_widget.isFloating()) + self.tabbedCheckBox.setChecked(dock_widget.isTabbed()) + self.isCurrentTabCheckBox.setChecked(dock_widget.isCurrentTab()) + self.closableCheckBox.setChecked(dock_widget.features() & QtAds.CDockWidget.DockWidgetClosable) + self.movableCheckBox.setChecked(dock_widget.features() & QtAds.CDockWidget.DockWidgetMovable) + self.floatableCheckBox.setChecked(dock_widget.features() & QtAds.CDockWidget.DockWidgetFloatable) + self.deleteOnCloseCheckBox.setChecked(dock_widget.features() & QtAds.CDockWidget.DockWidgetDeleteOnClose) + self.customCloseHandlingCheckBox.setChecked(dock_widget.features() & QtAds.CDockWidget.CustomCloseHandling) diff --git a/doc/AutoHide_Animation.gif b/doc/AutoHide_Animation.gif new file mode 100644 index 000000000..c82fad1a2 Binary files /dev/null and b/doc/AutoHide_Animation.gif differ diff --git a/doc/AutoHide_Change_Sidebar.gif b/doc/AutoHide_Change_Sidebar.gif new file mode 100644 index 000000000..07312fb57 Binary files /dev/null and b/doc/AutoHide_Change_Sidebar.gif differ diff --git a/doc/AutoHide_Context_Menu.png b/doc/AutoHide_Context_Menu.png new file mode 100644 index 000000000..614e7062d Binary files /dev/null and b/doc/AutoHide_Context_Menu.png differ diff --git a/doc/AutoHide_Drag_DockArea.gif b/doc/AutoHide_Drag_DockArea.gif new file mode 100644 index 000000000..50e18723c Binary files /dev/null and b/doc/AutoHide_Drag_DockArea.gif differ diff --git a/doc/AutoHide_Drag_to_Float_or_Dock.gif b/doc/AutoHide_Drag_to_Float_or_Dock.gif new file mode 100644 index 000000000..fa7f0ba36 Binary files /dev/null and b/doc/AutoHide_Drag_to_Float_or_Dock.gif differ diff --git a/doc/AutoHide_Drag_to_Sidebar.gif b/doc/AutoHide_Drag_to_Sidebar.gif new file mode 100644 index 000000000..72e998f61 Binary files /dev/null and b/doc/AutoHide_Drag_to_Sidebar.gif differ diff --git a/doc/AutoHide_Movie.gif b/doc/AutoHide_Movie.gif new file mode 100644 index 000000000..19aa78c69 Binary files /dev/null and b/doc/AutoHide_Movie.gif differ diff --git a/doc/AutoHide_PinTo.png b/doc/AutoHide_PinTo.png new file mode 100644 index 000000000..5f653e564 Binary files /dev/null and b/doc/AutoHide_PinTo.png differ diff --git a/doc/AutoHide_Sort_Tabs.gif b/doc/AutoHide_Sort_Tabs.gif new file mode 100644 index 000000000..6bac89cd5 Binary files /dev/null and b/doc/AutoHide_Sort_Tabs.gif differ diff --git a/doc/AutoHide_Tab_Insert_Order.gif b/doc/AutoHide_Tab_Insert_Order.gif new file mode 100644 index 000000000..391d80a3b Binary files /dev/null and b/doc/AutoHide_Tab_Insert_Order.gif differ diff --git a/doc/DockArea_Tab_Insertion_Order.gif b/doc/DockArea_Tab_Insertion_Order.gif new file mode 100644 index 000000000..2823d7666 Binary files /dev/null and b/doc/DockArea_Tab_Insertion_Order.gif differ diff --git a/doc/Feature_ImageViewer.png b/doc/Feature_ImageViewer.png new file mode 100644 index 000000000..b1eb5217a Binary files /dev/null and b/doc/Feature_ImageViewer.png differ diff --git a/doc/TabMenu.png b/doc/TabMenu.png new file mode 100644 index 000000000..44f176084 Binary files /dev/null and b/doc/TabMenu.png differ diff --git a/doc/TabMenu_dark.png b/doc/TabMenu_dark.png new file mode 100644 index 000000000..f7f74476a Binary files /dev/null and b/doc/TabMenu_dark.png differ diff --git a/doc/ads_icon.svg b/doc/ads_icon.svg new file mode 100644 index 000000000..4231be6c4 --- /dev/null +++ b/doc/ads_icon.svg @@ -0,0 +1,77 @@ + + + + + electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH + + + + + + + + + diff --git a/doc/ads_icon_256.png b/doc/ads_icon_256.png new file mode 100644 index 000000000..3e7c912e4 Binary files /dev/null and b/doc/ads_icon_256.png differ diff --git a/doc/ads_icon_512.png b/doc/ads_icon_512.png new file mode 100644 index 000000000..a86ff6119 Binary files /dev/null and b/doc/ads_icon_512.png differ diff --git a/doc/ads_logo.svg b/doc/ads_logo.svg new file mode 100644 index 000000000..13edf85cd --- /dev/null +++ b/doc/ads_logo.svg @@ -0,0 +1,33 @@ + + + electric_iron icon - Licensed under Iconfu Standard License v1.0 (https://www.iconfu.com/iconfu_standard_license) - Incors GmbH + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/ads_logo_ukraine.jpg b/doc/ads_logo_ukraine.jpg new file mode 100644 index 000000000..bf36a0267 Binary files /dev/null and b/doc/ads_logo_ukraine.jpg differ diff --git a/doc/ads_qt_marketplace_description.md b/doc/ads_qt_marketplace_description.md new file mode 100644 index 000000000..1d8c987df --- /dev/null +++ b/doc/ads_qt_marketplace_description.md @@ -0,0 +1,101 @@ +# Advanced Docking System for Qt + +Qt Advanced Docking System lets you create customizable layouts using a full +featured window docking system similar to what is found in many popular +integrated development environments (IDEs) such as Visual Studio. + +[![Video Advanced Docking](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/advanced-docking_video.png)](https://www.youtube.com/watch?v=7pdNfafg3Qc) + +Everything is implemented with standard Qt functionality without any +platform specific code. Basic usage of QWidgets and QLayouts and using basic +styles as much as possible. + +## Features + +### Docking everywhere - with or without a central widget + +The Advanced Docking System works with or without a central widget. +You can dock on every border of the main window or you can dock into each dock area - so you are +free to dock almost everywhere. + +![Dropping widgets](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/preview-dragndrop.png) + +![Dropping widgets](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/preview-dragndrop_dark.png) + +### Docking inside floating windows + +There is no difference between the main window and a floating window. Docking +into floating windows is supported. + +![Docking inside floating windows](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/floating-widget-dragndrop.png) + +![Docking inside floating windows](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/floating-widget-dragndrop_dark.png) + +### Grouped dragging + +When dragging the titlebar of a dock, all the tabs that are tabbed with it are +going to be dragged. So you can move complete groups of tabbed widgets into +a floating widget or from one dock area to another one. + +![Grouped dragging](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/grouped-dragging.gif) + +![Grouped dragging](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/grouped-dragging_dark.png) + +### Perspectives for fast switching of the complete main window layout + +A perspective defines the set and layout of dock windows in the main +window. You can save the current layout of the dockmanager into a named +perspective to make your own custom perspective. Later you can simply +select a perspective from the perspective list to quickly switch the complete +main window layout. + +![Perspective](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/perspectives.gif) + +![Perspective](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/perspectives_dark.png) + +### Opaque and non-opaque splitter resizing + +The advanced docking system uses standard QSplitters as resize separators and thus supports opaque and non-opaque resizing functionality of QSplitter. In some rare cases, for very complex widgets or on slow machines resizing via separator on the fly may cause flicking and glaring of rendered content inside a widget. The global dock manager flag `OpaqueSplitterResize` configures the resizing behaviour of the splitters. If this flag is set, then widgets are resized dynamically (opaquely) while interactively moving the splitters. + +![Opaque resizing](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/opaque_resizing.gif) + +If this flag is cleared, the widget resizing is deferred until the mouse button is released - this is some kind of lazy resizing separator. + +![Non-opaque resizing](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/non_opaque_resizing.gif) + +### Opaque and non-opaque undocking + +By default, opaque undocking is active. That means, as soon as you drag a dock widget or a dock area with a number of dock widgets it will be undocked and moved into a floating widget and then the floating widget will be dragged around. That means undocking will take place immediately. You can compare this with opaque splitter resizing. If the flag `OpaqueUndocking` is cleared, then non-opaque undocking is active. In this mode, undocking is more like a standard drag and drop operation. That means, the dragged dock widget or dock area is not undocked immediately. Instead, a drag preview widget is created and dragged around to indicate the future position of the dock widget or dock area. The actual dock operation is only executed when the mouse button is released. That makes it possible, to cancel an active drag operation with the escape key. + +The drag preview widget can be configured by a number of global dock manager flags: + +- `DragPreviewIsDynamic`: if this flag is enabled, the preview will be adjusted dynamically to the drop area +- `DragPreviewShowsContentPixmap`: the created drag preview window shows a static copy of the content of the dock widget / dock are that is dragged +- `DragPreviewHasWindowFrame`: this flag configures if the drag preview is frameless like a QRubberBand or looks like a real window + +The best way to test non-opaque undocking is to set the standard flags: `CDockManager::setConfigFlags(CDockManager::DefaultNonOpaqueConfig)`. + +### Tab-menu for easy handling of many tabbed dock widgets + +Tabs are a good way to quickly switch between dockwidgets in a dockarea. However, if the number of dockwidgets in a dockarea is too large, this may affect the usability of the tab bar. To keep track in this situation, you can use the tab menu. The menu allows you to quickly select the dockwidget you want to activate from a drop down menu. + +![Tab menu](https://raw.githubusercontent.com/githubuser0xFFFF/Qt-Advanced-Docking-System/master/doc/tab_menu.gif) + +### Many different ways to detach dock widgets + +You can detach dock widgets and also dock areas in the following ways: + +- by dragging the dock widget tab or the dock area title bar +- by double clicking the tab or title bar +- by using the detach menu entry from the tab and title bar drop down menu + +### 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). diff --git a/doc/ads_qt_marketplace_manifest.json b/doc/ads_qt_marketplace_manifest.json new file mode 100644 index 000000000..2c733a787 --- /dev/null +++ b/doc/ads_qt_marketplace_manifest.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://qt.io/schema/extension-schema-v1#", + "title": "Qt Advanced Docking System", + "extensionType": [ + "library" + ], + "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.svg", + "licenses": [ + { "licenseType": "LGPL-2.1-only", + "licenseUrl": "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" } + ], + "created": "2017-03-30", + "lastUpdate": "2022-03-02", + "platforms": [ + "Windows 7-11", "Kubuntu 18.04", "Kubuntu 19.10", "Ubuntu 19.10", "Ubuntu 20.04" + ], + "qtVersions": [ + "5.5.1 or newer" + ], + "tags": [ + "Widgets", "Docking"], + "bugUrl": "https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues", + "sourceRepoUrl": "https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System" +} diff --git a/doc/advanced-docking_video.png b/doc/advanced-docking_video.png new file mode 100644 index 000000000..3280ba8e1 Binary files /dev/null and b/doc/advanced-docking_video.png differ diff --git a/doc/autohide-feature-demo.png b/doc/autohide-feature-demo.png new file mode 100644 index 000000000..9e7eff704 Binary files /dev/null and b/doc/autohide-feature-demo.png differ diff --git a/doc/central_widget.gif b/doc/central_widget.gif new file mode 100644 index 000000000..e766741ec Binary files /dev/null and b/doc/central_widget.gif differ diff --git a/doc/cfg_flag_ActiveTabHasCloseButton_false.png b/doc/cfg_flag_ActiveTabHasCloseButton_false.png new file mode 100644 index 000000000..3632baac9 Binary files /dev/null and b/doc/cfg_flag_ActiveTabHasCloseButton_false.png differ diff --git a/doc/cfg_flag_ActiveTabHasCloseButton_true.png b/doc/cfg_flag_ActiveTabHasCloseButton_true.png new file mode 100644 index 000000000..be83d3726 Binary files /dev/null and b/doc/cfg_flag_ActiveTabHasCloseButton_true.png differ diff --git a/doc/cfg_flag_AllTabsHaveCloseButton_true.png b/doc/cfg_flag_AllTabsHaveCloseButton_true.png new file mode 100644 index 000000000..f925cbae4 Binary files /dev/null and b/doc/cfg_flag_AllTabsHaveCloseButton_true.png differ diff --git a/doc/cfg_flag_AlwaysShowTabs_false_true.png b/doc/cfg_flag_AlwaysShowTabs_false_true.png new file mode 100644 index 000000000..3c192cdb8 Binary files /dev/null and b/doc/cfg_flag_AlwaysShowTabs_false_true.png differ diff --git a/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_false.gif b/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_false.gif new file mode 100644 index 000000000..24c7c9946 Binary files /dev/null and b/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_false.gif differ diff --git a/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_true.gif b/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_true.gif new file mode 100644 index 000000000..4b971c487 Binary files /dev/null and b/doc/cfg_flag_AutoHideCloseButtonCollapsesDock_true.gif differ diff --git a/doc/cfg_flag_AutoHideHasCloseButton.png b/doc/cfg_flag_AutoHideHasCloseButton.png new file mode 100644 index 000000000..4cd16a37e Binary files /dev/null and b/doc/cfg_flag_AutoHideHasCloseButton.png differ diff --git a/doc/cfg_flag_AutoHideHasMinimizeButton.png b/doc/cfg_flag_AutoHideHasMinimizeButton.png new file mode 100644 index 000000000..f99143af6 Binary files /dev/null and b/doc/cfg_flag_AutoHideHasMinimizeButton.png differ diff --git a/doc/cfg_flag_AutoHideOpenOnDragHover.gif b/doc/cfg_flag_AutoHideOpenOnDragHover.gif new file mode 100644 index 000000000..8b62e26ce Binary files /dev/null and b/doc/cfg_flag_AutoHideOpenOnDragHover.gif differ diff --git a/doc/cfg_flag_AutoHideSideBarsIconOnly_false.png b/doc/cfg_flag_AutoHideSideBarsIconOnly_false.png new file mode 100644 index 000000000..95293a66a Binary files /dev/null and b/doc/cfg_flag_AutoHideSideBarsIconOnly_false.png differ diff --git a/doc/cfg_flag_AutoHideSideBarsIconOnly_true.png b/doc/cfg_flag_AutoHideSideBarsIconOnly_true.png new file mode 100644 index 000000000..575fdea4a Binary files /dev/null and b/doc/cfg_flag_AutoHideSideBarsIconOnly_true.png differ diff --git a/doc/cfg_flag_DisableTabTextEliding_false.png b/doc/cfg_flag_DisableTabTextEliding_false.png new file mode 100644 index 000000000..72acc4e3a Binary files /dev/null and b/doc/cfg_flag_DisableTabTextEliding_false.png differ diff --git a/doc/cfg_flag_DisableTabTextEliding_true.png b/doc/cfg_flag_DisableTabTextEliding_true.png new file mode 100644 index 000000000..256eba388 Binary files /dev/null and b/doc/cfg_flag_DisableTabTextEliding_true.png differ diff --git a/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_hidden.png b/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_hidden.png new file mode 100644 index 000000000..52a538897 Binary files /dev/null and b/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_hidden.png differ diff --git a/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_visible.png b/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_visible.png new file mode 100644 index 000000000..5d2ae0a5b Binary files /dev/null and b/doc/cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_visible.png differ diff --git a/doc/cfg_flag_DockAreaHasAutoHideButton.png b/doc/cfg_flag_DockAreaHasAutoHideButton.png new file mode 100644 index 000000000..0ca1013d3 Binary files /dev/null and b/doc/cfg_flag_DockAreaHasAutoHideButton.png differ diff --git a/doc/cfg_flag_DockAreaHasCloseButton_false.png b/doc/cfg_flag_DockAreaHasCloseButton_false.png new file mode 100644 index 000000000..9109146d2 Binary files /dev/null and b/doc/cfg_flag_DockAreaHasCloseButton_false.png differ diff --git a/doc/cfg_flag_DockAreaHasCloseButton_true.png b/doc/cfg_flag_DockAreaHasCloseButton_true.png new file mode 100644 index 000000000..b35afc270 Binary files /dev/null and b/doc/cfg_flag_DockAreaHasCloseButton_true.png differ diff --git a/doc/cfg_flag_DockAreaHasTabsMenuButton_false_true.png b/doc/cfg_flag_DockAreaHasTabsMenuButton_false_true.png new file mode 100644 index 000000000..d61801f5a Binary files /dev/null and b/doc/cfg_flag_DockAreaHasTabsMenuButton_false_true.png differ diff --git a/doc/cfg_flag_DockAreaHasUndockButton_false_true.png b/doc/cfg_flag_DockAreaHasUndockButton_false_true.png new file mode 100644 index 000000000..b42168453 Binary files /dev/null and b/doc/cfg_flag_DockAreaHasUndockButton_false_true.png differ diff --git a/doc/cfg_flag_DockAreaHideDisabledButtons_false.png b/doc/cfg_flag_DockAreaHideDisabledButtons_false.png new file mode 100644 index 000000000..e9831086c Binary files /dev/null and b/doc/cfg_flag_DockAreaHideDisabledButtons_false.png differ diff --git a/doc/cfg_flag_DockAreaHideDisabledButtons_true.png b/doc/cfg_flag_DockAreaHideDisabledButtons_true.png new file mode 100644 index 000000000..869e00410 Binary files /dev/null and b/doc/cfg_flag_DockAreaHideDisabledButtons_true.png differ diff --git a/doc/cfg_flag_DragPreviewHasWindowFrame_true.png b/doc/cfg_flag_DragPreviewHasWindowFrame_true.png new file mode 100644 index 000000000..c61396e8d Binary files /dev/null and b/doc/cfg_flag_DragPreviewHasWindowFrame_true.png differ diff --git a/doc/cfg_flag_DragPreviewShowsContentPixmap_false.png b/doc/cfg_flag_DragPreviewShowsContentPixmap_false.png new file mode 100644 index 000000000..6acdc28f7 Binary files /dev/null and b/doc/cfg_flag_DragPreviewShowsContentPixmap_false.png differ diff --git a/doc/cfg_flag_DragPreviewShowsContentPixmap_true.png b/doc/cfg_flag_DragPreviewShowsContentPixmap_true.png new file mode 100644 index 000000000..dc7f0904a Binary files /dev/null and b/doc/cfg_flag_DragPreviewShowsContentPixmap_true.png differ diff --git a/doc/cfg_flag_EqualSplitOnInsertion_false.png b/doc/cfg_flag_EqualSplitOnInsertion_false.png new file mode 100644 index 000000000..a72cb77f7 Binary files /dev/null and b/doc/cfg_flag_EqualSplitOnInsertion_false.png differ diff --git a/doc/cfg_flag_EqualSplitOnInsertion_true.png b/doc/cfg_flag_EqualSplitOnInsertion_true.png new file mode 100644 index 000000000..69891ac92 Binary files /dev/null and b/doc/cfg_flag_EqualSplitOnInsertion_true.png differ diff --git a/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png new file mode 100644 index 000000000..3cdb8c96c Binary files /dev/null and b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_false.png differ diff --git a/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png new file mode 100644 index 000000000..2b06fb203 Binary files /dev/null and b/doc/cfg_flag_FloatingContainerForceNativeTitleBar_true.png differ diff --git a/doc/cfg_flag_FloatingContainerHasWidgetIcon_false.png b/doc/cfg_flag_FloatingContainerHasWidgetIcon_false.png new file mode 100644 index 000000000..31b7d3372 Binary files /dev/null and b/doc/cfg_flag_FloatingContainerHasWidgetIcon_false.png differ diff --git a/doc/cfg_flag_FloatingContainerHasWidgetIcon_true.png b/doc/cfg_flag_FloatingContainerHasWidgetIcon_true.png new file mode 100644 index 000000000..3723ddabe Binary files /dev/null and b/doc/cfg_flag_FloatingContainerHasWidgetIcon_true.png differ diff --git a/doc/cfg_flag_FloatingContainerHasWidgetTitle_false.png b/doc/cfg_flag_FloatingContainerHasWidgetTitle_false.png new file mode 100644 index 000000000..8bb19d945 Binary files /dev/null and b/doc/cfg_flag_FloatingContainerHasWidgetTitle_false.png differ diff --git a/doc/cfg_flag_FloatingContainerHasWidgetTitle_true.png b/doc/cfg_flag_FloatingContainerHasWidgetTitle_true.png new file mode 100644 index 000000000..fc79173cc Binary files /dev/null and b/doc/cfg_flag_FloatingContainerHasWidgetTitle_true.png differ diff --git a/doc/cfg_flag_FocusHighlighting.gif b/doc/cfg_flag_FocusHighlighting.gif new file mode 100644 index 000000000..e9f57822e Binary files /dev/null and b/doc/cfg_flag_FocusHighlighting.gif differ diff --git a/doc/cfg_flag_HideSingleCentralWidgetTitleBar_false.png b/doc/cfg_flag_HideSingleCentralWidgetTitleBar_false.png new file mode 100644 index 000000000..0891661a6 Binary files /dev/null and b/doc/cfg_flag_HideSingleCentralWidgetTitleBar_false.png differ diff --git a/doc/cfg_flag_HideSingleCentralWidgetTitleBar_true.png b/doc/cfg_flag_HideSingleCentralWidgetTitleBar_true.png new file mode 100644 index 000000000..851efeb15 Binary files /dev/null and b/doc/cfg_flag_HideSingleCentralWidgetTitleBar_true.png differ diff --git a/doc/cfg_flag_MiddleMouseButtonClosesTab.gif b/doc/cfg_flag_MiddleMouseButtonClosesTab.gif new file mode 100644 index 000000000..a6b009728 Binary files /dev/null and b/doc/cfg_flag_MiddleMouseButtonClosesTab.gif differ diff --git a/doc/cfg_flag_RetainTabSizeWhenCloseButtonHidden_true.png b/doc/cfg_flag_RetainTabSizeWhenCloseButtonHidden_true.png new file mode 100644 index 000000000..0bc7f6fa6 Binary files /dev/null and b/doc/cfg_flag_RetainTabSizeWhenCloseButtonHidden_true.png differ diff --git a/doc/cfg_flag_ShowTabTextOnlyForActiveTab_true.png b/doc/cfg_flag_ShowTabTextOnlyForActiveTab_true.png new file mode 100644 index 000000000..8678bc45b Binary files /dev/null and b/doc/cfg_flag_ShowTabTextOnlyForActiveTab_true.png differ diff --git a/doc/donate.png b/doc/donate.png new file mode 100644 index 000000000..8d0b0abd5 Binary files /dev/null and b/doc/donate.png differ diff --git a/doc/dynamic_drag_preview.gif b/doc/dynamic_drag_preview.gif new file mode 100644 index 000000000..669a4fdfe Binary files /dev/null and b/doc/dynamic_drag_preview.gif differ diff --git a/doc/floating-widget-dragndrop.png b/doc/floating-widget-dragndrop.png new file mode 100644 index 000000000..b9f868233 Binary files /dev/null and b/doc/floating-widget-dragndrop.png differ diff --git a/doc/floating-widget-dragndrop_dark.png b/doc/floating-widget-dragndrop_dark.png new file mode 100644 index 000000000..afca24287 Binary files /dev/null and b/doc/floating-widget-dragndrop_dark.png differ diff --git a/doc/grouped-dragging.gif b/doc/grouped-dragging.gif new file mode 100644 index 000000000..a27d04d87 Binary files /dev/null and b/doc/grouped-dragging.gif differ diff --git a/doc/grouped-dragging.png b/doc/grouped-dragging.png new file mode 100644 index 000000000..d6a12e970 Binary files /dev/null and b/doc/grouped-dragging.png differ diff --git a/doc/grouped-dragging_dark.png b/doc/grouped-dragging_dark.png new file mode 100644 index 000000000..8fb659cc0 Binary files /dev/null and b/doc/grouped-dragging_dark.png differ diff --git a/doc/linux_kubuntu_1804.png b/doc/linux_kubuntu_1804.png new file mode 100644 index 000000000..e1c41e683 Binary files /dev/null and b/doc/linux_kubuntu_1804.png differ diff --git a/doc/linux_ubuntu_1910.png b/doc/linux_ubuntu_1910.png new file mode 100644 index 000000000..41ef80026 Binary files /dev/null and b/doc/linux_ubuntu_1910.png differ diff --git a/doc/macos.png b/doc/macos.png new file mode 100644 index 000000000..8c2842e6a Binary files /dev/null and b/doc/macos.png differ diff --git a/doc/non_opaque_resizing.gif b/doc/non_opaque_resizing.gif new file mode 100644 index 000000000..7582f5db0 Binary files /dev/null and b/doc/non_opaque_resizing.gif differ diff --git a/doc/non_opaque_undocking.gif b/doc/non_opaque_undocking.gif new file mode 100644 index 000000000..e0bf9bf84 Binary files /dev/null and b/doc/non_opaque_undocking.gif differ diff --git a/doc/opaque_resizing.gif b/doc/opaque_resizing.gif new file mode 100644 index 000000000..c97992476 Binary files /dev/null and b/doc/opaque_resizing.gif differ diff --git a/doc/opaque_undocking.gif b/doc/opaque_undocking.gif new file mode 100644 index 000000000..71be813d6 Binary files /dev/null and b/doc/opaque_undocking.gif differ diff --git a/doc/perspectives.gif b/doc/perspectives.gif new file mode 100644 index 000000000..1290921fd Binary files /dev/null and b/doc/perspectives.gif differ diff --git a/doc/perspectives.png b/doc/perspectives.png new file mode 100644 index 000000000..d90520985 Binary files /dev/null and b/doc/perspectives.png differ diff --git a/doc/perspectives_dark.png b/doc/perspectives_dark.png new file mode 100644 index 000000000..34fb27cf7 Binary files /dev/null and b/doc/perspectives_dark.png differ diff --git a/doc/preview-dragndrop.png b/doc/preview-dragndrop.png new file mode 100644 index 000000000..b1eae8025 Binary files /dev/null and b/doc/preview-dragndrop.png differ diff --git a/doc/preview-dragndrop_dark.png b/doc/preview-dragndrop_dark.png new file mode 100644 index 000000000..ba44afde8 Binary files /dev/null and b/doc/preview-dragndrop_dark.png differ diff --git a/doc/preview_dark.png b/doc/preview_dark.png new file mode 100644 index 000000000..2e530eabe Binary files /dev/null and b/doc/preview_dark.png differ diff --git a/doc/python_logo.png b/doc/python_logo.png new file mode 100644 index 000000000..f35b18006 Binary files /dev/null and b/doc/python_logo.png differ diff --git a/doc/showcase_adtf.png b/doc/showcase_adtf.png new file mode 100644 index 000000000..633bc0413 Binary files /dev/null and b/doc/showcase_adtf.png differ diff --git a/doc/showcase_d-tect-x.jpg b/doc/showcase_d-tect-x.jpg new file mode 100644 index 000000000..6157535c8 Binary files /dev/null and b/doc/showcase_d-tect-x.jpg differ diff --git a/doc/showcase_d-tect-x.png b/doc/showcase_d-tect-x.png new file mode 100644 index 000000000..16adf7f61 Binary files /dev/null and b/doc/showcase_d-tect-x.png differ diff --git a/doc/showcase_dream3d_nx.png b/doc/showcase_dream3d_nx.png new file mode 100644 index 000000000..b64434fd0 Binary files /dev/null and b/doc/showcase_dream3d_nx.png differ diff --git a/doc/showcase_ezEngine_editor.png b/doc/showcase_ezEngine_editor.png new file mode 100644 index 000000000..98df56747 Binary files /dev/null and b/doc/showcase_ezEngine_editor.png differ diff --git a/doc/showcase_hivewe.png b/doc/showcase_hivewe.png new file mode 100644 index 000000000..f7ba52b1e Binary files /dev/null and b/doc/showcase_hivewe.png differ diff --git a/doc/showcase_labplot.png b/doc/showcase_labplot.png new file mode 100644 index 000000000..a8a1d75b4 Binary files /dev/null and b/doc/showcase_labplot.png differ diff --git a/doc/showcase_metgem.png b/doc/showcase_metgem.png new file mode 100644 index 000000000..76e54bd3c Binary files /dev/null and b/doc/showcase_metgem.png differ diff --git a/doc/showcase_notepad_next.png b/doc/showcase_notepad_next.png new file mode 100644 index 000000000..2e8ea1d30 Binary files /dev/null and b/doc/showcase_notepad_next.png differ diff --git a/doc/showcase_plot_juggler.png b/doc/showcase_plot_juggler.png new file mode 100644 index 000000000..a4f074b72 Binary files /dev/null and b/doc/showcase_plot_juggler.png differ diff --git a/doc/showcase_pre_workbench.png b/doc/showcase_pre_workbench.png new file mode 100644 index 000000000..ac31f32ba Binary files /dev/null and b/doc/showcase_pre_workbench.png differ diff --git a/doc/showcase_qmix_elements.png b/doc/showcase_qmix_elements.png new file mode 100644 index 000000000..4223f2c33 Binary files /dev/null and b/doc/showcase_qmix_elements.png differ diff --git a/doc/showcase_qt_design_studio.png b/doc/showcase_qt_design_studio.png new file mode 100644 index 000000000..9353e3b7f Binary files /dev/null and b/doc/showcase_qt_design_studio.png differ diff --git a/doc/showcase_qt_design_studio_video.png b/doc/showcase_qt_design_studio_video.png new file mode 100644 index 000000000..98ca05425 Binary files /dev/null and b/doc/showcase_qt_design_studio_video.png differ diff --git a/doc/showcase_qtcreator.png b/doc/showcase_qtcreator.png new file mode 100644 index 000000000..11ae12201 Binary files /dev/null and b/doc/showcase_qtcreator.png differ diff --git a/doc/showcase_ramses_composer.png b/doc/showcase_ramses_composer.png new file mode 100644 index 000000000..1fd2ddd39 Binary files /dev/null and b/doc/showcase_ramses_composer.png differ diff --git a/doc/showcase_resinsight.png b/doc/showcase_resinsight.png new file mode 100644 index 000000000..d5ab8f51f Binary files /dev/null and b/doc/showcase_resinsight.png differ diff --git a/doc/showcase_robox_ide.png b/doc/showcase_robox_ide.png new file mode 100644 index 000000000..02d7e8956 Binary files /dev/null and b/doc/showcase_robox_ide.png differ diff --git a/doc/tab_menu.gif b/doc/tab_menu.gif new file mode 100644 index 000000000..49fb1e824 Binary files /dev/null and b/doc/tab_menu.gif differ diff --git a/doc/taiwan_ukraine.jpg b/doc/taiwan_ukraine.jpg new file mode 100644 index 000000000..f2f58229b Binary files /dev/null and b/doc/taiwan_ukraine.jpg differ diff --git a/doc/ukraine.jpg b/doc/ukraine.jpg new file mode 100644 index 000000000..2e5c065f9 Binary files /dev/null and b/doc/ukraine.jpg differ diff --git a/doc/user-guide.md b/doc/user-guide.md new file mode 100644 index 000000000..024c5e4dd --- /dev/null +++ b/doc/user-guide.md @@ -0,0 +1,854 @@ +# User Guide + +- [Configuration Flags](#configuration-flags) + - [Setting Configuration Flags](#setting-configuration-flags) + - [`ActiveTabHasCloseButton`](#activetabhasclosebutton) + - [`DockAreaHasCloseButton`](#dockareahasclosebutton) + - [`DockAreaCloseButtonClosesTab`](#dockareaclosebuttonclosestab) + - [`OpaqueSplitterResize`](#opaquesplitterresize) + - [`XmlAutoFormattingEnabled`](#xmlautoformattingenabled) + - [`XmlCompressionEnabled`](#xmlcompressionenabled) + - [`TabCloseButtonIsToolButton`](#tabclosebuttonistoolbutton) + - [`AllTabsHaveCloseButton`](#alltabshaveclosebutton) + - [`RetainTabSizeWhenCloseButtonHidden`](#retaintabsizewhenclosebuttonhidden) + - [`DragPreviewIsDynamic`](#dragpreviewisdynamic) + - [`DragPreviewShowsContentPixmap`](#dragpreviewshowscontentpixmap) + - [`DragPreviewHasWindowFrame`](#dragpreviewhaswindowframe) + - [`AlwaysShowTabs`](#alwaysshowtabs) + - [`DockAreaHasUndockButton`](#dockareahasundockbutton) + - [`DockAreaHasTabsMenuButton`](#dockareahastabsmenubutton) + - [`DockAreaHideDisabledButtons`](#dockareahidedisabledbuttons) + - [`DockAreaDynamicTabsMenuButtonVisibility`](#dockareadynamictabsmenubuttonvisibility) + - [`FloatingContainerHasWidgetTitle`](#floatingcontainerhaswidgettitle) + - [`FloatingContainerHasWidgetIcon`](#floatingcontainerhaswidgeticon) + - [`HideSingleCentralWidgetTitleBar`](#hidesinglecentralwidgettitlebar) + - [`FocusHighlighting`](#focushighlighting) + - [`EqualSplitOnInsertion`](#equalsplitoninsertion) + - [`FloatingContainerForceNativeTitleBar` (Linux only)](#floatingcontainerforcenativetitlebar-linux-only) + - [`FloatingContainerForceQWidgetTitleBar` (Linux only)](#floatingcontainerforceqwidgettitlebar-linux-only) + - [`MiddleMouseButtonClosesTab`](#middlemousebuttonclosestab) + - [`DisableTabTextEliding`](#disabletabtexteliding) + - [`ShowTabTextOnlyForActiveTab`](#showtabtextonlyforactivetab) +- [Auto-Hide Configuration Flags](#auto-hide-configuration-flags) + - [Auto Hide Dock Widgets](#auto-hide-dock-widgets) + - [Pinning Auto-Hide Widgets to a certain border](#pinning-auto-hide-widgets-to-a-certain-border) + - [Show / Hide Auto-Hide Widgets via Mouse Over](#show--hide-auto-hide-widgets-via-mouse-over) + - [Drag \& Drop to Auto-Hide](#drag--drop-to-auto-hide) + - [Auto-Hide Tab Insertion Order](#auto-hide-tab-insertion-order) + - [Auto-Hide Tab Sorting](#auto-hide-tab-sorting) + - [Auto-Hide Drag to Float / Dock](#auto-hide-drag-to-float--dock) + - [Auto-Hide Context Menu](#auto-hide-context-menu) + - [Adding Auto Hide Widgets](#adding-auto-hide-widgets) + - [Setting Auto-Hide Flags](#setting-auto-hide-flags) + - [`AutoHideFeatureEnabled`](#autohidefeatureenabled) + - [`DockAreaHasAutoHideButton`](#dockareahasautohidebutton) + - [`AutoHideButtonTogglesArea`](#autohidebuttontogglesarea) + - [`AutoHideButtonCheckable`](#autohidebuttoncheckable) + - [`AutoHideSideBarsIconOnly`](#autohidesidebarsicononly) + - [`AutoHideShowOnMouseOver`](#autohideshowonmouseover) + - [`AutoHideCloseButtonCollapsesDock`](#autohideclosebuttoncollapsesdock) + - [`AutoHideHasCloseButton`](#autohidehasclosebutton) + - [`AutoHideHasMinimizeButton`](#autohidehasminimizebutton) + - [`AutoHideOpenOnDragHover`](#autohideopenondraghover) +- [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) +- [Styling](#styling) + - [Disabling the Internal Style Sheet](#disabling-the-internal-style-sheet) + +## Configuration Flags + +The Advanced Docking System has a number of global configuration options to +configure the design and the functionality of the docking system. Each +configuration will be explained in detail in the following sections. + +### Setting Configuration Flags + +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); +CDockManager::setConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden, true); +... +d->DockManager = new CDockManager(this); +``` + +If you set the configurations flags, you can set individual flags using the +function `CDockManager::setConfigFlag` or you can set all flags using +the function `CDockManager::setConfigFlags`. Instead of settings all +flags individually, it is better to pick a predefined set of configuration +flags and then modify individual flags. The following predefined +configurations are available + +- `DefaultNonOpaqueConfig` - uses non opaque splitter resizing and non opaque docking +- `DefaultOpaqueConfig` - uses opaque splitter resizing and opaque docking + +Pick one of those predefined configurations and then modify the following +configurations flags to adjust the docking system to your needs. + +### `ActiveTabHasCloseButton` + +If this flag is set (default configuration), the active tab in a tab area has +a close button. + +![ActiveTabHasCloseButton true](cfg_flag_ActiveTabHasCloseButton_true.png) + +If this flag is cleared, the active tab has no close button. You can combine +this with the flag `DockAreaCloseButtonClosesTab` to use the close button +of the dock are to close the single tabs. + +![ActiveTabHasCloseButton true](cfg_flag_ActiveTabHasCloseButton_false.png) + +### `DockAreaHasCloseButton` + +If the flag is set (default configuration) each dock area has a close button. + +![DockAreaHasCloseButton true](cfg_flag_DockAreaHasCloseButton_true.png) + +If this flag is cleared, dock areas do not have a close button. + +![DockAreaHasCloseButton true](cfg_flag_DockAreaHasCloseButton_false.png) + +### `DockAreaCloseButtonClosesTab` + +If the flag is set, the dock area close button closes the active tab, +if not set, it closes the complete dock area (default). + +### `OpaqueSplitterResize` + +The advanced docking system uses standard `QSplitters` as resize separators and thus supports opaque and non-opaque resizing functionality of `QSplitter`. In some rare cases, for very complex widgets or on slow machines resizing via separator on the fly may cause flicking and glaring of rendered content inside a widget. This global dock manager flag configures the resizing behaviour of the splitters. If this flag is set, then widgets are resized dynamically (opaquely) while interactively moving the splitters. If you select the predefined configuration `DefaultOpaqueConfig`, then this is the configured behaviour. + +![Opaque resizing](opaque_resizing.gif) + +If this flag is cleared, the widget resizing is deferred until the mouse button is released - this is some kind of lazy resizing separator. If you select the predefined +configuration `DefaultNonOpaqueConfig`, then this is the configured behaviour. + +![Non-opaque resizing](non_opaque_resizing.gif) + +### `XmlAutoFormattingEnabled` + +If enabled, the XML writer automatically adds line-breaks and indentation to +empty sections between elements (ignorable whitespace). This is used, when +the current state or perspective is saved. It is disabled by default. + +### `XmlCompressionEnabled` + +If enabled, the XML output will be compressed and is not human readable anymore. +This ie enabled by default to minimize the size of the saved data. + +### `TabCloseButtonIsToolButton` + +If enabled the tab close buttons will be `QToolButtons` instead of `QPushButtons` - +disabled by default. Normally the default configuration should be ok but if your +application requires `QToolButtons` instead of `QPushButtons` for styling reasons +or for any other reasons, then you can enable this flag. + +### `AllTabsHaveCloseButton` + +If this flag is set, then all tabs that are closable show a close button. The +advantage of this setting is that the size of the tabs does not change and the +user can immediately close each tab. The disadvantage is that all tabs take up +more space. + +![AllTabsHaveCloseButton true](cfg_flag_AllTabsHaveCloseButton_true.png) + +If this flas is cleared, then only the active tab has a close button (default) +and therefore the tabs need less space. + +![AllTabsHaveCloseButton false](cfg_flag_ActiveTabHasCloseButton_true.png) + +### `RetainTabSizeWhenCloseButtonHidden` + +If this flag is set, the space for the close button is reserved even if the +close button is not visible. This flag is disabled by default. If this flag +is disabled, the tab size dynamically changes if the close button is +visible / hidden in a tab. If this flag is enabled, the tab size always remains +constant, that means, if enabled, the tabs need more space. + +![AllTabsHaveCloseButton false](cfg_flag_RetainTabSizeWhenCloseButtonHidden_true.png) + +### `DragPreviewIsDynamic` + +If non-opaque undocking is enabled, this flag defines the behavior of the drag +preview window. If this flag is enabled, then it will give the user the +impression, that the floating drag preview is dynamically adjusted to the drop +area. In order to give the perfect impression, you should disable the flags +`DragPreviewShowsContentPixmap` and `DragPreviewHasWindowFrame`. + +```c++ +CDockManager::setConfigFlag(CDockManager::DragPreviewIsDynamic, true); +CDockManager::setConfigFlag(CDockManager::DragPreviewShowsContentPixmap, false); +CDockManager::setConfigFlag(CDockManager::DragPreviewHasWindowFrame, false); +``` + +![DragPreviewIsDynamic true](dynamic_drag_preview.gif) + +### `DragPreviewShowsContentPixmap` + +If non-opaque undocking is enabled, the created drag preview window shows a +copy of the content of the dock widget / dock are that is dragged, if this +flag is enabled (default). + +![DragPreviewShowsContentPixmap true](cfg_flag_DragPreviewShowsContentPixmap_true.png) + +If this flag is disabled, the drag preview is only a transparent `QRubberBand` +like window without any content. + +![DragPreviewShowsContentPixmap true](cfg_flag_DragPreviewShowsContentPixmap_false.png) + +### `DragPreviewHasWindowFrame` + +If non-opaque undocking is enabled, then this flag configures if the drag +preview is frameless (default) or looks like a real window. If it is enabled, +then the drag preview is a transparent window with a system window frame. + +![DragPreviewHasWindowFrame true](cfg_flag_DragPreviewHasWindowFrame_true.png) + +### `AlwaysShowTabs` + +If this option is enabled, the tab of a dock widget is always displayed - even +if it is the only visible dock widget in a floating widget. In the image below +on the left side, the flag is disabled (default) and on the right side it is +enabled. + +![AlwaysShowTabs false true](cfg_flag_AlwaysShowTabs_false_true.png) + +### `DockAreaHasUndockButton` + +If the flag is set (default) each dock area has an undock button (right +image). If the flag is cleared, a dock area has no undock button (left image) + +![DockAreaHasUndockButton false true](cfg_flag_DockAreaHasUndockButton_false_true.png) + +### `DockAreaHasTabsMenuButton` + +Tabs are a good way to quickly switch between dockwidgets in a dockarea. +However, if the number of dockwidgets in a dockarea is too large, this may affect +the usability of the tab bar. To keep track in this situation, you can use the +tab menu. The menu allows you to quickly select the dockwidget you want to +activate from a drop down menu. This flag shows / hides the tabs menu button +in the dock area title bar. On the left side, the tabs menu button flag +is cleared. + +![DockAreaHasTabsMenuButton false true](cfg_flag_DockAreaHasTabsMenuButton_false_true.png) + +### `DockAreaHideDisabledButtons` + +If certain flags of a dock widget are disabled, like `DockWidgetClosable` or +`DockWidgetFloatable`, then the corresponding dock area buttons like close +button or detach button are disabled (greyed out). This is the default +setting. + +![DockAreaHideDisabledButtons false](cfg_flag_DockAreaHideDisabledButtons_false.png) + +If the flag is set, disabled dock area buttons will not appear on the toolbar at +all - they are hidden. + +![DockAreaHideDisabledButtons true](cfg_flag_DockAreaHideDisabledButtons_true.png) + +### `DockAreaDynamicTabsMenuButtonVisibility` + +If this flag is cleared, the the tabs menu button is always visible. This is +the default setting. If the flag is set, the tabs menu button will be shown +only when it is required - that means, if the tabs are elided. + +![DockAreaDynamicTabsMenuButtonVisibility false](cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_visible.png) + +If the tabs are not elided, the tabs menu button is hidden. + +![DockAreaDynamicTabsMenuButtonVisibility false](cfg_flag_DockAreaDynamicTabsMenuButtonVisibility_true_hidden.png) + +### `FloatingContainerHasWidgetTitle` + +If set (default), the floating widget window title reflects the title of the +current dock widget. + +![FloatingContainerHasWidgetTitle true](cfg_flag_FloatingContainerHasWidgetTitle_true.png) + +otherwise it displays the title set with `CDockManager::setFloatingContainersTitle` or +application name as window title. + +![FloatingContainerHasWidgetTitle false](cfg_flag_FloatingContainerHasWidgetTitle_false.png) + +### `FloatingContainerHasWidgetIcon` + +If set, the floating widget icon reflects the icon of the current dock widget + +![FloatingContainerHasWidgetIcon true](cfg_flag_FloatingContainerHasWidgetIcon_true.png) + +otherwise (default setting) it displays application icon. + +![FloatingContainerHasWidgetIcon false](cfg_flag_FloatingContainerHasWidgetIcon_false.png) + +### `HideSingleCentralWidgetTitleBar` + +If there is only one single visible dock widget in the main dock container (the dock manager) +and if this flag is set, then the titlebar of this dock widget will be hidden. +This only makes sense for non draggable and non floatable dock widgets and enables +the creation of some kind of "central" static widget. Because the titlebar is +hidden, it is not possible to drag out the central widget to make it floating +or to close it via the close button. + +![HideSingleCentralWidgetTitleBar true](cfg_flag_HideSingleCentralWidgetTitleBar_true.png) + +Unless a central widget explicitly has been set with setCentralWidget, the +Advanced Docking System is without a static central widget and it wouldn't know +about a central static widget. +Therefore this flag is disabled by default and a central single dock widget +still has a titlebar to drag it out of the main window. + +![HideSingleCentralWidgetTitleBar false](cfg_flag_HideSingleCentralWidgetTitleBar_false.png) + +### `FocusHighlighting` + +If this is enabled, the docking system is able to highlight the tab and the +components of a dock area with a different style (i.e. a different color). +This option is disabled by default and needs to be enabled explicitly +because it adds some overhead. The dock manager needs to react on focus +changes and dock widget dragging to highlight the right dock widget. You should +enable it only, if you really need it for your application. + +If the feature is enabled, you can also connect to the new dock manager +signal `focusedDockWidgetChanged(CDockWidget* old, CDockWidget* now)` to +react on focus changes and to prepare the content of the focused dock +widget. + +You can click into the tab, the titlebar or the content of a dock widget +to focus it. + +![FocusHighlighting](cfg_flag_FocusHighlighting.gif) + +For the focused dock widget and dock widget tab, the property `focused` will +be set to true and you can use this property to style the focused dock +widget differently. The picture above uses the following styling: + +```css +/* Color the tab with the highlight color */ +ads--CDockWidgetTab[focused="true"] +{ + background: palette(highlight); + border-color: palette(highlight); +} + +/* Use a different colored close button icon to match the test color */ +ads--CDockWidgetTab[focused="true"] > #tabCloseButton +{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + +/* Make a hovered focused close button a little bit lighter */ +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:hover +{ + background: rgba(255, 255, 255, 48); +} + +/* Make a pressed focused close button even more lighter */ +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:pressed +{ + background: rgba(255, 255, 255, 92); +} + +/* Use a different color for the tab label */ +ads--CDockWidgetTab[focused="true"] QLabel +{ + color: palette(light); +} + +/* Paint a nice solid line for the whole title bar to create the illusion + of an active tab */ +ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar +{ + background: transparent; + border-bottom: 2px solid palette(highlight); + padding-bottom: 0px; +} +``` + +If you have a content widget that does not support focussing for some reason +(like `QVTKOpenGLStereoWidget` from the [VTK library](https://github.com/Kitware/VTK)), +then you can manually switch the focus by reacting on mouse events. The +following code shows, how to install en event filter on the `QVTKOpenGLStereoWidget` +to properly switch the focus on `QEvent::MouseButtonPress`: + +```c++ +static ads::CDockWidget* createVTK2DWindow(QMenu* ViewMenu, QObject* EventFilter) +{ + QVTKOpenGLStereoWidget* qvtkOpenGLStereoWidget = new QVTKOpenGLStereoWidget; + ads::CDockWidget* DockWidget = new ads::CDockWidget("2D Window"); + DockWidget->setWidget(qvtkOpenGLStereoWidget); + qvtkOpenGLStereoWidget->installEventFilter(EventFilter); + qvtkOpenGLStereoWidget->setProperty("DockWidget", QVariant::fromValue(DockWidget)); + return DockWidget; +} +``` + +Now we can use the event filter function to react on mouse events and then +use the dock manager function `setDockWidgetFocused()` to switch the focus: + +```c++ +bool CMainWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonPress) + { + QVTKOpenGLStereoWidget* vtkWidget = qobject_cast(watched); + auto vDockWidget = vtkWidget->property("DockWidget"); + ads::CDockWidget* DockWidget = nullptr; + if (vDockWidget.isValid()) + { + DockWidget = qvariant_cast(vDockWidget); + } + + if (DockWidget) + { + d->DockManager->setDockWidgetFocused(DockWidget); + } + } + return false; +} +``` + +### `EqualSplitOnInsertion` + +This flag configures how the space is distributed if a new dock widget is +inserted into an existing dock area. The flag is disabled by default. If 3 +dock widgets are inserted with the following code + +```c++ +d->DockManager->addDockWidget(ads::RightDockWidgetArea, DockWidget, EditorArea); +``` + +then this is the result, if the flag is disabled: + +![EqualSplitOnInsertion false](cfg_flag_EqualSplitOnInsertion_false.png) + +If the flag is enabled, then the space is equally distributed to all widgets +in a splitter: + +![EqualSplitOnInsertion true](cfg_flag_EqualSplitOnInsertion_true.png) + + +### `FloatingContainerForceNativeTitleBar` (Linux only) + +Since release 3.6 the library supports native title bars and window decorations +for floating widgets on Linux (thanks to a user contribution). +Native title bars and window decorations are supported by most Linux window +managers, such as Compiz or Xfwm. Some window managers like KWin do not properly +support this feature. Native floating widgets look better because of the native +styling and the support all window manager features like snapping to window +borders or maximizing. The library tries to detect the window manager during +runtime and activates native window decorations if possible: + +![FloatingContainerForceNativeTitleBar true](cfg_flag_FloatingContainerForceNativeTitleBar_true.png) + +If you would like to overwrite this autodetection, then you can activate this +flag to force native window titlebars. You can overwrite autodetection and this +flag, if you set the environment variable `ADS_UseNativeTitle` to 0 or 1. + +### `FloatingContainerForceQWidgetTitleBar` (Linux only) + +If your window manager (i.e. KWin) does not properly support native floating +windows, the docking library falls back to QWidget based floating widget +title bars. + +![FloatingContainerForceNativeTitleBar false](cfg_flag_FloatingContainerForceNativeTitleBar_false.png) + +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) + +### `DisableTabTextEliding` + +Set this flag to disable eliding of tab texts in dock area tabs: + +![DisableTabTextEliding true](cfg_flag_DisableTabTextEliding_true.png) + +The flag is disabled by default and the text in all tabs is elided to show as +many tabs as possible even if there is not much space: + +![DisableTabTextEliding false](cfg_flag_DisableTabTextEliding_false.png) + +### `ShowTabTextOnlyForActiveTab` + +Set this flag (default = false) to show label texts in dock area tabs only +for active tabs. Inactive tabs only show their icon: + +![MShowTabTextOnlyForActiveTab true](cfg_flag_ShowTabTextOnlyForActiveTab_true.png) + +## Auto-Hide Configuration Flags + +### Auto Hide Dock Widgets + +The Advanced Docking System supports "Auto-Hide" functionality for **all** +dock containers. The "Auto Hide" feature allows to display more information +using less screen space by hiding or showing windows pinned to one of the +four dock container borders. + +Enabling this feature adds a button with a pin icon to each dock area. + +![DockAreaHasAutoHideButton true](cfg_flag_DockAreaHasAutoHideButton.png) + +By clicking this button, the current dock widget (or the complete area - depending on the +configuration flags) will be pinned to a certain border. The border is chosen +depending on the location of the dock area. If you click the pin button while +holding down the **Ctrl** key, the whole dock area will be pinned to a certain +border. + +### Pinning Auto-Hide Widgets to a certain border + +If you would like to pin a dock widget or a dock area to a certain border, +then you can right-click into the dock widget tab or into the dock area title bar +to show the context menu. Then you can select the location via the **Pin to** menu: + +![Pin to](AutoHide_PinTo.png) + +### Show / Hide Auto-Hide Widgets via Mouse Over + +Normally Auto-Hide widgets are shown by clicking the Auto-Hide tab and hidden by +clicking the Auto-Hide tab again or by clicking into any other dock widget in +the same container. If the Auto-Hide config flag `AutoHideShowOnMouseOver` is set, +the Auto-Hide widget is shown, if the user hovers over the Auto-Hide tab and is +collapsed if the mouse cursor leaves the Auto-Hide widget. Showing and hiding +by mouse click still works if this feature is enabled. + +### Drag & Drop to Auto-Hide + +You can easily drag any dock widget or any floating widget to the +borders of a window to pin it as a auto-hide tab in one of the 4 sidebars. +If you drag a dock widget close the one of the four window borders, special +drop overlays will be shown to indicate the drop area for auto-hide widgets: + +![Auo-Hide drag to Sidebar](AutoHide_Drag_to_Sidebar.gif) + +Of course, this also works with dock areas: + +![Auo-Hide drag Dock Area](AutoHide_Drag_DockArea.gif) + +If you drag a dock widget or dock area into a sidebar, then you even have +control over where tabs are inserted. Simply drag your mouse over a specific +auto-hide tab, and your dragged dock widget will be inserted before this tab. +Drag to the sidebar area behind the last tab, and the dragged widget will be +appended as last tab. In the following screen capture, the **Image Viewer 1** will +be inserted before the **Table 0** Auto-Hide tab and the **Image Viewer 2** +is appended behind the last tab: + +![Auo-Hide tab insert order](AutoHide_Tab_Insert_Order.gif) + +### Auto-Hide Tab Insertion Order + +It is also possible to drag Auto-Hide tabs to a new auto-hide position. +That means, you can drag them to a different border or sidebar: + +![Auto-Hide change sidebar](AutoHide_Change_Sidebar.gif) + +### Auto-Hide Tab Sorting + +You can drag Auto-Hide tabs to a new position in the current sidebar +to sort them: + +![Auo-Hide sort tabs](AutoHide_Sort_Tabs.gif) + +### Auto-Hide Drag to Float / Dock + +But that is not all. You can also simply move Auto-Hide tabs to another +floating widget or dock them via drag and drop: + +![Auo-Hide drag to float or dock](AutoHide_Drag_to_Float_or_Dock.gif) + +### Auto-Hide Context Menu + +All Auto-Hide tabs now have a context menu, that provides all the functionality +that you know from Dock widget tabs. With the **Pin To...** item from the +context menu it is very easy to move an Auto-Hide tab to a different Auto-Hide +sidebar: + +![Auo-Hide context menu](AutoHide_Context_Menu.png) + +### Adding Auto Hide Widgets + +Adding an auto hide widget is similar to adding a dock widget, simply call +`dockManager->addAutoHideDockWidget()`. + +```c++ +CDockManager::setAutoHideConfigFlags(CDockManager::DefaultAutoHideConfig); +d->DockManager = new CDockManager(this); +CDockWidget* TableDockWidget = new CDockWidget("Table 1"); +DockManager->addAutoHideDockWidget(SideBarLeft, TableDockWidget); +``` + +See `autohide` example or the demo application to learn how it works. + +### Setting Auto-Hide Flags + +The Advanced Docking System has a number of global configuration flags to +configure the Auto-Hide functionality. You should set the Auto-Hide flags before +creating the dock manager instance. That means, you should set the Auto-Hide +flags after setting the configuration flags. + +```c++ +CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); +CDockManager::setAutoHideConfigFlags(CDockManager::DefaultAutoHideConfig); +CDockManager::setAutoHideConfigFlag(CDockManager::AutoHideShowOnMouseOver, true); +... +d->DockManager = new CDockManager(this); +``` + +If you set the Auto-Hide flags, you can set individual flags using the +function `CDockManager::setAutoHideConfigFlag` or you can set all flags using +the function `CDockManager::setAutoHideConfigFlags`. Instead of settings all +flags individually, it is better to pick a predefined set of configuration +flags and then modify individual flags. The following predefined +configurations are available + +- `DefaultAutoHideConfig` - default auto hide config + +Pick one of those predefined configurations and then modify the following +configurations flags to adjust the docking system to your needs. + +### `AutoHideFeatureEnabled` + +Enables / disables the Auto-Hide functionality. Only if this flag is enabled, +the other Auto-Hide flags will be evaluated. + +### `DockAreaHasAutoHideButton` + +If this flag is set (default), then each dock area has a pin button in the title +bar to toggle Auto-Hide state. + +![DockAreaHasAutoHideButton true](cfg_flag_DockAreaHasAutoHideButton.png) + +### `AutoHideButtonTogglesArea` + +If set, the the pin button in the dock area title bar toggles the complete area. +If not set (default), then the pin button toggles the current active tab / dock +widget and pressing the **Ctrl** key while clicking the pin button toggles the +complete ara. + +### `AutoHideButtonCheckable` + +Normally the pin button in the dock area title bar is not checkable. If this +flag is set, then the button is checkable. That means, if button is checked, +the dock widget is pinned. + +### `AutoHideSideBarsIconOnly` + +Normally the Auto-Hide tabs show the icon and title of the dock widget: + +![AutoHideSideBarsIconOnly false](cfg_flag_AutoHideSideBarsIconOnly_false.png) + +You can set this flag, if you would like to have only icons in the Auto-Hide +tabs instead of icon and dock widget title. If this is set, the Auto-Hide tab +needs less space. The tooltip of each tab still shows the dock widget title. + +![AutoHideSideBarsIconOnly true](cfg_flag_AutoHideSideBarsIconOnly_true.png) + +### `AutoHideShowOnMouseOver` + +Normally Auto-Hide widgets are shown by clicking the Auto-Hide tab and +hidden by clicking the Auto-Hide tab again or by clicking into any other +dock widget in the same container. If this flag is set, the Auto-Hide widget +is shown, if the user hovers over the Auto-Hide tab or if the users moves the +mouse outside of the Auto-Hide widget. Showing and hiding my mouse click still +works if this feature is enabled. + +### `AutoHideCloseButtonCollapsesDock` + +Some users don't understand the distinction between closing an auto hide dock and +collapsing an auto hide dock. This may lead to situations where they press the +close button (losing the side tab widget) instead of simply clicking outside +the auto hide dock (collapsing the dock). + +![AutoHideCloseButtonCollapsesDock false](cfg_flag_AutoHideCloseButtonCollapsesDock_false.gif) + +If `AutoHideCloseButtonCollapsesDock` option is active, the +close button in an auto hide widget collapses the auto hide widget instead of +closing it. + +![AutoHideCloseButtonCollapsesDock true](cfg_flag_AutoHideCloseButtonCollapsesDock_true.gif) + +If you enable the `AutoHideHasMinimizeButton` flag, you should disable this +flag our you will have two buttons with minimize functionality. + +### `AutoHideHasCloseButton` + +If this flag is set (default), then each auto hide widget has a close button: + +![AutoHideHasCloseButton](cfg_flag_AutoHideHasCloseButton.png) + +The functionality of the close button (close or minimize) is configured by the +`AutoHideCloseButtonCollapsesDock` flag. + +### `AutoHideHasMinimizeButton` + +If this flag is set (disabled by default), then each auto hide widget has a minimize button. + +![AutoHideHasMinimizeButton](cfg_flag_AutoHideHasMinimizeButton.png) + + +### `AutoHideOpenOnDragHover` + +If this flag is set (disabled by default), then holding a dragging cursor hover an auto-hide collapsed dock's tab will open said dock: + +![AutoHideOpenOnDragHover](cfg_flag_AutoHideOpenOnDragHover.gif) + +Said dock must be set to accept drops to hide when cursor leaves its scope. See `AutoHideDragNDropExample` for more details. + +## 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. + +### `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 +the native Qt docking system with its central widget concept. This was the +reason that until version 3.6 of the library, there was no support for such +thing like a central widget. Thanks to the contribution of a user the library +now supports a central widget. + +In the Advanced Docking System a central widget is a docking widget that is +neither closable nor movable or floatable. A central widget has no title bar +and so it is not possible for the user to hide, close or drag the central +widget. If there is a central widget, then also the distribution of the sizes +for the dock widgets around the central widget is different: + +- **no central widget (default)** - on resizing the available space is +distributed to all dock widgets - the size of all dock widgets +shrinks or grows +- **with central widget** - on resizing only the central widget is resized - the +dock widgets around the central widget keep their size (see the animation below) + +![Central Widget](central_widget.gif) + +To set a central widget, you just need to pass your central dock widget +to the dock manager `setCentralWidget` function: + +```c++ +auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); +``` + +See the `centralwidget` example to learn how it works. + +> ##### Note +> The central widget needs to be the first dock widget that is added to the +> dock manager. The function does not work and returns a `nullptr` if there +> are already other dock widgets registered. So `setCentralWidget` should be +> the first function that you call when adding dock widgets. + +## Empty Dock Area + +Some applications require a fixed DockArea that is always visible, even if it +does not contain any DockWidgets. I.e. the DockArea is in this case a kind +of central widget that is always visible (see this +[issue](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/199)). + +Since version 3.7.1 the advanced docking system supports this feature. The +`emptydockarea` example shows how this can be implemented with the library. You +just need to create a dock widget and set the feature flag `CDockWidget::NoTab`. +This permanently hides the tab widget of this area and removes it from the tab +menu. For this special dock widget you should also disable all other features +(movable, closable and floatable) to prevent closing and moving of this widget. +If you use the `CDockManager::setCentralWidget` function like in the example +code below, then you don't need to disable these features because this is done +in the `setCentralWidget` function. + +```c++ +QLabel* label = new QLabel(); +label->setText("This is a DockArea which is always visible, even if it does not contain any DockWidgets."); +label->setAlignment(Qt::AlignCenter); +CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); +CentralDockWidget->setWidget(label); +CentralDockWidget->setFeature(ads::CDockWidget::NoTab, true);// set the flag before adding the widget to dock manager +auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); +``` + +## Custom Close Handling + +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. + +When an entire area is closed, the default behavior is to hide the dock widgets it contains regardless of the `DockWidgetDeleteOnClose` flag except if there is only one dock widget. In this special case, the `DockWidgetDeleteOnClose` flag is followed. This behavior can be changed by setting the `DockWidgetForceCloseWithArea` flag to all the dock widgets that needs to be closed with their area. + +## Styling + +The Advanced Docking System supports styling via [Qt Style Sheets](https://doc.qt.io/qt-5/stylesheet.html). All components like splitters, tabs, buttons, titlebar and +icons are styleable this way. + +### Disabling the Internal Style Sheet + +The dock manager uses an internal stylesheet to style its components. That +means, the style that you see in the demo application comes from the +internal stylesheets that you will find in `src/stylesheets` folder. If you want +to disable this internal stylesheet because your application uses its own, +just call the function for settings the stylesheet with an empty string. + +```c++ +DockManager->setStyleSheet(""); +``` + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..8da1f3330 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,11 @@ +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) +add_subdirectory(autohide) +add_subdirectory(autohidedragndrop) +add_subdirectory(emptydockarea) +add_subdirectory(dockindock) diff --git a/examples/autohide/CMakeLists.txt b/examples/autohide/CMakeLists.txt new file mode 100644 index 000000000..337906770 --- /dev/null +++ b/examples/autohide/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_autohide 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(AutoHideExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui +) +target_include_directories(AutoHideExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(AutoHideExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(AutoHideExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(AutoHideExample 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 Auto Hide 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" +) diff --git a/examples/autohide/autohide.pro b/examples/autohide/autohide.pro new file mode 100644 index 000000000..8a00c6253 --- /dev/null +++ b/examples/autohide/autohide.pro @@ -0,0 +1,34 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = AutoHideExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +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 + diff --git a/examples/autohide/main.cpp b/examples/autohide/main.cpp new file mode 100644 index 000000000..fa4c4fdea --- /dev/null +++ b/examples/autohide/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/autohide/main.py b/examples/autohide/main.py new file mode 100644 index 000000000..48c29278f --- /dev/null +++ b/examples/autohide/main.py @@ -0,0 +1,106 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QTimer, QDir, QSignalBlocker +from PyQt5.QtGui import QCloseEvent, QIcon +from PyQt5.QtWidgets import (QApplication, QLabel, QCalendarWidget, QFrame, QTreeView, + QTableWidget, QFileSystemModel, QPlainTextEdit, QToolBar, + QWidgetAction, QComboBox, QAction, QSizePolicy, QInputDialog) + +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + +class MainWindow(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 + text_edit = QPlainTextEdit() + text_edit.setPlaceholderText("This is the central editor. Enter your text here.") + central_dock_widget = QtAds.CDockWidget("CentralWidget") + central_dock_widget.setWidget(text_edit) + central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget) + central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas) + + # 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) + 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() + 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.create_perspective_ui() + + def create_perspective_ui(self): + save_perspective_action = QAction("Create Perspective", self) + save_perspective_action.triggered.connect(self.save_perspective) + perspective_list_action = QWidgetAction(self) + self.perspective_combobox = QComboBox(self) + self.perspective_combobox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.perspective_combobox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.perspective_combobox.activated[str].connect(self.dock_manager.openPerspective) + perspective_list_action.setDefaultWidget(self.perspective_combobox) + self.toolBar.addSeparator() + self.toolBar.addAction(perspective_list_action) + self.toolBar.addAction(save_perspective_action) + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save Perspective", "Enter Unique name:") + if not ok or not perspective_name: + return + + self.dock_manager.addPerspective(perspective_name) + blocker = QSignalBlocker(self.perspective_combobox) + self.perspective_combobox.clear() + self.perspective_combobox.addItems(self.dock_manager.perspectiveNames()) + self.perspective_combobox.setCurrentText(perspective_name) + + def closeEvent(self, event: QCloseEvent): + self.dock_manager.deleteLater() + super().closeEvent(event) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/autohide/mainwindow.cpp b/examples/autohide/mainwindow.cpp new file mode 100644 index 000000000..d70b51ce7 --- /dev/null +++ b/examples/autohide/mainwindow.cpp @@ -0,0 +1,126 @@ +#include "mainwindow.h" + +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AutoHideDockContainer.h" +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" + +using namespace ads; + + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); + CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); + CDockManager::setAutoHideConfigFlags(CDockManager::DefaultAutoHideConfig); + DockManager = new CDockManager(this); + + // Set central widget + QPlainTextEdit* w = new QPlainTextEdit(); + w->setPlaceholderText("This is the central editor. Enter your text here."); + CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); + CentralDockWidget->setWidget(w); + auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); + CentralDockArea->setAllowedAreas(DockWidgetArea::OuterDockAreas); + + // create other dock widgets + QTableWidget* table = new QTableWidget(); + table->setColumnCount(3); + table->setRowCount(10); + CDockWidget* TableDockWidget = new CDockWidget("Table 1"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->setMinimumSize(200,150); + const auto autoHideContainer = DockManager->addAutoHideDockWidget(SideBarLocation::SideBarLeft, TableDockWidget); + autoHideContainer->setSize(480); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + table = new QTableWidget(); + table->setColumnCount(5); + table->setRowCount(1020); + TableDockWidget = new CDockWidget("Table 2"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + DockManager->addAutoHideDockWidget(SideBarLocation::SideBarLeft, TableDockWidget); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + QTableWidget* propertiesTable = new QTableWidget(); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); + + createPerspectiveUi(); +} + +CMainWindow::~CMainWindow() +{ + delete ui; +} + + +void CMainWindow::createPerspectiveUi() +{ + SavePerspectiveAction = new QAction("Create Perspective", this); + connect(SavePerspectiveAction, SIGNAL(triggered()), SLOT(savePerspective())); + PerspectiveListAction = new QWidgetAction(this); + PerspectiveComboBox = new QComboBox(this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(PerspectiveComboBox, SIGNAL(currentTextChanged(const QString&)), + DockManager, SLOT(openPerspective(const QString&))); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(PerspectiveListAction); + ui->toolBar->addAction(SavePerspectiveAction); +} + + +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(PerspectiveComboBox); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); + PerspectiveComboBox->setCurrentText(PerspectiveName); +} + + +//============================================================================ +void CMainWindow::closeEvent(QCloseEvent* event) +{ + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + DockManager->deleteLater(); + QMainWindow::closeEvent(event); +} + + diff --git a/examples/autohide/mainwindow.h b/examples/autohide/mainwindow.h new file mode 100644 index 000000000..75869da84 --- /dev/null +++ b/examples/autohide/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +protected: + virtual void closeEvent(QCloseEvent* event) override; + +private: + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + + Ui::CMainWindow *ui; + + ads::CDockManager* DockManager; + ads::CDockAreaWidget* StatusDockArea; + ads::CDockWidget* TimelineDockWidget; + + void createPerspectiveUi(); + +private slots: + void savePerspective(); +}; +#endif // MAINWINDOW_H diff --git a/examples/autohide/mainwindow.ui b/examples/autohide/mainwindow.ui new file mode 100644 index 000000000..f7d3b09e8 --- /dev/null +++ b/examples/autohide/mainwindow.ui @@ -0,0 +1,47 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + diff --git a/examples/autohidedragndrop/CMakeLists.txt b/examples/autohidedragndrop/CMakeLists.txt new file mode 100644 index 000000000..3b8f077c9 --- /dev/null +++ b/examples/autohidedragndrop/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_autohide_dragndrop 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(AutoHideDragNDropExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui + droppableitem.cpp +) +target_include_directories(AutoHideDragNDropExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(AutoHideDragNDropExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(AutoHideDragNDropExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(AutoHideDragNDropExample 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 Auto Hide With Drag N Drop 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" +) diff --git a/examples/autohidedragndrop/autohidedragndrop.pro b/examples/autohidedragndrop/autohidedragndrop.pro new file mode 100644 index 000000000..f812355fe --- /dev/null +++ b/examples/autohidedragndrop/autohidedragndrop.pro @@ -0,0 +1,36 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = AutoHideDragNDropExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +SOURCES += \ + main.cpp \ + mainwindow.cpp \ + droppableitem.cpp + +HEADERS += \ + mainwindow.h \ + droppableitem.h + +FORMS += \ + mainwindow.ui + +LIBS += -L$${ADS_OUT_ROOT}/lib +include(../../ads.pri) +INCLUDEPATH += ../../src +DEPENDPATH += ../../src + diff --git a/examples/autohidedragndrop/droppableitem.cpp b/examples/autohidedragndrop/droppableitem.cpp new file mode 100644 index 000000000..d7d2addf5 --- /dev/null +++ b/examples/autohidedragndrop/droppableitem.cpp @@ -0,0 +1,38 @@ +#include "droppableitem.h" + +#include +#include +#include +#include +#include + +DroppableItem::DroppableItem(const QString& text, QWidget* parent) + : QPushButton(text, parent) +{ + setAcceptDrops(true); + setSizePolicy(QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Expanding); +} + +void DroppableItem::dragEnterEvent(QDragEnterEvent* event) +{ + if (event->mimeData()->hasText()) + { + event->acceptProposedAction(); + setCursor(Qt::DragMoveCursor); + } +} + +void DroppableItem::dragLeaveEvent(QDragLeaveEvent* event) +{ + Q_UNUSED(event); + unsetCursor(); +} + +void DroppableItem::dropEvent(QDropEvent* event) +{ + if (event->mimeData()->hasText()) + { + event->acceptProposedAction(); + setText(event->mimeData()->text()); + } +} diff --git a/examples/autohidedragndrop/droppableitem.h b/examples/autohidedragndrop/droppableitem.h new file mode 100644 index 000000000..a8fcd913e --- /dev/null +++ b/examples/autohidedragndrop/droppableitem.h @@ -0,0 +1,19 @@ +#include +#include + +class QDragEnterEvent; +class QDragLeaveEvent; +class QDropEvent; + +class DroppableItem : public QPushButton +{ + Q_OBJECT; + +public: + DroppableItem(const QString& text = QString(), QWidget* parent = nullptr); + +protected: + void dragEnterEvent(QDragEnterEvent* event) override; + void dragLeaveEvent(QDragLeaveEvent* event) override; + void dropEvent(QDropEvent* event) override; +}; diff --git a/examples/autohidedragndrop/main.cpp b/examples/autohidedragndrop/main.cpp new file mode 100644 index 000000000..fa4c4fdea --- /dev/null +++ b/examples/autohidedragndrop/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/autohidedragndrop/main.py b/examples/autohidedragndrop/main.py new file mode 100644 index 000000000..4ec084b07 --- /dev/null +++ b/examples/autohidedragndrop/main.py @@ -0,0 +1,84 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QTimer, QDir, QSignalBlocker +from PyQt5.QtGui import QCloseEvent, QIcon +from PyQt5.QtWidgets import (QApplication, QLabel, QCalendarWidget, QFrame, QTreeView, + QTableWidget, QFileSystemModel, QPlainTextEdit, QToolBar, + QWidgetAction, QComboBox, QAction, QSizePolicy, QInputDialog) + +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + +class MainWindow(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) + QtAds.CDockManager.setAutoHideConfigFlag(QtAds.CDockManager.AutoHideOpenOnDragHover, True); + self.dock_manager = QtAds.CDockManager(self) + + # Set central widget + text_edit = QPlainTextEdit() + text_edit.setPlaceholderText("This is the central editor. Enter your text here.") + central_dock_widget = QtAds.CDockWidget("CentralWidget") + central_dock_widget.setWidget(text_edit) + central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget) + central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas) + + + droppable_item = DroppableItem("Drop text here.") + drop_dock_widget = QtAds.CDockWidget("Tab") + drop_dock_widget.setWidget(droppable_item) + drop_dock_widget.setMinimumSizeHintMode(QtAds.CDockWidget.MinimumSizeHintFromDockWidget) + drop_dock_widget.setMinimumSize(200, 150) + drop_dock_widget.setAcceptDrops(True) + drop_area = self.dock_manager.addDockWidget(QtAds.DockWidgetArea.LeftDockWidgetArea, drop_dock_widget) + drop_area.setAcceptDrops(True) + self.menuView.addAction(drop_dock_widget.toggleViewAction()) + + self.create_perspective_ui() + + def create_perspective_ui(self): + save_perspective_action = QAction("Create Perspective", self) + save_perspective_action.triggered.connect(self.save_perspective) + perspective_list_action = QWidgetAction(self) + self.perspective_combobox = QComboBox(self) + self.perspective_combobox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.perspective_combobox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.perspective_combobox.activated[str].connect(self.dock_manager.openPerspective) + perspective_list_action.setDefaultWidget(self.perspective_combobox) + self.toolBar.addSeparator() + self.toolBar.addAction(perspective_list_action) + self.toolBar.addAction(save_perspective_action) + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save Perspective", "Enter Unique name:") + if not ok or not perspective_name: + return + + self.dock_manager.addPerspective(perspective_name) + blocker = QSignalBlocker(self.perspective_combobox) + self.perspective_combobox.clear() + self.perspective_combobox.addItems(self.dock_manager.perspectiveNames()) + self.perspective_combobox.setCurrentText(perspective_name) + + def closeEvent(self, event: QCloseEvent): + self.dock_manager.deleteLater() + super().closeEvent(event) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/autohidedragndrop/mainwindow.cpp b/examples/autohidedragndrop/mainwindow.cpp new file mode 100644 index 000000000..e6d405f1c --- /dev/null +++ b/examples/autohidedragndrop/mainwindow.cpp @@ -0,0 +1,131 @@ +#include "mainwindow.h" +#include "droppableitem.h" + +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AutoHideDockContainer.h" +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" + +using namespace ads; + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); + CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); + CDockManager::setAutoHideConfigFlags(CDockManager::DefaultAutoHideConfig); + CDockManager::setAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover, true); + CDockManager::setConfigParam(CDockManager::AutoHideOpenOnDragHoverDelay_ms, 500); + DockManager = new CDockManager(this); + + // Set central widget + QPlainTextEdit* w = new QPlainTextEdit(); + w->setPlaceholderText("This is the central editor. Enter your text here."); + CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); + CentralDockWidget->setWidget(w); + auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); + CentralDockArea->setAllowedAreas(DockWidgetArea::OuterDockAreas); + + { + DroppableItem* droppableItem = new DroppableItem("Drop text here."); + CDockWidget* dropDockWidget = new CDockWidget("Tab 1"); + dropDockWidget->setWidget(droppableItem); + dropDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + dropDockWidget->setMinimumSize(200,150); + dropDockWidget->setAcceptDrops(true); + const auto autoHideContainer = DockManager->addAutoHideDockWidget(SideBarLocation::SideBarLeft, dropDockWidget); + autoHideContainer->setSize(480); + autoHideContainer->setAcceptDrops(true); + ui->menuView->addAction(dropDockWidget->toggleViewAction()); + } + { + DroppableItem* droppableItem = new DroppableItem("Drop text here."); + CDockWidget* dropDockWidget = new CDockWidget("Tab 2"); + dropDockWidget->setWidget(droppableItem); + dropDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + dropDockWidget->setMinimumSize(200,150); + dropDockWidget->setAcceptDrops(true); + const auto autoHideContainer = DockManager->addAutoHideDockWidget(SideBarLocation::SideBarRight, dropDockWidget); + autoHideContainer->setSize(480); + autoHideContainer->setAcceptDrops(true); + ui->menuView->addAction(dropDockWidget->toggleViewAction()); + } + + QTableWidget* propertiesTable = new QTableWidget(); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); + + createPerspectiveUi(); +} + +CMainWindow::~CMainWindow() +{ + delete ui; +} + + +void CMainWindow::createPerspectiveUi() +{ + SavePerspectiveAction = new QAction("Create Perspective", this); + connect(SavePerspectiveAction, SIGNAL(triggered()), SLOT(savePerspective())); + PerspectiveListAction = new QWidgetAction(this); + PerspectiveComboBox = new QComboBox(this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(PerspectiveComboBox, SIGNAL(currentTextChanged(const QString&)), + DockManager, SLOT(openPerspective(const QString&))); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(PerspectiveListAction); + ui->toolBar->addAction(SavePerspectiveAction); +} + + +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(PerspectiveComboBox); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); + PerspectiveComboBox->setCurrentText(PerspectiveName); +} + + +//============================================================================ +void CMainWindow::closeEvent(QCloseEvent* event) +{ + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + DockManager->deleteLater(); + QMainWindow::closeEvent(event); +} + + + diff --git a/examples/autohidedragndrop/mainwindow.h b/examples/autohidedragndrop/mainwindow.h new file mode 100644 index 000000000..75869da84 --- /dev/null +++ b/examples/autohidedragndrop/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +protected: + virtual void closeEvent(QCloseEvent* event) override; + +private: + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + + Ui::CMainWindow *ui; + + ads::CDockManager* DockManager; + ads::CDockAreaWidget* StatusDockArea; + ads::CDockWidget* TimelineDockWidget; + + void createPerspectiveUi(); + +private slots: + void savePerspective(); +}; +#endif // MAINWINDOW_H diff --git a/examples/autohidedragndrop/mainwindow.ui b/examples/autohidedragndrop/mainwindow.ui new file mode 100644 index 000000000..f7d3b09e8 --- /dev/null +++ b/examples/autohidedragndrop/mainwindow.ui @@ -0,0 +1,47 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + diff --git a/examples/centralwidget/CMakeLists.txt b/examples/centralwidget/CMakeLists.txt new file mode 100644 index 000000000..ae42cdfb9 --- /dev/null +++ b/examples/centralwidget/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_centralwidget 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(CentralWidgetExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui +) +target_include_directories(CentralWidgetExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(CentralWidgetExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(CentralWidgetExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(CentralWidgetExample 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 Central Widget 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" +) diff --git a/examples/centralwidget/centralwidget.pro b/examples/centralwidget/centralwidget.pro new file mode 100644 index 000000000..738ae781f --- /dev/null +++ b/examples/centralwidget/centralwidget.pro @@ -0,0 +1,34 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = CentralWidgetExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +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 + diff --git a/examples/centralwidget/main.cpp b/examples/centralwidget/main.cpp new file mode 100644 index 000000000..fa4c4fdea --- /dev/null +++ b/examples/centralwidget/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/centralwidget/main.py b/examples/centralwidget/main.py new file mode 100644 index 000000000..48c29278f --- /dev/null +++ b/examples/centralwidget/main.py @@ -0,0 +1,106 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QTimer, QDir, QSignalBlocker +from PyQt5.QtGui import QCloseEvent, QIcon +from PyQt5.QtWidgets import (QApplication, QLabel, QCalendarWidget, QFrame, QTreeView, + QTableWidget, QFileSystemModel, QPlainTextEdit, QToolBar, + QWidgetAction, QComboBox, QAction, QSizePolicy, QInputDialog) + +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'mainwindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + +class MainWindow(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 + text_edit = QPlainTextEdit() + text_edit.setPlaceholderText("This is the central editor. Enter your text here.") + central_dock_widget = QtAds.CDockWidget("CentralWidget") + central_dock_widget.setWidget(text_edit) + central_dock_area = self.dock_manager.setCentralWidget(central_dock_widget) + central_dock_area.setAllowedAreas(QtAds.DockWidgetArea.OuterDockAreas) + + # 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) + 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() + 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.create_perspective_ui() + + def create_perspective_ui(self): + save_perspective_action = QAction("Create Perspective", self) + save_perspective_action.triggered.connect(self.save_perspective) + perspective_list_action = QWidgetAction(self) + self.perspective_combobox = QComboBox(self) + self.perspective_combobox.setSizeAdjustPolicy(QComboBox.AdjustToContents) + self.perspective_combobox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) + self.perspective_combobox.activated[str].connect(self.dock_manager.openPerspective) + perspective_list_action.setDefaultWidget(self.perspective_combobox) + self.toolBar.addSeparator() + self.toolBar.addAction(perspective_list_action) + self.toolBar.addAction(save_perspective_action) + + def save_perspective(self): + perspective_name, ok = QInputDialog.getText(self, "Save Perspective", "Enter Unique name:") + if not ok or not perspective_name: + return + + self.dock_manager.addPerspective(perspective_name) + blocker = QSignalBlocker(self.perspective_combobox) + self.perspective_combobox.clear() + self.perspective_combobox.addItems(self.dock_manager.perspectiveNames()) + self.perspective_combobox.setCurrentText(perspective_name) + + def closeEvent(self, event: QCloseEvent): + self.dock_manager.deleteLater() + super().closeEvent(event) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/centralwidget/mainwindow.cpp b/examples/centralwidget/mainwindow.cpp new file mode 100644 index 000000000..3ad0056df --- /dev/null +++ b/examples/centralwidget/mainwindow.cpp @@ -0,0 +1,133 @@ +#include "mainwindow.h" + +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" +#include "DockAreaTabBar.h" +#include "FloatingDockContainer.h" +#include "DockComponentsFactory.h" + +using namespace ads; + + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); + CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); + DockManager = new CDockManager(this); + + // Set central widget + QPlainTextEdit* w = new QPlainTextEdit(); + w->setPlaceholderText("This is the central editor. Enter your text here."); + CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); + CentralDockWidget->setWidget(w); + auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); + CentralDockArea->setAllowedAreas(DockWidgetArea::OuterDockAreas); + + // create other dock widgets + QTableWidget* table = new QTableWidget(); + table->setColumnCount(3); + table->setRowCount(10); + CDockWidget* TableDockWidget = new CDockWidget("Table 1"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + auto TableArea = DockManager->addDockWidget(DockWidgetArea::LeftDockWidgetArea, TableDockWidget); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + table = new QTableWidget(); + table->setColumnCount(5); + table->setRowCount(1020); + TableDockWidget = new CDockWidget("Table 2"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::BottomDockWidgetArea, TableDockWidget, TableArea); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + QTableWidget* propertiesTable = new QTableWidget(); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); + + createPerspectiveUi(); +} + +CMainWindow::~CMainWindow() +{ + delete ui; +} + + +void CMainWindow::createPerspectiveUi() +{ + SavePerspectiveAction = new QAction("Create Perspective", this); + connect(SavePerspectiveAction, SIGNAL(triggered()), SLOT(savePerspective())); + PerspectiveListAction = new QWidgetAction(this); + PerspectiveComboBox = new QComboBox(this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(PerspectiveComboBox, SIGNAL(currentTextChanged(const QString&)), + DockManager, SLOT(openPerspective(const QString&))); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(PerspectiveListAction); + ui->toolBar->addAction(SavePerspectiveAction); +} + + +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(PerspectiveComboBox); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); + PerspectiveComboBox->setCurrentText(PerspectiveName); +} + + +//============================================================================ +void CMainWindow::closeEvent(QCloseEvent* event) +{ + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + DockManager->deleteLater(); + QMainWindow::closeEvent(event); +} + + diff --git a/examples/centralwidget/mainwindow.h b/examples/centralwidget/mainwindow.h new file mode 100644 index 000000000..75869da84 --- /dev/null +++ b/examples/centralwidget/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +protected: + virtual void closeEvent(QCloseEvent* event) override; + +private: + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + + Ui::CMainWindow *ui; + + ads::CDockManager* DockManager; + ads::CDockAreaWidget* StatusDockArea; + ads::CDockWidget* TimelineDockWidget; + + void createPerspectiveUi(); + +private slots: + void savePerspective(); +}; +#endif // MAINWINDOW_H diff --git a/examples/centralwidget/mainwindow.ui b/examples/centralwidget/mainwindow.ui new file mode 100644 index 000000000..f7d3b09e8 --- /dev/null +++ b/examples/centralwidget/mainwindow.ui @@ -0,0 +1,47 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + diff --git a/examples/configflags/CMakeLists.txt b/examples/configflags/CMakeLists.txt new file mode 100644 index 000000000..c231ee7f3 --- /dev/null +++ b/examples/configflags/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_centralwidget 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(configFlagsExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui +) +target_include_directories(CentralWidgetExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(CentralWidgetExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(CentralWidgetExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(CentralWidgetExample 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 Central Widget 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" +) diff --git a/examples/configflags/configflags.pro b/examples/configflags/configflags.pro new file mode 100644 index 000000000..83271745d --- /dev/null +++ b/examples/configflags/configflags.pro @@ -0,0 +1,34 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = ConfigFlagsExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +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 + diff --git a/examples/configflags/main.cpp b/examples/configflags/main.cpp new file mode 100644 index 000000000..fa4c4fdea --- /dev/null +++ b/examples/configflags/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/configflags/mainwindow.cpp b/examples/configflags/mainwindow.cpp new file mode 100644 index 000000000..2ce359b10 --- /dev/null +++ b/examples/configflags/mainwindow.cpp @@ -0,0 +1,65 @@ +#include "mainwindow.h" + +#include "ui_mainwindow.h" + +#include +#include + +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" + + +using namespace ads; + + +CMainWindow::CMainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + + // Add the toolbar + auto toolbar_ = addToolBar("Top Toolbar"); + + // Create the dock manager + ads::CDockManager::setConfigFlags(ads::CDockManager::DefaultOpaqueConfig); + ads::CDockManager::setConfigFlag(ads::CDockManager::DockAreaHasCloseButton, + false); + ads::CDockManager::setConfigFlag(ads::CDockManager::DockAreaHasUndockButton, + false); + ads::CDockManager::setConfigFlag( + ads::CDockManager::DockAreaHasTabsMenuButton, false); + auto DockManager = new ads::CDockManager(this); + + // Create a dockable widget + QLabel *l1 = new QLabel(); + l1->setWordWrap(true); + l1->setAlignment(Qt::AlignTop | Qt::AlignLeft); + l1->setText("Docking widget 1"); + ads::CDockWidget *dockWidget1 = new ads::CDockWidget("Dock 1"); + dockWidget1->setWidget(l1); + DockManager->addDockWidget(ads::LeftDockWidgetArea, dockWidget1); + + QLabel *l2 = new QLabel(); + l2->setWordWrap(true); + l2->setAlignment(Qt::AlignTop | Qt::AlignLeft); + l2->setText("Docking widget 2"); + ads::CDockWidget *dockWidget2 = new ads::CDockWidget("Dock 2"); + dockWidget2->setWidget(l2); + DockManager->addDockWidget(ads::RightDockWidgetArea, dockWidget2); + + // Add menu actions + ui->menuView->addAction(dockWidget1->toggleViewAction()); + ui->menuView->addAction(dockWidget2->toggleViewAction()); + toolbar_->addAction(dockWidget1->toggleViewAction()); + toolbar_->addAction(dockWidget2->toggleViewAction()); +} + + +CMainWindow::~CMainWindow() +{ + delete ui; +} + + + diff --git a/examples/configflags/mainwindow.h b/examples/configflags/mainwindow.h new file mode 100644 index 000000000..a2b45da77 --- /dev/null +++ b/examples/configflags/mainwindow.h @@ -0,0 +1,27 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +private: + Ui::CMainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/examples/configflags/mainwindow.ui b/examples/configflags/mainwindow.ui new file mode 100644 index 000000000..f7d3b09e8 --- /dev/null +++ b/examples/configflags/mainwindow.ui @@ -0,0 +1,47 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + diff --git a/examples/deleteonclose/CMakeLists.txt b/examples/deleteonclose/CMakeLists.txt new file mode 100644 index 000000000..98557a6fd --- /dev/null +++ b/examples/deleteonclose/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_deleteonclose 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(DeleteOnCloseTest WIN32 + main.cpp +) +target_include_directories(DeleteOnCloseTest PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(DeleteOnCloseTest PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(DeleteOnCloseTest PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(DeleteOnCloseTest PROPERTIES + AUTOMOC ON + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + VERSION ${VERSION_SHORT} + EXPORT_NAME "Qt Advanced Docking System Delete on Close 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" +) diff --git a/examples/deleteonclose/deleteonclose.pro b/examples/deleteonclose/deleteonclose.pro new file mode 100644 index 000000000..95d1db075 --- /dev/null +++ b/examples/deleteonclose/deleteonclose.pro @@ -0,0 +1,23 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = DeleteOnCloseTest +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 + + +LIBS += -L$${ADS_OUT_ROOT}/lib +include(../../ads.pri) +INCLUDEPATH += ../../src +DEPENDPATH += ../../src + diff --git a/examples/deleteonclose/main.cpp b/examples/deleteonclose/main.cpp new file mode 100644 index 000000000..25f8ed14c --- /dev/null +++ b/examples/deleteonclose/main.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include "DockManager.h" + + +class MainWindow : public QMainWindow +{ +private: + ads::CDockManager* m_DockManager = nullptr; + +protected: + virtual void closeEvent(QCloseEvent *event) override + { + QMainWindow::closeEvent(event); + if (m_DockManager) + { + m_DockManager->deleteLater(); + } + } + +public: + void setDockManager(ads::CDockManager* DockManager) {m_DockManager = DockManager;} +}; + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + + ads::CDockManager::setConfigFlag(ads::CDockManager::FocusHighlighting, true); + ads::CDockManager::setConfigFlag(ads::CDockManager::AllTabsHaveCloseButton, true); + auto dockManager = new ads::CDockManager(&w); + w.setDockManager(dockManager); + QObject::connect(dockManager, &ads::CDockManager::focusedDockWidgetChanged, [] (ads::CDockWidget* old, ads::CDockWidget* now) { + static int Count = 0; + qDebug() << Count++ << " CDockManager::focusedDockWidgetChanged old: " << (old ? old->objectName() : "-") << " now: " << now->objectName() << " visible: " << now->isVisible(); + now->widget()->setFocus(); + }); + + 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 %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, [&]() { + auto dw = new ads::CDockWidget(QStringLiteral("test %1").arg(i++), &w); + auto editor = new QTextEdit(QStringLiteral("lorem ipsum..."), dw); + dw->setWidget(editor); + auto area = dockManager->addDockWidgetTab(ads::CenterDockWidgetArea, dw); + qDebug() << "dock widget created!" << dw << area; + }); + + w.show(); + + return a.exec(); +} diff --git a/examples/deleteonclose/main.py b/examples/deleteonclose/main.py new file mode 100644 index 000000000..d1504bffc --- /dev/null +++ b/examples/deleteonclose/main.py @@ -0,0 +1,73 @@ +import sys + +import PyQtAds as QtAds +from PyQt5.QtGui import QCloseEvent +from PyQt5.QtCore import (qDebug, pyqtSlot, QObject, pyqtSignal) +from PyQt5.QtWidgets import (QMainWindow, QAction, QTextEdit, QApplication, + QMenuBar) + + +class MainWindow(QMainWindow): + dock_manager = None + + def closeEvent(self, event: QCloseEvent): + super().closeEvent(event) + if self.dock_manager is not None: + self.dock_manager.deleteLater() + + def setDockManager(self, dock_manager: QtAds.CDockManager): + self.dock_manager = dock_manager + + +if __name__ == '__main__': + app = QApplication(sys.argv) + w = MainWindow() + + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.FocusHighlighting, True) + QtAds.CDockManager.setConfigFlag(QtAds.CDockManager.AllTabsHaveCloseButton, True) + dock_manager = QtAds.CDockManager(w) + w.setDockManager(dock_manager) + + count = 0 + + def on_focused_dock_widget_changed(old: QtAds.CDockWidget, now: QtAds.CDockWidget): + global count + qDebug( "{:d} CDockManager::focusedDockWidgetChanged old: {} now: {} visible: {}".format( + count, + old.objectName() if old else "-", + now.objectName(), + now.isVisible())) + count += 1 + now.widget().setFocus() + + dock_manager.focusedDockWidgetChanged.connect(on_focused_dock_widget_changed) + + action = QAction("New Delete On Close", w) + w.menuBar().addAction(action) + + i = 0 + def on_action_triggered(): + global i + dw = QtAds.CDockWidget("test doc {:d}".format(i)) + i += 1 + editor = QTextEdit("lorem ipsum...", dw) + dw.setWidget(editor) + dw.setFeature(QtAds.CDockWidget.DockWidgetDeleteOnClose, True) + area = dock_manager.addDockWidgetTab(QtAds.CenterDockWidgetArea, dw) + qDebug("doc dock widget created! {} {}".format(dw, area)) + action.triggered.connect(on_action_triggered) + + action = QAction("New", w) + w.menuBar().addAction(action) + def on_action2_triggered(): + global i + dw = QtAds.CDockWidget("test {:d}".format(i)) + i += 1 + editor = QTextEdit("lorem ipsum...", dw) + dw.setWidget(editor) + area = dock_manager.addDockWidgetTab(QtAds.CenterDockWidgetArea, dw) + qDebug("dock widget created! {} {}".format(dw, area)) + action.triggered.connect(on_action2_triggered) + + w.show() + app.exec_() diff --git a/examples/dockindock/CMakeLists.txt b/examples/dockindock/CMakeLists.txt new file mode 100644 index 000000000..39ba98cad --- /dev/null +++ b/examples/dockindock/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_dockindock 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(DockInDockExample WIN32 + dockindock.cpp + dockindockmanager.cpp + perspectiveactions.cpp + perspectives.cpp + main.cpp + mainframe.cpp +) +target_include_directories(DockInDockExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(DockInDockExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(DockInDockExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(DockInDockExample 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 Simple 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" +) diff --git a/examples/dockindock/dockindock.cpp b/examples/dockindock/dockindock.cpp new file mode 100644 index 000000000..bacf42cbd --- /dev/null +++ b/examples/dockindock/dockindock.cpp @@ -0,0 +1,300 @@ +#include "dockindock.h" +#include "perspectives.h" +#include "dockindockmanager.h" +#include "perspectiveactions.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace QtAdsUtl; + +DockInDockWidget::DockInDockWidget( QWidget* parent, bool canCreateNewGroups, PerspectivesManager* perspectivesManager ) : + DockInDockWidget( parent, (DockInDockWidget*)NULL, perspectivesManager ) +{ + m_canCreateNewGroups = canCreateNewGroups; + m_topLevelDockWidget = this; +} + +DockInDockWidget::DockInDockWidget( QWidget* parent, DockInDockWidget* topLevelDockWidget, PerspectivesManager* perspectivesManager ) : + baseClass( parent ), + m_topLevelDockWidget( topLevelDockWidget ), + m_canCreateNewGroups( (topLevelDockWidget) ? topLevelDockWidget->m_canCreateNewGroups : false ), + m_perspectivesManager( perspectivesManager ) +{ + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins( 0,0,0,0 ); + layout->addWidget( m_mgr = new DockInDockManager(*this) ); +} + +DockInDockWidget::~DockInDockWidget() +{ + +} + +ads::CDockAreaWidget* DockInDockWidget::addTabWidget( QWidget* widget, const QString& name, ads::CDockAreaWidget* after ) +{ + return addTabWidget( widget, name, QIcon(), after ); +} + +ads::CDockAreaWidget* DockInDockWidget::addTabWidget( QWidget* widget, const QString& name, QIcon icon, ads::CDockAreaWidget* after ) +{ + for ( auto existing : getTopLevelDockWidget()->getManager()->allDockWidgets(true,true) ) + { + if ( existing.second->objectName() == name ) + { + QMessageBox::critical( this, "Error", "Name '" + name + "' already in use" ); + return NULL; + } + } + + ads::CDockWidget* DockWidget = new ads::CDockWidget(name); + DockWidget->setWidget(widget); + DockWidget->setIcon( icon ); + + // Add the dock widget to the top dock widget area + return m_mgr->addDockWidget(ads::CenterDockWidgetArea, DockWidget, after); +} + +bool DockInDockWidget::isTopLevel() +{ + return objectName().isEmpty(); +} + +QString DockInDockWidget::getGroupNameError( const QString& groupName ) +{ + if ( groupName.isEmpty() ) + { + return "Group must have a non-empty name"; + } + + std::vector dockManagers = m_mgr->allManagers( true, true ); + for ( auto mgr : dockManagers ) + { + if ( mgr->getGroupName() == groupName ) + return "Group name '" + groupName + "' already used"; + } + + return ""; +} + +DockInDockWidget* DockInDockWidget::createGroup( const QString& groupName, ads::CDockAreaWidget*& insertPos ) +{ + return createGroup( groupName, QIcon(), insertPos ); +} + +DockInDockWidget* DockInDockWidget::createGroup( const QString& groupName, QIcon icon, ads::CDockAreaWidget*& insertPos ) +{ + QString error = getGroupNameError( groupName ); + if ( !error.isEmpty() ) + { + QMessageBox::critical( NULL, "Error", error ); + return NULL; + } + + DockInDockWidget* child = new DockInDockWidget( this, m_topLevelDockWidget, m_perspectivesManager ); + child->setObjectName( groupName ); + + ads::CDockWidget* DockWidget = new ads::CDockWidget(groupName); + DockWidget->setWidget(child); + DockWidget->setIcon(icon); + + insertPos = m_mgr->addDockWidget(ads::CenterDockWidgetArea, DockWidget, insertPos); + + return child; +} + +void DockInDockWidget::destroyGroup( DockInDockWidget* widgetToRemove ) +{ + auto topLevelWidget = widgetToRemove->getTopLevelDockWidget(); + + if ( topLevelWidget && topLevelWidget != widgetToRemove ) + { + // reaffect all child docks to top-level + for ( auto dockwidget : widgetToRemove->getManager()->getWidgetsInGUIOrder() ) // don't use allDockWidgets to preserve sub-groups + { + MoveDockWidgetAction::move( dockwidget, topLevelWidget->getManager() ); + } + assert( widgetToRemove->getManager()->allDockWidgets( true, true ).empty() ); + + // find widget's parent: + for ( auto dockwidget : topLevelWidget->getManager()->allDockWidgets( true, true ) ) + { + if ( dockwidget.second->widget() == widgetToRemove ) + { + dockwidget.first->removeDockWidget( dockwidget.second ); + delete dockwidget.second; + //delete widgetToRemove; automatically deleted when dockWidget is deleted + widgetToRemove = NULL; + break; + } + } + + assert( widgetToRemove == NULL ); + } + else + { + assert( false ); + } +} + +void DockInDockWidget::attachViewMenu( QMenu* menu ) +{ + connect( menu, SIGNAL(aboutToShow()), this, SLOT(autoFillAttachedViewMenu()) ); +} + +void DockInDockWidget::autoFillAttachedViewMenu() +{ + QMenu* menu = dynamic_cast( QObject::sender() ); + + if ( menu ) + { + menu->clear(); + setupViewMenu( menu ); + } + else + { + assert( false ); + } +} + +void DockInDockWidget::setupViewMenu( QMenu* menu ) +{ + std::vector dockManagers = m_mgr->allManagers( true, true ); + + bool hasPerspectivesMenu = false; + if ( getTopLevelDockWidget() == this ) + hasPerspectivesMenu = (m_perspectivesManager != NULL); + else + assert( false ); + + QMenu* organize = menu; + if ( hasPerspectivesMenu ) + organize = menu->addMenu( "Organize" ); + + setupMenu( organize, dockManagers ); + + if ( hasPerspectivesMenu ) + { + QMenu* perspectives = menu->addMenu( "Perspectives" ); + fillPerspectivesMenu( perspectives ); + } +} + +void DockInDockWidget::setupMenu( QMenu* menu, const std::vector& moveTo ) +{ + m_mgr->fillViewMenu( menu, moveTo ); + menu->addSeparator(); + auto moveMenu = menu->addMenu( "Move" ); + m_mgr->fillMoveMenu( moveMenu, moveTo ); +} + +void DockInDockWidget::fillPerspectivesMenu( QMenu* menu ) +{ + menu->addAction( "Create perspective...", this, SLOT(createPerspective()) ); + + QStringList perspectiveNames; + if ( m_perspectivesManager ) + perspectiveNames = m_perspectivesManager->perspectiveNames(); + + if ( !perspectiveNames.isEmpty() ) + { + QMenu* load = menu->addMenu( "Load perspective" ); + for (const auto& name : perspectiveNames) + { + load->addAction(new LoadPerspectiveAction( load, name, *this)); + } + QMenu* remove = menu->addMenu( "Remove perspective" ); + for (const auto& name : perspectiveNames) + { + remove->addAction( new RemovePerspectiveAction( remove, name, *this )); + } + } +} + +void DockInDockWidget::setNewPerspectiveDefaultName( const QString& defaultName ) +{ + m_newPerspectiveDefaultName = defaultName; +} + +void DockInDockWidget::createPerspective() +{ + if ( !m_perspectivesManager ) + return; + + QString name = m_newPerspectiveDefaultName; + if ( !m_newPerspectiveDefaultName.isEmpty() ) + { + int index = 2; + while ( m_perspectivesManager->perspectiveNames().contains( name ) ) + { + name = m_newPerspectiveDefaultName + " (" + QString::number(index) + ")"; + ++index; + } + } + + while ( true ) + { + bool ok = false; + name = QInputDialog::getText( NULL, "Create perspective", "Enter perspective name", QLineEdit::Normal, name, &ok ); + if ( ok ) + { + if ( name.isEmpty() ) + { + QMessageBox::critical( NULL, "Error", "Perspective name cannot be empty" ); + continue; + } + else if ( m_perspectivesManager->perspectiveNames().contains( name ) ) + { + if ( QMessageBox::critical( NULL, "Error", "Perspective '" + name + "' already exists, overwrite it?", QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::No ) + continue; + } + + m_perspectivesManager->addPerspective( name, *this ); + break; + } + else + { + break; + } + } +} + +static void dumpStatus( std::ostream& str, ads::CDockWidget* widget, const std::string& tab, std::string suffix ) +{ + DockInDockManager* asMgr = DockInDockManager::dockInAManager( widget ); + if ( asMgr ) + { + asMgr->parent().dumpStatus( str, tab ); + } + else + { + str << tab << widget->objectName().toStdString() << suffix << std::endl; + } +} + +void DockInDockWidget::dumpStatus( std::ostream& str, std::string tab ) +{ + str << tab << "Group: " << getManager()->getGroupName().toStdString() << std::endl; + tab += " "; + std::set visibleWidgets; + for ( auto widget : getManager()->getWidgetsInGUIOrder() ) + { + visibleWidgets.insert( widget ); + ::dumpStatus( str, widget, tab, "" ); + } + + for ( auto closed : getManager()->dockWidgetsMap() ) + { + if ( visibleWidgets.find( closed ) == visibleWidgets.end() ) + { + ::dumpStatus( str, closed, tab, " (closed)" ); + } + } +} diff --git a/examples/dockindock/dockindock.h b/examples/dockindock/dockindock.h new file mode 100644 index 000000000..aaa9ffde2 --- /dev/null +++ b/examples/dockindock/dockindock.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include +#include + +class QMenu; + +namespace ads +{ + class CDockAreaWidget; +} + +namespace QtAdsUtl +{ + +class DockInDockManager; +class PerspectivesManager; +// tab of tab example for https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/306 +class DockInDockWidget : public QWidget +{ + typedef QWidget baseClass; + + Q_OBJECT + +public: + DockInDockWidget( QWidget* parent, bool canCreateNewGroups, PerspectivesManager* perspectivesManager ); + ~DockInDockWidget() override; + + ads::CDockAreaWidget* addTabWidget( QWidget* widget, const QString& name, ads::CDockAreaWidget* after ); + DockInDockWidget* createGroup( const QString& groupName, ads::CDockAreaWidget*& insertPos ); + + ads::CDockAreaWidget* addTabWidget( QWidget* widget, const QString& name, QIcon icon, ads::CDockAreaWidget* after ); + DockInDockWidget* createGroup( const QString& groupName, QIcon icon, ads::CDockAreaWidget*& insertPos ); + + QString getGroupNameError( const QString& groupName ); + void destroyGroup( DockInDockWidget* widget ); + + /** Manually fill a given view menu */ + void setupViewMenu( QMenu* menu ); + + /** Attach a view menu that will be automatically fill */ + void attachViewMenu( QMenu* menu ); + + bool isTopLevel(); + void setupMenu( QMenu* menu, const std::vector& moveTo ); + + inline DockInDockManager* getManager() { return m_mgr; } + inline DockInDockWidget* getTopLevelDockWidget() { return m_topLevelDockWidget; } + + inline bool canCreateNewGroups() const { return m_canCreateNewGroups; } + + void dumpStatus( std::ostream& str, std::string tab = "" ); + + inline PerspectivesManager* getPerspectivesManager() { return m_perspectivesManager; } + + void setNewPerspectiveDefaultName( const QString& defaultName ); + +private slots: + void autoFillAttachedViewMenu(); + void createPerspective(); + +private: + DockInDockManager* m_mgr; + DockInDockWidget* m_topLevelDockWidget; + + bool m_canCreateNewGroups; + + DockInDockWidget( QWidget* parent, DockInDockWidget* topLevelDockWidget, PerspectivesManager* perspectivesManager ); + + PerspectivesManager* m_perspectivesManager; + QString m_newPerspectiveDefaultName; + + void fillPerspectivesMenu( QMenu* menu ); +}; + +} + diff --git a/examples/dockindock/dockindock.pro b/examples/dockindock/dockindock.pro new file mode 100644 index 000000000..45242d161 --- /dev/null +++ b/examples/dockindock/dockindock.pro @@ -0,0 +1,35 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = DockInDock +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +DEFINES += QT_DEPRECATED_WARNINGS + +SOURCES += \ + dockindock.cpp \ + dockindockmanager.cpp \ + perspectiveactions.cpp \ + perspectives.cpp \ + main.cpp \ + mainframe.cpp + +HEADERS += \ + dockindock.h \ + dockindockmanager.h \ + perspectiveactions.h \ + perspectives.h \ + mainframe.h + +LIBS += -L$${ADS_OUT_ROOT}/lib +include(../../ads.pri) +INCLUDEPATH += ../../src +DEPENDPATH += ../../src + diff --git a/examples/dockindock/dockindock.py b/examples/dockindock/dockindock.py new file mode 100644 index 000000000..5d64fe170 --- /dev/null +++ b/examples/dockindock/dockindock.py @@ -0,0 +1,203 @@ +import sys + +from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QMessageBox, + QInputDialog, QMenu, QLineEdit) +from PyQt5.QtGui import QIcon +import PyQtAds as 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)") diff --git a/examples/dockindock/dockindockmanager.cpp b/examples/dockindock/dockindockmanager.cpp new file mode 100644 index 000000000..160318c6e --- /dev/null +++ b/examples/dockindock/dockindockmanager.cpp @@ -0,0 +1,334 @@ +#include "dockindockmanager.h" +#include "dockindock.h" + +#include "DockAreaWidget.h" + +#include +#include +#include +#include + +#include + +using namespace QtAdsUtl; + +///////////////////////////////////// +// DockInDockManager +///////////////////////////////////// +DockInDockManager::DockInDockManager( DockInDockWidget& parent ) : + baseClass( &parent ), + m_parent( parent ) +{ + +} + +DockInDockManager::~DockInDockManager() +{ + +} + +void DockInDockManager::fillViewMenu( QMenu* menu, const std::vector& moveTo ) +{ + auto widgetsMap = dockWidgetsMap(); + for ( auto iter = widgetsMap.begin(); iter != widgetsMap.end(); ++iter ) + { + auto widget = iter.value()->widget(); + auto action = iter.value()->toggleViewAction(); + + DockInDockWidget* asMgr = dynamic_cast( widget ); + if ( asMgr ) + { + auto subMenu = menu->addMenu( iter.key() ); + + subMenu->addAction( action ); + subMenu->addSeparator(); + + asMgr->setupMenu( subMenu, moveTo ); + } + else + { + menu->addAction(action); + } + } + + if ( 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( new QtAdsUtl::CreateChildDockAction( m_parent, menu ) ); + + if ( parent().getTopLevelDockWidget()->getManager() != this ) + menu->addAction( new QtAdsUtl::DestroyGroupAction( &m_parent, menu ) ); + } +} + +void DockInDockManager::fillMoveMenu( QMenu* menu, const std::vector& moveTo ) +{ + auto widgetsMap = dockWidgetsMap(); + for ( auto iter = widgetsMap.begin(); iter != widgetsMap.end(); ++iter ) + { + auto subMenu = menu->addMenu( iter.key() ); + + for ( auto mgr : moveTo ) + { + // iterate over all possible target managers + if ( mgr == this ) + { + // if dock is already in mgr, no reason to move it there + } + else if ( mgr == dockInAManager( iter.value() ) ) + { + // if target is the group itself, can't move it there, would make no sense + } + else + { + subMenu->addAction( new MoveDockWidgetAction( iter.value(), mgr, subMenu ) ); + } + } + } +} + +void DockInDockManager::addPerspectiveRec( const QString& name ) +{ + std::vector managers = allManagers( true, true ); + + for ( auto child : managers ) + child->addPerspective( name ); +} + +void DockInDockManager::openPerspectiveRec( const QString& name ) +{ + std::vector managers = allManagers( true, true ); + + for ( auto child : managers ) + child->openPerspective( name ); +} + +QString DockInDockManager::getGroupName() +{ + return parent().objectName(); +} + +#define CHILD_PREFIX QString("Child-") +QString DockInDockManager::getPersistGroupName() +{ + QString group = "Top"; + if ( !getGroupName().isEmpty() ) + group = CHILD_PREFIX + getGroupName(); + return group; +} + +QString DockInDockManager::getGroupNameFromPersistGroupName( QString persistGroupName ) +{ + if ( persistGroupName.startsWith( CHILD_PREFIX ) ) + { + persistGroupName = persistGroupName.mid( CHILD_PREFIX.size() ); + } + else + { + assert( false ); + } + return persistGroupName; +} + +void DockInDockManager::loadPerspectivesRec(QSettings& Settings) +{ + std::vector children = allManagers( true, true ); + + for ( auto mgr : children ) + { + Settings.beginGroup(mgr->getPersistGroupName()); + mgr->loadPerspectives( Settings ); + Settings.endGroup(); + } +} + +void DockInDockManager::savePerspectivesRec(QSettings& Settings) const +{ + std::vector children = allManagers( true, true ); + + for ( auto mgr : children ) + { + Settings.beginGroup(mgr->getPersistGroupName()); + mgr->savePerspectives( Settings ); + Settings.endGroup(); + } +} + +void DockInDockManager::removePerspectivesRec() +{ + std::vector managers = allManagers( true, true ); + + for ( auto child : managers ) + child->removePerspectives( child->perspectiveNames() ); +} + +DockInDockManager* DockInDockManager::dockInAManager( ads::CDockWidget* widget ) +{ + DockInDockWidget* dockWidget = widget ? dynamic_cast( widget->widget() ) : NULL; + return ( dockWidget ) ? dockWidget->getManager() : NULL; +} + +void DockInDockManager::childManagers( std::vector& managers, bool rec ) const +{ + auto widgets = getWidgetsInGUIOrder(); + for ( auto widget : widgets ) + { + DockInDockManager* asMgr = dockInAManager( widget ); + if ( asMgr ) + { + managers.push_back( asMgr ); + if ( rec ) + asMgr->childManagers( managers, rec ); + } + } +} + +std::vector DockInDockManager::allManagers( bool includeThis, bool rec ) const +{ + std::vector managers; + if ( includeThis ) + managers.push_back( const_cast(this) ); + childManagers( managers, rec ); + return managers; +} + +std::vector> DockInDockManager::allDockWidgets( bool includeThis, bool rec ) const +{ + std::vector> widgets; + for ( auto mgr : allManagers( includeThis, rec ) ) + { + for ( auto widget : mgr->getWidgetsInGUIOrder() ) + widgets.push_back( std::make_pair(mgr, widget) ); + } + return widgets; +} + +QMap DockInDockManager::getGroupContents() +{ + QMap result; + std::vector managers = allManagers( true, true ); + for ( auto mgr : managers ) + { + result[mgr->getPersistGroupName()] = mgr->dockWidgetsMap().keys(); + } + return result; +} + +ads::CDockAreaWidget* DockInDockManager::getInsertDefaultPos() +{ + ads::CDockAreaWidget* defaultPos = NULL; + if ( dockAreaCount() != 0 ) + defaultPos = dockArea(dockAreaCount()-1); + return defaultPos; +} + +std::vector DockInDockManager::getWidgetsInGUIOrder() const +{ + std::vector result; + result.reserve( dockWidgetsMap().size() ); + for ( int i = 0; i != dockAreaCount(); ++i ) + { + for ( auto widget : dockArea(i)->dockWidgets() ) + result.push_back( widget ); + } + return result; +} + +///////////////////////////////////// +// CreateChildDockAction +///////////////////////////////////// +CreateChildDockAction::CreateChildDockAction( DockInDockWidget& dockInDock, QMenu* menu ) : + QAction("New group...", menu), + m_dockInDock( dockInDock ) +{ + connect( this, SIGNAL(triggered()), this, SLOT(createGroup()) ); +} + +void CreateChildDockAction::createGroup() +{ + QString name = ""; + while ( true ) + { + bool ok = false; + name = QInputDialog::getText( NULL, this->text(), "Enter group name", QLineEdit::Normal, name, &ok ); + if ( ok ) + { + QString error = ""; + if ( m_dockInDock.getTopLevelDockWidget() ) + error = m_dockInDock.getTopLevelDockWidget()->getGroupNameError( name ); + else + assert( false ); + + if ( error.isEmpty() ) + { + ads::CDockAreaWidget* insertPos = NULL; + m_dockInDock.createGroup( name, insertPos ); + break; + } + else + { + QMessageBox::critical( NULL, "Error", error ); + continue; + } + } + else + { + break; + } + } +} + +///////////////////////////////////// +// DestroyGroupAction +///////////////////////////////////// +DestroyGroupAction::DestroyGroupAction( DockInDockWidget* widget, QMenu* menu ) : + QAction("Destroy " + widget->getManager()->getGroupName(), menu), + m_widget( widget ) +{ + connect( this, SIGNAL(triggered()), this, SLOT(destroyGroup()) ); +} + +void DestroyGroupAction::destroyGroup() +{ + m_widget->getTopLevelDockWidget()->destroyGroup( m_widget ); +} + + +///////////////////////////////////// +// MoveDockWidgetAction +///////////////////////////////////// +MoveDockWidgetAction::MoveDockWidgetAction( ads::CDockWidget* widget, DockInDockManager* moveTo, QMenu* menu ) : + QAction(menu), + m_widget( widget ), + m_moveTo( moveTo ) +{ + if ( moveTo->parent().isTopLevel() ) + { + setText( "To top" ); + } + else + { + setText( "To " + moveTo->parent().objectName() ); + } + connect( this, SIGNAL(triggered()), this, SLOT(move()) ); +} + +void MoveDockWidgetAction::move() +{ + move( m_widget, m_moveTo ); +} + +void MoveDockWidgetAction::move( ads::CDockWidget* widget, DockInDockManager* moveTo ) +{ + if ( widget && moveTo ) + { + widget->dockManager()->removeDockWidget( widget ); + moveTo->addDockWidget(ads::CenterDockWidgetArea, widget, moveTo->getInsertDefaultPos()); + } + else + { + assert( false ); + } +} diff --git a/examples/dockindock/dockindockmanager.h b/examples/dockindock/dockindockmanager.h new file mode 100644 index 000000000..886cb827a --- /dev/null +++ b/examples/dockindock/dockindockmanager.h @@ -0,0 +1,96 @@ +#pragma once + +#include "DockManager.h" + +#include +#include + +namespace QtAdsUtl +{ + +class DockInDockWidget; +class DockInDockManager : public ads::CDockManager +{ + Q_OBJECT + + typedef ads::CDockManager baseClass; + +public: + DockInDockManager( DockInDockWidget& parent ); + ~DockInDockManager() override; + + void fillViewMenu( QMenu* menu, const std::vector& moveTo ); + void fillMoveMenu( QMenu* menu, const std::vector& moveTo ); + + void addPerspectiveRec( const QString& name ); + void openPerspectiveRec( const QString& name ); + void removePerspectivesRec(); + void loadPerspectivesRec(QSettings& Settings); + void savePerspectivesRec(QSettings& Settings) const; + + static DockInDockManager* dockInAManager( ads::CDockWidget* widget ); + + inline DockInDockWidget& parent() { return m_parent; } + + void childManagers( std::vector& managers, bool rec ) const; + std::vector allManagers( bool includeThis, bool rec ) const; + std::vector> allDockWidgets( bool includeThis, bool rec ) const; + + QString getGroupName(); + QString getPersistGroupName(); + static QString getGroupNameFromPersistGroupName( QString persistGroupName ); + + QMap getGroupContents(); + + ads::CDockAreaWidget* getInsertDefaultPos(); + + std::vector getWidgetsInGUIOrder() const; + +private: + DockInDockWidget& m_parent; +}; + +class CreateChildDockAction : public QAction +{ + Q_OBJECT +public: + CreateChildDockAction( DockInDockWidget& dockInDock, QMenu* menu ); + +public slots: + void createGroup(); + +private: + DockInDockWidget& m_dockInDock; +}; + +class DestroyGroupAction : public QAction +{ + Q_OBJECT +public: + DestroyGroupAction( DockInDockWidget* widget, QMenu* menu ); + +public slots: + void destroyGroup(); + +private: + DockInDockWidget* m_widget; +}; + +class MoveDockWidgetAction : public QAction +{ + Q_OBJECT +public: + MoveDockWidgetAction( ads::CDockWidget* widget, DockInDockManager* moveTo, QMenu* menu ); + + static void move( ads::CDockWidget* widget, DockInDockManager* moveTo ); + +public slots: + void move(); + +private: + ads::CDockWidget* m_widget; + DockInDockManager* m_moveTo; +}; + +} + diff --git a/examples/dockindock/dockindockmanager.py b/examples/dockindock/dockindockmanager.py new file mode 100644 index 000000000..224d7195a --- /dev/null +++ b/examples/dockindock/dockindockmanager.py @@ -0,0 +1,214 @@ +from PyQt5.QtWidgets import QAction, QMenu, QInputDialog, QLineEdit +from PyQt5.QtCore import QSettings + +import PyQtAds as 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 diff --git a/examples/dockindock/main.cpp b/examples/dockindock/main.cpp new file mode 100644 index 000000000..9ae353929 --- /dev/null +++ b/examples/dockindock/main.cpp @@ -0,0 +1,11 @@ +#include +#include "mainframe.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/dockindock/main.py b/examples/dockindock/main.py new file mode 100644 index 000000000..be32b082d --- /dev/null +++ b/examples/dockindock/main.py @@ -0,0 +1,72 @@ +import sys +import os +import atexit + +from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel +from PyQt5.QtCore import Qt +import PyQtAds as 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_() diff --git a/examples/dockindock/mainframe.cpp b/examples/dockindock/mainframe.cpp new file mode 100644 index 000000000..3e8f26fe3 --- /dev/null +++ b/examples/dockindock/mainframe.cpp @@ -0,0 +1,76 @@ +#include "mainframe.h" + +#include "dockindock.h" +#include "perspectives.h" + +#include +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + m_perspectivesManager( new QtAdsUtl::PerspectivesManager( "persist" ) ) +{ + resize( 400, 400 ); + + setCentralWidget( m_dockManager = new QtAdsUtl::DockInDockWidget(this,true,m_perspectivesManager.get()) ); + + m_dockManager->attachViewMenu( menuBar()->addMenu( "View" ) ); + + ads::CDockAreaWidget* previousDockWidget = NULL; + for ( int i = 0; i != 3; ++i ) + { + // 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. "); + + previousDockWidget = m_dockManager->addTabWidget( l, "Top label " + QString::number(i), previousDockWidget ); + } + + auto lastTopLevelDock = previousDockWidget; + + for ( int j = 0; j != 2; ++j ) + { + QtAdsUtl::DockInDockWidget* groupManager = m_dockManager->createGroup( "Group " + QString::number(j), lastTopLevelDock ); + + previousDockWidget = NULL; + for ( int i = 0; i != 3; ++i ) + { + // 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. "); + + previousDockWidget = groupManager->addTabWidget( l, "ZInner " + QString::number(j) + "/" + QString::number(i), previousDockWidget ); + } + + // create sub-group + auto subGroup = groupManager->createGroup( "SubGroup " + QString::number(j), previousDockWidget ); + previousDockWidget = NULL; + for ( int i = 0; i != 3; ++i ) + { + // 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. "); + + previousDockWidget = subGroup->addTabWidget( l, "SubInner " + QString::number(j) + "/" + QString::number(i), previousDockWidget ); + } + } + + m_perspectivesManager->loadPerspectives(); +} + +MainWindow::~MainWindow() +{ + m_perspectivesManager->savePerspectives(); +} + diff --git a/examples/dockindock/mainframe.h b/examples/dockindock/mainframe.h new file mode 100644 index 000000000..0305b36be --- /dev/null +++ b/examples/dockindock/mainframe.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include +namespace QtAdsUtl +{ + class DockInDockWidget; + class PerspectivesManager; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + + QtAdsUtl::DockInDockWidget* m_dockManager; + std::unique_ptr m_perspectivesManager; +}; + + + + + + diff --git a/examples/dockindock/perspectiveactions.cpp b/examples/dockindock/perspectiveactions.cpp new file mode 100644 index 000000000..1e088c82e --- /dev/null +++ b/examples/dockindock/perspectiveactions.cpp @@ -0,0 +1,39 @@ +#include "perspectiveactions.h" +#include "dockindock.h" +#include "perspectives.h" + +#include + +using namespace QtAdsUtl; + +////////////////////////////// +// LoadPerspectiveAction +////////////////////////////// +LoadPerspectiveAction::LoadPerspectiveAction( QMenu* parent, const QString& name, QtAdsUtl::DockInDockWidget& dockManager ) : + QAction( name, parent ), + name( name ), + dockManager( dockManager ) +{ + connect( this, SIGNAL(triggered()), this, SLOT(load()) ); +} + +void LoadPerspectiveAction::load() +{ + dockManager.getPerspectivesManager()->openPerspective( name, dockManager ); +} + +////////////////////////////// +// RemovePerspectiveAction +////////////////////////////// +RemovePerspectiveAction::RemovePerspectiveAction( QMenu* parent, const QString& name, QtAdsUtl::DockInDockWidget& dockManager ) : + QAction( name, parent ), + name( name ), + dockManager( dockManager ) +{ + connect( this, SIGNAL(triggered()), this, SLOT(remove()) ); +} + +void RemovePerspectiveAction::remove() +{ + dockManager.getPerspectivesManager()->removePerspective( name ); +} diff --git a/examples/dockindock/perspectiveactions.h b/examples/dockindock/perspectiveactions.h new file mode 100644 index 000000000..1614eabba --- /dev/null +++ b/examples/dockindock/perspectiveactions.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace QtAdsUtl +{ + +class DockInDockWidget; +class LoadPerspectiveAction : public QAction +{ + Q_OBJECT +public: + LoadPerspectiveAction( QMenu* parent, const QString& name, QtAdsUtl::DockInDockWidget& dockManager ); + +public slots: + void load(); + +private: + QString name; + QtAdsUtl::DockInDockWidget& dockManager; +}; + +class RemovePerspectiveAction : public QAction +{ + Q_OBJECT +public: + RemovePerspectiveAction( QMenu* parent, const QString& name, QtAdsUtl::DockInDockWidget& dockManager ); + +public slots: + void remove(); + +private: + QString name; + QtAdsUtl::DockInDockWidget& dockManager; +}; + +} + diff --git a/examples/dockindock/perspectiveactions.py b/examples/dockindock/perspectiveactions.py new file mode 100644 index 000000000..9328855eb --- /dev/null +++ b/examples/dockindock/perspectiveactions.py @@ -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) \ No newline at end of file diff --git a/examples/dockindock/perspectives.cpp b/examples/dockindock/perspectives.cpp new file mode 100644 index 000000000..e788c2233 --- /dev/null +++ b/examples/dockindock/perspectives.cpp @@ -0,0 +1,280 @@ +#include "perspectives.h" +#include "dockindock.h" +#include "dockindockmanager.h" + +#include +#include +#include + +#include + +#define GROUP_PREFIX QString("Group") + +using namespace QtAdsUtl; + +PerspectivesManager::PerspectivesManager( const QString& perspectivesFolder ) : + m_perspectivesFolder( perspectivesFolder ) +{ + +} + +PerspectivesManager::~PerspectivesManager() +{ + // remove temp files: + for ( auto perspective : m_perspectives ) + { + QString fileName = perspective.settings->fileName(); + perspective.settings.reset(); + QFile::remove(fileName); + } +} + + +QStringList PerspectivesManager::perspectiveNames() const +{ + return m_perspectives.keys(); +} + +void PerspectivesManager::addPerspective( const QString& name, DockInDockWidget& widget ) +{ + if ( !m_perspectivesFolder.isEmpty() ) + { + m_perspectives[name].settings = getSettingsObject( getSettingsFileName( name, true ) ); + m_perspectives[name].groups = widget.getManager()->getGroupContents(); + + // save perspective internally + widget.getManager()->addPerspectiveRec( name ); + // store it in QSettings object + widget.getManager()->savePerspectivesRec( *(m_perspectives[name].settings) ); + // remove internal perspectives + widget.getManager()->removePerspectives( widget.getManager()->perspectiveNames() ); + } + else + { + assert( false ); + } + + emit perspectivesListChanged(); +} + + +ads::CDockWidget* findWidget( QString name, const std::vector& managers ) +{ + for ( auto mgr : managers ) + { + auto widget = mgr->findDockWidget(name); + if ( widget ) + return widget; + } + return NULL; +} + +void PerspectivesManager::openPerspective( const QString& name, DockInDockWidget& widget ) +{ + assert( widget.getTopLevelDockWidget() == &widget ); + + if ( !m_perspectivesFolder.isEmpty() ) + { + if ( m_perspectives.contains( name ) ) + { + emit openingPerspective(); + + if ( widget.canCreateNewGroups() ) + { + auto curGroups = widget.getManager()->allManagers(true,true); + for ( auto group : m_perspectives[name].groups.keys() ) + { + bool found = false; + for ( auto curgroup : curGroups ) + { + if ( curgroup->getPersistGroupName() == group ) + { + found = true; + break; + } + } + if ( !found ) + { + group = DockInDockManager::getGroupNameFromPersistGroupName( group ); + + // restore group in file but not in GUI yet + ads::CDockAreaWidget* insertPos = NULL; + widget.createGroup( group, insertPos ); + } + } + + curGroups = widget.getManager()->allManagers(false,true); + for ( auto curgroup : curGroups ) + { + if ( !m_perspectives[name].groups.keys().contains( curgroup->getPersistGroupName() ) ) + { + widget.destroyGroup( &curgroup->parent() ); + } + } + } + + auto managers = widget.getManager()->allManagers(true,true); + for ( auto group : m_perspectives[name].groups.keys() ) + { + for ( auto mgr : managers ) + { + if ( mgr->getPersistGroupName() == group ) + { + for ( QString widgetName : m_perspectives[name].groups[group] ) + { + ads::CDockWidget* widget = findWidget( widgetName, { mgr } ); + if ( widget ) + { + // OK, widget is already in the good manager! + } + else + { + widget = findWidget( widgetName, 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( *(m_perspectives[name].settings) ); + // load perspective (update GUI) + widget.getManager()->openPerspectiveRec( name ); + // remove internal perspectives + widget.getManager()->removePerspectives( widget.getManager()->perspectiveNames() ); + + emit openedPerspective(); + } + } + else + { + assert( false ); + } +} + +void PerspectivesManager::removePerspectives() +{ + m_perspectives.clear(); + emit perspectivesListChanged(); +} + +void PerspectivesManager::removePerspective( const QString& name ) +{ + m_perspectives.remove( name ); + emit perspectivesListChanged(); +} + +QString PerspectivesManager::getSettingsFileName( const QString& perspective, bool temp ) const +{ + auto name = ( perspective.isEmpty() ) ? "perspectives.ini" : "perspective_" + perspective + (temp?".tmp":".ini"); + + return m_perspectivesFolder + "/" + name; +} + +std::shared_ptr PerspectivesManager::getSettingsObject( const QString& filePath ) const +{ + return std::make_shared(filePath, QSettings::IniFormat); +} + +void PerspectivesManager::loadPerspectives() +{ + if ( !m_perspectivesFolder.isEmpty() ) + { + QDir().mkpath( m_perspectivesFolder ); + + m_perspectives.clear(); + + auto mainSettings = getSettingsObject( getSettingsFileName( "", false ) ); + std::string debug = mainSettings->fileName().toStdString(); + + int Size = mainSettings->beginReadArray("Perspectives"); + + for (int i = 0; i < Size; ++i) + { + mainSettings->setArrayIndex(i); + QString perspective = mainSettings->value("Name").toString(); + + if ( !perspective.isEmpty() ) + { + // load perspective file: + auto toLoad = getSettingsFileName( perspective, false ); + auto loaded = getSettingsFileName( perspective, true ); + +#ifdef _DEBUG + std::string debug1 = loaded.toStdString(); + std::string debug2 = toLoad.toStdString(); +#endif + + QFile::remove( loaded ); + if ( !QFile::copy( toLoad, loaded ) ) + assert( false ); + + m_perspectives[perspective].settings = getSettingsObject( loaded ); + + // load group info: + mainSettings->beginGroup(GROUP_PREFIX); + for (const auto& key : mainSettings->allKeys()) + { + m_perspectives[perspective].groups[key] = mainSettings->value( key ).toStringList(); + } + mainSettings->endGroup(); + } + else + { + assert( false ); + } + } + + mainSettings->endArray(); + } + + emit perspectivesListChanged(); +} + +void PerspectivesManager::savePerspectives() const +{ + if ( !m_perspectivesFolder.isEmpty() ) + { + auto mainSettings = getSettingsObject( getSettingsFileName( "", false ) ); + + // Save list of perspective and group organization + mainSettings->beginWriteArray("Perspectives", m_perspectives.size()); + int i = 0; + for ( auto perspective : m_perspectives.keys() ) + { + mainSettings->setArrayIndex(i); + mainSettings->setValue("Name", perspective); + mainSettings->beginGroup(GROUP_PREFIX); + for ( auto group : m_perspectives[perspective].groups.keys() ) + { + mainSettings->setValue( group, m_perspectives[perspective].groups[group] ); + } + mainSettings->endGroup(); + ++i; + } + mainSettings->endArray(); + + // Save perspectives themselves + for ( auto perspectiveName : m_perspectives.keys() ) + { + auto toSave = getSettingsFileName( perspectiveName, false ); + QSettings& settings = *(m_perspectives[perspectiveName].settings); + settings.sync(); + +#ifdef _DEBUG + std::string debug1 = settings.fileName().toStdString(); + std::string debug2 = toSave.toStdString(); +#endif + + QFile::remove( toSave ); + if ( !QFile::copy( settings.fileName(), toSave ) ) + assert( false ); + } + } +} diff --git a/examples/dockindock/perspectives.h b/examples/dockindock/perspectives.h new file mode 100644 index 000000000..42592fbf6 --- /dev/null +++ b/examples/dockindock/perspectives.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include + +class QMenu; +class QSettings; + +namespace QtAdsUtl +{ + +class DockInDockWidget; +class PerspectivesManager : public QObject +{ + Q_OBJECT + +public: + PerspectivesManager( const QString& perspectivesFolder ); + virtual ~PerspectivesManager(); + + QStringList perspectiveNames() const; + + void addPerspective( const QString& name, DockInDockWidget& widget ); + void openPerspective( const QString& name, DockInDockWidget& widget ); + void removePerspectives(); + void removePerspective( const QString& name ); + + void loadPerspectives(); + void savePerspectives() const; + +signals: + void perspectivesListChanged(); + void openingPerspective(); + void openedPerspective(); + +private: + + // 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! + class PerspectiveInfo + { + public: + std::shared_ptr settings; + QMap groups; + }; + QMap m_perspectives; + + QString m_perspectivesFolder; + QString getSettingsFileName( const QString& perspective, bool temp ) const; + std::shared_ptr getSettingsObject( const QString& filePath ) const; +}; + +} + diff --git a/examples/dockindock/perspectives.py b/examples/dockindock/perspectives.py new file mode 100644 index 000000000..9b6c00df4 --- /dev/null +++ b/examples/dockindock/perspectives.py @@ -0,0 +1,203 @@ +import os +import tempfile +import shutil +import atexit + +from PyQt5.QtCore import pyqtSignal, QSettings, QObject +import PyQtAds as 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 diff --git a/examples/emptydockarea/CMakeLists.txt b/examples/emptydockarea/CMakeLists.txt new file mode 100644 index 000000000..c9b02f219 --- /dev/null +++ b/examples/emptydockarea/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_centralwidget 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(EmptyDockAreaExample WIN32 + main.cpp + mainwindow.cpp + mainwindow.ui +) +target_include_directories(EmptyDockAreaExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(EmptyDockAreaExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(EmptyDockAreaExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(EmptyDockAreaExample 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 Empty Dock Area 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" +) diff --git a/examples/emptydockarea/emptydockarea.pro b/examples/emptydockarea/emptydockarea.pro new file mode 100644 index 000000000..b45979b19 --- /dev/null +++ b/examples/emptydockarea/emptydockarea.pro @@ -0,0 +1,34 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = EmptyDockareaExample +DESTDIR = $${ADS_OUT_ROOT}/lib +TEMPLATE = app +CONFIG += c++14 +CONFIG += debug_and_release +adsBuildStatic { + DEFINES += ADS_STATIC +} + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +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 + diff --git a/examples/emptydockarea/main.cpp b/examples/emptydockarea/main.cpp new file mode 100644 index 000000000..fa4c4fdea --- /dev/null +++ b/examples/emptydockarea/main.cpp @@ -0,0 +1,10 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + CMainWindow w; + w.show(); + return a.exec(); +} diff --git a/examples/emptydockarea/main.py b/examples/emptydockarea/main.py new file mode 100644 index 000000000..475cc2ed9 --- /dev/null +++ b/examples/emptydockarea/main.py @@ -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 +import PyQtAds as 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_() diff --git a/examples/emptydockarea/mainwindow.cpp b/examples/emptydockarea/mainwindow.cpp new file mode 100644 index 000000000..2dbe99ca5 --- /dev/null +++ b/examples/emptydockarea/mainwindow.cpp @@ -0,0 +1,135 @@ +#include "mainwindow.h" + +#include "ui_mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" +#include "DockAreaTabBar.h" +#include "FloatingDockContainer.h" +#include "DockComponentsFactory.h" + +using namespace ads; + + +CMainWindow::CMainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CMainWindow) +{ + ui->setupUi(this); + CDockManager::setConfigFlag(CDockManager::OpaqueSplitterResize, true); + CDockManager::setConfigFlag(CDockManager::XmlCompressionEnabled, false); + CDockManager::setConfigFlag(CDockManager::FocusHighlighting, true); + DockManager = new CDockManager(this); + + // Set central widget + QLabel* label = new QLabel(); + label->setText("This is a DockArea which is always visible, even if it does not contain any DockWidgets."); + label->setAlignment(Qt::AlignCenter); + CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget"); + CentralDockWidget->setWidget(label); + CentralDockWidget->setFeature(ads::CDockWidget::NoTab, true); + auto* CentralDockArea = DockManager->setCentralWidget(CentralDockWidget); + + // create other dock widgets + QTableWidget* table = new QTableWidget(); + table->setColumnCount(3); + table->setRowCount(10); + CDockWidget* TableDockWidget = new CDockWidget("Table 1"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + DockManager->addDockWidgetTabToArea(TableDockWidget, CentralDockArea); + auto TableArea = DockManager->addDockWidget(DockWidgetArea::LeftDockWidgetArea, TableDockWidget); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + table = new QTableWidget(); + table->setColumnCount(5); + table->setRowCount(1020); + TableDockWidget = new CDockWidget("Table 2"); + TableDockWidget->setWidget(table); + TableDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + TableDockWidget->resize(250, 150); + TableDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::BottomDockWidgetArea, TableDockWidget, TableArea); + ui->menuView->addAction(TableDockWidget->toggleViewAction()); + + QTableWidget* propertiesTable = new QTableWidget(); + propertiesTable->setColumnCount(3); + propertiesTable->setRowCount(10); + CDockWidget* PropertiesDockWidget = new CDockWidget("Properties"); + PropertiesDockWidget->setWidget(propertiesTable); + PropertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget); + PropertiesDockWidget->resize(250, 150); + PropertiesDockWidget->setMinimumSize(200,150); + DockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, PropertiesDockWidget, CentralDockArea); + ui->menuView->addAction(PropertiesDockWidget->toggleViewAction()); + + createPerspectiveUi(); +} + +CMainWindow::~CMainWindow() +{ + delete ui; +} + + +void CMainWindow::createPerspectiveUi() +{ + SavePerspectiveAction = new QAction("Create Perspective", this); + connect(SavePerspectiveAction, SIGNAL(triggered()), SLOT(savePerspective())); + PerspectiveListAction = new QWidgetAction(this); + PerspectiveComboBox = new QComboBox(this); + PerspectiveComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); + PerspectiveComboBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(PerspectiveComboBox, SIGNAL(activated(const QString&)), + DockManager, SLOT(openPerspective(const QString&))); + PerspectiveListAction->setDefaultWidget(PerspectiveComboBox); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(PerspectiveListAction); + ui->toolBar->addAction(SavePerspectiveAction); +} + + +void CMainWindow::savePerspective() +{ + QString PerspectiveName = QInputDialog::getText(this, "Save Perspective", "Enter unique name:"); + if (PerspectiveName.isEmpty()) + { + return; + } + + DockManager->addPerspective(PerspectiveName); + QSignalBlocker Blocker(PerspectiveComboBox); + PerspectiveComboBox->clear(); + PerspectiveComboBox->addItems(DockManager->perspectiveNames()); + PerspectiveComboBox->setCurrentText(PerspectiveName); +} + + +//============================================================================ +void CMainWindow::closeEvent(QCloseEvent* event) +{ + // Delete dock manager here to delete all floating widgets. This ensures + // that all top level windows of the dock manager are properly closed + DockManager->deleteLater(); + QMainWindow::closeEvent(event); +} + + diff --git a/examples/emptydockarea/mainwindow.h b/examples/emptydockarea/mainwindow.h new file mode 100644 index 000000000..75869da84 --- /dev/null +++ b/examples/emptydockarea/mainwindow.h @@ -0,0 +1,43 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class CMainWindow; } +QT_END_NAMESPACE + +class CMainWindow : public QMainWindow +{ + Q_OBJECT + +public: + CMainWindow(QWidget *parent = nullptr); + ~CMainWindow(); + +protected: + virtual void closeEvent(QCloseEvent* event) override; + +private: + QAction* SavePerspectiveAction = nullptr; + QWidgetAction* PerspectiveListAction = nullptr; + QComboBox* PerspectiveComboBox = nullptr; + + Ui::CMainWindow *ui; + + ads::CDockManager* DockManager; + ads::CDockAreaWidget* StatusDockArea; + ads::CDockWidget* TimelineDockWidget; + + void createPerspectiveUi(); + +private slots: + void savePerspective(); +}; +#endif // MAINWINDOW_H diff --git a/examples/emptydockarea/mainwindow.ui b/examples/emptydockarea/mainwindow.ui new file mode 100644 index 000000000..f7d3b09e8 --- /dev/null +++ b/examples/emptydockarea/mainwindow.ui @@ -0,0 +1,47 @@ + + + CMainWindow + + + + 0 + 0 + 1284 + 757 + + + + MainWindow + + + + + + 0 + 0 + 1284 + 21 + + + + + View + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + diff --git a/examples/examples.pro b/examples/examples.pro new file mode 100644 index 000000000..05e739a94 --- /dev/null +++ b/examples/examples.pro @@ -0,0 +1,13 @@ +TEMPLATE = subdirs + +SUBDIRS = \ + autohide \ + autohidedragndrop \ + centralwidget \ + simple \ + hideshow \ + sidebar \ + deleteonclose \ + emptydockarea \ + dockindock \ + configflags diff --git a/examples/hideshow/CMakeLists.txt b/examples/hideshow/CMakeLists.txt new file mode 100644 index 000000000..becab358f --- /dev/null +++ b/examples/hideshow/CMakeLists.txt @@ -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 qt${QT_VERSION_MAJOR}advanceddocking) +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" +) diff --git a/examples/hideshow/MainWindow.cpp b/examples/hideshow/MainWindow.cpp new file mode 100644 index 000000000..44ce67219 --- /dev/null +++ b/examples/hideshow/MainWindow.cpp @@ -0,0 +1,80 @@ +#include "../../examples/hideshow/MainWindow.h" + +#include "ui_MainWindow.h" + +#include +#include + +using namespace ads; + +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 ); +} + diff --git a/examples/hideshow/MainWindow.h b/examples/hideshow/MainWindow.h new file mode 100644 index 000000000..6668387b0 --- /dev/null +++ b/examples/hideshow/MainWindow.h @@ -0,0 +1,33 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#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 diff --git a/examples/hideshow/MainWindow.ui b/examples/hideshow/MainWindow.ui new file mode 100644 index 000000000..260b1f4ee --- /dev/null +++ b/examples/hideshow/MainWindow.ui @@ -0,0 +1,56 @@ + + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + + + + 0 + 0 + 400 + 26 + + + + + View + + + + + File + + + + + + + + + + + Open project + + + + + Close project + + + + + + + diff --git a/examples/hideshow/hideshow.pro b/examples/hideshow/hideshow.pro new file mode 100644 index 000000000..86782d982 --- /dev/null +++ b/examples/hideshow/hideshow.pro @@ -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 + diff --git a/examples/hideshow/main.cpp b/examples/hideshow/main.cpp new file mode 100644 index 000000000..e62035c28 --- /dev/null +++ b/examples/hideshow/main.cpp @@ -0,0 +1,11 @@ +#include +#include "../../examples/hideshow/MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/sidebar/CMakeLists.txt b/examples/sidebar/CMakeLists.txt new file mode 100644 index 000000000..a2e05e6e5 --- /dev/null +++ b/examples/sidebar/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_sidebar 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(SidebarExample WIN32 + main.cpp + MainWindow.cpp + MainWindow.ui +) +target_include_directories(SidebarExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(SidebarExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(SidebarExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(SidebarExample 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 Sidebar 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" +) diff --git a/examples/sidebar/MainWindow.cpp b/examples/sidebar/MainWindow.cpp new file mode 100644 index 000000000..9d9390897 --- /dev/null +++ b/examples/sidebar/MainWindow.cpp @@ -0,0 +1,64 @@ +#include "MainWindow.h" + +#include "ui_MainWindow.h" + +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // Create the dock manager. Because the parent parameter is a QMainWindow + // the dock manager registers itself as the central widget. + QVBoxLayout* Layout = new QVBoxLayout(ui->dockContainer); + Layout->setContentsMargins(QMargins(0, 0, 0, 0)); + m_DockManager = new ads::CDockManager(ui->dockContainer); + Layout->addWidget(m_DockManager); + + // 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()); + + + // Add the dock widget to the top dock widget area + m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); + + // Create an example editor + 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); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + + +void MainWindow::closeEvent(QCloseEvent *event) +{ + QMainWindow::closeEvent(event); + if (m_DockManager) + { + m_DockManager->deleteLater(); + } +} diff --git a/examples/sidebar/MainWindow.h b/examples/sidebar/MainWindow.h new file mode 100644 index 000000000..d371a4132 --- /dev/null +++ b/examples/sidebar/MainWindow.h @@ -0,0 +1,33 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "DockManager.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +/** + * This example shows, how to place a dock widget container and a static + * sidebar into a QMainWindow + */ +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +protected: + virtual void closeEvent(QCloseEvent *event) override; + +private: + Ui::MainWindow *ui; + ads::CDockManager* m_DockManager; +}; + +#endif // MAINWINDOW_H diff --git a/examples/sidebar/MainWindow.ui b/examples/sidebar/MainWindow.ui new file mode 100644 index 000000000..9bcac640b --- /dev/null +++ b/examples/sidebar/MainWindow.ui @@ -0,0 +1,85 @@ + + + MainWindow + + + + 0 + 0 + 650 + 376 + + + + MainWindow + + + + + + + + + + + + PushButton + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + + + + + + + 0 + 0 + 650 + 21 + + + + + View + + + + + + + + + + diff --git a/examples/sidebar/main.cpp b/examples/sidebar/main.cpp new file mode 100644 index 000000000..83e8bb7e4 --- /dev/null +++ b/examples/sidebar/main.cpp @@ -0,0 +1,11 @@ +#include +#include "../../examples/simple/MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/sidebar/main.py b/examples/sidebar/main.py new file mode 100644 index 000000000..8005908d8 --- /dev/null +++ b/examples/sidebar/main.py @@ -0,0 +1,60 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QMargins +from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QPlainTextEdit + +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'MainWindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + + +class MainWindow(MainWindowUI, MainWindowBase): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setupUi(self) + + # Create the dock manager. Because the parent parameter is a QMainWindow + # the dock manager registers itself as the central widget. + layout = QVBoxLayout(self.dockContainer); + layout.setContentsMargins(QMargins(0, 0, 0, 0)) + self.dock_manager = QtAds.CDockManager(self.dockContainer) + layout.addWidget(self.dock_manager) + + # 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. ") + + # Create a dock widget with the title Label 1 and set the created label + # as the dock widget content + dock_widget = QtAds.CDockWidget("Label 1") + dock_widget.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 + self.menuView.addAction(dock_widget.toggleViewAction()) + + # Add the dock widget to the top dock widget area + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + + # Create an example editor + te = QPlainTextEdit() + te.setPlaceholderText("Please enter your text here into this QPlainTextEdit...") + dock_widget = QtAds.CDockWidget("Editor 1") + self.menuView.addAction(dock_widget.toggleViewAction()) + self.dock_manager.addDockWidget(QtAds.BottomDockWidgetArea, dock_widget) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/sidebar/sidebar.pro b/examples/sidebar/sidebar.pro new file mode 100644 index 000000000..6d7b44593 --- /dev/null +++ b/examples/sidebar/sidebar.pro @@ -0,0 +1,31 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = SidebarExample +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 + diff --git a/examples/simple/CMakeLists.txt b/examples/simple/CMakeLists.txt new file mode 100644 index 000000000..99f42a398 --- /dev/null +++ b/examples/simple/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.5) +project(ads_example_simple 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(SimpleExample WIN32 + main.cpp + MainWindow.cpp + MainWindow.ui +) +target_include_directories(SimpleExample PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../../src") +target_link_libraries(SimpleExample PRIVATE qt${QT_VERSION_MAJOR}advanceddocking) +target_link_libraries(SimpleExample PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +set_target_properties(SimpleExample 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 Simple 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" +) diff --git a/examples/simple/MainWindow.cpp b/examples/simple/MainWindow.cpp new file mode 100644 index 000000000..46e24b3ab --- /dev/null +++ b/examples/simple/MainWindow.cpp @@ -0,0 +1,42 @@ +#include "../../examples/simple/MainWindow.h" + +#include "ui_MainWindow.h" + +#include +#include + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + // Create the dock manager. Because the parent parameter is a QMainWindow + // the dock manager registers itself as the central widget. + m_DockManager = new ads::CDockManager(this); + + // 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()); + + // Add the dock widget to the top dock widget area + m_DockManager->addDockWidget(ads::TopDockWidgetArea, DockWidget); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + diff --git a/examples/simple/MainWindow.h b/examples/simple/MainWindow.h new file mode 100644 index 000000000..8d9088549 --- /dev/null +++ b/examples/simple/MainWindow.h @@ -0,0 +1,26 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#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: + Ui::MainWindow *ui; + ads::CDockManager* m_DockManager; +}; + +#endif // MAINWINDOW_H diff --git a/examples/simple/MainWindow.ui b/examples/simple/MainWindow.ui new file mode 100644 index 000000000..c8cdb8efd --- /dev/null +++ b/examples/simple/MainWindow.ui @@ -0,0 +1,38 @@ + + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + + + + 0 + 0 + 400 + 21 + + + + + View + + + + + + + + + + diff --git a/examples/simple/main.cpp b/examples/simple/main.cpp new file mode 100644 index 000000000..83e8bb7e4 --- /dev/null +++ b/examples/simple/main.cpp @@ -0,0 +1,11 @@ +#include +#include "../../examples/simple/MainWindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/simple/main.py b/examples/simple/main.py new file mode 100644 index 000000000..dcacef893 --- /dev/null +++ b/examples/simple/main.py @@ -0,0 +1,52 @@ +import os +import sys + +from PyQt5 import uic +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtGui import QCloseEvent +from PyQt5.QtWidgets import QApplication, QLabel + +import PyQtAds as QtAds + +UI_FILE = os.path.join(os.path.dirname(__file__), 'MainWindow.ui') +MainWindowUI, MainWindowBase = uic.loadUiType(UI_FILE) + + +class MainWindow(MainWindowUI, MainWindowBase): + + def __init__(self, parent=None): + super().__init__(parent) + + self.setupUi(self) + + # Create the dock manager. Because the parent parameter is a QMainWindow + # the dock manager registers itself as the central widget. + self.dock_manager = QtAds.CDockManager(self) + + # 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. ") + + # Create a dock widget with the title Label 1 and set the created label + # as the dock widget content + dock_widget = QtAds.CDockWidget("Label 1") + dock_widget.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 + self.menuView.addAction(dock_widget.toggleViewAction()) + + # Add the dock widget to the top dock widget area + self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + w = MainWindow() + w.show() + app.exec_() diff --git a/examples/simple/simple.pro b/examples/simple/simple.pro new file mode 100644 index 000000000..92a166b10 --- /dev/null +++ b/examples/simple/simple.pro @@ -0,0 +1,31 @@ +ADS_OUT_ROOT = $${OUT_PWD}/../.. + +QT += core gui widgets + +TARGET = SimpleExample +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 + diff --git a/gnu-lgpl-v2.1.md b/gnu-lgpl-v2.1.md new file mode 100644 index 000000000..3a3af496f --- /dev/null +++ b/gnu-lgpl-v2.1.md @@ -0,0 +1,494 @@ +GNU Lesser General Public License +================================= + +_Version 2.1, February 1999_ +_Copyright © 1991, 1999 Free Software Foundation, Inc._ +_51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_ + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +_This is the first released version of the Lesser GPL. It also counts +as the successor of the GNU Library Public License, version 2, hence +the version number 2.1._ + +### Preamble + +The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + +To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: **(1)** we copyright the +library, and **(2)** we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + +Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + +When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + +We call this license the “Lesser” General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + +For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + +Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +“work based on the library” and a “work that uses the library”. The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + +### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called “this License”). +Each licensee is addressed as “you”. + +A “library” means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + +The “Library”, below, refers to any such software library or work +which has been distributed under these terms. A “work based on the +Library” means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term “modification”.) + +“Source code” for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + +**1.** You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +* **a)** The modified work must itself be a software library. +* **b)** You must cause the files modified to carry prominent notices +stating that you changed the files and the date of any change. +* **c)** You must cause the whole of the work to be licensed at no +charge to all third parties under the terms of this License. +* **d)** If a facility in the modified Library refers to a function or a +table of data to be supplied by an application program that uses +the facility, other than as an argument passed when the facility +is invoked, then you must make a good faith effort to ensure that, +in the event an application does not supply such function or +table, the facility still operates, and performs whatever part of +its purpose remains meaningful. +(For example, a function in a library to compute square roots has +a purpose that is entirely well-defined independent of the +application. Therefore, Subsection 2d requires that any +application-supplied function or table used by this function must +be optional: if the application does not supply it, the square +root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + +Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + +**4.** You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + +**5.** A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a “work that uses the Library”. Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + +However, linking a “work that uses the Library” with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a “work that uses the +library”. The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + +When a “work that uses the Library” uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + +**6.** As an exception to the Sections above, you may also combine or +link a “work that uses the Library” with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + +* **a)** Accompany the work with the complete corresponding +machine-readable source code for the Library including whatever +changes were used in the work (which must be distributed under +Sections 1 and 2 above); and, if the work is an executable linked +with the Library, with the complete machine-readable “work that +uses the Library”, as object code and/or source code, so that the +user can modify the Library and then relink to produce a modified +executable containing the modified Library. (It is understood +that the user who changes the contents of definitions files in the +Library will not necessarily be able to recompile the application +to use the modified definitions.) +* **b)** Use a suitable shared library mechanism for linking with the +Library. A suitable mechanism is one that (1) uses at run time a +copy of the library already present on the user's computer system, +rather than copying library functions into the executable, and (2) +will operate properly with a modified version of the library, if +the user installs one, as long as the modified version is +interface-compatible with the version that the work was made with. +* **c)** Accompany the work with a written offer, valid for at +least three years, to give the same user the materials +specified in Subsection 6a, above, for a charge no more +than the cost of performing this distribution. +* **d)** If distribution of the work is made by offering access to copy +from a designated place, offer equivalent access to copy the above +specified materials from the same place. +* **e)** Verify that the user has already received a copy of these +materials or that you have already sent this user a copy. + +For an executable, the required form of the “work that uses the +Library” must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + +**7.** You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + +* **a)** Accompany the combined library with a copy of the same work +based on the Library, uncombined with any other library +facilities. This must be distributed under the terms of the +Sections above. +* **b)** Give prominent notice with the combined library of the fact +that part of it is a work based on the Library, and explaining +where to find the accompanying uncombined form of the same work. + +**8.** You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +**9.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +**10.** Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + +**11.** If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**12.** If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + +**13.** The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +“any later version”, you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + +**14.** If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + +### NO WARRANTY + +**15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY “AS IS” WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**16.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +_END OF TERMS AND CONDITIONS_ + +### How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +“copyright” line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a “copyright disclaimer” for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/license.png b/license.png deleted file mode 100644 index 7355c10d0..000000000 Binary files a/license.png and /dev/null differ diff --git a/persist/perspectives.ini b/persist/perspectives.ini new file mode 100644 index 000000000..e342ad72f --- /dev/null +++ b/persist/perspectives.ini @@ -0,0 +1,2 @@ +[Perspectives] +size=0 diff --git a/preview-dragndrop.png b/preview-dragndrop.png deleted file mode 100644 index 1ebabcaa9..000000000 Binary files a/preview-dragndrop.png and /dev/null differ diff --git a/preview.png b/preview.png deleted file mode 100644 index 38f3c6d57..000000000 Binary files a/preview.png and /dev/null differ diff --git a/project.py b/project.py new file mode 100644 index 000000000..5b1fd9b35 --- /dev/null +++ b/project.py @@ -0,0 +1,53 @@ +import os +import sys + +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 get_options(self): + """Our custom options that a user can pass to sip-build.""" + options = super().get_options() + options += [ + Option('ads_incdir', + help='the directory containing the ads header file', + metavar='DIR'), + Option('ads_libdir', + help='the directory containing the ads library', + metavar='DIR'), + Option('ads_lib', + help='the ads library', + metavar='LIB'), + ] + return options + + 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) + + if self.ads_lib is not None: + self.libraries.append(self.ads_lib) + + if self.ads_incdir is not None: + self.include_dirs.append(self.ads_incdir) + + if self.ads_libdir is not None: + self.library_dirs.append(self.ads_libdir) + + super().apply_user_defaults(tool) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..a9b93bb77 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,77 @@ +# Specify the build system. +[build-system] +requires = ["sip >=6.0.2", "PyQt-builder >=1.6", "PyQt5>=5.15", "PyQt5-sip>=12.8"] +build-backend = "sipbuild.api" + +# Specify the PEP 566 metadata for the project. +[tool.sip.metadata] +name = "PyQtAds" +version = "4.4.1" +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" +dunder-init = true + +[tool.sip.bindings.ads] +pep484-pyi = true +define-macros = ["ADS_SHARED_EXPORT"] +sip-file = "ads.sip" +include-dirs = ["src"] +qmake-QT = ["widgets", "gui-private; platform_system == 'Linux'"] +headers = [ + "src/AutoHideDockContainer.h", + "src/AutoHideSideBar.h", + "src/AutoHideTab.h", + "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/PushButton.h", + "src/ResizeHandle.h", + "src/ads_globals.h", + "src/linux/FloatingWidgetTitleBar.h; platform_system == 'Linux'", +] +sources = [ + "src/AutoHideTab.cpp", + "src/AutoHideDockContainer.cpp", + "src/AutoHideSideBar.cpp", + "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/PushButton.cpp", + "src/ResizeHandle.cpp", + "src/ads_globals.cpp", + "src/linux/FloatingWidgetTitleBar.cpp; platform_system == 'Linux'", +] diff --git a/sip/AutoHideDockContainer.sip b/sip/AutoHideDockContainer.sip new file mode 100644 index 000000000..3b13d10e0 --- /dev/null +++ b/sip/AutoHideDockContainer.sip @@ -0,0 +1,49 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CAutoHideDockContainer : QFrame +{ + + %TypeHeaderCode + #include + %End + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event); + virtual void resizeEvent(QResizeEvent* event); + virtual void leaveEvent(QEvent *event); + virtual bool event(QEvent* event); + void updateSize(); + void saveState(QXmlStreamWriter& Stream); + +public: + CAutoHideDockContainer(ads::CDockWidget* DockWidget /Transfer/, ads::SideBarLocation area, + ads::CDockContainerWidget* parent /TransferThis/); + virtual ~CAutoHideDockContainer(); + ads::CAutoHideSideBar* autoHideSideBar() const; + ads::CAutoHideTab* autoHideTab() const; + ads::CDockWidget* dockWidget() const; + int tabIndex() const; + void addDockWidget(ads::CDockWidget* DockWidget /Transfer/); + ads::SideBarLocation sideBarLocation() const; + void setSideBarLocation(ads::SideBarLocation SideBarLocation); + ads::CDockAreaWidget* dockAreaWidget() const; + ads::CDockContainerWidget* dockContainer() const; + void moveContentsToParent(); + void cleanupAndDelete(); + void toggleView(bool Enable); + void collapseView(bool Enable); + void toggleCollapseState(); + void setSize(int Size); + void resetToInitialDockWidgetSize(); + Qt::Orientation orientation() const; + void moveToNewSideBarLocation(ads::SideBarLocation); +}; + +}; + +%End \ No newline at end of file diff --git a/sip/AutoHideSideBar.sip b/sip/AutoHideSideBar.sip new file mode 100644 index 000000000..69047481d --- /dev/null +++ b/sip/AutoHideSideBar.sip @@ -0,0 +1,46 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + + +class CAutoHideSideBar : QScrollArea +{ + + %TypeHeaderCode + #include + %End + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event); + void saveState(QXmlStreamWriter& Stream) const; + void insertTab(int Index, ads::CAutoHideTab* SideTab /Transfer/); + +public: + CAutoHideSideBar(ads::CDockContainerWidget* parent /TransferThis/, SideBarLocation area); + virtual ~CAutoHideSideBar(); + void removeTab(ads::CAutoHideTab* SideTab) /TransferBack/; + ads::CAutoHideDockContainer* insertDockWidget(int Index, ads::CDockWidget* DockWidget /Transfer/); + void removeAutoHideWidget(ads::CAutoHideDockContainer* AutoHideWidget) /TransferBack/; + void addAutoHideWidget(ads::CAutoHideDockContainer* AutoHideWidget, int Index); + Qt::Orientation orientation() const; + ads::CAutoHideTab* tab(int index) const; + int tabAt(const QPoint& Pos) const; + int tabInsertIndexAt(const QPoint& Pos) const; + int indexOfTab(const CAutoHideTab& Tab) const; + int count() const; + int visibleTabCount() const; + bool hasVisibleTabs() const; + ads::SideBarLocation sideBarLocation() const; + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; + int spacing() const; + void setSpacing(int Spacing); + CDockContainerWidget* dockContainer() const; +}; + +}; + +%End \ No newline at end of file diff --git a/sip/AutoHideTab.sip b/sip/AutoHideTab.sip new file mode 100644 index 000000000..4d72e7beb --- /dev/null +++ b/sip/AutoHideTab.sip @@ -0,0 +1,44 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CAutoHideTab : CPushButton +{ + + %TypeHeaderCode + #include + %End + +protected: + void setSideBar(ads::CAutoHideSideBar *SideTabBar); + void removeFromSideBar(); + virtual bool event(QEvent* event); + virtual void contextMenuEvent(QContextMenuEvent* ev); + virtual void mousePressEvent(QMouseEvent* ev); + virtual void mouseReleaseEvent(QMouseEvent* ev); + virtual void mouseMoveEvent(QMouseEvent* ev); + +public: + CAutoHideTab(QWidget* parent /TransferThis/ = 0); + virtual ~CAutoHideTab(); + void updateStyle(); + ads::SideBarLocation sideBarLocation() const; + void setOrientation(Qt::Orientation Orientation); + Qt::Orientation orientation() const; + bool isActiveTab() const; + ads::CDockWidget* dockWidget() const; + void setDockWidget(ads::CDockWidget* DockWidget); + bool iconOnly() const; + ads::CAutoHideSideBar* sideBar() const; + int tabIndex() const; + void setDockWidgetFloating(); + void unpinDockWidget(); + void requestCloseDockWidget(); +}; + +}; + +%End diff --git a/sip/DockAreaTabBar.sip b/sip/DockAreaTabBar.sip new file mode 100644 index 000000000..ab6e72ff0 --- /dev/null +++ b/sip/DockAreaTabBar.sip @@ -0,0 +1,52 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockAreaTabBar : QScrollArea +{ + %TypeHeaderCode + #include + %End + +protected: + virtual void wheelEvent(QWheelEvent* Event); + +public: + CDockAreaTabBar(ads::CDockAreaWidget* parent /TransferThis/); + virtual ~CDockAreaTabBar(); + void insertTab(int Index, ads::CDockWidgetTab* Tab /Transfer/); + void removeTab(ads::CDockWidgetTab* Tab) /TransferBack/; + int count() const; + int currentIndex() const; + ads::CDockWidgetTab* currentTab() const; + ads::CDockWidgetTab* tab(int Index) const; + int tabAt(const QPoint& Pos) const; + int tabInsertIndexAt(const QPoint& Pos) const; + virtual bool eventFilter(QObject *watched, QEvent *event); + bool isTabOpen(int Index) const; + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; + void elidedChanged(bool elided); + +public slots: + void setCurrentIndex(int Index); + void closeTab(int Index); + +signals: + void currentChanging(int Index); + void currentChanged(int Index); + void tabBarClicked(int index); + void tabCloseRequested(int index); + void tabClosed(int index); + void tabOpened(int index); + void tabMoved(int from, int to); + void removingTab(int index); + void tabInserted(int index); +}; + +}; + +%End diff --git a/sip/DockAreaTitleBar.sip b/sip/DockAreaTitleBar.sip new file mode 100644 index 000000000..06c7b1971 --- /dev/null +++ b/sip/DockAreaTitleBar.sip @@ -0,0 +1,67 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CTitleBarButton : QToolButton +{ + %TypeHeaderCode + #include + %End + +public: + CTitleBarButton(bool ShowInTitleBar, bool HideWhenDisabled, TitleBarButton ButtonId, + QWidget* /TransferThis/ = Q_NULLPTR ); + virtual void setVisible(bool); + void setShowInTitleBar(bool); + + TitleBarButton buttonId() const; + ads::CDockAreaTitleBar* titleBar() const; + bool isInAutoHideArea() const; + +protected: + bool event(QEvent *ev); +}; + +class CDockAreaTitleBar : QFrame +{ + %TypeHeaderCode + #include + %End + +protected: + virtual void mousePressEvent(QMouseEvent* ev); + virtual void mouseReleaseEvent(QMouseEvent* ev); + virtual void mouseMoveEvent(QMouseEvent* ev); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + virtual void contextMenuEvent(QContextMenuEvent *event); + +public slots: + void markTabsMenuOutdated(); + + +public: + CDockAreaTitleBar(ads::CDockAreaWidget* parent /TransferThis/); + virtual ~CDockAreaTitleBar(); + ads::CDockAreaTabBar* tabBar() const; + ads::CTitleBarButton* button(ads::TitleBarButton which) const; + ads::CElidingLabel* autoHideTitleLabel() const; + ads::CDockAreaWidget* dockAreaWidget() const; + void updateDockWidgetActionsButtons(); + virtual void setVisible(bool Visible); + void insertWidget(int index, QWidget *widget /Transfer/ ); + int indexOf(QWidget *widget) const; + QString titleBarButtonToolTip(ads::TitleBarButton Button) const; + void setAreaFloating(); + void showAutoHideControls(bool Show); + bool isAutoHide() const; + +signals: + void tabBarClicked(int index); +}; + +}; + +%End diff --git a/sip/DockAreaTitleBar_p.sip b/sip/DockAreaTitleBar_p.sip new file mode 100644 index 000000000..243a97864 --- /dev/null +++ b/sip/DockAreaTitleBar_p.sip @@ -0,0 +1,22 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CSpacerWidget : QWidget +{ + %TypeHeaderCode + #include + %End + +public: + CSpacerWidget(QWidget* Parent /TransferThis/ = 0 ); + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; +}; + +}; + +%End diff --git a/sip/DockAreaWidget.sip b/sip/DockAreaWidget.sip new file mode 100644 index 000000000..6e091f6ab --- /dev/null +++ b/sip/DockAreaWidget.sip @@ -0,0 +1,95 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockAreaWidget : QFrame +{ + + %TypeHeaderCode + #include + %End + +protected: + void insertDockWidget(int index, ads::CDockWidget* DockWidget /Transfer/, bool Activate = true); + void addDockWidget(ads::CDockWidget* DockWidget /Transfer/); + void removeDockWidget(ads::CDockWidget* DockWidget) /TransferBack/; + void toggleDockWidgetView(ads::CDockWidget* DockWidget, bool Open); + CDockWidget* nextOpenDockWidget(ads::CDockWidget* DockWidget) const; + int index(ads::CDockWidget* DockWidget); + void hideAreaWithNoVisibleContent(); + void updateTitleBarVisibility(); + void internalSetCurrentDockWidget(ads::CDockWidget* DockWidget /Transfer/); + void markTitleBarMenuOutdated(); + void updateTitleBarButtonVisibility(bool IsTopLevel) const; + +protected slots: + void toggleView(bool Open); + +public: + enum eDockAreaFlag + { + HideSingleWidgetTitleBar, + DefaultFlags + }; + typedef QFlags DockAreaFlags; + + CDockAreaWidget(ads::CDockManager* DockManager /TransferThis/, ads::CDockContainerWidget* parent /TransferThis/); + virtual ~CDockAreaWidget(); + ads::CDockManager* dockManager() const; + ads::CDockContainerWidget* dockContainer() const; + ads::CAutoHideDockContainer* autoHideDockContainer() const; + ads::CDockSplitter* parentSplitter() const; + bool isAutoHide() const; + void setAutoHideDockContainer(CAutoHideDockContainer*); + virtual QSize minimumSizeHint() const; + QRect titleBarGeometry() const; + QRect contentAreaGeometry() const; + int dockWidgetsCount() const; + QList dockWidgets() const; + int openDockWidgetsCount() const; + QList openedDockWidgets() const; + ads::CDockWidget* dockWidget(int Index) const; + int currentIndex() const; + int indexOfFirstOpenDockWidget() const; + ads::CDockWidget* currentDockWidget() const; + void setCurrentDockWidget(ads::CDockWidget* DockWidget); + void saveState(QXmlStreamWriter& Stream) const; + static bool restoreState(ads::CDockingStateReader& Stream, ads::CDockAreaWidget*& CreatedWidget, + bool Testing, ads::CDockContainerWidget* ParentContainer); + ads::CDockWidget::DockWidgetFeatures features(ads::eBitwiseOperator Mode = ads::BitwiseAnd) const; + QAbstractButton* titleBarButton(ads::TitleBarButton which) const; + virtual void setVisible(bool Visible); + + void setAllowedAreas(DockWidgetAreas areas); + DockWidgetAreas allowedAreas() const; + CDockAreaTitleBar* titleBar() const; + + DockAreaFlags dockAreaFlags() const; + void setDockAreaFlags(DockAreaFlags Flags); + void setDockAreaFlag(eDockAreaFlag Flag, bool On); + + bool isCentralWidgetArea() const; + bool containsCentralWidget() const; + bool isTopLevelArea() const; + +public slots: + void setCurrentIndex(int index); + void closeArea(); + void setAutoHide(bool Enable, SideBarLocation Location = ads::SideBarNone, int TabIndex = -1); + void toggleAutoHide(SideBarLocation Location = ads::SideBarNone); + void closeOtherAreas(); + void setFloating(); + +signals: + void tabBarClicked(int index); + void currentChanging(int index); + void currentChanged(int index); + void viewToggled(bool Open); +}; + +}; + +%End diff --git a/sip/DockComponentsFactory.sip b/sip/DockComponentsFactory.sip new file mode 100644 index 000000000..b00eb623b --- /dev/null +++ b/sip/DockComponentsFactory.sip @@ -0,0 +1,27 @@ +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockComponentsFactory +{ + + %TypeHeaderCode + #include + %End + +public: + virtual ~CDockComponentsFactory(); + virtual CDockWidgetTab* createDockWidgetTab(CDockWidget* DockWidget /Transfer/ ) const; + virtual CAutoHideTab* createDockWidgetSideTab(CDockWidget* DockWidget /Transfer/) const; + virtual CDockAreaTabBar* createDockAreaTabBar(CDockAreaWidget* DockArea /Transfer/ ) const; + virtual CDockAreaTitleBar* createDockAreaTitleBar(CDockAreaWidget* DockArea /Transfer/ ) const; + static const CDockComponentsFactory* factory(); + static void setFactory(CDockComponentsFactory* Factory /KeepReference/); + static void resetDefaultFactory(); + +}; + +}; + +%End diff --git a/sip/DockContainerWidget.sip b/sip/DockContainerWidget.sip new file mode 100644 index 000000000..982dcf6c9 --- /dev/null +++ b/sip/DockContainerWidget.sip @@ -0,0 +1,78 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +/** + * Container that manages a number of dock areas with single dock widgets + * or tabyfied dock widgets in each area. + * Each window that support docking has a DockContainerWidget. That means + * the main application window and all floating windows contain a + * DockContainerWidget. + */ +class CDockContainerWidget : QFrame +{ + %TypeHeaderCode + #include + %End + +protected: + virtual bool event(QEvent *e); + ads::CDockSplitter* rootSplitter() const; + ads::CAutoHideDockContainer* createAndSetupAutoHideContainer(ads::SideBarLocation area, ads::CDockWidget* DockWidget /Transfer/, int TabIndex = -1); + void createRootSplitter(); + void dropFloatingWidget(ads::CFloatingDockContainer* FloatingWidget, const QPoint& TargetPos); + void dropWidget(QWidget* Widget, DockWidgetArea DropArea, CDockAreaWidget* TargetAreaWidget, int TabIndex = -1); + void addDockArea(ads::CDockAreaWidget* DockAreaWidget /Transfer/, ads::DockWidgetArea area = ads::CenterDockWidgetArea); + void removeDockArea(ads::CDockAreaWidget* area /TransferBack/); + /*QList> removeAllDockAreas();*/ + void saveState(QXmlStreamWriter& Stream) const; + bool restoreState(CDockingStateReader& Stream, bool Testing); + ads::CDockAreaWidget* lastAddedDockAreaWidget(ads::DockWidgetArea area) const; + ads::CDockWidget* topLevelDockWidget() const; + ads::CDockAreaWidget* topLevelDockArea() const; + QList dockWidgets() const; + void updateSplitterHandles(QSplitter* splitter); + void registerAutoHideWidget(ads::CAutoHideDockContainer* AutoHideWidget /Transfer/); + void removeAutoHideWidget(ads::CAutoHideDockContainer* AutoHideWidget /TransferBack/); + void handleAutoHideWidgetEvent(QEvent* e, QWidget* w); + +public: + CDockContainerWidget(ads::CDockManager* DockManager /TransferThis/, QWidget* parent /TransferThis/ = 0); + virtual ~CDockContainerWidget(); + ads::CDockAreaWidget* addDockWidget(ads::DockWidgetArea area, ads::CDockWidget* Dockwidget /Transfer/, + ads::CDockAreaWidget* DockAreaWidget /Transfer/ = 0, + int Index = -1); + void removeDockWidget(ads::CDockWidget* Dockwidget) /TransferBack/; + virtual unsigned int zOrderIndex() const; + bool isInFrontOf(ads::CDockContainerWidget* Other) const; + ads::CDockAreaWidget* dockAreaAt(const QPoint& GlobalPos) const; + ads::CDockAreaWidget* dockArea(int Index) const; + QList openedDockAreas() const; + QList openedDockWidgets() const; + bool hasTopLevelDockWidget() const; + int dockAreaCount() const; + int visibleDockAreaCount() const; + bool isFloating() const; + void dumpLayout(); + ads::CDockWidget::DockWidgetFeatures features() const; + ads::CFloatingDockContainer* floatingWidget() const; + void closeOtherAreas(ads::CDockAreaWidget* KeepOpenArea); + ads::CAutoHideSideBar* autoHideSideBar(SideBarLocation area) const; + QList autoHideWidgets() const; + QRect contentRect() const; + QRect contentRectGlobal() const; + ads::CDockManager* dockManager() const; + +signals: + void dockAreasAdded(); + void autoHideWidgetCreated(ads::CAutoHideDockContainer* AutoHideWidget); + void dockAreasRemoved(); + void dockAreaViewToggled(ads::CDockAreaWidget* DockArea, bool Open); +}; + +}; + +%End diff --git a/sip/DockFocusController.sip b/sip/DockFocusController.sip new file mode 100644 index 000000000..9a9f5494a --- /dev/null +++ b/sip/DockFocusController.sip @@ -0,0 +1,35 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +/** + * Manages focus styling of dock widgets and handling of focus changes + */ +class CDockFocusController : QObject +{ + %TypeHeaderCode + #include + %End + +public: + CDockFocusController(ads::CDockManager* DockManager); + virtual ~CDockFocusController(); + + void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); + void notifyFloatingWidgetDrop(ads::CFloatingDockContainer* FloatingWidget); + ads::CDockWidget* focusedDockWidget() const; + void setDockWidgetTabFocused(ads::CDockWidgetTab* Tab); + void clearDockWidgetFocus(ads::CDockWidget* dockWidget); + void setDockWidgetTabPressed(bool Value); + +public slots: + void setDockWidgetFocused(ads::CDockWidget* focusedNow); + +}; // class DockFocusController +}; + // namespace ads + +%End \ No newline at end of file diff --git a/sip/DockManager.sip b/sip/DockManager.sip new file mode 100644 index 000000000..8ece9c6db --- /dev/null +++ b/sip/DockManager.sip @@ -0,0 +1,281 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +%MappedType QMap + /TypeHint="Dict[QString, CDockWidget*]", TypeHintValue="{}"/ +{ +%TypeHeaderCode +#include +%End + +%ConvertFromTypeCode + PyObject *d = PyDict_New(); + + if (!d) + return 0; + + QMap::const_iterator it = sipCpp->constBegin(); + QMap::const_iterator end = sipCpp->constEnd(); + + while (it != end) + { + QString *k = new QString(it.key()); + PyObject *kobj = sipConvertFromType(k, sipType_QString, + sipTransferObj); + + if (!kobj) + { + delete k; + Py_DECREF(d); + + return 0; + } + + PyObject *vobj = sipConvertFromType(it.value(), sipType_ads_CDockWidget, + sipTransferObj); + + if (!vobj) + { + Py_DECREF(kobj); + Py_DECREF(d); + + return 0; + } + + int rc = PyDict_SetItem(d, kobj, vobj); + + Py_DECREF(vobj); + Py_DECREF(kobj); + + if (rc < 0) + { + Py_DECREF(d); + + return 0; + } + + ++it; + } + + return d; +%End + +%ConvertToTypeCode + if (!sipIsErr) + return PyDict_Check(sipPy); + + QMap *qm = new QMap; + + Py_ssize_t pos = 0; + PyObject *kobj, *vobj; + while (PyDict_Next(sipPy, &pos, &kobj, &vobj)) + { + int kstate; + QString *k = reinterpret_cast( + sipForceConvertToType(kobj, sipType_QString, sipTransferObj, + SIP_NOT_NONE, &kstate, sipIsErr)); + + if (*sipIsErr) + { + PyErr_Format(PyExc_TypeError, + "a dict key has type '%s' but '_TYPE1_' is expected", + sipPyTypeName(Py_TYPE(kobj))); + + delete qm; + + return 0; + } + + int vstate; + ads::CDockWidget *v = reinterpret_cast( + sipForceConvertToType(vobj, sipType_ads_CDockWidget, sipTransferObj, + SIP_NOT_NONE, &vstate, sipIsErr)); + + if (*sipIsErr) + { + PyErr_Format(PyExc_TypeError, + "a dict value has type '%s' but '_TYPE2_' is expected", + sipPyTypeName(Py_TYPE(vobj))); + + sipReleaseType(k, sipType_QString, kstate); + delete qm; + + return 0; + } + + qm->insert(*k, v); + + sipReleaseType(v, sipType_ads_CDockWidget, vstate); + sipReleaseType(k, sipType_QString, kstate); + } + + *sipCppPtr = qm; + + return sipGetState(sipTransferObj); +%End +}; + +namespace ads +{ + +class CDockManager : ads::CDockContainerWidget +{ + + %TypeHeaderCode + #include + %End + +protected: + void registerFloatingWidget(ads::CFloatingDockContainer* FloatingWidget /Transfer/); + void removeFloatingWidget(ads::CFloatingDockContainer* FloatingWidget) /TransferBack/; + void registerDockContainer(ads::CDockContainerWidget* DockContainer /Transfer/); + void removeDockContainer(ads::CDockContainerWidget* DockContainer /TransferBack/); + ads::CDockOverlay* containerOverlay() const; + ads::CDockOverlay* dockAreaOverlay() const; + void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); + void notifyFloatingWidgetDrop(ads::CFloatingDockContainer* FloatingWidget); + + virtual void showEvent(QShowEvent *event); + +public: + enum eViewMenuInsertionOrder + { + MenuSortedByInsertion, + MenuAlphabeticallySorted + }; + + enum eConfigFlag + { + ActiveTabHasCloseButton, + DockAreaHasCloseButton, + DockAreaCloseButtonClosesTab, + OpaqueSplitterResize, + XmlAutoFormattingEnabled, + XmlCompressionEnabled, + TabCloseButtonIsToolButton, + AllTabsHaveCloseButton, + RetainTabSizeWhenCloseButtonHidden, + DragPreviewIsDynamic, + DragPreviewShowsContentPixmap, + DragPreviewHasWindowFrame, + AlwaysShowTabs, + DockAreaHasUndockButton, + DockAreaHasTabsMenuButton, + DockAreaHideDisabledButtons, + DockAreaDynamicTabsMenuButtonVisibility, + FloatingContainerHasWidgetTitle, + FloatingContainerHasWidgetIcon, + HideSingleCentralWidgetTitleBar, + FocusHighlighting, + EqualSplitOnInsertion, + FloatingContainerForceNativeTitleBar, + FloatingContainerForceQWidgetTitleBar, + MiddleMouseButtonClosesTab, + DisableTabTextEliding, + ShowTabTextOnlyForActiveTab, + DefaultDockAreaButtons, + DefaultBaseConfig, + DefaultOpaqueConfig, + DefaultNonOpaqueConfig, + NonOpaqueWithWindowFrame, + }; + typedef QFlags ConfigFlags; + enum eAutoHideFlag + { + AutoHideFeatureEnabled, + DockAreaHasAutoHideButton, + AutoHideButtonTogglesArea, + AutoHideButtonCheckable, + AutoHideSideBarsIconOnly, + AutoHideShowOnMouseOver, + AutoHideCloseButtonCollapsesDock, + AutoHideHasCloseButton, + AutoHideHasMinimizeButton, + DefaultAutoHideConfig, + }; + typedef QFlags AutoHideFlags; + + CDockManager(QWidget* parent /TransferThis/ = 0); + virtual ~CDockManager(); + static ads::CDockManager::ConfigFlags configFlags(); + static void setConfigFlags(const ads::CDockManager::ConfigFlags Flags); + static void setConfigFlag(ads::CDockManager::eConfigFlag Flag, bool On = true); + static bool testConfigFlag(eConfigFlag Flag); + static ads::CDockManager::AutoHideFlags autoHideConfigFlags(); + static void setAutoHideConfigFlags(const ads::CDockManager::AutoHideFlags Flags); + static void setAutoHideConfigFlag(ads::CDockManager::eAutoHideFlag Flag, bool On = true); + static bool testAutoHideConfigFlag(eAutoHideFlag Flag); + static ads::CIconProvider& iconProvider(); + ads::CDockAreaWidget* addDockWidget(ads::DockWidgetArea area, ads::CDockWidget* Dockwidget /Transfer/, + ads::CDockAreaWidget* DockAreaWidget /Transfer/ = 0, + int Index = -1); + ads::CDockAreaWidget* addDockWidgetToContainer(ads::DockWidgetArea area, ads::CDockWidget* Dockwidget /Transfer/, + ads::CDockContainerWidget* DockContainerWidget /Transfer/ = 0); + ads::CAutoHideDockContainer* addAutoHideDockWidget(ads::SideBarLocation Location, ads::CDockWidget* Dockwidget /Transfer/); + ads::CAutoHideDockContainer* addAutoHideDockWidgetToContainer(SideBarLocation Location, + ads::CDockWidget* Dockwidget /Transfer/, ads::CDockContainerWidget* DockContainerWidget); + ads::CDockAreaWidget* addDockWidgetTab(ads::DockWidgetArea area, + ads::CDockWidget* Dockwidget /Transfer/); + ads::CDockAreaWidget* addDockWidgetTabToArea(ads::CDockWidget* Dockwidget /Transfer/, + ads::CDockAreaWidget* DockAreaWidget /Transfer/, + int Index = -1); + ads::CFloatingDockContainer* addDockWidgetFloating(ads::CDockWidget* DockWidget /Transfer/); + ads::CDockWidget* findDockWidget(const QString& ObjectName) const; + void removeDockWidget(ads::CDockWidget* Dockwidget) /TransferBack/; + QMap dockWidgetsMap() const; + const QList dockContainers() const; + const QList floatingWidgets() const; + unsigned int zOrderIndex() const; + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + void addPerspective(const QString& UniquePrespectiveName); + void removePerspective(const QString& Name); + void removePerspectives(const QStringList& Names); + QStringList perspectiveNames() const; + void savePerspectives(QSettings& Settings) const; + void loadPerspectives(QSettings& Settings); + CDockWidget* centralWidget() const; + CDockAreaWidget* setCentralWidget(CDockWidget* widget /Transfer/); + QAction* addToggleViewActionToMenu(QAction* ToggleViewAction /Transfer/, + const QString& Group = QString(), const QIcon& GroupIcon = QIcon()); + QMenu* viewMenu() const; + void setViewMenuInsertionOrder(ads::CDockManager::eViewMenuInsertionOrder Order); + bool isRestoringState() const; + bool isLeavingMinimizedState() const; + static int startDragDistance(); + ads::CDockWidget* focusedDockWidget() const; + QList splitterSizes(ads::CDockAreaWidget *ContainedArea) const; + void setSplitterSizes(ads::CDockAreaWidget *ContainedArea, const QList& sizes); + static void setFloatingContainersTitle(const QString& Title); + static QString floatingContainersTitle(); + void setDockWidgetToolBarStyle(Qt::ToolButtonStyle Style, ads::CDockWidget::eState State); + Qt::ToolButtonStyle dockWidgetToolBarStyle(ads::CDockWidget::eState State) const; + void setDockWidgetToolBarIconSize(const QSize& IconSize, ads::CDockWidget::eState State); + QSize dockWidgetToolBarIconSize(ads::CDockWidget::eState State) const; + ads::CDockWidget::DockWidgetFeatures globallyLockedDockWidgetFeatures() const; + void lockDockWidgetFeaturesGlobally(ads::CDockWidget::DockWidgetFeatures Features = ads::CDockWidget::GloballyLockableFeatures); + +public slots: + void endLeavingMinimizedState(); + void openPerspective(const QString& PerspectiveName); + void setDockWidgetFocused(ads::CDockWidget* DockWidget); + +signals: + void perspectiveListChanged(); + void perspectivesRemoved(); + void restoringState(); + void stateRestored(); + void openingPerspective(const QString& PerspectiveName); + void perspectiveOpened(const QString& PerspectiveName); + void floatingWidgetCreated(ads::CFloatingDockContainer*); + void dockAreaCreated(ads::CDockAreaWidget*); + void dockWidgetAdded(ads::CDockWidget* DockWidget); + void dockWidgetAboutToBeRemoved(ads::CDockWidget*); + void dockWidgetRemoved(ads::CDockWidget*); + void focusedDockWidgetChanged(ads::CDockWidget*, ads::CDockWidget*); +}; + +}; + +%End diff --git a/sip/DockOverlay.sip b/sip/DockOverlay.sip new file mode 100644 index 000000000..feb6d45b7 --- /dev/null +++ b/sip/DockOverlay.sip @@ -0,0 +1,83 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockOverlay : public QFrame +{ + %TypeHeaderCode + #include + %End + +public: + + enum eMode + { + ModeDockAreaOverlay, + ModeContainerOverlay + }; + + CDockOverlay(QWidget* parent /TransferThis/, eMode Mode = ads::CDockOverlay::ModeDockAreaOverlay); + virtual ~CDockOverlay(); + void setAllowedAreas(ads::DockWidgetAreas areas); + void setAllowedArea(ads::DockWidgetArea area, bool Enable); + ads::DockWidgetAreas allowedAreas() const; + ads::DockWidgetArea dropAreaUnderCursor() const; + int tabIndexUnderCursor() const; + ads::DockWidgetArea visibleDropAreaUnderCursor() const; + ads::DockWidgetArea showOverlay(QWidget* target); + void hideOverlay(); + void enableDropPreview(bool Enable); + bool dropPreviewEnabled() const; + QRect dropOverlayRect() const; + virtual bool event(QEvent *e); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void showEvent(QShowEvent* e); + virtual void hideEvent(QHideEvent* e); +}; + + +class CDockOverlayCross : public QWidget +{ + +public: + enum eIconColor + { + FrameColor, + WindowBackgroundColor, + OverlayColor, + ArrowColor, + ShadowColor + }; + +protected: + QString iconColors() const; + QColor iconColor() const; + void setIconFrameColor(const QColor& Color); + void setIconBackgroundColor(const QColor& Color); + void setIconOverlayColor(const QColor& Color); + void setIconArrowColor(const QColor& Color); + void setIconShadowColor(const QColor& Color); + virtual void showEvent(QShowEvent* e); + void setAreaWidgets(const QHash& widgets); + +public: + CDockOverlayCross(ads::CDockOverlay* overlay /TransferThis/); + virtual ~CDockOverlayCross(); + void setIconColor(ads::CDockOverlayCross::eIconColor ColorIndex, const QColor& Color); + QColor iconColor(ads::CDockOverlayCross::eIconColor ColorIndex) const; + ads::DockWidgetArea cursorLocation() const; + void setupOverlayCross(ads::CDockOverlay::eMode Mode); + void updateOverlayIcons(); + void reset(); + void updatePosition(); + void setIconColors(const QString& Colors); +}; + +}; + +%End diff --git a/sip/DockSplitter.sip b/sip/DockSplitter.sip new file mode 100644 index 000000000..2b567e2ad --- /dev/null +++ b/sip/DockSplitter.sip @@ -0,0 +1,27 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockSplitter : QSplitter +{ + %TypeHeaderCode + #include + %End + +public: + CDockSplitter(QWidget *parent /TransferThis/ = 0); + CDockSplitter(Qt::Orientation orientation, QWidget *parent /TransferThis/ = 0); + virtual ~CDockSplitter(); + bool hasVisibleContent() const; + QWidget* firstWidget() const; + QWidget* lastWidget() const; + bool isResizingWithContainer() const; + +}; + +}; + +%End diff --git a/sip/DockWidget.sip b/sip/DockWidget.sip new file mode 100644 index 000000000..c19656114 --- /dev/null +++ b/sip/DockWidget.sip @@ -0,0 +1,158 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockWidget : QFrame +{ + %TypeHeaderCode + #include + %End + +protected: + void setDockManager(ads::CDockManager* DockManager /Transfer/ ); + void setDockArea(ads::CDockAreaWidget* DockArea /Transfer/ ); + void setToggleViewActionChecked(bool Checked); + void saveState(QXmlStreamWriter& Stream) const; + void flagAsUnassigned(); + static void emitTopLevelEventForWidget(ads::CDockWidget* TopLevelDockWidget, bool Floating); + void emitTopLevelChanged(bool Floating); + void setClosedState(bool Closed); + void toggleViewInternal(bool Open); + bool closeDockWidgetInternal(bool ForceClose = false); + +public: + enum DockWidgetFeature + { + DockWidgetClosable, + DockWidgetMovable, + DockWidgetFloatable, + DockWidgetDeleteOnClose, + CustomCloseHandling, + DockWidgetFocusable, + DockWidgetForceCloseWithArea, + NoTab, + DeleteContentOnClose, + DockWidgetPinnable, + DefaultDockWidgetFeatures, + AllDockWidgetFeatures, + DockWidgetAlwaysCloseAndDelete, + GloballyLockableFeatures, + NoDockWidgetFeatures + }; + typedef QFlags DockWidgetFeatures; + + enum eState + { + StateHidden, + StateDocked, + StateFloating + }; + + enum eToolBarStyleSource + { + ToolBarStyleFromDockManager, + ToolBarStyleFromDockWidget + }; + + enum eInsertMode + { + AutoScrollArea, + ForceScrollArea, + ForceNoScrollArea + }; + + enum eMinimumSizeHintMode + { + MinimumSizeHintFromDockWidget, + MinimumSizeHintFromContent, + MinimumSizeHintFromDockWidgetMinimumSize, + MinimumSizeHintFromContentMinimumSize, + }; + + enum eToggleViewActionMode + { + ActionModeToggle, + ActionModeShow + }; + + + CDockWidget(const QString &title, QWidget* parent /TransferThis/ = Q_NULLPTR); + virtual ~CDockWidget(); + virtual QSize minimumSizeHint() const; + void setWidget(QWidget* widget /Transfer/, ads::CDockWidget::eInsertMode InsertMode = AutoScrollArea); + QWidget* takeWidget() /TransferBack/; + QWidget* widget() const; + ads::CDockWidgetTab* tabWidget() const; + void setFeatures(ads::CDockWidget::DockWidgetFeatures features); + void setFeature(ads::CDockWidget::DockWidgetFeature flag, bool on); + ads::CDockWidget::DockWidgetFeatures features() const; + void notifyFeaturesChanged(); + ads::CDockManager* dockManager() const; + ads::CDockContainerWidget* dockContainer() const; + ads::CFloatingDockContainer* floatingDockContainer() const; + ads::CDockAreaWidget* dockAreaWidget() const; + ads::CAutoHideTab* sideTabWidget() const; + void setSideTabWidget(ads::CAutoHideTab* SideTab /Transfer/) const; + bool isAutoHide() const; + ads::CAutoHideDockContainer* autoHideDockContainer() const; + ads::SideBarLocation autoHideLocation() const; + bool isFloating() const; + bool isInFloatingContainer() const; + bool isClosed() const; + QAction* toggleViewAction() const; + void setToggleViewAction(QAction* action); + void setToggleViewActionMode(ads::CDockWidget::eToggleViewActionMode Mode); + void setMinimumSizeHintMode(ads::CDockWidget::eMinimumSizeHintMode Mode); + ads::CDockWidget::eMinimumSizeHintMode minimumSizeHintMode() const; + bool isCentralWidget() const; + void setIcon(const QIcon& Icon); + QIcon icon() const; + QToolBar* toolBar() const; + QToolBar* createDefaultToolBar(); + void setToolBar(QToolBar* ToolBar /Transfer/ ); + void setToolBarStyleSource(ads::CDockWidget::eToolBarStyleSource Source); + ads::CDockWidget::eToolBarStyleSource toolBarStyleSource() const; + void setToolBarStyle(Qt::ToolButtonStyle Style, ads::CDockWidget::eState State); + Qt::ToolButtonStyle toolBarStyle(ads::CDockWidget::eState State) const; + void setToolBarIconSize(const QSize& IconSize, ads::CDockWidget::eState State); + QSize toolBarIconSize(eState State) const; + void setTitleBarActions(QList actions); + virtual QList titleBarActions() const; + + void setTabToolTip(const QString &text); + bool isFullScreen() const; + bool isTabbed() const; + bool isCurrentTab() const; + +public: + virtual bool event(QEvent *e); + +public slots: + void toggleView(bool Open = true); + void setAsCurrentTab(); + void raise(); + void setFloating(); + void deleteDockWidget(); + void closeDockWidget(); + void requestCloseDockWidget(); + void showFullScreen(); + void showNormal(); + void setAutoHide(bool Enable, ads::SideBarLocation Location = ads::SideBarNone, int TabIndex = -1); + void toggleAutoHide(ads::SideBarLocation Location = ads::SideBarNone); + +signals: + void viewToggled(bool Open); + void closed(); + void titleChanged(const QString& Title); + void topLevelChanged(bool topLevel); + void closeRequested(); + void visibilityChanged(bool visible); + void featuresChanged(ads::CDockWidget::DockWidgetFeatures features); +}; + +}; + +%End diff --git a/sip/DockWidgetTab.sip b/sip/DockWidgetTab.sip new file mode 100644 index 000000000..0bc610aaf --- /dev/null +++ b/sip/DockWidgetTab.sip @@ -0,0 +1,55 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockWidgetTab : QFrame +{ + %TypeHeaderCode + #include + %End + +protected: + virtual void mousePressEvent(QMouseEvent* ev); + virtual void mouseReleaseEvent(QMouseEvent* ev); + virtual void mouseMoveEvent(QMouseEvent* ev); + virtual void contextMenuEvent(QContextMenuEvent* ev); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + +public: + CDockWidgetTab(ads::CDockWidget* DockWidget /TransferThis/, QWidget* parent /TransferThis/ = 0); + virtual ~CDockWidgetTab(); + bool isActiveTab() const; + void setActiveTab(bool active); + void setDockAreaWidget(ads::CDockAreaWidget* DockArea /Transfer/); + ads::CDockAreaWidget* dockAreaWidget() const; + ads::CDockWidget* dockWidget() const; + void setIcon(const QIcon& Icon); + const QIcon& icon() const; + QString text() const; + void setText(const QString& title); + bool isTitleElided() const; + bool isClosable() const; + virtual bool event(QEvent *e); + void setElideMode(Qt::TextElideMode mode); + void updateStyle(); + QSize iconSize() const; + void setIconSize(const QSize& Size); + +public slots: + virtual void setVisible(bool visible); + +signals: + void activeTabChanged(); + void clicked(); + void closeRequested(); + void closeOtherTabsRequested(); + void moved(const QPoint& GlobalPos); + void elidedChanged(bool elided); +}; // class DockWidgetTab +}; + // namespace ads + +%End diff --git a/sip/DockingStateReader.sip b/sip/DockingStateReader.sip new file mode 100644 index 000000000..e235efb90 --- /dev/null +++ b/sip/DockingStateReader.sip @@ -0,0 +1,21 @@ +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CDockingStateReader : QXmlStreamReader +{ + + %TypeHeaderCode + #include + %End + + +public: + void setFileVersion(int FileVersion); + int fileVersion() const; +}; + +}; + +%End \ No newline at end of file diff --git a/sip/ElidingLabel.sip b/sip/ElidingLabel.sip new file mode 100644 index 000000000..57fef9c18 --- /dev/null +++ b/sip/ElidingLabel.sip @@ -0,0 +1,41 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CElidingLabel : QLabel +{ + %TypeHeaderCode + #include + %End + +protected: + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void resizeEvent( QResizeEvent *event ); + virtual void mouseDoubleClickEvent( QMouseEvent *ev ); + +public: + CElidingLabel(QWidget* parent /TransferThis/ = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags ()); + CElidingLabel(const QString& text, QWidget* parent /TransferThis/ = Q_NULLPTR, Qt::WindowFlags f = Qt::WindowFlags ()); + virtual ~CElidingLabel(); + Qt::TextElideMode elideMode() const; + void setElideMode(Qt::TextElideMode mode); + bool isElided() const; + +public: + virtual QSize minimumSizeHint() const; + virtual QSize sizeHint() const; + void setText(const QString &text); + QString text() const; + +signals: + void clicked(); + void doubleClicked(); + void elidedChanged(bool elided); +}; + +}; + +%End diff --git a/sip/FloatingDockContainer.sip b/sip/FloatingDockContainer.sip new file mode 100644 index 000000000..79ed06e38 --- /dev/null +++ b/sip/FloatingDockContainer.sip @@ -0,0 +1,100 @@ +// NOTE: there is a separate sip/linux/FloatingDockContainer.sip as the base +// class for CFloatingDockContainer changes for Linux. + +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +%If (WS_X11) + typedef QDockWidget tFloatingWidgetBase; +%End +%If (!WS_X11) + typedef QWidget tFloatingWidgetBase; +%End + +namespace ads +{ + +class IFloatingWidget +{ + %TypeHeaderCode + #include + %End + +public: + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + ads::eDragState DragState, QWidget* MouseEventHandler) = 0; + + virtual void moveFloating() = 0; + virtual void finishDragging() = 0; + virtual ~IFloatingWidget(); +}; + + +class CFloatingDockContainer : tFloatingWidgetBase, ads::IFloatingWidget +{ + + %TypeHeaderCode + #include + %End + +protected: + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + ads::eDragState DragState, QWidget* MouseEventHandler); + void startDragging(const QPoint& DragStartMousePos, const QSize& Size, + QWidget* MouseEventHandler); + virtual void finishDragging(); + void deleteContent(); + void initFloatingGeometry(const QPoint& DragStartMousePos, const QSize& Size); + void moveFloating(); + bool restoreState(ads::CDockingStateReader& Stream, bool Testing); + void updateWindowTitle(); + + +protected: + virtual void changeEvent(QEvent *event); + virtual void closeEvent(QCloseEvent *event); + virtual void hideEvent(QHideEvent *event); + virtual void showEvent(QShowEvent *event); + + %If (WS_MACX) + virtual bool event(QEvent *e); + virtual void moveEvent(QMoveEvent *event); + %End + + %If (WS_X11) + virtual void moveEvent(QMoveEvent *event); + virtual void resizeEvent(QResizeEvent *event); + virtual bool event(QEvent *e); + %End + + %If (WS_WIN) + virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result); + %End + + +public: + CFloatingDockContainer(ads::CDockManager* DockManager /TransferThis/); + CFloatingDockContainer(ads::CDockAreaWidget* DockArea /TransferThis/); + CFloatingDockContainer(ads::CDockWidget* DockWidget /TransferThis/); + virtual ~CFloatingDockContainer(); + ads::CDockContainerWidget* dockContainer() const; + bool isClosable() const; + bool hasTopLevelDockWidget() const; + ads::CDockWidget* topLevelDockWidget() const; + QList dockWidgets() const; + void finishDropOperation(); + + %If (WS_X11) + void onMaximizeRequest(); + void showNormal(bool fixGeometry); + void showMaximized(); + bool isMaximized() const; + void show(); + bool hasNativeTitleBar(); + %End +}; + +}; + +%End diff --git a/sip/FloatingDragPreview.sip b/sip/FloatingDragPreview.sip new file mode 100644 index 000000000..ad5be7133 --- /dev/null +++ b/sip/FloatingDragPreview.sip @@ -0,0 +1,41 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CFloatingDragPreview : QWidget, ads::IFloatingWidget +{ + + %TypeHeaderCode + #include + %End + +protected: + virtual void paintEvent(QPaintEvent *e); + CFloatingDragPreview(QWidget* Content /TransferThis/, QWidget* parent /TransferThis/); + +public: + CFloatingDragPreview(ads::CDockWidget* Content /TransferThis/ ); + CFloatingDragPreview(ads::CDockAreaWidget* Content /TransferThis/ ); + + virtual ~CFloatingDragPreview(); + + virtual bool eventFilter(QObject* watched, QEvent* event); + +public: // implements IFloatingWidget + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + ads::eDragState DragState, QWidget* MouseEventHandler); + virtual void moveFloating(); + virtual void finishDragging(); + void cleanupAutoHideContainerWidget(ads::DockWidgetArea ContainerDropArea); + +signals: + void draggingCanceled(); + +}; + +}; + +%End diff --git a/sip/IconProvider.sip b/sip/IconProvider.sip new file mode 100644 index 000000000..e271651c8 --- /dev/null +++ b/sip/IconProvider.sip @@ -0,0 +1,28 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CIconProvider +{ + + %TypeHeaderCode + #include + %End + + +public: + CIconProvider(); + + virtual ~CIconProvider(); + + QIcon customIcon(eIcon IconId); + + void registerCustomIcon(eIcon IconId, const QIcon& icon /TransferThis/ ); +}; + +}; + +%End \ No newline at end of file diff --git a/sip/PushButton.sip b/sip/PushButton.sip new file mode 100644 index 000000000..5613f9271 --- /dev/null +++ b/sip/PushButton.sip @@ -0,0 +1,31 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + + +class CPushButton : QPushButton +{ + %TypeHeaderCode + #include + %End + +public: + enum Orientation { + Horizontal, + VerticalTopToBottom, + VerticalBottomToTop + }; + virtual QSize sizeHint() const; + + ads::CPushButton::Orientation buttonOrientation() const; + void setButtonOrientation(ads::CPushButton::Orientation orientation); + +}; + +}; + +%End + diff --git a/sip/ResizeHandle.sip b/sip/ResizeHandle.sip new file mode 100644 index 000000000..0cfdea1ab --- /dev/null +++ b/sip/ResizeHandle.sip @@ -0,0 +1,36 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + +class CResizeHandle : QFrame +{ + + %TypeHeaderCode + #include + %End + +protected: + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + +public: + CResizeHandle(Qt::Edge HandlePosition, QWidget* parent /TransferThis/); + virtual ~CResizeHandle(); + void setHandlePosition(Qt::Edge HandlePosition); + Qt::Edge handlePostion() const; + Qt::Orientation orientation() const; + QSize sizeHint() const; + bool isResizing() const; + void setMinResizeSize(int MinSize); + void setMaxResizeSize(int MaxSize); + void setOpaqueResize(bool opaque = true); + bool opaqueResize() const; +}; + +}; + +%End diff --git a/sip/ads.sip b/sip/ads.sip new file mode 100644 index 000000000..28c2695f3 --- /dev/null +++ b/sip/ads.sip @@ -0,0 +1,32 @@ +%Module(name=PyQtAds, call_super_init=True, keyword_arguments="Optional", use_limited_api=True) +%HideNamespace(name=ads) + +%Import QtCore/QtCoremod.sip +%DefaultSupertype sip.simplewrapper + +%Include ads_globals.sip +%Include AutoHideDockContainer.sip +%Include AutoHideSideBar.sip +%Include AutoHideTab.sip +%Include DockWidget.sip +%Include DockAreaTabBar.sip +%Include DockAreaTitleBar_p.sip +%Include DockAreaTitleBar.sip +%Include DockAreaWidget.sip +%Include DockComponentsFactory.sip +%Include DockContainerWidget.sip +%Include DockingStateReader.sip +%Include DockFocusController.sip +%Include DockManager.sip +%Include DockOverlay.sip +%Include DockSplitter.sip +%Include DockWidgetTab.sip +%Include ElidingLabel.sip +%Include FloatingDockContainer.sip +%Include FloatingDragPreview.sip +%Include IconProvider.sip +%Include PushButton.sip +%Include ResizeHandle.sip +%If (WS_X11) + %Include linux/FloatingWidgetTitleBar.sip +%End diff --git a/sip/ads_globals.sip b/sip/ads_globals.sip new file mode 100644 index 000000000..719b9306e --- /dev/null +++ b/sip/ads_globals.sip @@ -0,0 +1,163 @@ +%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 + %End + + enum DockWidgetArea + { + NoDockWidgetArea, + LeftDockWidgetArea, + RightDockWidgetArea, + TopDockWidgetArea, + BottomDockWidgetArea, + CenterDockWidgetArea, + LeftAutoHideArea, + RightAutoHideArea, + TopAutoHideArea, + BottomAutoHideArea, + InvalidDockWidgetArea, + OuterDockAreas, + AutoHideDockAreas, + AllDockAreas + }; + typedef QFlags DockWidgetAreas; + + + enum eTabIndex + { + TabDefaultInsertIndex, + TabInvalidIndex + }; + + enum TitleBarButton + { + TitleBarButtonTabsMenu, + TitleBarButtonUndock, + TitleBarButtonClose, + TitleBarButtonAutoHide, + TitleBarButtonMinimize + }; + + enum eDragState + { + DraggingInactive, + DraggingMousePressed, + DraggingTab, + DraggingFloatingWidget + }; + + enum eIcon + { + TabCloseIcon, + AutoHideIcon, + DockAreaMenuIcon, + DockAreaUndockIcon, + DockAreaCloseIcon, + IconCount, + }; + + enum eBitwiseOperator + { + BitwiseAnd, + BitwiseOr + }; + + enum SideBarLocation + { + SideBarTop, + SideBarLeft, + SideBarRight, + SideBarBottom, + SideBarNone + }; + + namespace internal + { + void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To); + void hideEmptyParentSplitters(ads::CDockSplitter* FirstParentSplitter); + + class CDockInsertParam + { + %TypeHeaderCode + #include + %End + + public: + Qt::Orientation orientation() const; + bool append() const; + int insertOffset() const; + }; + ads::internal::CDockInsertParam dockAreaInsertParameters(ads::DockWidgetArea Area); + ads::SideBarLocation toSideBarLocation(ads::DockWidgetArea Area); + bool isHorizontalSideBarLocation(ads::SideBarLocation Location); + bool isSideBarArea(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); + QRect globalGeometry(QWidget* w); + }; + +}; + +%End diff --git a/sip/linux/FloatingWidgetTitleBar.sip b/sip/linux/FloatingWidgetTitleBar.sip new file mode 100644 index 000000000..44a2d38ba --- /dev/null +++ b/sip/linux/FloatingWidgetTitleBar.sip @@ -0,0 +1,40 @@ +%Import QtWidgets/QtWidgetsmod.sip + +%If (Qt_5_0_0 -) + +namespace ads +{ + %TypeHeaderCode + #include + %End + +class CFloatingWidgetTitleBar : QWidget +{ + +protected: + virtual void mousePressEvent(QMouseEvent *ev); + virtual void mouseReleaseEvent(QMouseEvent *ev); + virtual void mouseMoveEvent(QMouseEvent *ev); + virtual void mouseDoubleClickEvent(QMouseEvent *event); + + void setMaximizeIcon(const QIcon& Icon); + QIcon maximizeIcon() const; + void setNormalIcon(const QIcon& Icon); + QIcon normalIcon() const; + +public: + explicit CFloatingWidgetTitleBar(CFloatingDockContainer *parent /TransferThis/ = 0); + virtual ~CFloatingWidgetTitleBar(); + void enableCloseButton(bool Enable); + void setTitle(const QString &Text); + void updateStyle(); + void setMaximizedIcon(bool maximized); + +signals: + void closeRequested(); + void maximizeRequested(); +}; + +}; + +%End diff --git a/src/AutoHideDockContainer.cpp b/src/AutoHideDockContainer.cpp new file mode 100644 index 000000000..c073791f7 --- /dev/null +++ b/src/AutoHideDockContainer.cpp @@ -0,0 +1,719 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideDockContainer.cpp +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Implementation of CAutoHideDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "AutoHideDockContainer.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "ResizeHandle.h" +#include "DockComponentsFactory.h" +#include "AutoHideSideBar.h" +#include "AutoHideTab.h" + + +#include + +namespace ads +{ +static const int ResizeMargin = 30; + +//============================================================================ +bool static isHorizontalArea(SideBarLocation Area) +{ + switch (Area) + { + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarBottom: return true; + case SideBarLocation::SideBarLeft: + case SideBarLocation::SideBarRight: return false; + default: + return true; + } + + return true; +} + + +//============================================================================ +Qt::Edge static edgeFromSideTabBarArea(SideBarLocation Area) +{ + switch (Area) + { + case SideBarLocation::SideBarTop: return Qt::BottomEdge; + case SideBarLocation::SideBarBottom: return Qt::TopEdge; + case SideBarLocation::SideBarLeft: return Qt::RightEdge; + case SideBarLocation::SideBarRight: return Qt::LeftEdge; + default: + return Qt::LeftEdge; + } + + return Qt::LeftEdge; +} + + +//============================================================================ +int resizeHandleLayoutPosition(SideBarLocation Area) +{ + switch (Area) + { + case SideBarLocation::SideBarBottom: + case SideBarLocation::SideBarRight: return 0; + + case SideBarLocation::SideBarTop: + case SideBarLocation::SideBarLeft: return 1; + + default: + return 0; + } + + return 0; +} + + +/** + * Private data of CAutoHideDockContainer - pimpl + */ +struct AutoHideDockContainerPrivate +{ + CAutoHideDockContainer* _this; + CDockAreaWidget* DockArea{nullptr}; + CDockWidget* DockWidget{nullptr}; + SideBarLocation SideTabBarArea = SideBarNone; + QBoxLayout* Layout = nullptr; + CResizeHandle* ResizeHandle = nullptr; + QSize Size; // creates invalid size + QPointer SideTab; + QSize SizeCache; + + /** + * Private data constructor + */ + AutoHideDockContainerPrivate(CAutoHideDockContainer *_public); + + /** + * Convenience function to get a dock widget area + */ + DockWidgetArea getDockWidgetArea(SideBarLocation area) + { + switch (area) + { + case SideBarLocation::SideBarLeft: return LeftDockWidgetArea; + case SideBarLocation::SideBarRight: return RightDockWidgetArea; + case SideBarLocation::SideBarBottom: return BottomDockWidgetArea; + case SideBarLocation::SideBarTop: return TopDockWidgetArea; + default: + return LeftDockWidgetArea; + } + + return LeftDockWidgetArea; + } + + /** + * Update the resize limit of the resize handle + */ + void updateResizeHandleSizeLimitMax() + { + auto Rect = _this->dockContainer()->contentRect(); + const auto maxResizeHandleSize = ResizeHandle->orientation() == Qt::Horizontal + ? Rect.width() : Rect.height(); + ResizeHandle->setMaxResizeSize(maxResizeHandleSize - ResizeMargin); + } + + /** + * Convenience function to check, if this is an horizontal area + */ + bool isHorizontal() const + { + return isHorizontalArea(SideTabBarArea); + } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent* event) + { + auto DockContainer = _this->dockContainer(); + if (DockContainer) + { + DockContainer->handleAutoHideWidgetEvent(event, _this); + } + } + +}; // struct AutoHideDockContainerPrivate + + +//============================================================================ +AutoHideDockContainerPrivate::AutoHideDockContainerPrivate( + CAutoHideDockContainer *_public) : + _this(_public) +{ + +} + + +//============================================================================ +CDockContainerWidget* CAutoHideDockContainer::dockContainer() const +{ + return internal::findParent(this); +} + + +//============================================================================ +CAutoHideDockContainer::CAutoHideDockContainer(CDockWidget* DockWidget, SideBarLocation area, CDockContainerWidget* parent) : + Super(parent), + d(new AutoHideDockContainerPrivate(this)) +{ + hide(); // auto hide dock container is initially always hidden + d->SideTabBarArea = area; + d->SideTab = componentsFactory()->createDockWidgetSideTab(nullptr); + connect(d->SideTab, &CAutoHideTab::pressed, this, &CAutoHideDockContainer::toggleCollapseState); + d->DockArea = new CDockAreaWidget(DockWidget->dockManager(), parent); + d->DockArea->setObjectName("autoHideDockArea"); + d->DockArea->setAutoHideDockContainer(this); + + setObjectName("autoHideDockContainer"); + + d->Layout = new QBoxLayout(isHorizontalArea(area) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + d->ResizeHandle = new CResizeHandle(edgeFromSideTabBarArea(area), this); + d->ResizeHandle->setMinResizeSize(64); + bool OpaqueResize = CDockManager::testConfigFlag(CDockManager::OpaqueSplitterResize); + d->ResizeHandle->setOpaqueResize(OpaqueResize); + d->Size = d->DockArea->size(); + d->SizeCache = DockWidget->size(); + + addDockWidget(DockWidget); + parent->registerAutoHideWidget(this); + // The dock area should not be added to the layout before it contains the + // dock widget. If you add it to the layout before it contains the dock widget + // then you will likely see this warning for OpenGL widgets or QAxWidgets: + // setGeometry: Unable to set geometry XxY+Width+Height on QWidgetWindow/'WidgetClassWindow + d->Layout->addWidget(d->DockArea); + d->Layout->insertWidget(resizeHandleLayoutPosition(area), d->ResizeHandle); +} + + +//============================================================================ +void CAutoHideDockContainer::updateSize() +{ + auto dockContainerParent = dockContainer(); + if (!dockContainerParent) + { + return; + } + + auto rect = dockContainerParent->contentRect(); + switch (sideBarLocation()) + { + case SideBarLocation::SideBarTop: + resize(rect.width(), qMin(rect.height() - ResizeMargin, d->Size.height())); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarLeft: + resize(qMin(d->Size.width(), rect.width() - ResizeMargin), rect.height()); + move(rect.topLeft()); + break; + + case SideBarLocation::SideBarRight: + { + resize(qMin(d->Size.width(), rect.width() - ResizeMargin), rect.height()); + QPoint p = rect.topRight(); + p.rx() -= (width() - 1); + move(p); + } + break; + + case SideBarLocation::SideBarBottom: + { + resize(rect.width(), qMin(rect.height() - ResizeMargin, d->Size.height())); + QPoint p = rect.bottomLeft(); + p.ry() -= (height() - 1); + move(p); + } + break; + + default: + break; + } + + if (orientation() == Qt::Horizontal) + { + d->SizeCache.setHeight(this->height()); + } + else + { + d->SizeCache.setWidth(this->width()); + } +} + +//============================================================================ +CAutoHideDockContainer::~CAutoHideDockContainer() +{ + ADS_PRINT("~CAutoHideDockContainer"); + + // Remove event filter in case there are any queued messages + qApp->removeEventFilter(this); + if (dockContainer()) + { + dockContainer()->removeAutoHideWidget(this); + } + + if (d->SideTab) + { + delete d->SideTab; + } + + delete d; +} + +//============================================================================ +CAutoHideSideBar* CAutoHideDockContainer::autoHideSideBar() const +{ + if (d->SideTab) + { + return d->SideTab->sideBar(); + } + else + { + auto DockContainer = dockContainer(); + return DockContainer ? DockContainer->autoHideSideBar(d->SideTabBarArea) : nullptr; + } +} + + +//============================================================================ +CAutoHideTab* CAutoHideDockContainer::autoHideTab() const +{ + return d->SideTab; +} + + +//============================================================================ +CDockWidget* CAutoHideDockContainer::dockWidget() const +{ + return d->DockWidget; +} + +//============================================================================ +void CAutoHideDockContainer::addDockWidget(CDockWidget* DockWidget) +{ + if (d->DockWidget) + { + // Remove the old dock widget at this area + d->DockArea->removeDockWidget(d->DockWidget); + } + + d->DockWidget = DockWidget; + d->SideTab->setDockWidget(DockWidget); + CDockAreaWidget* OldDockArea = DockWidget->dockAreaWidget(); + auto IsRestoringState = DockWidget->dockManager()->isRestoringState(); + if (OldDockArea && !IsRestoringState) + { + // The initial size should be a little bit bigger than the original dock + // area size to prevent that the resize handle of this auto hid dock area + // is near of the splitter of the old dock area. + d->Size = OldDockArea->size() + QSize(16, 16); + OldDockArea->removeDockWidget(DockWidget); + } + d->DockArea->addDockWidget(DockWidget); + updateSize(); + // The dock area is not visible and will not update the size when updateSize() + // is called for this auto hide container. Therefore we explicitly resize + // it here. As soon as it will become visible, it will get the right size + d->DockArea->resize(size()); +} + + +//============================================================================ +SideBarLocation CAutoHideDockContainer::sideBarLocation() const +{ + return d->SideTabBarArea; +} + + +//============================================================================ +void CAutoHideDockContainer::setSideBarLocation(SideBarLocation SideBarLocation) +{ + if (d->SideTabBarArea == SideBarLocation) + { + return; + } + + d->SideTabBarArea = SideBarLocation; + d->Layout->removeWidget(d->ResizeHandle); + d->Layout->setDirection(isHorizontalArea(SideBarLocation) ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + d->Layout->insertWidget(resizeHandleLayoutPosition(SideBarLocation), d->ResizeHandle); + d->ResizeHandle->setHandlePosition(edgeFromSideTabBarArea(SideBarLocation)); + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + + +//============================================================================ +CDockAreaWidget* CAutoHideDockContainer::dockAreaWidget() const +{ + return d->DockArea; +} + +//============================================================================ +void CAutoHideDockContainer::moveContentsToParent() +{ + cleanupAndDelete(); + // If we unpin the auto hide dock widget, then we insert it into the same + // location like it had as a auto hide widget. This brings the least surprise + // to the user and he does not have to search where the widget was inserted. + d->DockWidget->setDockArea(nullptr); + auto DockContainer = dockContainer(); + DockContainer->addDockWidget(d->getDockWidgetArea(d->SideTabBarArea), d->DockWidget); +} + + +//============================================================================ +void CAutoHideDockContainer::cleanupAndDelete() +{ + const auto dockWidget = d->DockWidget; + if (dockWidget) + { + + auto SideTab = d->SideTab; + SideTab->removeFromSideBar(); + SideTab->setParent(nullptr); + SideTab->hide(); + } + + hide(); + deleteLater(); +} + + +//============================================================================ +void CAutoHideDockContainer::saveState(QXmlStreamWriter& s) +{ + s.writeStartElement("Widget"); + s.writeAttribute("Name", d->DockWidget->objectName()); + s.writeAttribute("Closed", QString::number(d->DockWidget->isClosed() ? 1 : 0)); + s.writeAttribute("Size", QString::number(d->isHorizontal() ? d->Size.height() : d->Size.width())); + s.writeEndElement(); +} + + +//============================================================================ +void CAutoHideDockContainer::toggleView(bool Enable) +{ + if (Enable) + { + if (d->SideTab) + { + d->SideTab->show(); + } + } + else + { + if (d->SideTab) + { + d->SideTab->hide(); + } + hide(); + qApp->removeEventFilter(this); + } +} + + +//============================================================================ +void CAutoHideDockContainer::collapseView(bool Enable) +{ + if (Enable) + { + hide(); + qApp->removeEventFilter(this); + } + else + { + updateSize(); + d->updateResizeHandleSizeLimitMax(); + raise(); + show(); + d->DockWidget->dockManager()->setDockWidgetFocused(d->DockWidget); + qApp->installEventFilter(this); + } + + ADS_PRINT("CAutoHideDockContainer::collapseView " << Enable); + d->SideTab->updateStyle(); +} + + +//============================================================================ +void CAutoHideDockContainer::toggleCollapseState() +{ + collapseView(isVisible()); +} + + +//============================================================================ +void CAutoHideDockContainer::setSize(int Size) +{ + if (d->isHorizontal()) + { + d->Size.setHeight(Size); + } + else + { + d->Size.setWidth(Size); + } + + updateSize(); +} + + +//============================================================================ +/** + * Returns true if the object given in ancestor is an ancestor of the object + * given in descendant + */ +static bool objectIsAncestorOf(const QObject* descendant, const QObject* ancestor) +{ + if (!ancestor) + { + return false; + } + while (descendant) + { + if (descendant == ancestor) + { + return true; + } + descendant = descendant->parent(); + } + return false; +} + + +//============================================================================ +/** + * Returns true if the object given in ancestor is the object given in descendant + * or if it is an ancestor of the object given in descendant + */ +static bool isObjectOrAncestor(const QObject *descendant, const QObject *ancestor) +{ + if (ancestor && (descendant == ancestor)) + { + return true; + } + else + { + return objectIsAncestorOf(descendant, ancestor); + } +} + + +//============================================================================ +bool CAutoHideDockContainer::eventFilter(QObject* watched, QEvent* event) +{ + // A switch case statement would be nicer here, but we cannot use + // internal::FloatingWidgetDragStartEvent in a switch case + if (event->type() == QEvent::Resize) + { + if (!d->ResizeHandle->isResizing()) + { + updateSize(); + } + } + else if (event->type() == QEvent::MouseButtonPress) + { + auto widget = qobject_cast(watched); + // Ignore non widget events + if (!widget) + { + return Super::eventFilter(watched, event); + } + + // Now check, if the user clicked into the side tab and ignore this event, + // because the side tab click handler will call collapseView(). If we + // do not ignore this here, then we will collapse the container and the side tab + // click handler will uncollapse it + if (widget == d->SideTab.data()) + { + return Super::eventFilter(watched, event); + } + + // Now we check, if the user clicked inside of this auto hide container. + // If the click is inside of this auto hide container, then we can + // ignore the event, because the auto hide overlay should not get collapsed if + // user works in it + if (isObjectOrAncestor(widget, this)) + { + return Super::eventFilter(watched, event); + } + + // Ignore the mouse click if it is not inside of this container + if (!isObjectOrAncestor(widget, dockContainer())) + { + return Super::eventFilter(watched, event); + } + + // user clicked into container - collapse the auto hide widget + collapseView(true); + } + else if (event->type() == internal::FloatingWidgetDragStartEvent) + { + // If we are dragging our own floating widget, the we do not need to + // collapse the view + auto FloatingWidget = dockContainer()->floatingWidget(); + if (FloatingWidget != watched) + { + collapseView(true); + } + } + else if (event->type() == internal::DockedWidgetDragStartEvent) + { + collapseView(true); + } + + return Super::eventFilter(watched, event); +} + + +//============================================================================ +void CAutoHideDockContainer::resizeEvent(QResizeEvent* event) +{ + Super::resizeEvent(event); + if (d->ResizeHandle->isResizing()) + { + d->Size = this->size(); + d->updateResizeHandleSizeLimitMax(); + } +} + + +//============================================================================ +void CAutoHideDockContainer::leaveEvent(QEvent *event) +{ + // Resizing of the dock container via the resize handle in non opaque mode + // mays cause a leave event that is not really a leave event. Therefore + // we check here, if we are really outside of our rect. + auto pos = mapFromGlobal(QCursor::pos()); + if (!rect().contains(pos)) + { + d->forwardEventToDockContainer(event); + } + Super::leaveEvent(event); +} + + +//============================================================================ +bool CAutoHideDockContainer::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::Enter: + case QEvent::Hide: + d->forwardEventToDockContainer(event); + break; + + case QEvent::MouseButtonPress: + return true; + break; + + default: + break; + } + + return Super::event(event); +} + +//============================================================================ +void CAutoHideDockContainer::dragLeaveEvent(QDragLeaveEvent*) +{ + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover)) + { + collapseView(true); + } +} + +//============================================================================ +Qt::Orientation CAutoHideDockContainer::orientation() const +{ + return ads::internal::isHorizontalSideBarLocation(d->SideTabBarArea) + ? Qt::Horizontal : Qt::Vertical; +} + + +//============================================================================ +void CAutoHideDockContainer::resetToInitialDockWidgetSize() +{ + if (orientation() == Qt::Horizontal) + { + setSize(d->SizeCache.height()); + } + else + { + setSize(d->SizeCache.width()); + } +} + + +//============================================================================ +void CAutoHideDockContainer::moveToNewSideBarLocation(SideBarLocation NewSideBarLocation, + int TabIndex) +{ + if (NewSideBarLocation == sideBarLocation() && TabIndex == this->tabIndex()) + { + return; + } + + auto OldOrientation = orientation(); + auto SideBar = dockContainer()->autoHideSideBar(NewSideBarLocation); + SideBar->addAutoHideWidget(this, TabIndex); + // If we move a horizontal auto hide container to a vertical position + // then we resize it to the original dock widget size, to avoid + // an extremely stretched dock widget after insertion + if (SideBar->orientation() != OldOrientation) + { + resetToInitialDockWidgetSize(); + } +} + + +//============================================================================ +int CAutoHideDockContainer::tabIndex() const +{ + return d->SideTab->tabIndex(); +} + +} diff --git a/src/AutoHideDockContainer.h b/src/AutoHideDockContainer.h new file mode 100644 index 000000000..e12dbbaca --- /dev/null +++ b/src/AutoHideDockContainer.h @@ -0,0 +1,203 @@ +#ifndef AutoHideDockContainerH +#define AutoHideDockContainerH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideDockContainer.h +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Declaration of CAutoHideDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" + +#include +#include "AutoHideTab.h" + +QT_FORWARD_DECLARE_CLASS(QXmlStreamWriter) + +namespace ads +{ +struct AutoHideDockContainerPrivate; +class CDockManager; +class CDockWidget; +class CDockContainerWidget; +class CAutoHideSideBar; +class CDockAreaWidget; +class CDockingStateReader; +struct SideTabBarPrivate; + +/** + * Auto hide container for hosting an auto hide dock widget + */ +class ADS_EXPORT CAutoHideDockContainer : public QFrame +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation) +private: + AutoHideDockContainerPrivate* d; ///< private data (pimpl) + friend struct AutoHideDockContainerPrivate; + friend CAutoHideSideBar; + friend SideTabBarPrivate; + +protected: + virtual bool eventFilter(QObject* watched, QEvent* event) override; + virtual void resizeEvent(QResizeEvent* event) override; + virtual void leaveEvent(QEvent *event) override; + virtual bool event(QEvent* event) override; + virtual void dragLeaveEvent(QDragLeaveEvent* ev) override; + + /** + * Updates the size considering the size limits and the resize margins + */ + void updateSize(); + + /* + * Saves the state and size + */ + void saveState(QXmlStreamWriter& Stream); + +public: + using Super = QFrame; + + /** + * Create Auto Hide widget with the given dock widget + */ + CAutoHideDockContainer(CDockWidget* DockWidget, SideBarLocation area, + CDockContainerWidget* parent); + + /** + * Virtual Destructor + */ + virtual ~CAutoHideDockContainer(); + + /** + * Gets the side tab bar + */ + CAutoHideSideBar* autoHideSideBar() const; + + /** + * Returns the side tab + */ + CAutoHideTab* autoHideTab() const; + + /** + * Gets the dock widget in this dock container + */ + CDockWidget* dockWidget() const; + + /** + * Returns the index of this container in the sidebar + */ + int tabIndex() const; + + /** + * Adds a dock widget and removes the previous dock widget + */ + void addDockWidget(CDockWidget* DockWidget); + + /** + * Returns the side tab bar area of this Auto Hide dock container + */ + SideBarLocation sideBarLocation() const; + + /** + * Sets a new SideBarLocation. + * If a new side bar location is set, the auto hide dock container needs + * to update its resize handle position + */ + void setSideBarLocation(SideBarLocation SideBarLocation); + + /** + * Returns the dock area widget of this Auto Hide dock container + */ + CDockAreaWidget* dockAreaWidget() const; + + /** + * Returns the parent container that hosts this auto hide container + */ + CDockContainerWidget* dockContainer() const; + + /** + * Moves the contents to the parent container widget + * Used before removing this Auto Hide dock container + */ + void moveContentsToParent(); + + /** + * Cleanups up the side tab widget and then deletes itself + */ + void cleanupAndDelete(); + + /* + * Toggles the auto Hide dock container widget + * This will also hide the side tab widget + */ + void toggleView(bool Enable); + + /* + * Collapses the auto hide dock container widget + * Does not hide the side tab widget + */ + void collapseView(bool Enable); + + /** + * Toggles the current collapse state + */ + void toggleCollapseState(); + + /** + * Use this instead of resize. + * Depending on the sidebar location this will set the width or height + * of this auto hide container. + */ + void setSize(int Size); + + /** + * Resets the width or height to the initial dock widget size dependinng on + * the orientation. + * If the orientation is Qt::Horizontal, then the height is reset to + * the initial size and if orientation is Qt::Vertical, then the width is + * reset to the initial size + */ + void resetToInitialDockWidgetSize(); + + /** + * Returns orientation of this container. + * Left and right containers have a Qt::Vertical orientation and top / bottom + * containers have a Qt::Horizontal orientation. + * The function returns the orientation of the corresponding auto hide + * side bar. + */ + Qt::Orientation orientation() const; + + /** + * Removes the AutoHide container from the current side bar and adds + * it to the new side bar given in SideBarLocation + */ + void moveToNewSideBarLocation(SideBarLocation SideBarLocation, int TabIndex = -1); +}; +} // namespace ads + +//----------------------------------------------------------------------------- +#endif diff --git a/src/AutoHideSideBar.cpp b/src/AutoHideSideBar.cpp new file mode 100644 index 000000000..7dea2fe3e --- /dev/null +++ b/src/AutoHideSideBar.cpp @@ -0,0 +1,508 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideSideBar.cpp +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Implementation of CAutoHideSideBar class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include "AutoHideSideBar.h" + +#include +#include +#include +#include + +#include "DockContainerWidget.h" +#include "DockWidgetTab.h" +#include "DockFocusController.h" +#include "AutoHideDockContainer.h" +#include "DockAreaWidget.h" +#include "DockingStateReader.h" +#include "AutoHideTab.h" + +namespace ads +{ +class CTabsWidget; + +/** + * Private data class of CSideTabBar class (pimpl) + */ +struct AutoHideSideBarPrivate +{ + /** + * Private data constructor + */ + AutoHideSideBarPrivate(CAutoHideSideBar* _public); + + CAutoHideSideBar* _this; + CDockContainerWidget* ContainerWidget; + CTabsWidget* TabsContainerWidget; + QBoxLayout* TabsLayout; + Qt::Orientation Orientation; + SideBarLocation SideTabArea = SideBarLocation::SideBarLeft; + + /** + * Convenience function to check if this is a horizontal side bar + */ + bool isHorizontal() const + { + return Qt::Horizontal == Orientation; + } + + /** + * Called from viewport to forward event handling to this + */ + void handleViewportEvent(QEvent* e); +}; // struct AutoHideSideBarPrivate + + +/** + * This widget stores the tab buttons + */ +class CTabsWidget : public QWidget +{ +public: + using QWidget::QWidget; + using Super = QWidget; + AutoHideSideBarPrivate* EventHandler; + + /** + * Returns the size hint as minimum size hint + */ + virtual QSize minimumSizeHint() const override + { + return Super::sizeHint(); + } + + /** + * Forward event handling to EventHandler + */ + virtual bool event(QEvent* e) override + { + EventHandler->handleViewportEvent(e); + return Super::event(e); + } +}; + + +//============================================================================ +AutoHideSideBarPrivate::AutoHideSideBarPrivate(CAutoHideSideBar* _public) : + _this(_public) +{ +} + + +//============================================================================ +void AutoHideSideBarPrivate::handleViewportEvent(QEvent* e) +{ + switch (e->type()) + { + case QEvent::ChildRemoved: + if (TabsLayout->isEmpty()) + { + _this->hide(); + } + break; + + default: + break; + } +} + + +//============================================================================ +CAutoHideSideBar::CAutoHideSideBar(CDockContainerWidget* parent, SideBarLocation area) : + Super(parent), + d(new AutoHideSideBarPrivate(this)) +{ + d->SideTabArea = area; + d->ContainerWidget = parent; + d->Orientation = (area == SideBarLocation::SideBarBottom || area == SideBarLocation::SideBarTop) + ? Qt::Horizontal : Qt::Vertical; + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->TabsContainerWidget = new CTabsWidget(); + d->TabsContainerWidget->EventHandler = d; + d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->TabsContainerWidget->setObjectName("sideTabsContainerWidget"); + + + d->TabsLayout = new QBoxLayout(d->Orientation == Qt::Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight); + d->TabsLayout->setContentsMargins(0, 0, 0, 0); + d->TabsLayout->setSpacing(12); + d->TabsLayout->addStretch(1); + d->TabsContainerWidget->setLayout(d->TabsLayout); + setWidget(d->TabsContainerWidget); + + setFocusPolicy(Qt::NoFocus); + if (d->isHorizontal()) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + } + else + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + } + + hide(); +} + + +//============================================================================ +CAutoHideSideBar::~CAutoHideSideBar() +{ + ADS_PRINT("~CSideTabBar()"); + // The SideTabeBar is not the owner of the tabs and to prevent deletion + // we set the parent here to nullptr to remove it from the children + auto Tabs = findChildren(QString(), Qt::FindDirectChildrenOnly); + for (auto Tab : Tabs) + { + Tab->setParent(nullptr); + } + delete d; +} + + +//============================================================================ +void CAutoHideSideBar::insertTab(int Index, CAutoHideTab* SideTab) +{ + SideTab->setSideBar(this); + SideTab->installEventFilter(this); + // Default insertion is append + if (Index < 0) + { + d->TabsLayout->insertWidget(d->TabsLayout->count() - 1, SideTab); + } + else + { + d->TabsLayout->insertWidget(Index, SideTab); + } + show(); +} + + +//============================================================================ +CAutoHideDockContainer* CAutoHideSideBar::insertDockWidget(int Index, CDockWidget* DockWidget) +{ + auto AutoHideContainer = new CAutoHideDockContainer(DockWidget, d->SideTabArea, d->ContainerWidget); + DockWidget->dockManager()->dockFocusController()->clearDockWidgetFocus(DockWidget); + auto Tab = AutoHideContainer->autoHideTab(); + DockWidget->setSideTabWidget(Tab); + insertTab(Index, Tab); + return AutoHideContainer; +} + + +//============================================================================ +void CAutoHideSideBar::removeAutoHideWidget(CAutoHideDockContainer* AutoHideWidget) +{ + AutoHideWidget->autoHideTab()->removeFromSideBar(); + auto DockContainer = AutoHideWidget->dockContainer(); + if (DockContainer) + { + DockContainer->removeAutoHideWidget(AutoHideWidget); + } + AutoHideWidget->setParent(nullptr); +} + +//============================================================================ +void CAutoHideSideBar::addAutoHideWidget(CAutoHideDockContainer* AutoHideWidget, + int TabIndex) +{ + auto SideBar = AutoHideWidget->autoHideTab()->sideBar(); + if (SideBar == this) + { + // If we move to the same tab index or if we insert before the next + // tab index, then we will end at the same tab position and can leave + if (AutoHideWidget->tabIndex() == TabIndex || (AutoHideWidget->tabIndex() + 1) == TabIndex) + { + return; + } + + // We remove this auto hide widget from the sidebar in the code below + // and therefore need to correct the TabIndex here + if (AutoHideWidget->tabIndex() < TabIndex) + { + --TabIndex; + } + } + + if (SideBar) + { + SideBar->removeAutoHideWidget(AutoHideWidget); + } + AutoHideWidget->setParent(d->ContainerWidget); + AutoHideWidget->setSideBarLocation(d->SideTabArea); + d->ContainerWidget->registerAutoHideWidget(AutoHideWidget); + insertTab(TabIndex, AutoHideWidget->autoHideTab()); +} + + +//============================================================================ +void CAutoHideSideBar::removeTab(CAutoHideTab* SideTab) +{ + SideTab->removeEventFilter(this); + d->TabsLayout->removeWidget(SideTab); + if (d->TabsLayout->isEmpty()) + { + hide(); + } +} + + +//============================================================================ +bool CAutoHideSideBar::eventFilter(QObject *watched, QEvent *event) +{ + auto Tab = qobject_cast(watched); + if (!Tab) + { + return false; + } + + switch (event->type()) + { + case QEvent::ShowToParent: + show(); + break; + + case QEvent::HideToParent: + if (!hasVisibleTabs()) + { + hide(); + } + break; + + default: + break; + } + + return false; +} + + +//============================================================================ +Qt::Orientation CAutoHideSideBar::orientation() const +{ + return d->Orientation; +} + + +//============================================================================ +CAutoHideTab* CAutoHideSideBar::tab(int index) const +{ + return qobject_cast(d->TabsLayout->itemAt(index)->widget()); +} + + +//============================================================================ +int CAutoHideSideBar::count() const +{ + return d->TabsLayout->count() - 1; +} + + +//============================================================================ +int CAutoHideSideBar::visibleTabCount() const +{ + int VisibleTabCount = 0; + auto ParentWidget = parentWidget(); + for (auto i = 0; i < count(); i++) + { + if (tab(i)->isVisibleTo(ParentWidget)) + { + VisibleTabCount++; + } + } + + return VisibleTabCount; +} + + +//============================================================================ +bool CAutoHideSideBar::hasVisibleTabs() const +{ + auto ParentWidget = parentWidget(); + for (auto i = 0; i < count(); i++) + { + if (tab(i)->isVisibleTo(ParentWidget)) + { + return true; + } + } + + return false; +} + + +//============================================================================ +int CAutoHideSideBar::indexOfTab(const CAutoHideTab& Tab) const +{ + for (auto i = 0; i < count(); i++) + { + if (tab(i) == &Tab) + { + return i; + } + } + + return -1; +} + + +//============================================================================ +SideBarLocation CAutoHideSideBar::sideBarLocation() const +{ + return d->SideTabArea; +} + + +//============================================================================ +void CAutoHideSideBar::saveState(QXmlStreamWriter& s) const +{ + if (!count()) + { + return; + } + + s.writeStartElement("SideBar"); + s.writeAttribute("Area", QString::number(sideBarLocation())); + s.writeAttribute("Tabs", QString::number(count())); + + for (auto i = 0; i < count(); ++i) + { + auto Tab = tab(i); + if (!Tab) + { + continue; + } + + Tab->dockWidget()->autoHideDockContainer()->saveState(s); + } + + s.writeEndElement(); +} + +//=========================================================================== +QSize CAutoHideSideBar::minimumSizeHint() const +{ + QSize Size = sizeHint(); + if (d->isHorizontal()) + { + Size.setWidth(0); + } + else + { + Size.setHeight(0); + } + return Size; +} + + +//=========================================================================== +QSize CAutoHideSideBar::sizeHint() const +{ + return d->TabsContainerWidget->sizeHint(); +} + + +//=========================================================================== +int CAutoHideSideBar::spacing() const +{ + return d->TabsLayout->spacing(); +} + + +//=========================================================================== +void CAutoHideSideBar::setSpacing(int Spacing) +{ + d->TabsLayout->setSpacing(Spacing); +} + + +//=========================================================================== +CDockContainerWidget* CAutoHideSideBar::dockContainer() const +{ + return d->ContainerWidget; +} + + +//=========================================================================== +int CAutoHideSideBar::tabAt(const QPoint& Pos) const +{ + if (!isVisible()) + { + return TabInvalidIndex; + } + + if (orientation() == Qt::Horizontal) + { + if (Pos.x() < tab(0)->geometry().x()) + { + return -1; + } + } + else + { + if (Pos.y() < tab(0)->geometry().y()) + { + return -1; + } + } + + + for (int i = 0; i < count(); ++i) + { + if (tab(i)->geometry().contains(Pos)) + { + return i; + } + } + + return count(); +} + + +//=========================================================================== +int CAutoHideSideBar::tabInsertIndexAt(const QPoint& Pos) const +{ + int Index = tabAt(Pos); + if (Index == TabInvalidIndex) + { + return TabDefaultInsertIndex; + } + else + { + return (Index < 0) ? 0 : Index; + } +} + +} // namespace ads + diff --git a/src/AutoHideSideBar.h b/src/AutoHideSideBar.h new file mode 100644 index 000000000..350568253 --- /dev/null +++ b/src/AutoHideSideBar.h @@ -0,0 +1,205 @@ +#ifndef AutoHideSideBarH +#define AutoHideSideBarH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideSideBar.h +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Declaration of CAutoHideSideBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "ads_globals.h" +#include "AutoHideTab.h" + +QT_FORWARD_DECLARE_CLASS(QXmlStreamWriter) + +namespace ads +{ +struct AutoHideSideBarPrivate; +class DockContainerWidgetPrivate; +class CDockContainerWidget; +class CAutoHideTab; +class CAutoHideDockContainer; +class CDockingStateReader; + +/** + * Side tab bar widget that is shown at the edges of a dock container. + * The tab bar is only visible, if it contains visible content, that means if + * it contains visible tabs. If it is empty or all tabs are hidden, then the + * side bar is also hidden. As soon as one single tab becomes visible, this + * tab bar will be shown. + * The CAutoHideSideBar uses a QScrollArea here, to enable proper resizing. + * If the side bar contains many tabs, then the tabs are simply clipped - this + * is the same like in visual studio + */ +class ADS_EXPORT CAutoHideSideBar : public QScrollArea +{ + Q_OBJECT + Q_PROPERTY(int sideBarLocation READ sideBarLocation) + Q_PROPERTY(Qt::Orientation orientation READ orientation) + Q_PROPERTY(int spacing READ spacing WRITE setSpacing) + +private: + AutoHideSideBarPrivate* d; ///< private data (pimpl) + friend struct AutoHideSideBarPrivate; + friend class DockWidgetSideTab; + friend DockContainerWidgetPrivate; + friend CDockContainerWidget; + +protected: + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter& Stream) const; + + /** + * Inserts the given dock widget tab at the given position. + * An Index value of -1 appends the side tab at the end. + */ + void insertTab(int Index, CAutoHideTab* SideTab); + +public: + using Super = QScrollArea; + + /** + * Default Constructor + */ + CAutoHideSideBar(CDockContainerWidget* parent, SideBarLocation area); + + /** + * Virtual Destructor + */ + virtual ~CAutoHideSideBar(); + + /** + * Removes the given DockWidgetSideTab from the tabbar + */ + void removeTab(CAutoHideTab* SideTab); + + /** + * Insert dock widget into the side bar. + * The function creates the auto hide dock container, inserts the + * auto hide tab + */ + CAutoHideDockContainer* insertDockWidget(int Index, CDockWidget* DockWidget); + + /** + * Removes the auto hide widget from this side bar + */ + void removeAutoHideWidget(CAutoHideDockContainer* AutoHideWidget); + + /** + * Adds the given AutoHideWidget to this sidebar. + * If the AutoHideWidget is in another sidebar, then it will be removed + * from this sidebar. + */ + void addAutoHideWidget(CAutoHideDockContainer* AutoHideWidget, int Index = TabDefaultInsertIndex); + + /** + * Returns orientation of side tab. + */ + Qt::Orientation orientation() const; + + /* + * Get the side tab widget at position, returns nullptr if it's out of bounds + */ + CAutoHideTab* tab(int index) const; + + /** + * Returns the tab at the given position. + * Returns -1 if the position is left of the first tab and count() if the + * position is right of the last tab. Returns InvalidTabIndex (-2) to + * indicate an invalid value. + */ + int tabAt(const QPoint& Pos) const; + + /** + * Returns the tab insertion index for the given mouse cursor position + */ + int tabInsertIndexAt(const QPoint& Pos) const; + + /** + * Returns the index of the given tab + */ + int indexOfTab(const CAutoHideTab& Tab) const; + + /* + * Gets the count of the tab widgets + */ + int count() const; + + /** + * Returns the number of visible tabs to its parent widget. + */ + int visibleTabCount() const; + + /** + * Returns true, if the sidebar contains visible tabs to its parent widget. + * The function returns as soon as it finds the first visible tab. + * That means, if you just want to find out if there are visible tabs + * then this function is quicker than visibleTabCount() + */ + bool hasVisibleTabs() const; + + /** + * Getter for side tab bar area property + */ + SideBarLocation sideBarLocation() const; + + /** + * Overrides the minimumSizeHint() function of QScrollArea + * The minimumSizeHint() is bigger than the sizeHint () for the scroll + * area because even if the scrollbars are invisible, the required space + * is reserved in the minimumSizeHint(). This override simply returns + * sizeHint(); + */ + virtual QSize minimumSizeHint() const override; + + /** + * The function provides a sizeHint that matches the height of the + * internal viewport. + */ + virtual QSize sizeHint() const override; + + /** + * Getter for spacing property - returns the spacing of the tabs + */ + int spacing() const; + + /** + * Setter for spacing property - sets the spacing + */ + void setSpacing(int Spacing); + + /** + * Returns the dock container that hosts this sideBar() + */ + CDockContainerWidget* dockContainer() const; +}; +} // namespace ads +//----------------------------------------------------------------------------- +#endif diff --git a/src/AutoHideTab.cpp b/src/AutoHideTab.cpp new file mode 100644 index 000000000..66d1277d7 --- /dev/null +++ b/src/AutoHideTab.cpp @@ -0,0 +1,620 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideTab.cpp +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Implementation of CAutoHideTab class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "AutoHideTab.h" + +#include +#include +#include +#include +#include +#include + +#include "AutoHideDockContainer.h" +#include "AutoHideSideBar.h" +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "DockWidget.h" +#include "FloatingDragPreview.h" +#include "DockOverlay.h" +#include "ads_globals.h" + +namespace ads +{ + +/** + * Private data class of CDockWidgetTab class (pimpl) + */ +struct AutoHideTabPrivate +{ + CAutoHideTab* _this; + CDockWidget* DockWidget = nullptr; + CAutoHideSideBar* SideBar = nullptr; + Qt::Orientation Orientation{Qt::Vertical}; + QElapsedTimer TimeSinceHoverMousePress; + QTimer DragOverTimer; + bool MousePressed = false; + eDragState DragState = DraggingInactive; + QPoint GlobalDragStartMousePosition; + QPoint DragStartMousePosition; + IFloatingWidget* FloatingWidget = nullptr; + Qt::Orientation DragStartOrientation; + + /** + * Private data constructor + */ + AutoHideTabPrivate(CAutoHideTab* _public); + + /** + * Update the orientation, visibility and spacing based on the area of + * the side bar + */ + void updateOrientation(); + + /** + * Convenience function to ease dock container access + */ + CDockContainerWidget* dockContainer() const + { + return DockWidget ? DockWidget->dockContainer() : nullptr; + } + + /** + * Forward this event to the dock container + */ + void forwardEventToDockContainer(QEvent* event) + { + auto DockContainer = dockContainer(); + if (DockContainer) + { + DockContainer->handleAutoHideWidgetEvent(event, _this); + } + } + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction* createAutoHideToAction(const QString& Title, SideBarLocation Location, + QMenu* Menu) + { + auto Action = Menu->addAction(Title); + Action->setProperty(internal::LocationProperty, Location); + QObject::connect(Action, &QAction::triggered, _this, &CAutoHideTab::onAutoHideToActionClicked); + Action->setEnabled(Location != _this->sideBarLocation()); + return Action; + } + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const + { + return this->DragState == dragState; + } + + /** + * Saves the drag start position in global and local coordinates + */ + void saveDragStartMousePosition(const QPoint& GlobalPos) + { + GlobalDragStartMousePosition = GlobalPos; + DragStartMousePosition = _this->mapFromGlobal(GlobalPos); + } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(eDragState DraggingState = DraggingFloatingWidget); + + template + IFloatingWidget* createFloatingWidget(T* Widget) + { + auto w = new CFloatingDragPreview(Widget); + _this->connect(w, &CFloatingDragPreview::draggingCanceled, [=]() + { + DragState = DraggingInactive; + }); + return w; + } +}; // struct DockWidgetTabPrivate + + +//============================================================================ +AutoHideTabPrivate::AutoHideTabPrivate(CAutoHideTab* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void AutoHideTabPrivate::updateOrientation() +{ + bool IconOnly = CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideSideBarsIconOnly); + if (IconOnly && !_this->icon().isNull()) + { + _this->setText(""); + _this->setOrientation(Qt::Horizontal); + } + else + { + auto area = SideBar->sideBarLocation(); + _this->setOrientation((area == SideBarBottom || area == SideBarTop) ? Qt::Horizontal : Qt::Vertical); + } +} + + +//============================================================================ +bool AutoHideTabPrivate::startFloating(eDragState DraggingState) +{ + auto DockArea = DockWidget->dockAreaWidget(); + ADS_PRINT("isFloating " << dockContainer()->isFloating()); + + ADS_PRINT("startFloating"); + DragState = DraggingState; + IFloatingWidget* FloatingWidget = nullptr; + FloatingWidget = createFloatingWidget(DockArea); + auto Size = DockArea->size(); + auto StartPos = DragStartMousePosition; + auto AutoHideContainer = DockWidget->autoHideDockContainer(); + DragStartOrientation = AutoHideContainer->orientation(); + switch (SideBar->sideBarLocation()) + { + case SideBarLeft: + StartPos.rx() = AutoHideContainer->rect().left() + 10; + break; + + case SideBarRight: + StartPos.rx() = AutoHideContainer->rect().right() - 10; + break; + + case SideBarTop: + StartPos.ry() = AutoHideContainer->rect().top() + 10; + break; + + case SideBarBottom: + StartPos.ry() = AutoHideContainer->rect().bottom() - 10; + break; + + case SideBarNone: + return false; + } + FloatingWidget->startFloating(StartPos, Size, DraggingFloatingWidget, _this); + auto DockManager = DockWidget->dockManager(); + auto Overlay = DockManager->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + this->FloatingWidget = FloatingWidget; + qApp->postEvent(DockWidget, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent)); + + return true; +} + + + +//============================================================================ +void CAutoHideTab::setSideBar(CAutoHideSideBar* SideTabBar) +{ + d->SideBar = SideTabBar; + if (d->SideBar) + { + d->updateOrientation(); + } +} + + +//============================================================================ +CAutoHideSideBar* CAutoHideTab::sideBar() const +{ + return d->SideBar; +} + + +//============================================================================ +void CAutoHideTab::removeFromSideBar() +{ + if (d->SideBar == nullptr) + { + return; + } + d->SideBar->removeTab(this); + setSideBar(nullptr); +} + +//============================================================================ +CAutoHideTab::CAutoHideTab(QWidget* parent) : + CPushButton(parent), + d(new AutoHideTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation); + setFocusPolicy(Qt::NoFocus); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover)) { + setAcceptDrops(true); + } + + d->DragOverTimer.setInterval(CDockManager::configParam( + CDockManager::AutoHideOpenOnDragHoverDelay_ms, 500).toInt()); + d->DragOverTimer.setSingleShot(true); + connect(&d->DragOverTimer, &QTimer::timeout, this, &CAutoHideTab::onDragHoverDelayExpired); +} + + +//============================================================================ +CAutoHideTab::~CAutoHideTab() +{ + ADS_PRINT("~CDockWidgetSideTab()"); + delete d; +} + + +//============================================================================ +void CAutoHideTab::updateStyle() +{ + internal::repolishStyle(this, internal::RepolishDirectChildren); + update(); +} + + +//============================================================================ +SideBarLocation CAutoHideTab::sideBarLocation() const +{ + if (d->SideBar) + { + return d->SideBar->sideBarLocation(); + } + + return SideBarLeft; +} + + +//============================================================================ +void CAutoHideTab::setOrientation(Qt::Orientation Orientation) +{ + d->Orientation = Orientation; + if (orientation() == Qt::Horizontal) + { + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + } + else + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); + } + CPushButton::setButtonOrientation((Qt::Horizontal == Orientation) + ? CPushButton::Horizontal : CPushButton::VerticalTopToBottom); + updateStyle(); +} + + +//============================================================================ +Qt::Orientation CAutoHideTab::orientation() const +{ + return d->Orientation; +} + + +//============================================================================ +bool CAutoHideTab::isActiveTab() const +{ + if (d->DockWidget && d->DockWidget->autoHideDockContainer()) + { + return d->DockWidget->autoHideDockContainer()->isVisible(); + } + + return false; +} + + +//============================================================================ +CDockWidget* CAutoHideTab::dockWidget() const +{ + return d->DockWidget; +} + + +//============================================================================ +void CAutoHideTab::setDockWidget(CDockWidget* DockWidget) +{ + if (!DockWidget) + { + return; + } + d->DockWidget = DockWidget; + setText(DockWidget->windowTitle()); + setIcon(d->DockWidget->icon()); + setToolTip(DockWidget->windowTitle()); +} + + +//============================================================================ +bool CAutoHideTab::event(QEvent* event) +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideShowOnMouseOver)) + { + return Super::event(event); + } + + switch (event->type()) + { + case QEvent::Enter: + case QEvent::Leave: + d->forwardEventToDockContainer(event); + break; + default: + break; + } + return Super::event(event); +} + + +//============================================================================ +bool CAutoHideTab::iconOnly() const +{ + return CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideSideBarsIconOnly) && !icon().isNull(); +} + + +//============================================================================ +void CAutoHideTab::contextMenuEvent(QContextMenuEvent* ev) +{ + ev->accept(); + d->saveDragStartMousePosition(ev->globalPos()); + + const bool isFloatable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable); + QAction* Action; + QMenu Menu(this); + + Action = Menu.addAction(tr("Detach"), this, SLOT(setDockWidgetFloating())); + Action->setEnabled(isFloatable); + auto IsPinnable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetPinnable); + Action->setEnabled(IsPinnable); + + auto menu = Menu.addMenu(tr("Pin To...")); + menu->setEnabled(IsPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, menu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, menu); + d->createAutoHideToAction(tr("Right"), SideBarRight, menu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, menu); + + Action = Menu.addAction(tr("Unpin (Dock)"), this, SLOT(unpinDockWidget())); + Menu.addSeparator(); + Action = Menu.addAction(tr("Close"), this, SLOT(requestCloseDockWidget())); + Action->setEnabled(d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable)); + + Menu.exec(ev->globalPos()); +} + + +//============================================================================ +void CAutoHideTab::setDockWidgetFloating() +{ + d->DockWidget->setFloating(); +} + + +//============================================================================ +void CAutoHideTab::unpinDockWidget() +{ + d->DockWidget->setAutoHide(false); +} + + +//=========================================================================== +void CAutoHideTab::onAutoHideToActionClicked() +{ + int Location = sender()->property(internal::LocationProperty).toInt(); + d->DockWidget->setAutoHide(true, (SideBarLocation)Location); +} + + +//============================================================================ +void CAutoHideTab::mousePressEvent(QMouseEvent* ev) +{ + // If AutoHideShowOnMouseOver is active, then the showing is triggered + // by a MousePressEvent sent to this tab. To prevent accidental hiding + // of the tab by a mouse click, we wait at least 500 ms before we accept + // the mouse click + if (!ev->spontaneous()) + { + d->TimeSinceHoverMousePress.restart(); + d->forwardEventToDockContainer(ev); + } + else if (d->TimeSinceHoverMousePress.hasExpired(500)) + { + d->forwardEventToDockContainer(ev); + } + + if (ev->button() == Qt::LeftButton) + { + ev->accept(); + d->MousePressed = true; + d->saveDragStartMousePosition(internal::globalPositionOf(ev)); + d->DragState = DraggingMousePressed; + } + Super::mousePressEvent(ev); +} + + + +//============================================================================ +void CAutoHideTab::mouseReleaseEvent(QMouseEvent* ev) +{ + if (ev->button() == Qt::LeftButton) + { + d->MousePressed = false; + auto CurrentDragState = d->DragState; + d->GlobalDragStartMousePosition = QPoint(); + d->DragStartMousePosition = QPoint(); + d->DragState = DraggingInactive; + + switch (CurrentDragState) + { + case DraggingTab: + // 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(); + if (d->DockWidget->autoHideDockContainer() && d->DragStartOrientation != orientation()) + { + d->DockWidget->autoHideDockContainer()->resetToInitialDockWidgetSize(); + } + break; + + default: + break; // do nothing + } + } + + Super::mouseReleaseEvent(ev); +} + + +//============================================================================ +void CAutoHideTab::mouseMoveEvent(QMouseEvent *ev) +{ + if (!(ev->buttons() & Qt::LeftButton) + || d->isDraggingState(DraggingInactive)) + { + d->DragState = DraggingInactive; + Super::mouseMoveEvent(ev); + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) + { + d->FloatingWidget->moveFloating(); + Super::mouseMoveEvent(ev); + return; + } + + // move tab + if (d->isDraggingState(DraggingTab)) + { + // Moving the tab is always allowed because it does not mean moving the + // dock widget around + //d->moveTab(ev); + } + + auto MappedPos = mapToParent(ev->pos()); + bool MouseOutsideBar = (MappedPos.x() < 0) + || (MappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int DragDistanceY = qAbs( + d->GlobalDragStartMousePosition.y() + - internal::globalPositionOf(ev).y()); + if (DragDistanceY >= CDockManager::startDragDistance() || MouseOutsideBar) + { + // Floating is only allowed for widgets that are floatable + // We can create the drag preview if the widget is movable. + auto Features = d->DockWidget->features(); + if (Features.testFlag(CDockWidget::DockWidgetFloatable) + || (Features.testFlag(CDockWidget::DockWidgetMovable))) + { + d->startFloating(); + } + return; + } + + Super::mouseMoveEvent(ev); +} + +//============================================================================ +void CAutoHideTab::dragEnterEvent(QDragEnterEvent *ev) +{ + Q_UNUSED(ev); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover)) + { + d->DragOverTimer.start(); + ev->accept(); + } +} + +//============================================================================ +void CAutoHideTab::dragLeaveEvent(QDragLeaveEvent *ev) +{ + Q_UNUSED(ev); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideOpenOnDragHover)) + { + d->DragOverTimer.stop(); + } +} + + +//============================================================================ +void CAutoHideTab::requestCloseDockWidget() +{ + d->DockWidget->requestCloseDockWidget(); +} + +//============================================================================ +int CAutoHideTab::tabIndex() const +{ + if (!d->SideBar) + { + return -1; + } + + return d->SideBar->indexOfTab(*this); +} + + +//============================================================================ +void CAutoHideTab::onDragHoverDelayExpired() +{ + static const char* const PropertyId = "ActiveDragOverAutoHideContainer"; + + // First we check if there is an active auto hide container that is visible + // In this case, we collapse it before we open the new one + auto v = d->DockWidget->dockManager()->property(PropertyId); + if (v.isValid()) + { + auto ActiveAutoHideContainer = v.value>(); + if (ActiveAutoHideContainer) + { + ActiveAutoHideContainer->collapseView(true); + } + } + + auto AutoHideContainer = d->DockWidget->autoHideDockContainer(); + AutoHideContainer->collapseView(false); + d->DockWidget->dockManager()->setProperty(PropertyId, + QVariant::fromValue(QPointer(AutoHideContainer))); +} + + +} diff --git a/src/AutoHideTab.h b/src/AutoHideTab.h new file mode 100644 index 000000000..20ee70a91 --- /dev/null +++ b/src/AutoHideTab.h @@ -0,0 +1,171 @@ +#ifndef AutoHideTabH +#define AutoHideTabH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file AutoHideTab.h +/// \author Syarif Fakhri +/// \date 05.09.2022 +/// \brief Declaration of CAutoHideTab class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "PushButton.h" + +#include "ads_globals.h" + +namespace ads +{ +struct AutoHideTabPrivate; +class CDockWidget; +class CAutoHideSideBar; +class CDockWidgetTab; +class DockContainerWidgetPrivate; + +/** + * A dock widget Side tab that shows a title or an icon. + * The dock widget tab is shown in the side tab bar to switch between + * pinned dock widgets + */ +class ADS_EXPORT CAutoHideTab : public CPushButton +{ + Q_OBJECT + + Q_PROPERTY(int sideBarLocation READ sideBarLocation) + Q_PROPERTY(Qt::Orientation orientation READ orientation) + Q_PROPERTY(bool activeTab READ isActiveTab) + Q_PROPERTY(bool iconOnly READ iconOnly) + +private: + AutoHideTabPrivate* d; ///< private data (pimpl) + friend struct AutoHideTabPrivate; + friend class CDockWidget; + friend class CAutoHideDockContainer; + friend class CAutoHideSideBar; + friend class CDockAreaWidget; + friend class CDockContainerWidget; + friend DockContainerWidgetPrivate; + +private Q_SLOTS: + void onAutoHideToActionClicked(); + void onDragHoverDelayExpired(); + +protected: + void setSideBar(CAutoHideSideBar *SideTabBar); + void removeFromSideBar(); + virtual bool event(QEvent* event) override; + virtual void contextMenuEvent(QContextMenuEvent* ev) override; + virtual void mousePressEvent(QMouseEvent* ev) override; + virtual void mouseReleaseEvent(QMouseEvent* ev) override; + virtual void mouseMoveEvent(QMouseEvent* ev) override; + virtual void dragEnterEvent(QDragEnterEvent* ev) override; + virtual void dragLeaveEvent(QDragLeaveEvent* ev) override; + +public: + using Super = CPushButton; + + /** + * Default Constructor + * param[in] DockWidget The dock widget this title bar belongs to + * param[in] parent The parent widget of this title bar + */ + CAutoHideTab(QWidget* parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~CAutoHideTab(); + + /** + * Update stylesheet style if a property changes + */ + void updateStyle(); + + /** + * Getter for side tab bar area property + */ + SideBarLocation sideBarLocation() const; + + /** + * Set orientation vertical or horizontal + */ + void setOrientation(Qt::Orientation Orientation); + + /** + * Returns the current orientation + */ + Qt::Orientation orientation() const; + + /** + * Returns true, if this is the active tab. The tab is active if the auto + * hide widget is visible + */ + bool isActiveTab() const; + + /** + * returns the dock widget this belongs to + */ + CDockWidget* dockWidget() const; + + /** + * Sets the dock widget that is controlled by this tab + */ + void setDockWidget(CDockWidget* DockWidget); + + /** + * Returns true if the auto hide config flag AutoHideSideBarsIconOnly + * is set and if the tab has an icon - that means the icon is not null + */ + bool iconOnly() const; + + /** + * Returns the side bar that contains this tab or a nullptr if the tab is + * not in a side bar + */ + CAutoHideSideBar* sideBar() const; + + /** + * Returns the index of this tab in the sideBar + */ + int tabIndex() const; + +public Q_SLOTS: + /** + * Set the dock widget floating, if it is floatable + */ + void setDockWidgetFloating(); + + /** + * Unpin and dock the auto hide widget + */ + void unpinDockWidget(); + + /** + * Calls the requestCloseDockWidget() function for the assigned dock widget + */ + void requestCloseDockWidget(); +}; // class AutoHideTab +} + // namespace ads +//----------------------------------------------------------------------------- + +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..9690be767 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,156 @@ +cmake_minimum_required(VERSION 3.5) +project(QtAdvancedDockingSystem LANGUAGES CXX VERSION ${VERSION_SHORT}) +include(GNUInstallDirs) +if (${QT_VERSION_MAJOR}) + message(STATUS "Forced to use Qt version ${QT_VERSION_MAJOR}") + find_package(QT NAMES Qt${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED) +else() + find_package(QT NAMES Qt6 Qt5 COMPONENTS Core REQUIRED) +endif() +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets REQUIRED) +if (UNIX AND NOT APPLE) + include_directories(${Qt${QT_VERSION_MAJOR}Gui_PRIVATE_INCLUDE_DIRS}) +endif() +set(CMAKE_INCLUDE_CURRENT_DIR ON) +if(BUILD_STATIC) + set(CMAKE_STATIC_LIBRARY_SUFFIX "_static${CMAKE_STATIC_LIBRARY_SUFFIX}") +endif() +set(ads_SRCS + ads_globals.cpp + DockAreaTabBar.cpp + DockAreaTitleBar.cpp + DockAreaWidget.cpp + DockContainerWidget.cpp + DockManager.cpp + DockOverlay.cpp + DockSplitter.cpp + DockWidget.cpp + DockWidgetTab.cpp + DockingStateReader.cpp + DockFocusController.cpp + ElidingLabel.cpp + FloatingDockContainer.cpp + FloatingDragPreview.cpp + IconProvider.cpp + DockComponentsFactory.cpp + AutoHideSideBar.cpp + AutoHideTab.cpp + AutoHideDockContainer.cpp + PushButton.cpp + ResizeHandle.cpp + ads.qrc +) +set(ads_HEADERS + ads_globals.h + DockAreaTabBar.h + DockAreaTitleBar.h + DockAreaTitleBar_p.h + DockAreaWidget.h + DockContainerWidget.h + DockManager.h + DockOverlay.h + DockSplitter.h + DockWidget.h + DockWidgetTab.h + DockingStateReader.h + DockFocusController.h + ElidingLabel.h + FloatingDockContainer.h + FloatingDragPreview.h + IconProvider.h + DockComponentsFactory.h + AutoHideSideBar.h + AutoHideTab.h + AutoHideDockContainer.h + PushButton.h + ResizeHandle.h +) +add_compile_options("$<$:/utf-8>") +if (UNIX AND NOT APPLE) + set(ads_SRCS linux/FloatingWidgetTitleBar.cpp ${ads_SRCS}) + set(ads_HEADERS linux/FloatingWidgetTitleBar.h ${ads_HEADERS}) +endif() + +set(library_name "qt${QT_VERSION_MAJOR}advanceddocking") +if(BUILD_STATIC) + add_library(${library_name} STATIC ${ads_SRCS} ${ads_HEADERS}) + target_compile_definitions( ${library_name} PUBLIC ADS_STATIC) +else() + add_library( ${library_name} SHARED ${ads_SRCS} ${ads_HEADERS}) + target_compile_definitions( ${library_name} PRIVATE ADS_SHARED_EXPORT) +endif() + +add_library(ads::${library_name} ALIAS ${library_name}) + +target_link_libraries(${library_name} PUBLIC Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets) +if (UNIX AND NOT APPLE) + if (${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") + find_package(X11 REQUIRED) + target_link_libraries(${library_name} PUBLIC X11::xcb) + else() + target_link_libraries(${library_name} PUBLIC xcb) + endif() +endif() +set_target_properties(${library_name} PROPERTIES + AUTOMOC ON + AUTORCC ON + CXX_EXTENSIONS OFF + VERSION ${VERSION_SHORT} + EXPORT_NAME ${library_name} + DEBUG_POSTFIX "d" + 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" +) +if(QT_VERSION_MAJOR STREQUAL "5") + set_target_properties(${library_name} PROPERTIES + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON) +elseif(QT_VERSION_MAJOR STREQUAL "6") + set_target_properties(${library_name} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON) +endif() + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${library_name}ConfigVersion.cmake" + VERSION ${VERSION_SHORT} + COMPATIBILITY SameMajorVersion +) +install(FILES ${ads_HEADERS} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${library_name} + COMPONENT headers +) +install(FILES + "${CMAKE_CURRENT_SOURCE_DIR}/../LICENSE" + "${CMAKE_CURRENT_SOURCE_DIR}/../gnu-lgpl-v2.1.md" + DESTINATION license/ads + COMPONENT license +) +install(TARGETS ${library_name} + EXPORT adsTargets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${library_name} +) + +install(EXPORT adsTargets + FILE adsTargets.cmake + NAMESPACE ads:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${library_name} +) +install(FILES qtadvanceddockingConfig.cmake RENAME ${library_name}Config.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${library_name} +) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${library_name}ConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${library_name} +) + +target_include_directories(${library_name} PUBLIC + $ + $ +) diff --git a/src/DockAreaTabBar.cpp b/src/DockAreaTabBar.cpp new file mode 100644 index 000000000..cf1cb09d2 --- /dev/null +++ b/src/DockAreaTabBar.cpp @@ -0,0 +1,571 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaTabBar.cpp +/// \author Uwe Kindler +/// \date 24.08.2018 +/// \brief Implementation of CDockAreaTabBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "FloatingDragPreview.h" +#include "DockAreaTabBar.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "FloatingDockContainer.h" +#include "DockAreaWidget.h" +#include "DockOverlay.h" +#include "DockManager.h" +#include "DockWidget.h" +#include "DockWidgetTab.h" + +#include + + +namespace ads +{ +/** + * Private data class of CDockAreaTabBar class (pimpl) + */ +struct DockAreaTabBarPrivate +{ + CDockAreaTabBar* _this; + CDockAreaWidget* DockArea; + QWidget* TabsContainerWidget; + QBoxLayout* TabsLayout; + int CurrentIndex = -1; + + /** + * Private data constructor + */ + DockAreaTabBarPrivate(CDockAreaTabBar* _public); + + /** + * Update tabs after current index changed or when tabs are removed. + * The function reassigns the stylesheet to update the tabs + */ + void updateTabs(); + + /** + * Convenience function to access first tab + */ + CDockWidgetTab* firstTab() const {return _this->tab(0);} + + /** + * Convenience function to access last tab + */ + CDockWidgetTab* lastTab() const {return _this->tab(_this->count() - 1);} +}; +// struct DockAreaTabBarPrivate + +//============================================================================ +DockAreaTabBarPrivate::DockAreaTabBarPrivate(CDockAreaTabBar* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockAreaTabBarPrivate::updateTabs() +{ + // Set active TAB and update all other tabs to be inactive + for (int i = 0; i < _this->count(); ++i) + { + auto TabWidget = _this->tab(i); + if (!TabWidget) + { + continue; + } + + if (i == CurrentIndex) + { + TabWidget->show(); + TabWidget->setActiveTab(true); + // Sometimes the synchronous calculation of the rectangular area fails + // Therefore we use QTimer::singleShot here to execute the call + // within the event loop - see #520 + QTimer::singleShot(0, _this, [&, TabWidget] + { + _this->ensureWidgetVisible(TabWidget); + }); + } + else + { + TabWidget->setActiveTab(false); + } + } +} + + +//============================================================================ +CDockAreaTabBar::CDockAreaTabBar(CDockAreaWidget* parent) : + QScrollArea(parent), + d(new DockAreaTabBarPrivate(this)) +{ + d->DockArea = parent; + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + d->TabsContainerWidget = new QWidget(); + d->TabsContainerWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + d->TabsContainerWidget->setObjectName("tabsContainerWidget"); + d->TabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); + d->TabsLayout->setContentsMargins(0, 0, 0, 0); + d->TabsLayout->setSpacing(0); + d->TabsLayout->addStretch(1); + d->TabsContainerWidget->setLayout(d->TabsLayout); + setWidget(d->TabsContainerWidget); + + setFocusPolicy(Qt::NoFocus); +} + + +//============================================================================ +CDockAreaTabBar::~CDockAreaTabBar() +{ + delete d; +} + + +//============================================================================ +void CDockAreaTabBar::wheelEvent(QWheelEvent* Event) +{ + Event->accept(); + const int direction = Event->angleDelta().y(); + if (direction < 0) + { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); + } + else + { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); + } +} + + +//============================================================================ +void CDockAreaTabBar::setCurrentIndex(int index) +{ + if (index == d->CurrentIndex) + { + return; + } + + if (index < -1 || index > (count() - 1)) + { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + Q_EMIT currentChanging(index); + d->CurrentIndex = index; + d->updateTabs(); + updateGeometry(); + Q_EMIT currentChanged(index); +} + + +//============================================================================ +int CDockAreaTabBar::count() const +{ + // The tab bar contains a stretch item as last item + return d->TabsLayout->count() - 1; +} + + +//=========================================================================== +void CDockAreaTabBar::insertTab(int Index, CDockWidgetTab* Tab) +{ + d->TabsLayout->insertWidget(Index, Tab); + connect(Tab, SIGNAL(clicked()), this, SLOT(onTabClicked())); + connect(Tab, SIGNAL(closeRequested()), this, SLOT(onTabCloseRequested())); + connect(Tab, SIGNAL(closeOtherTabsRequested()), this, SLOT(onCloseOtherTabsRequested())); + connect(Tab, SIGNAL(moved(QPoint)), this, SLOT(onTabWidgetMoved(QPoint))); + connect(Tab, SIGNAL(elidedChanged(bool)), this, SIGNAL(elidedChanged(bool))); + Tab->installEventFilter(this); + Q_EMIT tabInserted(Index); + if (Index <= d->CurrentIndex) + { + setCurrentIndex(d->CurrentIndex + 1); + } + else if (d->CurrentIndex == -1) + { + setCurrentIndex(Index); + } + + updateGeometry(); +} + + +//=========================================================================== +void CDockAreaTabBar::removeTab(CDockWidgetTab* Tab) +{ + if (!count()) + { + return; + } + ADS_PRINT("CDockAreaTabBar::removeTab "); + int NewCurrentIndex = currentIndex(); + int RemoveIndex = d->TabsLayout->indexOf(Tab); + if (count() == 1) + { + NewCurrentIndex = -1; + } + if (NewCurrentIndex > RemoveIndex) + { + NewCurrentIndex--; + } + else if (NewCurrentIndex == RemoveIndex) + { + NewCurrentIndex = -1; + // First we walk to the right to search for the next visible tab + for (int i = (RemoveIndex + 1); i < count(); ++i) + { + if (tab(i)->isVisibleTo(this)) + { + NewCurrentIndex = i - 1; + break; + } + } + + // If there is no visible tab right to this tab then we walk to + // the left to find a visible tab + if (NewCurrentIndex < 0) + { + for (int i = (RemoveIndex - 1); i >= 0; --i) + { + if (tab(i)->isVisibleTo(this)) + { + NewCurrentIndex = i; + break; + } + } + } + } + + Q_EMIT removingTab(RemoveIndex); + d->TabsLayout->removeWidget(Tab); + Tab->disconnect(this); + Tab->removeEventFilter(this); + ADS_PRINT("NewCurrentIndex " << NewCurrentIndex); + if (NewCurrentIndex != d->CurrentIndex) + { + setCurrentIndex(NewCurrentIndex); + } + else + { + d->updateTabs(); + } + + updateGeometry(); +} + + +//=========================================================================== +int CDockAreaTabBar::currentIndex() const +{ + return d->CurrentIndex; +} + + +//=========================================================================== +CDockWidgetTab* CDockAreaTabBar::currentTab() const +{ + if (d->CurrentIndex < 0 || d->CurrentIndex >= d->TabsLayout->count()) + { + return nullptr; + } + else + { + return qobject_cast(d->TabsLayout->itemAt(d->CurrentIndex)->widget()); + } +} + + +//=========================================================================== +void CDockAreaTabBar::onTabClicked() +{ + CDockWidgetTab* Tab = qobject_cast(sender()); + if (!Tab) + { + return; + } + + int index = d->TabsLayout->indexOf(Tab); + if (index < 0) + { + return; + } + setCurrentIndex(index); + Q_EMIT tabBarClicked(index); +} + + +//=========================================================================== +void CDockAreaTabBar::onTabCloseRequested() +{ + CDockWidgetTab* Tab = qobject_cast(sender()); + int Index = d->TabsLayout->indexOf(Tab); + closeTab(Index); +} + + +//=========================================================================== +void CDockAreaTabBar::onCloseOtherTabsRequested() +{ + auto Sender = qobject_cast(sender()); + for (int i = 0; i < count(); ++i) + { + auto Tab = tab(i); + if (Tab->isClosable() && !Tab->isHidden() && Tab != Sender) + { + // If the dock widget is deleted with the closeTab() call, its tab + // it will no longer be in the layout, and thus the index needs to + // be updated to not skip any tabs + int Offset = Tab->dockWidget()->features().testFlag( + CDockWidget::DockWidgetDeleteOnClose) ? 1 : 0; + closeTab(i); + + // If the the dock widget blocks closing, i.e. if the flag + // CustomCloseHandling is set, and the dock widget is still open, + // then we do not need to correct the index + if (Tab->dockWidget()->isClosed()) + { + i -= Offset; + } + } + } +} + + +//=========================================================================== +CDockWidgetTab* CDockAreaTabBar::tab(int Index) const +{ + if (Index >= count() || Index < 0) + { + return nullptr; + } + return qobject_cast(d->TabsLayout->itemAt(Index)->widget()); +} + + +//=========================================================================== +void CDockAreaTabBar::onTabWidgetMoved(const QPoint& GlobalPos) +{ + CDockWidgetTab* MovingTab = qobject_cast(sender()); + if (!MovingTab) + { + return; + } + + int fromIndex = d->TabsLayout->indexOf(MovingTab); + auto MousePos = mapFromGlobal(GlobalPos); + MousePos.rx() = qMax(0, MousePos.x()); + MousePos.rx() = qMin(width(), MousePos.x()); + int toIndex = -1; + // Find tab under mouse + for (int i = 0; i < count(); ++i) + { + CDockWidgetTab* DropTab = tab(i); + auto TabGeometry = DropTab->geometry(); + TabGeometry.setTopLeft(d->TabsContainerWidget->mapToParent(TabGeometry.topLeft())); + TabGeometry.setBottomRight(d->TabsContainerWidget->mapToParent(TabGeometry.bottomRight())); + if (DropTab == MovingTab || !DropTab->isVisibleTo(this) + || !TabGeometry.contains(MousePos)) + { + continue; + } + + toIndex = d->TabsLayout->indexOf(DropTab); + if (toIndex == fromIndex) + { + toIndex = -1; + } + break; + } + + if (toIndex > -1) + { + d->TabsLayout->removeWidget(MovingTab); + d->TabsLayout->insertWidget(toIndex, MovingTab); + ADS_PRINT("tabMoved from " << fromIndex << " to " << toIndex); + Q_EMIT tabMoved(fromIndex, toIndex); + setCurrentIndex(toIndex); + } + else + { + // Ensure that the moved tab is reset to its start position + d->TabsLayout->update(); + } +} + +//=========================================================================== +void CDockAreaTabBar::closeTab(int Index) +{ + if (Index < 0 || Index >= count()) + { + return; + } + + auto Tab = tab(Index); + if (Tab->isHidden()) + { + return; + } + Q_EMIT tabCloseRequested(Index); +} + + +//=========================================================================== +bool CDockAreaTabBar::eventFilter(QObject *watched, QEvent *event) +{ + bool Result = Super::eventFilter(watched, event); + CDockWidgetTab* Tab = qobject_cast(watched); + if (!Tab) + { + return Result; + } + + switch (event->type()) + { + case QEvent::Hide: + Q_EMIT tabClosed(d->TabsLayout->indexOf(Tab)); + updateGeometry(); + break; + + case QEvent::Show: + Q_EMIT tabOpened(d->TabsLayout->indexOf(Tab)); + updateGeometry(); + break; + + // Setting the text of a tab will cause a LayoutRequest event + case QEvent::LayoutRequest: + updateGeometry(); + break; + + // Manage wheel event + case QEvent::Wheel: + // Ignore wheel events if tab is currently dragged + if (Tab->dragState() == DraggingInactive) + { + wheelEvent((QWheelEvent* )event); + } + break; + + default: + break; + } + + return Result; +} + + +//=========================================================================== +bool CDockAreaTabBar::isTabOpen(int Index) const +{ + if (Index < 0 || Index >= count()) + { + return false; + } + + return !tab(Index)->isHidden(); +} + + +//=========================================================================== +QSize CDockAreaTabBar::minimumSizeHint() const +{ + QSize Size = sizeHint(); + Size.setWidth(10); + return Size; +} + + +//=========================================================================== +QSize CDockAreaTabBar::sizeHint() const +{ + return d->TabsContainerWidget->sizeHint(); +} + + +//=========================================================================== +int CDockAreaTabBar::tabAt(const QPoint& Pos) const +{ + if (!isVisible()) + { + return TabInvalidIndex; + } + + if (Pos.x() < tab(0)->geometry().x()) + { + return -1; + } + + for (int i = 0; i < count(); ++i) + { + if (tab(i)->geometry().contains(Pos)) + { + return i; + } + } + + return count(); +} + + +//=========================================================================== +int CDockAreaTabBar::tabInsertIndexAt(const QPoint& Pos) const +{ + int Index = tabAt(Pos); + if (Index == TabInvalidIndex) + { + return TabDefaultInsertIndex; + } + else + { + return (Index < 0) ? 0 : Index; + } +} + + +//=========================================================================== +bool CDockAreaTabBar::areTabsOverflowing() const +{ + return d->TabsContainerWidget->width() > width(); +} + +} // namespace ads + + +//--------------------------------------------------------------------------- +// EOF DockAreaTabBar.cpp diff --git a/src/DockAreaTabBar.h b/src/DockAreaTabBar.h new file mode 100644 index 000000000..2d5700940 --- /dev/null +++ b/src/DockAreaTabBar.h @@ -0,0 +1,234 @@ +#ifndef DockAreaTabBarH +#define DockAreaTabBarH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaTabBar.h +/// \author Uwe Kindler +/// \date 24.08.2018 +/// \brief Declaration of CDockAreaTabBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "ads_globals.h" + +namespace ads +{ +class CDockAreaWidget; +class CDockWidgetTab; +struct DockAreaTabBarPrivate; +class CDockAreaTitleBar; +class CFloatingDockContainer; +class IFloatingWidget; + +/** + * Custom tabbar implementation for tab area that is shown on top of a + * dock area widget. + * The tabbar displays the tab widgets of the contained dock widgets. + * We cannot use QTabBar here because it does a lot of fancy animations + * that will crash the application if a tab is removed while the animation + * has not finished. And we need to remove a tab, if the user drags a + * a dock widget out of a group of tabbed widgets + */ +class ADS_EXPORT CDockAreaTabBar : public QScrollArea +{ + Q_OBJECT +private: + DockAreaTabBarPrivate* d; ///< private data (pimpl) + friend struct DockAreaTabBarPrivate; + friend class CDockAreaTitleBar; + +private Q_SLOTS: + void onTabClicked(); + void onTabCloseRequested(); + void onCloseOtherTabsRequested(); + void onTabWidgetMoved(const QPoint& GlobalPos); + +protected: + virtual void wheelEvent(QWheelEvent* Event) override; + + +public: + using Super = QScrollArea; + + /** + * Default Constructor + */ + CDockAreaTabBar(CDockAreaWidget* parent); + + /** + * Virtual Destructor + */ + virtual ~CDockAreaTabBar(); + + /** + * Inserts the given dock widget tab at the given position. + * Inserting a new tab at an index less than or equal to the current index + * will increment the current index, but keep the current tab. + */ + void insertTab(int Index, CDockWidgetTab* Tab); + + /** + * Removes the given DockWidgetTab from the tabbar + */ + void removeTab(CDockWidgetTab* Tab); + + /** + * Returns the number of tabs in this tabbar + */ + int count() const; + + /** + * Returns the current index or -1 if no tab is selected + */ + int currentIndex() const; + + /** + * Returns the current tab or a nullptr if no tab is selected. + */ + CDockWidgetTab* currentTab() const; + + /** + * Returns the tab with the given index + */ + CDockWidgetTab* tab(int Index) const; + + /** + * Returns the tab at the given position. + * Returns -1 if the position is left of the first tab and count() if the + * position is right of the last tab. Returns -2 to indicate an invalid + * value. + */ + int tabAt(const QPoint& Pos) const; + + /** + * Returns the tab insertion index for the given mouse cursor position + */ + int tabInsertIndexAt(const QPoint& Pos) const; + + /** + * Filters the tab widget events + */ + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + /** + * This function returns true if the tab is open, that means if it is + * visible to the user. If the function returns false, the tab is + * closed + */ + bool isTabOpen(int Index) const; + + /** + * Overrides the minimumSizeHint() function of QScrollArea + * The minimumSizeHint() is bigger than the sizeHint () for the scroll + * area because even if the scrollbars are invisible, the required space + * is reserved in the minimumSizeHint(). This override simply returns + * sizeHint(); + */ + virtual QSize minimumSizeHint() const override; + + /** + * The function provides a sizeHint that matches the height of the + * internal viewport. + */ + virtual QSize sizeHint() const override; + + /** + * This function returns true, if the tabs need more space than the size + * of the tab bar. + */ + bool areTabsOverflowing() const; + +public Q_SLOTS: + /** + * This property sets the index of the tab bar's visible tab + */ + void setCurrentIndex(int Index); + + /** + * This function will close the tab given in Index param. + * Closing a tab means, the tab will be hidden, it will not be removed + */ + void closeTab(int Index); + +Q_SIGNALS: + /** + * This signal is emitted when the tab bar's current tab is about to be changed. The new + * current has the given index, or -1 if there isn't a new one. + */ + void currentChanging(int Index); + + /** + * This signal is emitted when the tab bar's current tab changes. The new + * current has the given index, or -1 if there isn't a new one + */ + void currentChanged(int Index); + + /** + * This signal is emitted when user clicks on a tab + */ + void tabBarClicked(int index); + + /** + * This signal is emitted when the close button on a tab is clicked. + * The index is the index that should be closed. + */ + void tabCloseRequested(int index); + + /** + * This signal is emitted if a tab has been closed + */ + void tabClosed(int index); + + /** + * This signal is emitted if a tab has been opened. + * A tab is opened if it has been made visible + */ + void tabOpened(int index); + + /** + * This signal is emitted when the tab has moved the tab at index position + * from to index position to. + */ + void tabMoved(int from, int to); + + /** + * This signal is emitted, just before the tab with the given index is + * removed + */ + void removingTab(int index); + + /** + * This signal is emitted if a tab has been inserted + */ + void tabInserted(int index); + + /** + * This signal is emitted when a tab title elide state has been changed + */ + void elidedChanged(bool elided); +}; // class CDockAreaTabBar +} // namespace ads +//----------------------------------------------------------------------------- +#endif // DockAreaTabBarH + diff --git a/src/DockAreaTitleBar.cpp b/src/DockAreaTitleBar.cpp new file mode 100644 index 000000000..8c19cfdc5 --- /dev/null +++ b/src/DockAreaTitleBar.cpp @@ -0,0 +1,999 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaTitleBar.cpp +/// \author Uwe Kindler +/// \date 12.10.2018 +/// \brief Implementation of CDockAreaTitleBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockAreaTitleBar.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaTitleBar_p.h" +#include "ads_globals.h" +#include "FloatingDockContainer.h" +#include "FloatingDragPreview.h" +#include "DockAreaWidget.h" +#include "DockOverlay.h" +#include "DockManager.h" +#include "DockWidget.h" +#include "DockWidgetTab.h" +#include "DockAreaTabBar.h" +#include "DockComponentsFactory.h" +#include "DockFocusController.h" +#include "ElidingLabel.h" +#include "AutoHideDockContainer.h" +#include "IconProvider.h" + +#include + +namespace ads +{ + +/** + * Private data class of CDockAreaTitleBar class (pimpl) + */ +struct DockAreaTitleBarPrivate +{ + CDockAreaTitleBar* _this; + QPointer TabsMenuButton; + QPointer AutoHideButton; + QPointer UndockButton; + QPointer CloseButton; + QPointer MinimizeButton; + QBoxLayout* Layout; + CDockAreaWidget* DockArea; + CDockAreaTabBar* TabBar; + CElidingLabel* AutoHideTitleLabel = nullptr; + bool MenuOutdated = true; + QMenu* TabsMenu; + QList DockWidgetActionsButtons; + + QPoint DragStartMousePos; + eDragState DragState = DraggingInactive; + IFloatingWidget* FloatingWidget = nullptr; + + + /** + * Private data constructor + */ + DockAreaTitleBarPrivate(CDockAreaTitleBar* _public); + + /** + * Creates the title bar close and menu buttons + */ + void createButtons(); + + + /** + * Creates the auto hide title label, only displayed when the dock area is overlayed + */ + void createAutoHideTitleLabel(); + + /** + * Creates the internal TabBar + */ + void createTabBar(); + + /** + * Convenience function for DockManager access + */ + CDockManager* dockManager() const + { + return DockArea->dockManager(); + } + + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testConfigFlag(CDockManager::eConfigFlag Flag) + { + return CDockManager::testConfigFlag(Flag); + } + + /** + * Returns true if the given config flag is set + * Convenience function to ease config flag testing + */ + static bool testAutoHideConfigFlag(CDockManager::eAutoHideFlag Flag) + { + return CDockManager::testAutoHideConfigFlag(Flag); + } + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const + { + return this->DragState == dragState; + } + + /** + * Starts floating + */ + void startFloating(const QPoint& Offset); + + /** + * Makes the dock area floating + */ + IFloatingWidget* makeAreaFloating(const QPoint& Offset, eDragState DragState); + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction* createAutoHideToAction(const QString& Title, SideBarLocation Location, + QMenu* Menu) + { + auto Action = Menu->addAction(Title); + Action->setProperty(internal::LocationProperty, Location); + QObject::connect(Action, &QAction::triggered, _this, &CDockAreaTitleBar::onAutoHideToActionClicked); + return Action; + } +};// struct DockAreaTitleBarPrivate + +//============================================================================ +DockAreaTitleBarPrivate::DockAreaTitleBarPrivate(CDockAreaTitleBar* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockAreaTitleBarPrivate::createButtons() +{ + QSizePolicy ButtonSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + // Tabs menu button + TabsMenuButton = new CTitleBarButton(testConfigFlag(CDockManager::DockAreaHasTabsMenuButton), + false, TitleBarButtonTabsMenu); + TabsMenuButton->setObjectName("tabsMenuButton"); + TabsMenuButton->setAutoRaise(true); + TabsMenuButton->setPopupMode(QToolButton::InstantPopup); + internal::setButtonIcon(TabsMenuButton, QStyle::SP_TitleBarUnshadeButton, ads::DockAreaMenuIcon); + QMenu* TabsMenu = new QMenu(TabsMenuButton); +#ifndef QT_NO_TOOLTIP + TabsMenu->setToolTipsVisible(true); +#endif + _this->connect(TabsMenu, SIGNAL(aboutToShow()), SLOT(onTabsMenuAboutToShow())); + TabsMenuButton->setMenu(TabsMenu); + internal::setToolTip(TabsMenuButton, QObject::tr("List All Tabs")); + TabsMenuButton->setSizePolicy(ButtonSizePolicy); + Layout->addWidget(TabsMenuButton, 0); + _this->connect(TabsMenuButton->menu(), SIGNAL(triggered(QAction*)), + SLOT(onTabsMenuActionTriggered(QAction*))); + + // Undock button + UndockButton = new CTitleBarButton(testConfigFlag(CDockManager::DockAreaHasUndockButton), + true, TitleBarButtonUndock); + UndockButton->setObjectName("detachGroupButton"); + UndockButton->setAutoRaise(true); + internal::setToolTip(UndockButton, QObject::tr("Detach Group")); + internal::setButtonIcon(UndockButton, QStyle::SP_TitleBarNormalButton, ads::DockAreaUndockIcon); + UndockButton->setSizePolicy(ButtonSizePolicy); + Layout->addWidget(UndockButton, 0); + _this->connect(UndockButton, SIGNAL(clicked()), SLOT(onUndockButtonClicked())); + + // AutoHide Button + const auto autoHideEnabled = testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled); + AutoHideButton = new CTitleBarButton(testAutoHideConfigFlag(CDockManager::DockAreaHasAutoHideButton) && autoHideEnabled, + true, TitleBarButtonAutoHide); + AutoHideButton->setObjectName("dockAreaAutoHideButton"); + AutoHideButton->setAutoRaise(true); + internal::setToolTip(AutoHideButton, _this->titleBarButtonToolTip(TitleBarButtonAutoHide)); + internal::setButtonIcon(AutoHideButton, QStyle::SP_DialogOkButton, ads::AutoHideIcon); + AutoHideButton->setSizePolicy(ButtonSizePolicy); + AutoHideButton->setCheckable(testAutoHideConfigFlag(CDockManager::AutoHideButtonCheckable)); + AutoHideButton->setChecked(false); + Layout->addWidget(AutoHideButton, 0); + _this->connect(AutoHideButton, SIGNAL(clicked()), SLOT(onAutoHideButtonClicked())); + + // Minimize button + MinimizeButton = new CTitleBarButton(testAutoHideConfigFlag(CDockManager::AutoHideHasMinimizeButton), + false, TitleBarButtonMinimize); + MinimizeButton->setObjectName("dockAreaMinimizeButton"); + MinimizeButton->setAutoRaise(true); + MinimizeButton->setVisible(false); + internal::setButtonIcon(MinimizeButton, QStyle::SP_TitleBarMinButton, ads::DockAreaMinimizeIcon); + internal::setToolTip(MinimizeButton, QObject::tr("Minimize")); + MinimizeButton->setSizePolicy(ButtonSizePolicy); + Layout->addWidget(MinimizeButton, 0); + _this->connect(MinimizeButton, SIGNAL(clicked()), SLOT(minimizeAutoHideContainer())); + + // Close button + CloseButton = new CTitleBarButton(testConfigFlag(CDockManager::DockAreaHasCloseButton), + true, TitleBarButtonClose); + CloseButton->setObjectName("dockAreaCloseButton"); + CloseButton->setAutoRaise(true); + internal::setButtonIcon(CloseButton, QStyle::SP_TitleBarCloseButton, ads::DockAreaCloseIcon); + internal::setToolTip(CloseButton, _this->titleBarButtonToolTip(TitleBarButtonClose)); + CloseButton->setSizePolicy(ButtonSizePolicy); + CloseButton->setIconSize(QSize(16, 16)); + Layout->addWidget(CloseButton, 0); + _this->connect(CloseButton, SIGNAL(clicked()), SLOT(onCloseButtonClicked())); +} + + +//============================================================================ +void DockAreaTitleBarPrivate::createAutoHideTitleLabel() +{ + AutoHideTitleLabel = new CElidingLabel(""); + AutoHideTitleLabel->setObjectName("autoHideTitleLabel"); + // At position 0 is the tab bar - insert behind tab bar + Layout->insertWidget(1, AutoHideTitleLabel); + AutoHideTitleLabel->setVisible(false); // Default hidden + Layout->insertWidget(2 ,new CSpacerWidget(_this)); +} + + +//============================================================================ +void DockAreaTitleBarPrivate::createTabBar() +{ + TabBar = componentsFactory()->createDockAreaTabBar(DockArea); + TabBar->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); + Layout->addWidget(TabBar); + _this->connect(TabBar, SIGNAL(tabClosed(int)), SLOT(markTabsMenuOutdated())); + _this->connect(TabBar, SIGNAL(tabOpened(int)), SLOT(markTabsMenuOutdated())); + _this->connect(TabBar, SIGNAL(tabInserted(int)), SLOT(markTabsMenuOutdated())); + _this->connect(TabBar, SIGNAL(removingTab(int)), SLOT(markTabsMenuOutdated())); + _this->connect(TabBar, SIGNAL(tabMoved(int, int)), SLOT(markTabsMenuOutdated())); + _this->connect(TabBar, SIGNAL(currentChanged(int)), SLOT(onCurrentTabChanged(int))); + _this->connect(TabBar, SIGNAL(tabBarClicked(int)), SIGNAL(tabBarClicked(int))); + _this->connect(TabBar, SIGNAL(elidedChanged(bool)), SLOT(markTabsMenuOutdated())); +} + + +//============================================================================ +IFloatingWidget* DockAreaTitleBarPrivate::makeAreaFloating(const QPoint& Offset, eDragState DragState) +{ + QSize Size = DockArea->size(); + this->DragState = DragState; + bool CreateFloatingDockContainer = (DraggingFloatingWidget != DragState); + CFloatingDockContainer* FloatingDockContainer = nullptr; + IFloatingWidget* FloatingWidget; + if (CreateFloatingDockContainer) + { + if (DockArea->autoHideDockContainer()) + { + DockArea->autoHideDockContainer()->cleanupAndDelete(); + } + FloatingWidget = FloatingDockContainer = new CFloatingDockContainer(DockArea); + } + else + { + auto w = new CFloatingDragPreview(DockArea); + QObject::connect(w, &CFloatingDragPreview::draggingCanceled, [=]() + { + this->DragState = DraggingInactive; + }); + FloatingWidget = w; + } + + FloatingWidget->startFloating(Offset, Size, DragState, nullptr); + if (FloatingDockContainer) + { + auto TopLevelDockWidget = FloatingDockContainer->topLevelDockWidget(); + if (TopLevelDockWidget) + { + TopLevelDockWidget->emitTopLevelChanged(true); + } + } + + return FloatingWidget; +} + + +//============================================================================ +void DockAreaTitleBarPrivate::startFloating(const QPoint& Offset) +{ + if (DockArea->autoHideDockContainer()) + { + DockArea->autoHideDockContainer()->hide(); + } + FloatingWidget = makeAreaFloating(Offset, DraggingFloatingWidget); + qApp->postEvent(DockArea, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent)); +} + + +//============================================================================ +CDockAreaTitleBar::CDockAreaTitleBar(CDockAreaWidget* parent) : + QFrame(parent), + d(new DockAreaTitleBarPrivate(this)) +{ + d->DockArea = parent; + + setObjectName("dockAreaTitleBar"); + d->Layout = new QBoxLayout(QBoxLayout::LeftToRight); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + + d->createTabBar(); + d->createButtons(); + d->createAutoHideTitleLabel(); + + setFocusPolicy(Qt::NoFocus); +} + + +//============================================================================ +CDockAreaTitleBar::~CDockAreaTitleBar() +{ + if (!d->CloseButton.isNull()) + { + delete d->CloseButton; + } + + if (!d->TabsMenuButton.isNull()) + { + delete d->TabsMenuButton; + } + + if (!d->UndockButton.isNull()) + { + delete d->UndockButton; + } + delete d; +} + + +//============================================================================ +CDockAreaTabBar* CDockAreaTitleBar::tabBar() const +{ + return d->TabBar; +} + + +//============================================================================ +void CDockAreaTitleBar::resizeEvent(QResizeEvent *event) +{ + Super::resizeEvent(event); + if (CDockManager::testConfigFlag(CDockManager::DockAreaDynamicTabsMenuButtonVisibility) + && CDockManager::testConfigFlag(CDockManager::DisableTabTextEliding)) + { + markTabsMenuOutdated(); + } +} + + +//============================================================================ +void CDockAreaTitleBar::markTabsMenuOutdated() +{ + if (CDockManager::testConfigFlag(CDockManager::DockAreaDynamicTabsMenuButtonVisibility)) + { + bool TabsMenuButtonVisible = false; + if (CDockManager::testConfigFlag(CDockManager::DisableTabTextEliding)) + { + TabsMenuButtonVisible = d->TabBar->areTabsOverflowing(); + } + else + { + bool hasElidedTabTitle = false; + for (int i = 0; i < d->TabBar->count(); ++i) + { + if (!d->TabBar->isTabOpen(i)) + { + continue; + } + CDockWidgetTab* Tab = d->TabBar->tab(i); + if(Tab->isTitleElided()) + { + hasElidedTabTitle = true; + break; + } + } + TabsMenuButtonVisible = (hasElidedTabTitle && (d->TabBar->count() > 1)); + } + QMetaObject::invokeMethod(d->TabsMenuButton, "setVisible", Qt::QueuedConnection, Q_ARG(bool, TabsMenuButtonVisible)); + } + d->MenuOutdated = true; +} + +//============================================================================ +void CDockAreaTitleBar::onTabsMenuAboutToShow() +{ + if (!d->MenuOutdated) + { + return; + } + + QMenu* menu = d->TabsMenuButton->menu(); + menu->clear(); + for (int i = 0; i < d->TabBar->count(); ++i) + { + if (!d->TabBar->isTabOpen(i)) + { + continue; + } + auto Tab = d->TabBar->tab(i); + QAction* Action = menu->addAction(Tab->icon(), Tab->text()); + internal::setToolTip(Action, Tab->toolTip()); + Action->setData(i); + } + + d->MenuOutdated = false; +} + + +//============================================================================ +void CDockAreaTitleBar::onCloseButtonClicked() +{ + ADS_PRINT("CDockAreaTitleBar::onCloseButtonClicked"); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideCloseButtonCollapsesDock) && + d->DockArea->autoHideDockContainer()) + { + d->DockArea->autoHideDockContainer()->collapseView(true); + } + else if (d->testConfigFlag(CDockManager::DockAreaCloseButtonClosesTab)) + { + d->TabBar->closeTab(d->TabBar->currentIndex()); + } + else + { + d->DockArea->closeArea(); + } +} + + +//============================================================================ +void CDockAreaTitleBar::onAutoHideCloseActionTriggered() +{ + d->DockArea->closeArea(); +} + + +//============================================================================ +void CDockAreaTitleBar::minimizeAutoHideContainer() +{ + auto AutoHideContainer = d->DockArea->autoHideDockContainer(); + if (AutoHideContainer) + { + AutoHideContainer->collapseView(true); + } +} + + +//============================================================================ +void CDockAreaTitleBar::onUndockButtonClicked() +{ + if (d->DockArea->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); + } +} + + +//============================================================================ +void CDockAreaTitleBar::onTabsMenuActionTriggered(QAction* Action) +{ + int Index = Action->data().toInt(); + d->TabBar->setCurrentIndex(Index); + Q_EMIT tabBarClicked(Index); +} + + +//============================================================================ +void CDockAreaTitleBar::updateDockWidgetActionsButtons() +{ + auto Tab = d->TabBar->currentTab(); + if (!Tab) + { + return; + } + + CDockWidget* DockWidget = Tab->dockWidget(); + if (!d->DockWidgetActionsButtons.isEmpty()) + { + for (auto Button : d->DockWidgetActionsButtons) + { + d->Layout->removeWidget(Button); + delete Button; + } + d->DockWidgetActionsButtons.clear(); + } + + auto Actions = DockWidget->titleBarActions(); + if (Actions.isEmpty()) + { + return; + } + + int InsertIndex = indexOf(d->TabsMenuButton); + for (auto Action : Actions) + { + auto Button = new CTitleBarButton(true, false, TitleBarButtonTabsMenu, this); + Button->setDefaultAction(Action); + Button->setAutoRaise(true); + Button->setPopupMode(QToolButton::InstantPopup); + Button->setObjectName(Action->objectName()); + d->Layout->insertWidget(InsertIndex++, Button, 0); + d->DockWidgetActionsButtons.append(Button); + } +} + + +//============================================================================ +void CDockAreaTitleBar::onCurrentTabChanged(int Index) +{ + if (Index < 0) + { + return; + } + + if (d->testConfigFlag(CDockManager::DockAreaCloseButtonClosesTab)) + { + CDockWidget* DockWidget = d->TabBar->tab(Index)->dockWidget(); + d->CloseButton->setEnabled(DockWidget->features().testFlag(CDockWidget::DockWidgetClosable)); + } + + updateDockWidgetActionsButtons(); +} + + +//============================================================================ +void CDockAreaTitleBar::onAutoHideButtonClicked() +{ + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideButtonTogglesArea) + || qApp->keyboardModifiers().testFlag(Qt::ControlModifier)) + { + d->DockArea->toggleAutoHide(); + } + else + { + d->DockArea->currentDockWidget()->toggleAutoHide(); + } +} + + +//============================================================================ +void CDockAreaTitleBar::onAutoHideDockAreaActionClicked() +{ + d->DockArea->toggleAutoHide(); +} + + +//============================================================================ +void CDockAreaTitleBar::onAutoHideToActionClicked() +{ + int Location = sender()->property(internal::LocationProperty).toInt(); + d->DockArea->toggleAutoHide((SideBarLocation)Location); +} + + +//============================================================================ +CTitleBarButton* CDockAreaTitleBar::button(TitleBarButton which) const +{ + switch (which) + { + case TitleBarButtonTabsMenu: return d->TabsMenuButton; + case TitleBarButtonUndock: return d->UndockButton; + case TitleBarButtonClose: return d->CloseButton; + case TitleBarButtonAutoHide: return d->AutoHideButton; + case TitleBarButtonMinimize: return d->MinimizeButton; + default: + return nullptr; + } +} + + +//============================================================================ +CElidingLabel* CDockAreaTitleBar::autoHideTitleLabel() const +{ + return d->AutoHideTitleLabel; +} + + +//============================================================================ +void CDockAreaTitleBar::setVisible(bool Visible) +{ + Super::setVisible(Visible); + markTabsMenuOutdated(); +} + + +//============================================================================ +void CDockAreaTitleBar::mousePressEvent(QMouseEvent* ev) +{ + if (ev->button() == Qt::LeftButton) + { + ev->accept(); + d->DragStartMousePos = ev->pos(); + d->DragState = DraggingMousePressed; + + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + d->dockManager()->dockFocusController()->setDockWidgetTabFocused(d->TabBar->currentTab()); + } + return; + } + Super::mousePressEvent(ev); +} + + +//============================================================================ +void CDockAreaTitleBar::mouseReleaseEvent(QMouseEvent* ev) +{ + if (ev->button() == Qt::LeftButton) + { + ADS_PRINT("CDockAreaTitleBar::mouseReleaseEvent"); + ev->accept(); + auto CurrentDragState = d->DragState; + d->DragStartMousePos = QPoint(); + d->DragState = DraggingInactive; + if (DraggingFloatingWidget == CurrentDragState) + { + d->FloatingWidget->finishDragging(); + } + + return; + } + Super::mouseReleaseEvent(ev); +} + + +//============================================================================ +void CDockAreaTitleBar::mouseMoveEvent(QMouseEvent* ev) +{ + Super::mouseMoveEvent(ev); + if (!(ev->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) + { + d->DragState = DraggingInactive; + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) + { + d->FloatingWidget->moveFloating(); + return; + } + + // If this is the last dock area in a floating 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->dockContainer()->visibleDockAreaCount() == 1 + && !d->DockArea->isAutoHide()) + { + return; + } + + // If one single dock widget in this area is not floatable then the whole + // area is not floatable + // We can create the floating drag preview if the dock widget is movable + auto Features = d->DockArea->features(); + if (!Features.testFlag(CDockWidget::DockWidgetFloatable) && !(Features.testFlag(CDockWidget::DockWidgetMovable))) + { + return; + } + + int DragDistance = (d->DragStartMousePos - ev->pos()).manhattanLength(); + if (DragDistance >= CDockManager::startDragDistance()) + { + ADS_PRINT("CDockAreaTitleBar::startFloating"); + d->startFloating(d->DragStartMousePos); + auto Overlay = d->DockArea->dockManager()->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + } + + return; +} + + +//============================================================================ +void CDockAreaTitleBar::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->dockContainer()->dockAreaCount() == 1) + { + return; + } + + if (!d->DockArea->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + return; + } + + d->makeAreaFloating(event->pos(), DraggingInactive); +} + + +//============================================================================ +void CDockAreaTitleBar::setAreaFloating() +{ + // 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. + auto DockContainer = d->DockArea->dockContainer(); + if (DockContainer->isFloating() && DockContainer->dockAreaCount() == 1 + && !d->DockArea->isAutoHide()) + { + return; + } + + if (!d->DockArea->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + return; + } + + d->makeAreaFloating(mapFromGlobal(QCursor::pos()), DraggingInactive); +} + + +//============================================================================ +void CDockAreaTitleBar::contextMenuEvent(QContextMenuEvent* ev) +{ + ev->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + { + return; + } + + auto Menu = buildContextMenu(nullptr); + Menu->exec(ev->globalPos()); + delete Menu; +} + +QMenu* CDockAreaTitleBar::buildContextMenu(QMenu *Menu) +{ + const bool isAutoHide = d->DockArea->isAutoHide(); + const bool isTopLevelArea = d->DockArea->isTopLevelArea(); + QAction* Action; + if (Menu == nullptr) + { + Menu = new QMenu(this); + } + + if (!isTopLevelArea) + { + Action = Menu->addAction(isAutoHide ? tr("Detach") : tr("Detach Group"), + this, SLOT(onUndockButtonClicked())); + Action->setEnabled(d->DockArea->features().testFlag(CDockWidget::DockWidgetFloatable)); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + Action = Menu->addAction(isAutoHide ? tr("Unpin (Dock)") : tr("Pin Group"), this, SLOT(onAutoHideDockAreaActionClicked())); + auto AreaIsPinnable = d->DockArea->features().testFlag(CDockWidget::DockWidgetPinnable); + Action->setEnabled(AreaIsPinnable); + + if (!isAutoHide) + { + auto menu = Menu->addMenu(tr("Pin Group To...")); + menu->setEnabled(AreaIsPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, menu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, menu); + d->createAutoHideToAction(tr("Right"), SideBarRight, menu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, menu); + } + } + Menu->addSeparator(); + } + + if (isAutoHide) + { + Action = Menu->addAction(tr("Minimize"), this, SLOT(minimizeAutoHideContainer())); + Action = Menu->addAction(tr("Close"), this, SLOT(onAutoHideCloseActionTriggered())); + } + else + { + Action = Menu->addAction(isAutoHide ? tr("Close") : tr("Close Group"), this, SLOT(onCloseButtonClicked())); + } + + Action->setEnabled(d->DockArea->features().testFlag(CDockWidget::DockWidgetClosable)); + if (!isAutoHide && !isTopLevelArea) + { + Action = Menu->addAction(tr("Close Other Groups"), d->DockArea, SLOT(closeOtherAreas())); + } + return Menu; +} + +//============================================================================ +void CDockAreaTitleBar::insertWidget(int index, QWidget *widget) +{ + d->Layout->insertWidget(index, widget); +} + + +//============================================================================ +int CDockAreaTitleBar::indexOf(QWidget *widget) const +{ + return d->Layout->indexOf(widget); +} + +//============================================================================ +QString CDockAreaTitleBar::titleBarButtonToolTip(TitleBarButton Button) const +{ + switch (Button) + { + case TitleBarButtonAutoHide: + if (d->DockArea->isAutoHide()) + { + return tr("Unpin (Dock)"); + } + + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideButtonTogglesArea)) + { + return tr("Pin Group"); + } + else + { + return tr("Pin Active Tab (Press Ctrl to Pin Group)"); + } + break; + + case TitleBarButtonClose: + if (d->DockArea->isAutoHide()) + { + bool Minimize = CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideCloseButtonCollapsesDock); + return Minimize ? tr("Minimize") : tr("Close"); + } + + if (CDockManager::testConfigFlag(CDockManager::DockAreaCloseButtonClosesTab)) + { + return tr("Close Active Tab"); + } + else + { + return tr("Close Group"); + } + break; + + default: + break; + } + + return QString(); +} + + +//============================================================================ +void CDockAreaTitleBar::showAutoHideControls(bool Show) +{ + d->TabBar->setVisible(!Show); // Auto hide toolbar never has tabs + d->MinimizeButton->setVisible(Show); + d->AutoHideTitleLabel->setVisible(Show); +} + + +//============================================================================ +bool CDockAreaTitleBar::isAutoHide() const +{ + return d->DockArea && d->DockArea->isAutoHide(); +} + + +//============================================================================ +CDockAreaWidget* CDockAreaTitleBar::dockAreaWidget() const +{ + return d->DockArea; +} + + +//============================================================================ +CTitleBarButton::CTitleBarButton(bool showInTitleBar, bool hideWhenDisabled, TitleBarButton ButtonId, QWidget* parent) + : tTitleBarButton(parent), + ShowInTitleBar(showInTitleBar), + HideWhenDisabled(CDockManager::testConfigFlag(CDockManager::DockAreaHideDisabledButtons) && hideWhenDisabled), + TitleBarButtonId(ButtonId) +{ + setFocusPolicy(Qt::NoFocus); +} + +//============================================================================ +void CTitleBarButton::setVisible(bool visible) +{ + // 'visible' can stay 'true' if and only if this button is configured to generally visible: + visible = visible && this->ShowInTitleBar; + + // 'visible' can stay 'true' unless: this button is configured to be invisible when it is disabled and it is currently disabled: + if (visible && HideWhenDisabled) + { + visible = isEnabled(); + } + + Super::setVisible(visible); +} + + +//============================================================================ +void CTitleBarButton::setShowInTitleBar(bool Show) +{ + this->ShowInTitleBar = Show; + if (!Show) + { + setVisible(false); + } +} + + +//============================================================================ +bool CTitleBarButton::event(QEvent *ev) +{ + if (QEvent::EnabledChange != ev->type() || !HideWhenDisabled || !ShowInTitleBar) + { + return Super::event(ev); + } + + bool Show = true; + if (isInAutoHideArea()) + { + switch (TitleBarButtonId) + { + case TitleBarButtonClose: Show = CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideHasCloseButton); break; + case TitleBarButtonUndock: Show = false; break; + default: + break; + } + } + + // force setVisible() call - Calling setVisible() directly here doesn't + // work well when button is expected to be shown first time + QMetaObject::invokeMethod(this, "setVisible", Qt::QueuedConnection, + Q_ARG(bool, isEnabledTo(this->parentWidget()) & Show)); + + return Super::event(ev); +} + + +//============================================================================ +CDockAreaTitleBar* CTitleBarButton::titleBar() const +{ + return qobject_cast(parentWidget()); +} + + +//============================================================================ +bool CTitleBarButton::isInAutoHideArea() const +{ + auto TitleBar = titleBar(); + return TitleBar && TitleBar->isAutoHide(); +} + + +//============================================================================ +CSpacerWidget::CSpacerWidget(QWidget* Parent /*= 0*/) : Super(Parent) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + setStyleSheet("border: none; background: none;"); +} + + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockAreaTitleBar.cpp diff --git a/src/DockAreaTitleBar.h b/src/DockAreaTitleBar.h new file mode 100644 index 000000000..56820c4b9 --- /dev/null +++ b/src/DockAreaTitleBar.h @@ -0,0 +1,277 @@ +#ifndef DockAreaTitleBarH +#define DockAreaTitleBarH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaTitleBar.h +/// \author Uwe Kindler +/// \date 12.10.2018 +/// \brief Declaration of CDockAreaTitleBar class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include + +#include "ads_globals.h" + +QT_FORWARD_DECLARE_CLASS(QAbstractButton) + +namespace ads +{ +class CDockAreaTabBar; +class CDockAreaWidget; +struct DockAreaTitleBarPrivate; +class CElidingLabel; +class CDockAreaTitleBar; + +using tTitleBarButton = QToolButton; + +/** +* Title bar button of a dock area that customizes tTitleBarButton appearance/behaviour +* according to various config flags such as: +* CDockManager::DockAreaHas_xxx_Button - if set to 'false' keeps the button always invisible +* CDockManager::DockAreaHideDisabledButtons - if set to 'true' hides button when it is disabled +*/ +class CTitleBarButton : public tTitleBarButton +{ + Q_OBJECT + +private: + bool ShowInTitleBar = true; + bool HideWhenDisabled = false; + TitleBarButton TitleBarButtonId; + +public: + using Super = tTitleBarButton; + CTitleBarButton(bool ShowInTitleBar, bool HideWhenDisabled, TitleBarButton ButtonId, + QWidget* parent = nullptr); + + /** + * Adjust this visibility change request with our internal settings: + */ + virtual void setVisible(bool visible) override; + + /** + * Configures, if the title bar button should be shown in title bar + */ + void setShowInTitleBar(bool Show); + + /** + * Identifier for the title bar button + */ + TitleBarButton buttonId() const {return TitleBarButtonId;} + + /** + * Return the title bar that contains this button + */ + CDockAreaTitleBar* titleBar() const; + + /** + * Returns true, if the button is in a title bar in an auto hide area + */ + bool isInAutoHideArea() const; + + +protected: + /** + * Handle EnabledChanged signal to set button invisible if the configured + */ + bool event(QEvent *ev) override; +}; + + +/** + * Title bar of a dock area. + * The title bar contains a tabbar with all tabs for a dock widget group and + * with a tabs menu button, a undock button and a close button. + */ +class ADS_EXPORT CDockAreaTitleBar : public QFrame +{ + Q_OBJECT +private: + DockAreaTitleBarPrivate* d; ///< private data (pimpl) + friend struct DockAreaTitleBarPrivate; + +private Q_SLOTS: + void onTabsMenuAboutToShow(); + void onCloseButtonClicked(); + void onAutoHideCloseActionTriggered(); + void minimizeAutoHideContainer(); + void onUndockButtonClicked(); + void onTabsMenuActionTriggered(QAction* Action); + void onCurrentTabChanged(int Index); + void onAutoHideButtonClicked(); + void onAutoHideDockAreaActionClicked(); + void onAutoHideToActionClicked(); + +protected: + /** + * Stores mouse position to detect dragging + */ + virtual void mousePressEvent(QMouseEvent* ev) override; + + /** + * Stores mouse position to detect dragging + */ + virtual void mouseReleaseEvent(QMouseEvent* ev) override; + + /** + * Starts floating the complete docking area including all dock widgets, + * if it is not the last dock area in a floating widget + */ + virtual void mouseMoveEvent(QMouseEvent* ev) override; + + /** + * Double clicking the title bar also starts floating of the complete area + */ + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + + /** + * Show context menu + */ + virtual void contextMenuEvent(QContextMenuEvent *event) override; + + /** + * Handle resize events + */ + virtual void resizeEvent(QResizeEvent *event) override; + +public Q_SLOTS: + /** + * Call this slot to tell the title bar that it should update the tabs menu + * the next time it is shown. + */ + void markTabsMenuOutdated(); + + +public: + using Super = QFrame; + + /** + * Default Constructor + */ + CDockAreaTitleBar(CDockAreaWidget* parent); + + /** + * Virtual Destructor + */ + virtual ~CDockAreaTitleBar(); + + /** + * Returns the pointer to the tabBar() + */ + CDockAreaTabBar* tabBar() const; + + /** + * Returns the button corresponding to the given title bar button identifier + */ + CTitleBarButton* button(TitleBarButton which) const; + + /** + * Returns the auto hide title label, used when the dock area is expanded and auto hidden + */ + CElidingLabel* autoHideTitleLabel() const; + + /** + * Returns the dock area widget that contains this title bar + */ + CDockAreaWidget* dockAreaWidget() const; + + /** + * Updates the visibility of the dock widget actions in the title bar + */ + void updateDockWidgetActionsButtons(); + + /** + * Marks the tabs menu outdated before it calls its base class + * implementation + */ + virtual void setVisible(bool Visible) override; + + /** + * Inserts a custom widget at position index into this title bar. + * If index is negative, the widget is added at the end. + * You can use this function to insert custom widgets into the title bar. + */ + void insertWidget(int index, QWidget *widget); + + /** + * Searches for widget widget in this title bar. + * You can use this function, to get the position of the default + * widget in the tile bar. + * \code + * int tabBarIndex = TitleBar->indexOf(TitleBar->tabBar()); + * int closeButtonIndex = TitleBar->indexOf(TitleBar->button(TitleBarButtonClose)); + * \endcode + */ + int indexOf(QWidget *widget) const; + + /** + * Close group tool tip based on the current state + * Auto hide widgets can only have one dock widget so it does not make sense for the tooltip to show close group + */ + QString titleBarButtonToolTip(TitleBarButton Button) const; + + /** + * Moves the dock area into its own floating widget if the area + * DockWidgetFloatable flag is true + */ + void setAreaFloating(); + + /** + * Call this function, to create all the required auto hide controls + */ + void showAutoHideControls(bool Show); + + /** + * Returns true, if the auto hide controls are visible + */ + bool isAutoHide() const; + + /** + * Fills the provided menu with standard entries. If a nullptr is passed, a + * new menu is created and filled with standard entries. + * This function is called from the actual version of contextMenuEvent, but + * can be called from any code. Caller is responsible of deleting the created + * object. + * + * @param menu The QMenu to fill with standard entries. If nullptr, a new + * QMenu will be created. + * @return The filled QMenu, either the provided one or a newly created one if + * nullptr was passed. + */ + virtual QMenu *buildContextMenu(QMenu *); + +Q_SIGNALS: + /** + * This signal is emitted if a tab in the tab bar is clicked by the user + * or if the user clicks on a tab item in the title bar tab menu. + */ + void tabBarClicked(int index); +}; // class name + +} + // namespace ads +//----------------------------------------------------------------------------- +#endif // DockAreaTitleBarH diff --git a/src/DockAreaTitleBar_p.h b/src/DockAreaTitleBar_p.h new file mode 100644 index 000000000..f097919ab --- /dev/null +++ b/src/DockAreaTitleBar_p.h @@ -0,0 +1,63 @@ +#ifndef DockAreaTitleBar_pH +#define DockAreaTitleBar_pH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaTitleBar_p.h +/// \author Uwe Kindler +/// \date 12.10.2018 +/// \brief Declaration of classes CTitleBarButton and CSpacerWidget +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +namespace ads +{ + + + +/** +* This spacer widget is here because of the following problem. +* The dock area title bar handles mouse dragging and moving the floating widget. +* The problem is, that if the title bar becomes invisible, i.e. if the dock +* area contains only one single dock widget and the dock area is moved +* into a floating widget, then mouse events are not handled anymore and dragging +* of the floating widget stops. +*/ +class CSpacerWidget : public QWidget +{ + Q_OBJECT +public: + using Super = QWidget; + CSpacerWidget(QWidget* Parent = nullptr); + virtual QSize sizeHint() const override {return QSize(0, 0);} + virtual QSize minimumSizeHint() const override {return QSize(0, 0);} +}; + +} + // namespace ads +//----------------------------------------------------------------------------- +#endif // DockAreaTitleBar_pH diff --git a/src/DockAreaWidget.cpp b/src/DockAreaWidget.cpp new file mode 100644 index 000000000..b3828d36a --- /dev/null +++ b/src/DockAreaWidget.cpp @@ -0,0 +1,1494 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaWidget.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of CDockAreaWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include "DockAreaWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ElidingLabel.h" +#include "DockContainerWidget.h" +#include "DockWidget.h" +#include "FloatingDockContainer.h" +#include "DockManager.h" +#include "DockOverlay.h" +#include "DockAreaTabBar.h" +#include "DockSplitter.h" +#include "DockAreaTitleBar.h" +#include "DockComponentsFactory.h" +#include "DockWidgetTab.h" +#include "DockingStateReader.h" + + +namespace ads +{ +static const char* const INDEX_PROPERTY = "index"; +static const char* const ACTION_PROPERTY = "action"; + +/** + * Check, if auto hide is enabled + */ +static bool isAutoHideFeatureEnabled() +{ + return CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled); +} + + +/** + * Internal dock area layout mimics stack layout but only inserts the current + * widget into the internal QLayout object. + * \warning Only the current widget has a parent. All other widgets + * do not have a parent. That means, a widget that is in this layout may + * return nullptr for its parent() function if it is not the current widget. + */ +class CDockAreaLayout +{ +private: + QBoxLayout* m_ParentLayout; + QList> m_Widgets; + int m_CurrentIndex = -1; + QWidget* m_CurrentWidget = nullptr; + +public: + /** + * Creates an instance with the given parent layout + */ + CDockAreaLayout(QBoxLayout* ParentLayout) + : m_ParentLayout(ParentLayout) + { + + } + + /** + * Returns the number of widgets in this layout + */ + int count() const + { + return m_Widgets.count(); + } + + /** + * Inserts the widget at the given index position into the internal widget + * list + */ + void insertWidget(int index, QWidget* Widget) + { + Widget->setParent(nullptr); + if (index < 0) + { + index = m_Widgets.count(); + } + m_Widgets.insert(index, Widget); + if (m_CurrentIndex < 0) + { + setCurrentIndex(index); + } + else + { + if (index <= m_CurrentIndex ) + { + ++m_CurrentIndex; + } + } + } + + /** + * Removes the given widget from the layout + */ + void removeWidget(QWidget* Widget) + { + if (currentWidget() == Widget) + { + auto LayoutItem = m_ParentLayout->takeAt(1); + if (LayoutItem) + { + LayoutItem->widget()->setParent(nullptr); + } + delete LayoutItem; + m_CurrentWidget = nullptr; + m_CurrentIndex = -1; + } + else if (indexOf(Widget) < m_CurrentIndex) + { + --m_CurrentIndex; + } + m_Widgets.removeOne(Widget); + } + + /** + * Returns the current selected widget + */ + QWidget* currentWidget() const + { + return m_CurrentWidget; + } + + /** + * Activates the widget with the give index. + */ + void setCurrentIndex(int index) + { + QWidget *prev = currentWidget(); + QWidget *next = widget(index); + if (!next || (next == prev && !m_CurrentWidget)) + { + return; + } + + bool reenableUpdates = false; + QWidget *parent = m_ParentLayout->parentWidget(); + + if (parent && parent->updatesEnabled()) + { + reenableUpdates = true; + parent->setUpdatesEnabled(false); + } + + auto LayoutItem = m_ParentLayout->takeAt(1); + if (LayoutItem) + { + LayoutItem->widget()->setParent(nullptr); + } + delete LayoutItem; + + m_ParentLayout->addWidget(next); + if (prev) + { + prev->hide(); + } + m_CurrentIndex = index; + m_CurrentWidget = next; + + + if (reenableUpdates) + { + parent->setUpdatesEnabled(true); + } + } + + /** + * Returns the index of the current active widget + */ + int currentIndex() const + { + return m_CurrentIndex; + } + + /** + * Returns true if there are no widgets in the layout + */ + bool isEmpty() const + { + return m_Widgets.empty(); + } + + /** + * Returns the index of the given widget + */ + int indexOf(QWidget* w) const + { + return m_Widgets.indexOf(w); + } + + /** + * Returns the widget for the given index + */ + QWidget* widget(int index) const + { + return (index < m_Widgets.size()) ? m_Widgets.at(index) : nullptr; + } + + /** + * Returns the geometry of the current active widget + */ + QRect geometry() const + { + return m_Widgets.empty() ? QRect() : currentWidget()->geometry(); + } +}; + + + +using DockAreaLayout = CDockAreaLayout; +static const DockWidgetAreas DefaultAllowedAreas = AllDockAreas; + + +/** + * Private data class of CDockAreaWidget class (pimpl) + */ +struct DockAreaWidgetPrivate +{ + CDockAreaWidget* _this = nullptr; + QBoxLayout* Layout = nullptr; + DockAreaLayout* ContentsLayout = nullptr; + CDockAreaTitleBar* TitleBar = nullptr; + CDockManager* DockManager = nullptr; + CAutoHideDockContainer* AutoHideDockContainer = nullptr; + bool UpdateTitleBarButtons = false; + DockWidgetAreas AllowedAreas = DefaultAllowedAreas; + QSize MinSizeHint; + CDockAreaWidget::DockAreaFlags Flags{CDockAreaWidget::DefaultFlags}; + + /** + * Private data constructor + */ + DockAreaWidgetPrivate(CDockAreaWidget* _public); + + /** + * Creates the layout for top area with tabs and close button + */ + void createTitleBar(); + + /** + * Returns the dock widget with the given index + */ + CDockWidget* dockWidgetAt(int index) + { + return qobject_cast(ContentsLayout->widget(index)); + } + + /** + * Convenience function to ease title widget access by index + */ + CDockWidgetTab* tabWidgetAt(int index) + { + return dockWidgetAt(index)->tabWidget(); + } + + + /** + * Returns the tab action of the given dock widget + */ + QAction* dockWidgetTabAction(CDockWidget* DockWidget) const + { + return qvariant_cast(DockWidget->property(ACTION_PROPERTY)); + } + + /** + * Returns the index of the given dock widget + */ + int dockWidgetIndex(CDockWidget* DockWidget) const + { + return DockWidget->property(INDEX_PROPERTY).toInt(); + } + + /** + * Convenience function for tabbar access + */ + CDockAreaTabBar* tabBar() const + { + return TitleBar->tabBar(); + } + + /** + * Updates the enable state of the close and detach button + */ + void updateTitleBarButtonStates(); + + /** + * Updates the enable state of the close and detach button + */ + void updateTitleBarButtonVisibility(bool isTopLevel); + + /** + * Scans all contained dock widgets for the max. minimum size hint + */ + void updateMinimumSizeHint() + { + MinSizeHint = QSize(); + for (int i = 0; i < ContentsLayout->count(); ++i) + { + auto Widget = ContentsLayout->widget(i); + MinSizeHint.setHeight(qMax(MinSizeHint.height(), Widget->minimumSizeHint().height())); + MinSizeHint.setWidth(qMax(MinSizeHint.width(), Widget->minimumSizeHint().width())); + } + } +}; + + +//============================================================================ +DockAreaWidgetPrivate::DockAreaWidgetPrivate(CDockAreaWidget* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockAreaWidgetPrivate::createTitleBar() +{ + TitleBar = componentsFactory()->createDockAreaTitleBar(_this); + Layout->addWidget(TitleBar); + QObject::connect(tabBar(), &CDockAreaTabBar::tabCloseRequested, _this, &CDockAreaWidget::onTabCloseRequested); + QObject::connect(TitleBar, &CDockAreaTitleBar::tabBarClicked, _this, &CDockAreaWidget::setCurrentIndex); + QObject::connect(tabBar(), &CDockAreaTabBar::tabMoved, _this, &CDockAreaWidget::reorderDockWidget); +} + + +//============================================================================ +void DockAreaWidgetPrivate::updateTitleBarButtonStates() +{ + if (_this->isHidden()) + { + UpdateTitleBarButtons = true; + return; + } + + if (_this->isAutoHide()) + { + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideHasCloseButton)) + { + TitleBar->button(TitleBarButtonClose)->setEnabled( + _this->features().testFlag(CDockWidget::DockWidgetClosable)); + } + } + else + { + TitleBar->button(TitleBarButtonUndock)->setEnabled( + _this->features().testFlag(CDockWidget::DockWidgetFloatable)); + TitleBar->button(TitleBarButtonClose)->setEnabled( + _this->features().testFlag(CDockWidget::DockWidgetClosable)); + } + TitleBar->button(TitleBarButtonAutoHide)->setEnabled( + _this->features().testFlag(CDockWidget::DockWidgetPinnable)); + TitleBar->updateDockWidgetActionsButtons(); + UpdateTitleBarButtons = false; +} + + +//============================================================================ +void DockAreaWidgetPrivate::updateTitleBarButtonVisibility(bool IsTopLevel) +{ + auto *const container = _this->dockContainer(); + if (!container) + { + return; + } + + bool IsAutoHide = _this->isAutoHide(); + if (IsAutoHide) + { + bool ShowCloseButton = CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideHasCloseButton); + TitleBar->button(TitleBarButtonClose)->setVisible(ShowCloseButton); + TitleBar->button(TitleBarButtonAutoHide)->setVisible(true); + TitleBar->button(TitleBarButtonUndock)->setVisible(false); + TitleBar->button(TitleBarButtonTabsMenu)->setVisible(false); + } + else if (IsTopLevel) + { + TitleBar->button(TitleBarButtonClose)->setVisible(!container->isFloating()); + TitleBar->button(TitleBarButtonAutoHide)->setVisible(!container->isFloating()); + // Undock and tabs should never show when auto hidden + TitleBar->button(TitleBarButtonUndock)->setVisible(!container->isFloating()); + TitleBar->button(TitleBarButtonTabsMenu)->setVisible(true); + } + else + { + TitleBar->button(TitleBarButtonClose)->setVisible(true); + bool ShowAutoHideButton = CDockManager::testAutoHideConfigFlag(CDockManager::DockAreaHasAutoHideButton); + TitleBar->button(TitleBarButtonAutoHide)->setVisible(ShowAutoHideButton); + TitleBar->button(TitleBarButtonUndock)->setVisible(true); + TitleBar->button(TitleBarButtonTabsMenu)->setVisible(true); + } +} + + +//============================================================================ +CDockAreaWidget::CDockAreaWidget(CDockManager* DockManager, CDockContainerWidget* parent) : + QFrame(parent), + d(new DockAreaWidgetPrivate(this)) +{ + d->DockManager = DockManager; + d->Layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + + d->createTitleBar(); + d->ContentsLayout = new DockAreaLayout(d->Layout); + if (d->DockManager) + { + Q_EMIT d->DockManager->dockAreaCreated(this); + } +} + +//============================================================================ +CDockAreaWidget::~CDockAreaWidget() +{ + ADS_PRINT("~CDockAreaWidget()"); + delete d->ContentsLayout; + delete d; +} + + +//============================================================================ +CDockManager* CDockAreaWidget::dockManager() const +{ + return d->DockManager; +} + + +//============================================================================ +CDockContainerWidget* CDockAreaWidget::dockContainer() const +{ + return internal::findParent(this); +} + +//============================================================================ +CAutoHideDockContainer* CDockAreaWidget::autoHideDockContainer() const +{ + return d->AutoHideDockContainer; +} + + +//============================================================================ +CDockSplitter* CDockAreaWidget::parentSplitter() const +{ + return internal::findParent(this); +} + +//============================================================================ +bool CDockAreaWidget::isAutoHide() const +{ + return d->AutoHideDockContainer != nullptr; +} + +//============================================================================ +void CDockAreaWidget::setAutoHideDockContainer(CAutoHideDockContainer* AutoHideDockContainer) +{ + d->AutoHideDockContainer = AutoHideDockContainer; + updateAutoHideButtonCheckState(); + updateTitleBarButtonsToolTips(); + d->TitleBar->button(TitleBarButtonAutoHide)->setShowInTitleBar(true); +} + + +//============================================================================ +void CDockAreaWidget::addDockWidget(CDockWidget* DockWidget) +{ + insertDockWidget(d->ContentsLayout->count(), DockWidget); +} + + +//============================================================================ +void CDockAreaWidget::insertDockWidget(int index, CDockWidget* DockWidget, + bool Activate) +{ + if (index < 0 || index > d->ContentsLayout->count()) + { + index = d->ContentsLayout->count(); + } + d->ContentsLayout->insertWidget(index, DockWidget); + DockWidget->setDockArea(this); + DockWidget->tabWidget()->setDockAreaWidget(this); + auto TabWidget = DockWidget->tabWidget(); + // Inserting the tab will change the current index which in turn will + // make the tab widget visible in the slot + d->tabBar()->blockSignals(true); + d->tabBar()->insertTab(index, TabWidget); + d->tabBar()->blockSignals(false); + TabWidget->setVisible(!DockWidget->isClosed()); + d->TitleBar->autoHideTitleLabel()->setText(DockWidget->windowTitle()); + DockWidget->setProperty(INDEX_PROPERTY, index); + d->MinSizeHint.setHeight(qMax(d->MinSizeHint.height(), DockWidget->minimumSizeHint().height())); + d->MinSizeHint.setWidth(qMax(d->MinSizeHint.width(), DockWidget->minimumSizeHint().width())); + if (Activate) + { + setCurrentIndex(index); + DockWidget->setClosedState(false); // Set current index can show the widget without changing the close state, added to keep the close state consistent + } + // If this dock area is hidden, then we need to make it visible again + // by calling DockWidget->toggleViewInternal(true); + if (!this->isVisible() && d->ContentsLayout->count() > 1 && !dockManager()->isRestoringState()) + { + DockWidget->toggleViewInternal(true); + } + d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); +} + + +//============================================================================ +void CDockAreaWidget::removeDockWidget(CDockWidget* DockWidget) +{ + ADS_PRINT("CDockAreaWidget::removeDockWidget"); + if (!DockWidget) + { + return; + } + + + // If this dock area is in a auto hide container, then we can delete + // the auto hide container now + if (isAutoHide()) + { + autoHideDockContainer()->cleanupAndDelete(); + return; + } + + auto CurrentDockWidget = currentDockWidget(); + auto NextOpenDockWidget = (DockWidget == CurrentDockWidget) ? nextOpenDockWidget(DockWidget) : nullptr; + + d->ContentsLayout->removeWidget(DockWidget); + auto TabWidget = DockWidget->tabWidget(); + TabWidget->hide(); + d->tabBar()->removeTab(TabWidget); + TabWidget->setParent(DockWidget); + DockWidget->setDockArea(nullptr); + CDockContainerWidget* DockContainer = dockContainer(); + if (NextOpenDockWidget) + { + setCurrentDockWidget(NextOpenDockWidget); + } + else if (d->ContentsLayout->isEmpty() && DockContainer->dockAreaCount() >= 1) + { + ADS_PRINT("Dock Area empty"); + DockContainer->removeDockArea(this); + this->deleteLater(); + if(DockContainer->dockAreaCount() == 0) + { + if(CFloatingDockContainer* FloatingDockContainer = DockContainer->floatingWidget()) + { + FloatingDockContainer->hide(); + FloatingDockContainer->deleteLater(); + } + } + } + else if (DockWidget == CurrentDockWidget) + { + // if contents layout is not empty but there are no more open dock + // widgets, then we need to hide the dock area because it does not + // contain any visible content + hideAreaWithNoVisibleContent(); + } + + d->updateTitleBarButtonStates(); + updateTitleBarVisibility(); + d->updateMinimumSizeHint(); + auto TopLevelDockWidget = DockContainer->topLevelDockWidget(); + if (TopLevelDockWidget) + { + TopLevelDockWidget->emitTopLevelChanged(true); + } + +#if (ADS_DEBUG_LEVEL > 0) + DockContainer->dumpLayout(); +#endif +} + + +//============================================================================ +void CDockAreaWidget::hideAreaWithNoVisibleContent() +{ + this->toggleView(false); + + // Hide empty parent splitters + auto Splitter = parentSplitter(); + internal::hideEmptyParentSplitters(Splitter); + + //Hide empty floating widget + CDockContainerWidget* Container = this->dockContainer(); + if (!Container->isFloating() && !CDockManager::testConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar)) + { + return; + } + + updateTitleBarVisibility(); + auto TopLevelWidget = Container->topLevelDockWidget(); + auto FloatingWidget = Container->floatingWidget(); + if (TopLevelWidget) + { + if (FloatingWidget) + { + FloatingWidget->updateWindowTitle(); + } + CDockWidget::emitTopLevelEventForWidget(TopLevelWidget, true); + } + else if (Container->openedDockAreas().isEmpty() && FloatingWidget) + { + FloatingWidget->hide(); + } + if (isAutoHide()) + { + autoHideDockContainer()->hide(); + } +} + + +//============================================================================ +void CDockAreaWidget::onTabCloseRequested(int Index) +{ + ADS_PRINT("CDockAreaWidget::onTabCloseRequested " << Index); + dockWidget(Index)->requestCloseDockWidget(); +} + + +//============================================================================ +CDockWidget* CDockAreaWidget::currentDockWidget() const +{ + int CurrentIndex = currentIndex(); + if (CurrentIndex < 0) + { + return nullptr; + } + + return dockWidget(CurrentIndex); +} + + +//============================================================================ +void CDockAreaWidget::setCurrentDockWidget(CDockWidget* DockWidget) +{ + if (dockManager()->isRestoringState()) + { + return; + } + + internalSetCurrentDockWidget(DockWidget); +} + + +//============================================================================ +void CDockAreaWidget::internalSetCurrentDockWidget(CDockWidget* DockWidget) +{ + int Index = index(DockWidget); + if (Index < 0) + { + return; + } + + setCurrentIndex(Index); + DockWidget->setClosedState(false); // Set current index can show the widget without changing the close state, added to keep the close state consistent +} + + +//============================================================================ +void CDockAreaWidget::setCurrentIndex(int index) +{ + auto TabBar = d->tabBar(); + if (index < 0 || index > (TabBar->count() - 1)) + { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + auto cw = d->ContentsLayout->currentWidget(); + auto nw = d->ContentsLayout->widget(index); + if (cw == nw && !nw->isHidden()) + { + return; + } + + Q_EMIT currentChanging(index); + TabBar->setCurrentIndex(index); + d->ContentsLayout->setCurrentIndex(index); + d->ContentsLayout->currentWidget()->show(); + Q_EMIT currentChanged(index); +} + + +//============================================================================ +int CDockAreaWidget::currentIndex() const +{ + return d->ContentsLayout->currentIndex(); +} + + +//============================================================================ +QRect CDockAreaWidget::titleBarGeometry() const +{ + return d->TitleBar->geometry(); +} + +//============================================================================ +QRect CDockAreaWidget::contentAreaGeometry() const +{ + return d->ContentsLayout->geometry(); +} + + +//============================================================================ +int CDockAreaWidget::index(CDockWidget* DockWidget) +{ + return d->ContentsLayout->indexOf(DockWidget); +} + + +//============================================================================ +QList CDockAreaWidget::dockWidgets() const +{ + QList DockWidgetList; + for (int i = 0; i < d->ContentsLayout->count(); ++i) + { + DockWidgetList.append(dockWidget(i)); + } + return DockWidgetList; +} + + +//============================================================================ +int CDockAreaWidget::openDockWidgetsCount() const +{ + int Count = 0; + for (int i = 0; i < d->ContentsLayout->count(); ++i) + { + if (dockWidget(i) && !dockWidget(i)->isClosed()) + { + ++Count; + } + } + return Count; +} + + +//============================================================================ +QList CDockAreaWidget::openedDockWidgets() const +{ + QList DockWidgetList; + for (int i = 0; i < d->ContentsLayout->count(); ++i) + { + CDockWidget* DockWidget = dockWidget(i); + if (DockWidget && !DockWidget->isClosed()) + { + DockWidgetList.append(dockWidget(i)); + } + } + return DockWidgetList; +} + + +//============================================================================ +int CDockAreaWidget::indexOfFirstOpenDockWidget() const +{ + for (int i = 0; i < d->ContentsLayout->count(); ++i) + { + if (dockWidget(i) && !dockWidget(i)->isClosed()) + { + return i; + } + } + + return -1; +} + + +//============================================================================ +int CDockAreaWidget::dockWidgetsCount() const +{ + return d->ContentsLayout->count(); +} + + +//============================================================================ +CDockWidget* CDockAreaWidget::dockWidget(int Index) const +{ + return qobject_cast(d->ContentsLayout->widget(Index)); +} + +//============================================================================ +void CDockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) +{ + ADS_PRINT("CDockAreaWidget::reorderDockWidget"); + if (fromIndex >= d->ContentsLayout->count() || fromIndex < 0 + || toIndex >= d->ContentsLayout->count() || toIndex < 0 || fromIndex == toIndex) + { + ADS_PRINT("Invalid index for tab movement" << fromIndex << toIndex); + return; + } + + auto Widget = d->ContentsLayout->widget(fromIndex); + d->ContentsLayout->removeWidget(Widget); + d->ContentsLayout->insertWidget(toIndex, Widget); + setCurrentIndex(toIndex); +} + + +//============================================================================ +void CDockAreaWidget::toggleDockWidgetView(CDockWidget* DockWidget, bool Open) +{ + Q_UNUSED(DockWidget); + Q_UNUSED(Open); + updateTitleBarVisibility(); +} + + +//============================================================================ +void CDockAreaWidget::updateTitleBarVisibility() +{ + CDockContainerWidget* Container = dockContainer(); + if (!Container) + { + return; + } + + if (!d->TitleBar) + { + return; + } + + bool IsAutoHide = isAutoHide(); + if (!CDockManager::testConfigFlag(CDockManager::AlwaysShowTabs)) + { + bool Hidden = Container->hasTopLevelDockWidget() && (Container->isFloating() + || CDockManager::testConfigFlag(CDockManager::HideSingleCentralWidgetTitleBar)); + Hidden |= (d->Flags.testFlag(HideSingleWidgetTitleBar) && openDockWidgetsCount() == 1); + Hidden &= !IsAutoHide; // Titlebar must always be visible when auto hidden so it can be dragged + d->TitleBar->setVisible(!Hidden); + } + + if (isAutoHideFeatureEnabled()) + { + d->TitleBar->showAutoHideControls(IsAutoHide); + updateTitleBarButtonVisibility(Container->topLevelDockArea() == this); + } +} + + +//============================================================================ +void CDockAreaWidget::markTitleBarMenuOutdated() +{ + if (d->TitleBar) + { + d->TitleBar->markTabsMenuOutdated(); + } +} + + +//============================================================================ +void CDockAreaWidget::updateAutoHideButtonCheckState() +{ + auto autoHideButton = titleBarButton(TitleBarButtonAutoHide); + autoHideButton->blockSignals(true); + autoHideButton->setChecked(isAutoHide()); + autoHideButton->blockSignals(false); +} + + +//============================================================================ +void CDockAreaWidget::updateTitleBarButtonVisibility(bool IsTopLevel) const +{ + d->updateTitleBarButtonVisibility(IsTopLevel); +} + + +//============================================================================ +void CDockAreaWidget::updateTitleBarButtonsToolTips() +{ + internal::setToolTip(titleBarButton(TitleBarButtonClose), + titleBar()->titleBarButtonToolTip(TitleBarButtonClose)); + internal::setToolTip(titleBarButton(TitleBarButtonAutoHide), + titleBar()->titleBarButtonToolTip(TitleBarButtonAutoHide)); +} + + +//============================================================================ +void CDockAreaWidget::saveState(QXmlStreamWriter& s) const +{ + s.writeStartElement("Area"); + s.writeAttribute("Tabs", QString::number(d->ContentsLayout->count())); + auto CurrentDockWidget = currentDockWidget(); + QString Name = CurrentDockWidget ? CurrentDockWidget->objectName() : ""; + s.writeAttribute("Current", Name); + + if (d->AllowedAreas != DefaultAllowedAreas) + { + s.writeAttribute("AllowedAreas", QString::number(d->AllowedAreas, 16)); + } + + if (d->Flags != DefaultFlags) + { + s.writeAttribute("Flags", QString::number(d->Flags, 16)); + } + ADS_PRINT("CDockAreaWidget::saveState TabCount: " << d->ContentsLayout->count() + << " Current: " << Name); + for (int i = 0; i < d->ContentsLayout->count(); ++i) + { + dockWidget(i)->saveState(s); + } + s.writeEndElement(); +} + + +//============================================================================ +bool CDockAreaWidget::restoreState(CDockingStateReader& s, CDockAreaWidget*& CreatedWidget, + bool Testing, CDockContainerWidget* Container) +{ + bool Ok; +#ifdef ADS_DEBUG_PRINT + int Tabs = s.attributes().value("Tabs").toInt(&Ok); + if (!Ok) + { + return false; + } +#endif + + QString CurrentDockWidget = s.attributes().value("Current").toString(); + ADS_PRINT("Restore NodeDockArea Tabs: " << Tabs << " Current: " + << CurrentDockWidget); + + auto DockManager = Container->dockManager(); + CDockAreaWidget* DockArea = nullptr; + if (!Testing) + { + DockArea = new CDockAreaWidget(DockManager, Container); + const auto AllowedAreasAttribute = s.attributes().value("AllowedAreas"); + if (!AllowedAreasAttribute.isEmpty()) + { + DockArea->setAllowedAreas((DockWidgetArea)AllowedAreasAttribute.toInt(nullptr, 16)); + } + + const auto FlagsAttribute = s.attributes().value("Flags"); + if (!FlagsAttribute.isEmpty()) + { + DockArea->setDockAreaFlags((CDockAreaWidget::DockAreaFlags)FlagsAttribute.toInt(nullptr, 16)); + } + } + + while (s.readNextStartElement()) + { + if (s.name() != QLatin1String("Widget")) + { + continue; + } + + auto ObjectName = s.attributes().value("Name"); + if (ObjectName.isEmpty()) + { + return false; + } + + bool Closed = s.attributes().value("Closed").toInt(&Ok); + if (!Ok) + { + return false; + } + + s.skipCurrentElement(); + CDockWidget* DockWidget = DockManager->findDockWidget(ObjectName.toString()); + if (!DockWidget || Testing) + { + continue; + } + + ADS_PRINT("Dock Widget found - parent " << DockWidget->parent()); + if (DockWidget->autoHideDockContainer()) + { + DockWidget->autoHideDockContainer()->cleanupAndDelete(); + } + + // We hide the DockArea here to prevent the short display (the flashing) + // of the dock areas during application startup + DockArea->hide(); + DockArea->addDockWidget(DockWidget); + DockWidget->setToggleViewActionChecked(!Closed); + DockWidget->setClosedState(Closed); + DockWidget->setProperty(internal::ClosedProperty, Closed); + DockWidget->setProperty(internal::DirtyProperty, false); + } + + if (Testing) + { + return true; + } + + if (!DockArea->dockWidgetsCount()) + { + delete DockArea; + DockArea = nullptr; + } + else + { + DockArea->setProperty("currentDockWidget", CurrentDockWidget); + } + + CreatedWidget = DockArea; + return true; +} + + +//============================================================================ +CDockWidget* CDockAreaWidget::nextOpenDockWidget(CDockWidget* DockWidget) const +{ + auto OpenDockWidgets = openedDockWidgets(); + if (OpenDockWidgets.count() > 1 || (OpenDockWidgets.count() == 1 && OpenDockWidgets[0] != DockWidget)) + { + if (OpenDockWidgets.last() == DockWidget) + { + CDockWidget* NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2]; + // search backwards for widget with tab + for (int i = OpenDockWidgets.count() - 2; i >= 0; --i) + { + auto dw = OpenDockWidgets[i]; + if (!dw->features().testFlag(CDockWidget::NoTab)) + { + return dw; + } + } + + // return widget without tab + return NextDockWidget; + } + else + { + int IndexOfDockWidget = OpenDockWidgets.indexOf(DockWidget); + CDockWidget* NextDockWidget = OpenDockWidgets[IndexOfDockWidget + 1]; + // search forwards for widget with tab + for (int i = IndexOfDockWidget + 1; i < OpenDockWidgets.count(); ++i) + { + auto dw = OpenDockWidgets[i]; + if (!dw->features().testFlag(CDockWidget::NoTab)) + { + return dw; + } + } + + // search backwards for widget with tab + for (int i = IndexOfDockWidget - 1; i >= 0; --i) + { + auto dw = OpenDockWidgets[i]; + if (!dw->features().testFlag(CDockWidget::NoTab)) + { + return dw; + } + } + + // return widget without tab + return NextDockWidget; + } + } + else + { + return nullptr; + } +} + + +//============================================================================ +CDockWidget::DockWidgetFeatures CDockAreaWidget::features(eBitwiseOperator Mode) const +{ + if (BitwiseAnd == Mode) + { + CDockWidget::DockWidgetFeatures Features(CDockWidget::AllDockWidgetFeatures); + for (const auto DockWidget : dockWidgets()) + { + Features &= DockWidget->features(); + } + return Features; + } + else + { + CDockWidget::DockWidgetFeatures Features(CDockWidget::NoDockWidgetFeatures); + for (const auto DockWidget : dockWidgets()) + { + Features |= DockWidget->features(); + } + return Features; + } +} + + +//============================================================================ +void CDockAreaWidget::toggleView(bool Open) +{ + setVisible(Open); + + Q_EMIT viewToggled(Open); +} + + +//============================================================================ +void CDockAreaWidget::setVisible(bool Visible) +{ + Super::setVisible(Visible); + if (d->UpdateTitleBarButtons) + { + d->updateTitleBarButtonStates(); + } +} + + +//============================================================================ +void CDockAreaWidget::setAllowedAreas(DockWidgetAreas areas) +{ + d->AllowedAreas = areas; +} + + +//============================================================================ +DockWidgetAreas CDockAreaWidget::allowedAreas() const +{ + return d->AllowedAreas; +} + + +//============================================================================ +CDockAreaWidget::DockAreaFlags CDockAreaWidget::dockAreaFlags() const +{ + return d->Flags; +} + + +//============================================================================ +void CDockAreaWidget::setDockAreaFlags(DockAreaFlags Flags) +{ + auto ChangedFlags = d->Flags ^ Flags; + d->Flags = Flags; + if (ChangedFlags.testFlag(HideSingleWidgetTitleBar)) + { + updateTitleBarVisibility(); + } +} + + +//============================================================================ +void CDockAreaWidget::setDockAreaFlag(eDockAreaFlag Flag, bool On) +{ + auto flags = dockAreaFlags(); + internal::setFlag(flags, Flag, On); + setDockAreaFlags(flags); +} + + +//============================================================================ +QAbstractButton* CDockAreaWidget::titleBarButton(TitleBarButton which) const +{ + return d->TitleBar->button(which); +} + + +//============================================================================ +void CDockAreaWidget::closeArea() +{ + // If there is only one single dock widget and this widget has the + // 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) || OpenDockWidgets[0]->features().testFlag(CDockWidget::CustomCloseHandling)) + && !isAutoHide()) + { + OpenDockWidgets[0]->closeDockWidgetInternal(); + } + else + { + for (auto DockWidget : openedDockWidgets()) + { + if ((DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) && DockWidget->features().testFlag(CDockWidget::DockWidgetForceCloseWithArea)) || + DockWidget->features().testFlag(CDockWidget::CustomCloseHandling)) + { + DockWidget->closeDockWidgetInternal(); + } + else if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) && isAutoHide()) + { + DockWidget->closeDockWidgetInternal(); + } + else + { + DockWidget->toggleView(false); + } + } + } +} + + +enum eBorderLocation +{ + BorderNone = 0, + BorderLeft = 0x01, + BorderRight = 0x02, + BorderTop = 0x04, + BorderBottom = 0x08, + BorderVertical = BorderLeft | BorderRight, + BorderHorizontal = BorderTop | BorderBottom, + BorderTopLeft = BorderTop | BorderLeft, + BorderTopRight = BorderTop | BorderRight, + BorderBottomLeft = BorderBottom | BorderLeft, + BorderBottomRight = BorderBottom | BorderRight, + BorderVerticalBottom = BorderVertical | BorderBottom, + BorderVerticalTop = BorderVertical | BorderTop, + BorderHorizontalLeft = BorderHorizontal | BorderLeft, + BorderHorizontalRight = BorderHorizontal | BorderRight, + BorderAll = BorderVertical | BorderHorizontal +}; + + +//============================================================================ +SideBarLocation CDockAreaWidget::calculateSideTabBarArea() const +{ + auto Container = dockContainer(); + auto ContentRect = Container->contentRect(); + + int borders = BorderNone; // contains all borders that are touched by the dock ware + auto DockAreaTopLeft = mapTo(Container, rect().topLeft()); + auto DockAreaRect = rect(); + DockAreaRect.moveTo(DockAreaTopLeft); + const qreal aspectRatio = DockAreaRect.width() / (qMax(1, DockAreaRect.height()) * 1.0); + const qreal sizeRatio = (qreal)ContentRect.width() / DockAreaRect.width(); + static const int MinBorderDistance = 16; + bool HorizontalOrientation = (aspectRatio > 1.0) && (sizeRatio < 3.0); + + // measure border distances - a distance less than 16 px means we touch the + // border + int BorderDistance[4]; + + int Distance = qAbs(ContentRect.topLeft().y() - DockAreaRect.topLeft().y()); + BorderDistance[SideBarLocation::SideBarTop] = (Distance < MinBorderDistance) ? 0 : Distance; + if (!BorderDistance[SideBarLocation::SideBarTop]) + { + borders |= BorderTop; + } + + Distance = qAbs(ContentRect.bottomRight().y() - DockAreaRect.bottomRight().y()); + BorderDistance[SideBarLocation::SideBarBottom] = (Distance < MinBorderDistance) ? 0 : Distance; + if (!BorderDistance[SideBarLocation::SideBarBottom]) + { + borders |= BorderBottom; + } + + Distance = qAbs(ContentRect.topLeft().x() - DockAreaRect.topLeft().x()); + BorderDistance[SideBarLocation::SideBarLeft] = (Distance < MinBorderDistance) ? 0 : Distance; + if (!BorderDistance[SideBarLocation::SideBarLeft]) + { + borders |= BorderLeft; + } + + Distance = qAbs(ContentRect.bottomRight().x() - DockAreaRect.bottomRight().x()); + BorderDistance[SideBarLocation::SideBarRight] = (Distance < MinBorderDistance) ? 0 : Distance; + if (!BorderDistance[SideBarLocation::SideBarRight]) + { + borders |= BorderRight; + } + + auto SideTab = SideBarLocation::SideBarRight; + switch (borders) + { + // 1. It's touching all borders + case BorderAll: SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight; break; + + // 2. It's touching 3 borders + case BorderVerticalBottom : SideTab = SideBarLocation::SideBarBottom; break; + case BorderVerticalTop : SideTab = SideBarLocation::SideBarTop; break; + case BorderHorizontalLeft: SideTab = SideBarLocation::SideBarLeft; break; + case BorderHorizontalRight: SideTab = SideBarLocation::SideBarRight; break; + + // 3. It's touching horizontal or vertical borders + case BorderVertical : SideTab = SideBarLocation::SideBarBottom; break; + case BorderHorizontal: SideTab = SideBarLocation::SideBarRight; break; + + // 4. It's in a corner + case BorderTopLeft : SideTab = HorizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarLeft; break; + case BorderTopRight : SideTab = HorizontalOrientation ? SideBarLocation::SideBarTop : SideBarLocation::SideBarRight; break; + case BorderBottomLeft : SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarLeft; break; + case BorderBottomRight : SideTab = HorizontalOrientation ? SideBarLocation::SideBarBottom : SideBarLocation::SideBarRight; break; + + // 5. It's touching only one border + case BorderLeft: SideTab = SideBarLocation::SideBarLeft; break; + case BorderRight: SideTab = SideBarLocation::SideBarRight; break; + case BorderTop: SideTab = SideBarLocation::SideBarTop; break; + case BorderBottom: SideTab = SideBarLocation::SideBarBottom; break; + } + + return SideTab; +} + + +//============================================================================ +void CDockAreaWidget::setAutoHide(bool Enable, SideBarLocation Location, int TabIndex) +{ + if (!isAutoHideFeatureEnabled()) + { + return; + } + + if (!Enable) + { + if (isAutoHide()) + { + d->AutoHideDockContainer->moveContentsToParent(); + } + return; + } + + // If this is already an auto hide container, then move it to new location + if (isAutoHide()) + { + d->AutoHideDockContainer->moveToNewSideBarLocation(Location, TabIndex); + return; + } + + auto area = (SideBarNone == Location) ? calculateSideTabBarArea() : Location; + for (const auto DockWidget : openedDockWidgets()) + { + if (Enable == isAutoHide()) + { + continue; + } + + if (!DockWidget->features().testFlag(CDockWidget::DockWidgetPinnable)) + { + continue; + } + + dockContainer()->createAndSetupAutoHideContainer(area, DockWidget, TabIndex++); + } +} + + +//============================================================================ +void CDockAreaWidget::toggleAutoHide(SideBarLocation Location) +{ + if (!isAutoHideFeatureEnabled()) + { + return; + } + + setAutoHide(!isAutoHide(), Location); +} + + +//============================================================================ +void CDockAreaWidget::closeOtherAreas() +{ + dockContainer()->closeOtherAreas(this); +} + + +//============================================================================ +CDockAreaTitleBar* CDockAreaWidget::titleBar() const +{ + return d->TitleBar; +} + + +//============================================================================ +bool CDockAreaWidget::isCentralWidgetArea() const +{ + if (dockWidgetsCount()!= 1) + { + return false; + } + + return dockManager()->centralWidget() == dockWidgets().constFirst(); +} + + +//============================================================================ +bool CDockAreaWidget::containsCentralWidget() const +{ + auto centralWidget = dockManager()->centralWidget(); + for (const auto &dockWidget : dockWidgets()) + { + if (dockWidget == centralWidget) + { + return true; + } + } + + return false; +} + + +//============================================================================ +QSize CDockAreaWidget::minimumSizeHint() const +{ + if (!d->MinSizeHint.isValid()) + { + return Super::minimumSizeHint(); + } + + if (d->TitleBar->isVisible()) + { + return d->MinSizeHint + QSize(0, d->TitleBar->minimumSizeHint().height()); + } + else + { + return d->MinSizeHint; + } +} + + +//============================================================================ +void CDockAreaWidget::onDockWidgetFeaturesChanged() +{ + if (d->TitleBar) + { + d->updateTitleBarButtonStates(); + } +} + + +//============================================================================ +bool CDockAreaWidget::isTopLevelArea() const +{ + auto Container = dockContainer(); + if (!Container) + { + return false; + } + + return (Container->topLevelDockArea() == this); +} + + +//============================================================================ +void CDockAreaWidget::setFloating() +{ + d->TitleBar->setAreaFloating(); +} + + +#ifdef Q_OS_WIN +//============================================================================ +bool CDockAreaWidget::event(QEvent *e) +{ + switch (e->type()) + { + case QEvent::PlatformSurface: return true; + default: + break; + } + + return Super::event(e); +} +#endif + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockAreaWidget.cpp diff --git a/src/DockAreaWidget.h b/src/DockAreaWidget.h new file mode 100644 index 000000000..e0c39c73c --- /dev/null +++ b/src/DockAreaWidget.h @@ -0,0 +1,459 @@ +#ifndef DockAreaWidgetH +#define DockAreaWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockAreaWidget.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of CDockAreaWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" +#include "AutoHideTab.h" +#include "DockWidget.h" + +QT_FORWARD_DECLARE_CLASS(QXmlStreamWriter) +QT_FORWARD_DECLARE_CLASS(QAbstractButton) +QT_FORWARD_DECLARE_CLASS(QMenu) + +namespace ads +{ +struct DockAreaWidgetPrivate; +class CDockManager; +class CDockContainerWidget; +class DockContainerWidgetPrivate; +class CDockAreaTitleBar; +class CDockingStateReader; +class CDockSplitter; + + +/** + * DockAreaWidget manages multiple instances of DockWidgets. + * It displays a title tab, which is clickable and will switch to + * the contents associated to the title when clicked. + */ +class ADS_EXPORT CDockAreaWidget : public QFrame +{ + Q_OBJECT +private: + DockAreaWidgetPrivate* d; ///< private data (pimpl) + friend struct DockAreaWidgetPrivate; + friend class CDockContainerWidget; + friend class DockContainerWidgetPrivate; + friend class CDockWidgetTab; + friend struct DockWidgetPrivate; + friend class CDockWidget; + friend struct DockManagerPrivate; + friend class CDockManager; + friend class CAutoHideDockContainer; + void onDockWidgetFeaturesChanged(); + +private Q_SLOTS: + void onTabCloseRequested(int Index); + + /** + * Reorder the index position of DockWidget at fromIndx to toIndex + * if a tab in the tabbar is dragged from one index to another one + */ + void reorderDockWidget(int fromIndex, int toIndex); + + /* + * Update the auto hide button checked state based on if it's contained in an auto hide container or not + */ + void updateAutoHideButtonCheckState(); + + /* + * Update the title bar button tooltips + */ + void updateTitleBarButtonsToolTips(); + + /** + * Calculate the auto hide side bar location depending on the dock area + * widget position in the container + */ + SideBarLocation calculateSideTabBarArea() const; + +protected: + +#ifdef Q_OS_WIN + /** + * Reimplements QWidget::event to handle QEvent::PlatformSurface + * This is here to fix issue #294 Tab refresh problem with a QGLWidget + * that exists since Qt version 5.12.7. So this function is here to + * work around a Qt issue. + */ + virtual bool event(QEvent *event) override; +#endif + + /** + * Inserts a dock widget into dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs. + * The index indicates the index of the new dockwidget in the tabbar and + * in the stacked layout. If the Activate parameter is true, the new + * DockWidget will be the active one in the stacked layout + */ + void insertDockWidget(int index, CDockWidget* DockWidget, bool Activate = true); + + /** + * Add a new dock widget to dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs + */ + void addDockWidget(CDockWidget* DockWidget); + + /** + * Removes the given dock widget from the dock area + */ + void removeDockWidget(CDockWidget* DockWidget); + + /** + * Called from dock widget if it is opened or closed + */ + void toggleDockWidgetView(CDockWidget* DockWidget, bool Open); + + /** + * This is a helper function to get the next open dock widget to activate + * if the given DockWidget will be closed or removed. + * The function returns the next widget that should be activated or + * nullptr in case there are no more open widgets in this area. + */ + CDockWidget* nextOpenDockWidget(CDockWidget* DockWidget) const; + + /** + * Returns the index of the given DockWidget in the internal layout + */ + int index(CDockWidget* DockWidget); + + /** + * Call this function, if you already know, that the dock does not + * contain any visible content (any open dock widgets). + */ + void hideAreaWithNoVisibleContent(); + + /** + * Updates the dock area layout and components visibility + */ + void updateTitleBarVisibility(); + + /** + * This is the internal private function for setting the current widget. + * This function is called by the public setCurrentDockWidget() function + * and by the dock manager when restoring the state + */ + void internalSetCurrentDockWidget(CDockWidget* DockWidget); + + /** + * Marks tabs menu to update + */ + void markTitleBarMenuOutdated(); + + /* + * Update the title bar button visibility based on if it's top level or not + */ + void updateTitleBarButtonVisibility(bool IsTopLevel) const; + +protected Q_SLOTS: + void toggleView(bool Open); + +public: + using Super = QFrame; + + /** + * Dock area related flags + */ + enum eDockAreaFlag + { + HideSingleWidgetTitleBar = 0x0001, + DefaultFlags = 0x0000 + }; + Q_DECLARE_FLAGS(DockAreaFlags, eDockAreaFlag) + + /** + * Default Constructor + */ + CDockAreaWidget(CDockManager* DockManager, CDockContainerWidget* parent); + + /** + * Virtual Destructor + */ + virtual ~CDockAreaWidget(); + + /** + * Returns the dock manager object this dock area belongs to + */ + CDockManager* dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if there is no + */ + CDockContainerWidget* dockContainer() const; + + /** + * Returns the auto hide dock container widget this dock area widget belongs to or 0 + * if there is no + */ + CAutoHideDockContainer* autoHideDockContainer() const; + + /** + * Returns the parent splitter that contains this dock area + */ + CDockSplitter* parentSplitter() const; + + /** + * Returns true if the dock area is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Sets the current auto hide dock container + */ + void setAutoHideDockContainer(CAutoHideDockContainer* AutoHideDockContainer); + + /** + * Returns the largest minimumSizeHint() of the dock widgets in this + * area. + * The minimum size hint is updated if a dock widget is removed or added. + */ + virtual QSize minimumSizeHint() const override; + + /** + * Returns the rectangle of the title area + */ + QRect titleBarGeometry() const; + + /** + * Returns the rectangle of the content + */ + QRect contentAreaGeometry() const; + + /** + * Returns the number of dock widgets in this area + */ + int dockWidgetsCount() const; + + /** + * Returns a list of all dock widgets in this dock area. + * This list contains open and closed dock widgets. + */ + QList dockWidgets() const; + + /** + * Returns the number of open dock widgets in this area + */ + int openDockWidgetsCount() const; + + /** + * Returns a list of dock widgets that are not closed. + */ + QList openedDockWidgets() const; + + /** + * Returns a dock widget by its index + */ + CDockWidget* dockWidget(int Index) const; + + /** + * Returns the index of the current active dock widget or -1 if there + * are is no active dock widget (ie.e if all dock widgets are closed) + */ + int currentIndex() const; + + /** + * Returns the index of the first open dock widgets in the list of + * dock widgets. + * This function is here for performance reasons. Normally it would + * be possible to take the first dock widget from the list returned by + * openedDockWidgets() function. But that function enumerates all + * dock widgets while this functions stops after the first open dock widget. + * If there are no open dock widgets, the function returns -1. + */ + int indexOfFirstOpenDockWidget() const; + + /** + * Returns the current active dock widget or a nullptr if there is no + * active dock widget (i.e. if all dock widgets are closed) + */ + CDockWidget* currentDockWidget() const; + + /** + * Shows the tab with the given dock widget + */ + void setCurrentDockWidget(CDockWidget* DockWidget); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter& Stream) const; + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + static bool restoreState(CDockingStateReader& Stream, CDockAreaWidget*& CreatedWidget, + bool Testing, CDockContainerWidget* ParentContainer); + + /** + * This functions returns the dock widget features of all dock widget in + * this area. + * A bitwise and is used to combine the flags of all dock widgets. That + * means, if only one single dock widget does not support a certain flag, + * the whole dock are does not support the flag. I.e. if one single + * dock widget in this area is not closable, the whole dock are is not + * closable. + */ + CDockWidget::DockWidgetFeatures features(eBitwiseOperator Mode = BitwiseAnd) const; + + /** + * Returns the title bar button corresponding to the given title bar + * button identifier + */ + QAbstractButton* titleBarButton(TitleBarButton which) const; + + /** + * Update the close button if visibility changed + */ + virtual void setVisible(bool Visible) override; + + /** + * Configures the areas of this particular dock area that are allowed for docking + */ + void setAllowedAreas(DockWidgetAreas areas); + + /** + * Returns flags with all allowed drop areas of this particular dock area + */ + DockWidgetAreas allowedAreas() const; + + /** + * Returns the title bar of this dock area + */ + CDockAreaTitleBar* titleBar() const; + + /** + * Returns the dock area flags - a combination of flags that configure the + * appearance and features of the dock area. + * \see setDockAreaFlasg() + */ + DockAreaFlags dockAreaFlags() const; + + /** + * Sets the dock area flags - a combination of flags that configure the + * appearance and features of the dock area + */ + void setDockAreaFlags(DockAreaFlags Flags); + + /** + * Sets the dock area flag Flag on this widget if on is true; otherwise + * clears the flag. + */ + void setDockAreaFlag(eDockAreaFlag Flag, bool On); + + /** + * Returns true if the area has a single dock widget and contains the central widget of it's manager. + */ + bool isCentralWidgetArea() const; + + /** + * Returns true if the area contains the central widget of it's manager. + */ + bool containsCentralWidget() const; + + /** + * If this dock area is the one and only visible area in a container, then + * this function returns true + */ + bool isTopLevelArea() const; + + +public Q_SLOTS: + /** + * This activates the tab for the given tab index. + * If the dock widget for the given tab is not visible, the this function + * call will make it visible. + */ + void setCurrentIndex(int index); + + /** + * Closes the dock area and all dock widgets in this area + */ + void closeArea(); + + /** + * Sets the dock area into auto hide mode or into normal mode. + * If the dock area is switched to auto hide mode, then all dock widgets + * that are pinable will be added to the sidebar + */ + void setAutoHide(bool Enable, SideBarLocation Location = SideBarNone, int TabIndex = -1); + + /** + * Switches the dock area to auto hide mode or vice versa depending on its + * current state. + */ + void toggleAutoHide(SideBarLocation Location = SideBarNone); + + /** + * This function closes all other areas except of this area + */ + void closeOtherAreas(); + + /** + * Moves the dock area into its own floating widget if the area + * DockWidgetFloatable flag is true + */ + void setFloating(); + +Q_SIGNALS: + /** + * This signal is emitted when user clicks on a tab at an index. + */ + void tabBarClicked(int index); + + /** + * This signal is emitted when the tab bar's current tab is about to be changed. The new + * current has the given index, or -1 if there isn't a new one. + * @param index + */ + void currentChanging(int index); + + /** + * This signal is emitted when the tab bar's current tab changes. The new + * current has the given index, or -1 if there isn't a new one + * @param index + */ + void currentChanged(int index); + + /** + * This signal is emitted if the visibility of this dock area is toggled + * via toggle view function + */ + void viewToggled(bool Open); +}; // class DockAreaWidget +} // namespace ads + +Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockAreaWidget::DockAreaFlags) +//----------------------------------------------------------------------------- +#endif // DockAreaWidgetH diff --git a/src/DockComponentsFactory.cpp b/src/DockComponentsFactory.cpp new file mode 100644 index 000000000..05259d7b4 --- /dev/null +++ b/src/DockComponentsFactory.cpp @@ -0,0 +1,76 @@ +//============================================================================ +/// \file DockComponentsFactory.cpp +/// \author Uwe Kindler +/// \date 10.02.2020 +/// \brief Implementation of DockComponentsFactory +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "DockComponentsFactory.h" + +#include + +#include "DockWidgetTab.h" +#include "DockAreaTabBar.h" +#include "DockAreaTitleBar.h" +#include "DockWidget.h" +#include "DockAreaWidget.h" + +namespace ads +{ +static std::unique_ptr DefaultFactory(new CDockComponentsFactory()); + + +//============================================================================ +CDockWidgetTab* CDockComponentsFactory::createDockWidgetTab(CDockWidget* DockWidget) const +{ + return new CDockWidgetTab(DockWidget); +} + +//============================================================================ +CAutoHideTab* CDockComponentsFactory::createDockWidgetSideTab(CDockWidget *DockWidget) const +{ + return new CAutoHideTab(DockWidget); +} + + +//============================================================================ +CDockAreaTabBar* CDockComponentsFactory::createDockAreaTabBar(CDockAreaWidget* DockArea) const +{ + return new CDockAreaTabBar(DockArea); +} + + +//============================================================================ +CDockAreaTitleBar* CDockComponentsFactory::createDockAreaTitleBar(CDockAreaWidget* DockArea) const +{ + return new CDockAreaTitleBar(DockArea); +} + + +//============================================================================ +const CDockComponentsFactory* CDockComponentsFactory::factory() +{ + return DefaultFactory.get(); +} + + +//============================================================================ +void CDockComponentsFactory::setFactory(CDockComponentsFactory* Factory) +{ + DefaultFactory.reset(Factory); +} + + +//============================================================================ +void CDockComponentsFactory::resetDefaultFactory() +{ + DefaultFactory.reset(new CDockComponentsFactory()); +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockComponentsFactory.cpp diff --git a/src/DockComponentsFactory.h b/src/DockComponentsFactory.h new file mode 100644 index 000000000..0d2b7d4c8 --- /dev/null +++ b/src/DockComponentsFactory.h @@ -0,0 +1,97 @@ +#ifndef DockComponentsFactoryH +#define DockComponentsFactoryH +//============================================================================ +/// \file DockComponentsFactory.h +/// \author Uwe Kindler +/// \date 10.02.2020 +/// \brief Declaration of DockComponentsFactory +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" + +namespace ads +{ +class CDockWidgetTab; +class CDockAreaTitleBar; +class CDockAreaTabBar; +class CDockAreaWidget; +class CDockWidget; +class CAutoHideTab; + + + +/** + * Factory for creation of certain GUI elements for the docking framework. + * A default unique instance provided by CDockComponentsFactory is used for + * creation of all supported components. To inject your custom components, + * you can create your own derived dock components factory and register + * it via setDefaultFactory() function. + * \code + * CDockComponentsFactory::setDefaultFactory(new MyComponentsFactory())); + * \endcode + */ +class ADS_EXPORT CDockComponentsFactory +{ +public: + /** + * Force virtual destructor + */ + virtual ~CDockComponentsFactory() {} + + /** + * This default implementation just creates a dock widget tab with + * new CDockWidgetTab(DockWIdget). + */ + virtual CDockWidgetTab* createDockWidgetTab(CDockWidget* DockWidget) const; + + /** + * This default implementation just creates a dock widget side tab with + * new CDockWidgetTab(DockWidget). + */ + virtual CAutoHideTab* createDockWidgetSideTab(CDockWidget* DockWidget) const; + + /** + * This default implementation just creates a dock area tab bar with + * new CDockAreaTabBar(DockArea). + */ + virtual CDockAreaTabBar* createDockAreaTabBar(CDockAreaWidget* DockArea) const; + + /** + * This default implementation just creates a dock area title bar with + * new CDockAreaTitleBar(DockArea). + */ + virtual CDockAreaTitleBar* createDockAreaTitleBar(CDockAreaWidget* DockArea) const; + + /** + * Returns the default components factory + */ + static const CDockComponentsFactory* factory(); + + /** + * Sets a new default factory for creation of GUI elements. + * This function takes ownership of the given Factory. + */ + static void setFactory(CDockComponentsFactory* Factory); + + /** + * Resets the current factory to the + */ + static void resetDefaultFactory(); +}; + + +/** + * Convenience function to ease factory instance access + */ +inline const CDockComponentsFactory* componentsFactory() +{ + return CDockComponentsFactory::factory(); +} + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // DockComponentsFactoryH diff --git a/src/DockContainerWidget.cpp b/src/DockContainerWidget.cpp new file mode 100644 index 000000000..0a4deafc2 --- /dev/null +++ b/src/DockContainerWidget.cpp @@ -0,0 +1,2290 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockContainerWidget.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of CDockContainerWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockContainerWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockManager.h" +#include "DockAreaWidget.h" +#include "DockWidget.h" +#include "DockingStateReader.h" +#include "FloatingDockContainer.h" +#include "DockOverlay.h" +#include "ads_globals.h" +#include "DockSplitter.h" +#include "DockWidgetTab.h" +#include "DockAreaTitleBar.h" +#include "DockFocusController.h" +#include "AutoHideDockContainer.h" +#include "AutoHideSideBar.h" +#include "AutoHideTab.h" + +#include +#include + +#if QT_VERSION < 0x050900 + +inline char toHexLower(uint value) +{ + return "0123456789abcdef"[value & 0xF]; +} + +QByteArray qByteArrayToHex(const QByteArray& src, char separator) +{ + if(src.size() == 0) + return QByteArray(); + + const int length = separator ? (src.size() * 3 - 1) : (src.size() * 2); + QByteArray hex(length, Qt::Uninitialized); + char *hexData = hex.data(); + const uchar *data = reinterpret_cast(src.data()); + for (int i = 0, o = 0; i < src.size(); ++i) { + hexData[o++] = toHexLower(data[i] >> 4); + hexData[o++] = toHexLower(data[i] & 0xf); + + if ((separator) && (o < length)) + hexData[o++] = separator; + } + return hex; +} +#endif + +namespace ads +{ +static unsigned int zOrderCounter = 0; + +enum eDropMode +{ + DropModeIntoArea,///< drop widget into a dock area + DropModeIntoContainer,///< drop into container + DropModeInvalid///< invalid mode - do not drop +}; + +/** + * Converts dock area ID to an index for array access + */ +static int areaIdToIndex(DockWidgetArea area) +{ + switch (area) + { + case LeftDockWidgetArea: return 0; + case RightDockWidgetArea: return 1; + case TopDockWidgetArea: return 2; + case BottomDockWidgetArea: return 3; + case CenterDockWidgetArea: return 4; + default: + return 4; + } +} + +/** + * Helper function to ease insertion of dock area into splitter + */ +static void insertWidgetIntoSplitter(QSplitter* Splitter, QWidget* widget, bool Append) +{ + if (Append) + { + Splitter->addWidget(widget); + } + else + { + Splitter->insertWidget(0, widget); + } +} + +/** + * Private data class of CDockContainerWidget class (pimpl) + */ +class DockContainerWidgetPrivate +{ +public: + CDockContainerWidget* _this; + QPointer DockManager; + unsigned int zOrderIndex = 0; + QList> DockAreas; + QList AutoHideWidgets; + QMap SideTabBarWidgets; + QGridLayout* Layout = nullptr; + CDockSplitter* RootSplitter = nullptr; + bool isFloating = false; + CDockAreaWidget* LastAddedAreaCache[5]; + int VisibleDockAreaCount = -1; + CDockAreaWidget* TopLevelDockArea = nullptr; + QTimer DelayedAutoHideTimer; + CAutoHideTab* DelayedAutoHideTab; + bool DelayedAutoHideShow = false; + + /** + * Private data constructor + */ + DockContainerWidgetPrivate(CDockContainerWidget* _public); + + /** + * Adds dock widget to container and returns the dock area that contains + * the inserted dock widget + */ + CDockAreaWidget* addDockWidgetToContainer(DockWidgetArea area, CDockWidget* Dockwidget); + + /** + * Adds dock widget to a existing DockWidgetArea + */ + CDockAreaWidget* addDockWidgetToDockArea(DockWidgetArea area, CDockWidget* Dockwidget, + CDockAreaWidget* TargetDockArea, int Index = -1); + + /** + * Add dock area to this container + */ + void addDockArea(CDockAreaWidget* NewDockWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Drop floating widget into container + */ + void dropIntoContainer(CFloatingDockContainer* FloatingWidget, DockWidgetArea area); + + /** + * Drop floating widget into auto hide side bar + */ + void dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void dropIntoCenterOfSection(CFloatingDockContainer* FloatingWidget, + CDockAreaWidget* TargetArea, int TabIndex = 0); + + /** + * Drop floating widget into dock area + */ + void dropIntoSection(CFloatingDockContainer* FloatingWidget, + CDockAreaWidget* TargetArea, DockWidgetArea area, int TabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * new dock widget area + */ + void moveToNewSection(QWidget* Widget, CDockAreaWidget* TargetArea, DockWidgetArea area, + int TabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to a + * a dock area in container + */ + void moveToContainer(QWidget* Widgett, DockWidgetArea area); + + /** + * Creates a new tab for a widget dropped into the center of a section + */ + void moveIntoCenterOfSection(QWidget* Widget, CDockAreaWidget* TargetArea, int TabIndex = 0); + + /** + * Moves the dock widget or dock area given in Widget parameter to + * a auto hide sidebar area + */ + void moveToAutoHideSideBar(QWidget* Widget, DockWidgetArea area, int TabIndex = TabDefaultInsertIndex); + + + /** + * Adds new dock areas to the internal dock area list + */ + void addDockAreasToList(const QList NewDockAreas); + + /** + * Wrapper function for DockAreas append, that ensures that dock area signals + * are properly connected to dock container slots + */ + void appendDockAreas(const QList NewDockAreas); + + /** + * Save state of child nodes + */ + void saveChildNodesState(QXmlStreamWriter& Stream, QWidget* Widget); + + /** + * Save state of auto hide widgets + */ + void saveAutoHideWidgetsState(QXmlStreamWriter& Stream); + + /** + * Restore state of child nodes. + * \param[in] Stream The data stream that contains the serialized state + * \param[out] CreatedWidget The widget created from parsed data or 0 if + * the parsed widget was an empty splitter + * \param[in] Testing If Testing is true, only the stream data is + * parsed without modifying anything. + */ + bool restoreChildNodes(CDockingStateReader& Stream, QWidget*& CreatedWidget, + bool Testing); + + /** + * Restores a splitter. + * \see restoreChildNodes() for details + */ + bool restoreSplitter(CDockingStateReader& Stream, QWidget*& CreatedWidget, + bool Testing); + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + bool restoreDockArea(CDockingStateReader& Stream, QWidget*& CreatedWidget, + bool Testing); + + /** + * Restores a auto hide side bar + */ + bool restoreSideBar(CDockingStateReader& Stream, QWidget*& CreatedWidget, + bool Testing); + + /** + * Helper function for recursive dumping of layout + */ + void dumpRecursive(int level, QWidget* widget); + + /** + * Calculate the drop mode from the given target position + */ + eDropMode getDropMode(const QPoint& TargetPos); + + /** + * Initializes the visible dock area count variable if it is not initialized + * yet + */ + void initVisibleDockAreaCount() + { + if (VisibleDockAreaCount > -1) + { + return; + } + + VisibleDockAreaCount = 0; + for (auto DockArea : DockAreas) + { + if (!DockArea) + { + continue; + } + VisibleDockAreaCount += (DockArea->isHidden() ? 0 : 1); + } + } + + /** + * Access function for the visible dock area counter + */ + int& visibleDockAreaCount() + { + // Lazy initialisation - we initialize the VisibleDockAreaCount variable + // on first use + initVisibleDockAreaCount(); + return VisibleDockAreaCount; + } + + /** + * The visible dock area count changes, if dock areas are remove, added or + * when its view is toggled + */ + void onVisibleDockAreaCountChanged(); + + void emitDockAreasRemoved() + { + onVisibleDockAreaCountChanged(); + Q_EMIT _this->dockAreasRemoved(); + } + + void emitDockAreasAdded() + { + onVisibleDockAreaCountChanged(); + Q_EMIT _this->dockAreasAdded(); + } + + /** + * Helper function for creation of new splitter + */ + CDockSplitter* newSplitter(Qt::Orientation orientation, QWidget* parent = nullptr) + { + CDockSplitter* s = new CDockSplitter(orientation, parent); + s->setOpaqueResize(CDockManager::testConfigFlag(CDockManager::OpaqueSplitterResize)); + s->setChildrenCollapsible(false); + return s; + } + + /** + * Ensures equal distribution of the sizes of a splitter if an dock widget + * is inserted from code + */ + void adjustSplitterSizesOnInsertion(QSplitter* Splitter, qreal LastRatio = 1.0) + { + int AreaSize = (Splitter->orientation() == Qt::Horizontal) ? Splitter->width() : Splitter->height(); + auto SplitterSizes = Splitter->sizes(); + + qreal TotRatio = SplitterSizes.size() - 1.0 + LastRatio; + for(int i = 0; i < SplitterSizes.size() -1; i++) + { + SplitterSizes[i] = AreaSize / TotRatio; + } + SplitterSizes.back() = AreaSize * LastRatio / TotRatio; + Splitter->setSizes(SplitterSizes); + } + + /** + * This function forces the dock container widget to update handles of splitters + * based if a central widget exists. + */ + void updateSplitterHandles(QSplitter* splitter); + + /** + * If no central widget exists, the widgets resize with the container. + * If a central widget exists, the widgets surrounding the central widget + * do not resize its height or width. + */ + bool widgetResizesWithContainer(QWidget* widget); + +// private slots: ------------------------------------------------------------ + void onDockAreaViewToggled(bool Visible) + { + CDockAreaWidget* DockArea = qobject_cast(_this->sender()); + VisibleDockAreaCount += Visible ? 1 : -1; + onVisibleDockAreaCountChanged(); + Q_EMIT _this->dockAreaViewToggled(DockArea, Visible); + } +}; // struct DockContainerWidgetPrivate + + +//============================================================================ +DockContainerWidgetPrivate::DockContainerWidgetPrivate(CDockContainerWidget* _public) : + _this(_public) +{ + std::fill(std::begin(LastAddedAreaCache),std::end(LastAddedAreaCache), nullptr); + DelayedAutoHideTimer.setSingleShot(true); + DelayedAutoHideTimer.setInterval(500); + QObject::connect(&DelayedAutoHideTimer, &QTimer::timeout, [this](){ + auto GlobalPos = DelayedAutoHideTab->mapToGlobal(QPoint(0, 0)); + qApp->sendEvent(DelayedAutoHideTab, new QMouseEvent(QEvent::MouseButtonPress, + QPoint(0, 0), GlobalPos, Qt::LeftButton, {Qt::LeftButton}, Qt::NoModifier)); + }); +} + + +//============================================================================ +eDropMode DockContainerWidgetPrivate::getDropMode(const QPoint& TargetPos) +{ + CDockAreaWidget* DockArea = _this->dockAreaAt(TargetPos); + auto dropArea = InvalidDockWidgetArea; + auto ContainerDropArea = DockManager->containerOverlay()->dropAreaUnderCursor(); + + if (DockArea) + { + auto dropOverlay = DockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(DockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(DockArea); + if (ContainerDropArea != InvalidDockWidgetArea && + ContainerDropArea != dropArea) + { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) + { + ADS_PRINT("Dock Area Drop Content: " << dropArea); + return DropModeIntoArea; + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) + { + dropArea = ContainerDropArea; + ADS_PRINT("Container Drop Content: " << dropArea); + if (dropArea != InvalidDockWidgetArea) + { + return DropModeIntoContainer; + } + } + + return DropModeInvalid; +} + + +//============================================================================ +void DockContainerWidgetPrivate::onVisibleDockAreaCountChanged() +{ + auto TopLevelDockArea = _this->topLevelDockArea(); + + if (TopLevelDockArea) + { + this->TopLevelDockArea = TopLevelDockArea; + TopLevelDockArea->updateTitleBarButtonVisibility(true); + } + else if (this->TopLevelDockArea) + { + this->TopLevelDockArea->updateTitleBarButtonVisibility(false); + this->TopLevelDockArea = nullptr; + } +} + + +//============================================================================ +void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer* FloatingWidget, + DockWidgetArea area) +{ + auto InsertParam = internal::dockAreaInsertParameters(area); + CDockContainerWidget* FloatingDockContainer = FloatingWidget->dockContainer(); + auto NewDockAreas = FloatingDockContainer->findChildren( + QString(), Qt::FindChildrenRecursively); + auto Splitter = RootSplitter; + + if (DockAreas.count() <= 1) + { + Splitter->setOrientation(InsertParam.orientation()); + } + else if (Splitter->orientation() != InsertParam.orientation()) + { + auto NewSplitter = newSplitter(InsertParam.orientation()); + QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + updateSplitterHandles(NewSplitter); + Splitter = NewSplitter; + delete li; + } + + // Now we can insert the floating widget content into this container + auto FloatingSplitter = FloatingDockContainer->rootSplitter(); + if (FloatingSplitter->count() == 1) + { + insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append()); + updateSplitterHandles(Splitter); + } + else if (FloatingSplitter->orientation() == InsertParam.orientation()) + { + int InsertIndex = InsertParam.append() ? Splitter->count() : 0; + while (FloatingSplitter->count()) + { + Splitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); + updateSplitterHandles(Splitter); + } + } + else + { + insertWidgetIntoSplitter(Splitter, FloatingSplitter, InsertParam.append()); + } + + RootSplitter = Splitter; + addDockAreasToList(NewDockAreas); + + // If we dropped the floating widget into the main dock container that does + // not contain any dock widgets, then splitter is invisible and we need to + // show it to display the docked widgets + if (!Splitter->isVisible()) + { + Splitter->show(); + } + _this->dumpLayout(); +} + + +//============================================================================ +void DockContainerWidgetPrivate::dropIntoAutoHideSideBar(CFloatingDockContainer* FloatingWidget, DockWidgetArea area) +{ + auto SideBarLocation = internal::toSideBarLocation(area); + auto NewDockAreas = FloatingWidget->findChildren( + QString(), Qt::FindChildrenRecursively); + int TabIndex = DockManager->containerOverlay()->tabIndexUnderCursor(); + for (auto DockArea : NewDockAreas) + { + auto DockWidgets = DockArea->dockWidgets(); + for (auto DockWidget : DockWidgets) + { + _this->createAndSetupAutoHideContainer(SideBarLocation, DockWidget, TabIndex++); + } + } +} + + +//============================================================================ +void DockContainerWidgetPrivate::dropIntoCenterOfSection( + CFloatingDockContainer* FloatingWidget, CDockAreaWidget* TargetArea, int TabIndex) +{ + CDockContainerWidget* FloatingContainer = FloatingWidget->dockContainer(); + auto NewDockWidgets = FloatingContainer->dockWidgets(); + auto TopLevelDockArea = FloatingContainer->topLevelDockArea(); + int NewCurrentIndex = -1; + TabIndex = qMax(0, TabIndex); + + // If the floating widget contains only one single dock are, then the + // current dock widget of the dock area will also be the future current + // dock widget in the drop area. + if (TopLevelDockArea) + { + NewCurrentIndex = TopLevelDockArea->currentIndex(); + } + + for (int i = 0; i < NewDockWidgets.count(); ++i) + { + CDockWidget* DockWidget = NewDockWidgets[i]; + TargetArea->insertDockWidget(TabIndex + i, DockWidget, false); + // If the floating widget contains multiple visible dock areas, then we + // simply pick the first visible open dock widget and make it + // the current one. + if (NewCurrentIndex < 0 && !DockWidget->isClosed()) + { + NewCurrentIndex = i; + } + } + TargetArea->setCurrentIndex(NewCurrentIndex + TabIndex); + TargetArea->updateTitleBarVisibility(); + return; +} + + +//============================================================================ +void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer* FloatingWidget, + CDockAreaWidget* TargetArea, DockWidgetArea area, int TabIndex) +{ + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) + { + dropIntoCenterOfSection(FloatingWidget, TargetArea, TabIndex); + return; + } + + CDockContainerWidget* FloatingContainer = FloatingWidget->dockContainer(); + auto InsertParam = internal::dockAreaInsertParameters(area); + auto NewDockAreas = FloatingContainer->findChildren( + QString(), Qt::FindChildrenRecursively); + auto TargetAreaSplitter = TargetArea->parentSplitter(); + int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); + auto FloatingSplitter = FloatingContainer->rootSplitter(); + if (TargetAreaSplitter->orientation() == InsertParam.orientation()) + { + auto Sizes = TargetAreaSplitter->sizes(); + int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); + bool AdjustSplitterSizes = true; + if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) + { + TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), FloatingSplitter); + updateSplitterHandles(TargetAreaSplitter); + } + else + { + AdjustSplitterSizes = (FloatingSplitter->count() == 1); + int InsertIndex = AreaIndex + InsertParam.insertOffset(); + while (FloatingSplitter->count()) + { + TargetAreaSplitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); + updateSplitterHandles(TargetAreaSplitter); + } + } + + if (AdjustSplitterSizes) + { + int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; + Sizes[AreaIndex] = Size; + Sizes.insert(AreaIndex, Size); + TargetAreaSplitter->setSizes(Sizes); + } + } + else + { + QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); + int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); + bool AdjustSplitterSizes = true; + if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) + { + NewSplitter->addWidget(FloatingSplitter); + updateSplitterHandles(NewSplitter); + } + else + { + AdjustSplitterSizes = (FloatingSplitter->count() == 1); + while (FloatingSplitter->count()) + { + NewSplitter->addWidget(FloatingSplitter->widget(0)); + updateSplitterHandles(NewSplitter); + } + } + + // Save the sizes before insertion and restore it later to prevent + // shrinking of existing area + auto Sizes = TargetAreaSplitter->sizes(); + insertWidgetIntoSplitter(NewSplitter, TargetArea, !InsertParam.append()); + updateSplitterHandles(NewSplitter); + if (AdjustSplitterSizes) + { + int Size = TargetAreaSize / 2; + NewSplitter->setSizes({Size, Size}); + } + TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); + TargetAreaSplitter->setSizes(Sizes); + updateSplitterHandles(TargetAreaSplitter); + } + + addDockAreasToList(NewDockAreas); + _this->dumpLayout(); +} + + +//============================================================================ +void DockContainerWidgetPrivate::moveIntoCenterOfSection(QWidget* Widget, CDockAreaWidget* TargetArea, + int TabIndex) +{ + auto DroppedDockWidget = qobject_cast(Widget); + auto DroppedArea = qobject_cast(Widget); + + TabIndex = qMax(0, TabIndex); + if (DroppedDockWidget) + { + CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); + if (OldDockArea == TargetArea) + { + return; + } + + if (OldDockArea) + { + OldDockArea->removeDockWidget(DroppedDockWidget); + } + TargetArea->insertDockWidget(TabIndex, DroppedDockWidget, true); + } + else + { + QList NewDockWidgets = DroppedArea->dockWidgets(); + int NewCurrentIndex = DroppedArea->currentIndex(); + for (int i = 0; i < NewDockWidgets.count(); ++i) + { + CDockWidget* DockWidget = NewDockWidgets[i]; + TargetArea->insertDockWidget(TabIndex + i, DockWidget, false); + } + TargetArea->setCurrentIndex(TabIndex + NewCurrentIndex); + DroppedArea->dockContainer()->removeDockArea(DroppedArea); + DroppedArea->deleteLater(); + } + + TargetArea->updateTitleBarVisibility(); + return; +} + + +//============================================================================ +void DockContainerWidgetPrivate::moveToNewSection(QWidget* Widget, CDockAreaWidget* TargetArea, DockWidgetArea area, + int TabIndex) +{ + // Dropping into center means all dock widgets in the dropped floating + // widget will become tabs of the drop area + if (CenterDockWidgetArea == area) + { + moveIntoCenterOfSection(Widget, TargetArea, TabIndex); + return; + } + + + CDockWidget* DroppedDockWidget = qobject_cast(Widget); + CDockAreaWidget* DroppedDockArea = qobject_cast(Widget); + CDockAreaWidget* NewDockArea; + if (DroppedDockWidget) + { + NewDockArea = new CDockAreaWidget(DockManager, _this); + CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); + if (OldDockArea) + { + OldDockArea->removeDockWidget(DroppedDockWidget); + } + NewDockArea->addDockWidget(DroppedDockWidget); + } + else + { + DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea); + NewDockArea = DroppedDockArea; + } + + auto InsertParam = internal::dockAreaInsertParameters(area); + auto TargetAreaSplitter = TargetArea->parentSplitter(); + int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); + auto Sizes = TargetAreaSplitter->sizes(); + if (TargetAreaSplitter->orientation() == InsertParam.orientation()) + { + int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); + TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), NewDockArea); + updateSplitterHandles(TargetAreaSplitter); + int Size = (TargetAreaSize - TargetAreaSplitter->handleWidth()) / 2; + Sizes[AreaIndex] = Size; + Sizes.insert(AreaIndex, Size); + } + else + { + int TargetAreaSize = (InsertParam.orientation() == Qt::Horizontal) ? TargetArea->width() : TargetArea->height(); + QSplitter* NewSplitter = newSplitter(InsertParam.orientation()); + NewSplitter->addWidget(TargetArea); + insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); + updateSplitterHandles(NewSplitter); + int Size = TargetAreaSize / 2; + NewSplitter->setSizes({Size, Size}); + TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); + updateSplitterHandles(TargetAreaSplitter); + } + TargetAreaSplitter->setSizes(Sizes); + + addDockAreasToList({NewDockArea}); +} + + +//============================================================================ +void DockContainerWidgetPrivate::moveToAutoHideSideBar(QWidget* Widget, DockWidgetArea area, int TabIndex) +{ + CDockWidget* DroppedDockWidget = qobject_cast(Widget); + CDockAreaWidget* DroppedDockArea = qobject_cast(Widget); + auto SideBarLocation = internal::toSideBarLocation(area); + + if (DroppedDockWidget) + { + if (_this == DroppedDockWidget->dockContainer()) + { + DroppedDockWidget->setAutoHide(true, SideBarLocation, TabIndex); + } + else + { + _this->createAndSetupAutoHideContainer(SideBarLocation, DroppedDockWidget, TabIndex); + } + } + else + { + if (_this == DroppedDockArea->dockContainer()) + { + DroppedDockArea->setAutoHide(true, SideBarLocation, TabIndex); + } + else + { + for (const auto DockWidget : DroppedDockArea->openedDockWidgets()) + { + if (!DockWidget->features().testFlag( + CDockWidget::DockWidgetPinnable)) + { + continue; + } + + _this->createAndSetupAutoHideContainer(SideBarLocation, + DockWidget, TabIndex++); + } + } + } +} + + +//============================================================================ +void DockContainerWidgetPrivate::updateSplitterHandles( QSplitter* splitter ) +{ + if (!DockManager->centralWidget() || !splitter) + { + return; + } + + for (int i = 0; i < splitter->count(); ++i) + { + splitter->setStretchFactor(i, widgetResizesWithContainer(splitter->widget(i)) ? 1 : 0); + } +} + + +//============================================================================ +bool DockContainerWidgetPrivate::widgetResizesWithContainer(QWidget* widget) +{ + if (!DockManager->centralWidget()) + { + return true; + } + + auto Area = qobject_cast(widget); + if(Area) + { + return Area->isCentralWidgetArea(); + } + + auto innerSplitter = qobject_cast(widget); + if (innerSplitter) + { + return innerSplitter->isResizingWithContainer(); + } + + return false; +} + + + +//============================================================================ +void DockContainerWidgetPrivate::moveToContainer(QWidget* Widget, DockWidgetArea area) +{ + CDockWidget* DroppedDockWidget = qobject_cast(Widget); + CDockAreaWidget* DroppedDockArea = qobject_cast(Widget); + CDockAreaWidget* NewDockArea; + + if (DroppedDockWidget) + { + NewDockArea = new CDockAreaWidget(DockManager, _this); + CDockAreaWidget* OldDockArea = DroppedDockWidget->dockAreaWidget(); + if (OldDockArea) + { + OldDockArea->removeDockWidget(DroppedDockWidget); + } + NewDockArea->addDockWidget(DroppedDockWidget); + } + else + { + // We check, if we insert the dropped widget into the same place that + // it already has and do nothing, if it is the same place. It would + // also work without this check, but it looks nicer with the check + // because there will be no layout updates + auto Splitter = DroppedDockArea->parentSplitter(); + auto InsertParam = internal::dockAreaInsertParameters(area); + if (Splitter == RootSplitter && InsertParam.orientation() == Splitter->orientation()) + { + if (InsertParam.append() && Splitter->lastWidget() == DroppedDockArea) + { + return; + } + else if (!InsertParam.append() && Splitter->firstWidget() == DroppedDockArea) + { + return; + } + } + DroppedDockArea->dockContainer()->removeDockArea(DroppedDockArea); + NewDockArea = DroppedDockArea; + } + + addDockArea(NewDockArea, area); + LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea; +} + + +//============================================================================ +void DockContainerWidgetPrivate::addDockAreasToList(const QList NewDockAreas) +{ + int CountBefore = DockAreas.count(); + int NewAreaCount = NewDockAreas.count(); + appendDockAreas(NewDockAreas); + // If the user dropped a floating widget that contains only one single + // visible dock area, then its title bar button TitleBarButtonUndock is + // likely hidden. We need to ensure, that it is visible + for (auto DockArea : NewDockAreas) + { + DockArea->titleBarButton(TitleBarButtonClose)->setVisible(true); + DockArea->titleBarButton(TitleBarButtonAutoHide)->setVisible(true); + } + + // We need to ensure, that the dock area title bar is visible. The title bar + // is invisible, if the dock are is a single dock area in a floating widget. + if (1 == CountBefore) + { + DockAreas.at(0)->updateTitleBarVisibility(); + } + + if (1 == NewAreaCount) + { + DockAreas.last()->updateTitleBarVisibility(); + } + + emitDockAreasAdded(); +} + + +//============================================================================ +void DockContainerWidgetPrivate::appendDockAreas(const QList NewDockAreas) +{ + for (auto *newDockArea : NewDockAreas) + { + DockAreas.append(newDockArea); + } + for (auto DockArea : NewDockAreas) + { + QObject::connect(DockArea, + &CDockAreaWidget::viewToggled, + _this, + std::bind(&DockContainerWidgetPrivate::onDockAreaViewToggled, this, std::placeholders::_1)); + } +} + + +//============================================================================ +void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter& s, QWidget* Widget) +{ + QSplitter* Splitter = qobject_cast(Widget); + if (Splitter) + { + s.writeStartElement("Splitter"); + s.writeAttribute("Orientation", (Splitter->orientation() == Qt::Horizontal) ? "|" : "-"); + s.writeAttribute("Count", QString::number(Splitter->count())); + ADS_PRINT("NodeSplitter orient: " << Splitter->orientation() + << " WidgetCont: " << Splitter->count()); + for (int i = 0; i < Splitter->count(); ++i) + { + saveChildNodesState(s, Splitter->widget(i)); + } + + s.writeStartElement("Sizes"); + for (auto Size : Splitter->sizes()) + { + s.writeCharacters(QString::number(Size) + " "); + } + s.writeEndElement(); + s.writeEndElement(); + } + else + { + CDockAreaWidget* DockArea = qobject_cast(Widget); + if (DockArea) + { + DockArea->saveState(s); + } + } +} + + +//============================================================================ +void DockContainerWidgetPrivate::saveAutoHideWidgetsState(QXmlStreamWriter& s) +{ + for (const auto sideTabBar : SideTabBarWidgets.values()) + { + if (!sideTabBar->count()) + { + continue; + } + + sideTabBar->saveState(s); + } +} + + +//============================================================================ +bool DockContainerWidgetPrivate::restoreSplitter(CDockingStateReader& s, + QWidget*& CreatedWidget, bool Testing) +{ + bool Ok; + QString OrientationStr = s.attributes().value("Orientation").toString(); + + // Check if the orientation string is right + if (!OrientationStr.startsWith("|") && !OrientationStr.startsWith("-")) + { + return false; + } + + // The "|" shall indicate a vertical splitter handle which in turn means + // a Horizontal orientation of the splitter layout. + bool HorizontalSplitter = OrientationStr.startsWith("|"); + // In version 0 we had a small bug. The "|" indicated a vertical orientation, + // but this is wrong, because only the splitter handle is vertical, the + // layout of the splitter is a horizontal layout. We fix this here + if (s.fileVersion() == 0) + { + HorizontalSplitter = !HorizontalSplitter; + } + + int Orientation = HorizontalSplitter ? Qt::Horizontal : Qt::Vertical; + int WidgetCount = s.attributes().value("Count").toInt(&Ok); + if (!Ok) + { + return false; + } + ADS_PRINT("Restore NodeSplitter Orientation: " << Orientation << + " WidgetCount: " << WidgetCount); + QSplitter* Splitter = nullptr; + if (!Testing) + { + Splitter = newSplitter(static_cast(Orientation)); + } + bool Visible = false; + QList Sizes; + while (s.readNextStartElement()) + { + QWidget* ChildNode = nullptr; + bool Result = true; + if (s.name() == QLatin1String("Splitter")) + { + Result = restoreSplitter(s, ChildNode, Testing); + } + else if (s.name() == QLatin1String("Area")) + { + Result = restoreDockArea(s, ChildNode, Testing); + } + else if (s.name() == QLatin1String("Sizes")) + { + QString sSizes = s.readElementText().trimmed(); + ADS_PRINT("Sizes: " << sSizes); + QTextStream TextStream(&sSizes); + while (!TextStream.atEnd()) + { + int value; + TextStream >> value; + Sizes.append(value); + } + } + else + { + s.skipCurrentElement(); + } + + if (!Result) + { + return false; + } + + if (Testing || !ChildNode) + { + continue; + } + + ADS_PRINT("ChildNode isVisible " << ChildNode->isVisible() + << " isVisibleTo " << ChildNode->isVisibleTo(Splitter)); + Splitter->addWidget(ChildNode); + Visible |= ChildNode->isVisibleTo(Splitter); + } + if(!Testing) + { + updateSplitterHandles(Splitter); + } + + if (Sizes.count() != WidgetCount) + { + return false; + } + + if (!Testing) + { + if (!Splitter->count()) + { + delete Splitter; + Splitter = nullptr; + } + else + { + Splitter->setSizes(Sizes); + Splitter->setVisible(Visible); + } + CreatedWidget = Splitter; + } + else + { + CreatedWidget = nullptr; + } + + return true; +} + + +//============================================================================ +bool DockContainerWidgetPrivate::restoreDockArea(CDockingStateReader& s, + QWidget*& CreatedWidget, bool Testing) +{ + CDockAreaWidget* DockArea = nullptr; + auto Result = CDockAreaWidget::restoreState(s, DockArea, Testing, _this); + if (Result && DockArea) + { + appendDockAreas({DockArea}); + } + CreatedWidget = DockArea; + return Result; +} + + +//============================================================================ +bool DockContainerWidgetPrivate::restoreSideBar(CDockingStateReader& s, + QWidget*& CreatedWidget, bool Testing) +{ + Q_UNUSED(CreatedWidget) + // Simply ignore side bar auto hide widgets from saved state if + // auto hide support is disabled + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + return true; + } + + bool Ok; + auto Area = (ads::SideBarLocation)s.attributes().value("Area").toInt(&Ok); + if (!Ok) + { + return false; + } + + while (s.readNextStartElement()) + { + if (s.name() != QLatin1String("Widget")) + { + continue; + } + + auto Name = s.attributes().value("Name"); + if (Name.isEmpty()) + { + return false; + } + + bool Ok; + bool Closed = s.attributes().value("Closed").toInt(&Ok); + if (!Ok) + { + return false; + } + + int Size = s.attributes().value("Size").toInt(&Ok); + if (!Ok) + { + return false; + } + + s.skipCurrentElement(); + CDockWidget* DockWidget = DockManager->findDockWidget(Name.toString()); + if (!DockWidget || Testing) + { + continue; + } + + auto SideBar = _this->autoHideSideBar(Area); + CAutoHideDockContainer* AutoHideContainer; + if (DockWidget->isAutoHide()) + { + AutoHideContainer = DockWidget->autoHideDockContainer(); + if (AutoHideContainer->autoHideSideBar() != SideBar) + { + SideBar->addAutoHideWidget(AutoHideContainer); + } + } + else + { + AutoHideContainer = SideBar->insertDockWidget(-1, DockWidget); + } + AutoHideContainer->setSize(Size); + DockWidget->setProperty(internal::ClosedProperty, Closed); + DockWidget->setProperty(internal::DirtyProperty, false); + } + + return true; +} + + +//============================================================================ +bool DockContainerWidgetPrivate::restoreChildNodes(CDockingStateReader& s, + QWidget*& CreatedWidget, bool Testing) +{ + bool Result = true; + while (s.readNextStartElement()) + { + if (s.name() == QLatin1String("Splitter")) + { + Result = restoreSplitter(s, CreatedWidget, Testing); + ADS_PRINT("Splitter"); + } + else if (s.name() == QLatin1String("Area")) + { + Result = restoreDockArea(s, CreatedWidget, Testing); + ADS_PRINT("DockAreaWidget"); + } + else if (s.name() == QLatin1String("SideBar")) + { + Result = restoreSideBar(s, CreatedWidget, Testing); + ADS_PRINT("SideBar"); + } + else + { + s.skipCurrentElement(); + ADS_PRINT("Unknown element"); + } + } + + return Result; +} + + +//============================================================================ +CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToContainer(DockWidgetArea area, + CDockWidget* Dockwidget) +{ + CDockAreaWidget* NewDockArea = new CDockAreaWidget(DockManager, _this); + NewDockArea->addDockWidget(Dockwidget); + addDockArea(NewDockArea, area); + NewDockArea->updateTitleBarVisibility(); + LastAddedAreaCache[areaIdToIndex(area)] = NewDockArea; + return NewDockArea; +} + + +//============================================================================ +void DockContainerWidgetPrivate::addDockArea(CDockAreaWidget* NewDockArea, DockWidgetArea area) +{ + auto InsertParam = internal::dockAreaInsertParameters(area); + // As long as we have only one dock area in the splitter we can adjust + // its orientation + if (DockAreas.count() <= 1) + { + RootSplitter->setOrientation(InsertParam.orientation()); + } + + QSplitter* Splitter = RootSplitter; + if (Splitter->orientation() == InsertParam.orientation()) + { + insertWidgetIntoSplitter(Splitter, NewDockArea, InsertParam.append()); + updateSplitterHandles(Splitter); + if (Splitter->isHidden()) + { + Splitter->show(); + } + } + else + { + auto NewSplitter = newSplitter(InsertParam.orientation()); + if (InsertParam.append()) + { + QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + NewSplitter->addWidget(NewDockArea); + updateSplitterHandles(NewSplitter); + delete li; + } + else + { + NewSplitter->addWidget(NewDockArea); + QLayoutItem* li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + updateSplitterHandles(NewSplitter); + delete li; + } + RootSplitter = NewSplitter; + } + + addDockAreasToList({NewDockArea}); +} + + +//============================================================================ +void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget* widget) +{ +#if defined(QT_DEBUG) + QSplitter* Splitter = qobject_cast(widget); + QByteArray buf; + buf.fill(' ', level * 4); + if (Splitter) + { +#ifdef ADS_DEBUG_PRINT + qDebug("%sSplitter %s v: %s c: %s", + (const char*)buf, + (Splitter->orientation() == Qt::Vertical) ? "--" : "|", + Splitter->isHidden() ? " " : "v", + QString::number(Splitter->count()).toStdString().c_str()); + std::cout << (const char*)buf << "Splitter " + << ((Splitter->orientation() == Qt::Vertical) ? "--" : "|") << " " + << (Splitter->isHidden() ? " " : "v") << " " + << QString::number(Splitter->count()).toStdString() << std::endl; +#endif + for (int i = 0; i < Splitter->count(); ++i) + { + dumpRecursive(level + 1, Splitter->widget(i)); + } + } + else + { + CDockAreaWidget* DockArea = qobject_cast(widget); + if (!DockArea) + { + return; + } +#ifdef ADS_DEBUG_PRINT + qDebug("%sDockArea", (const char*)buf); + std::cout << (const char*)buf + << (DockArea->isHidden() ? " " : "v") + << (DockArea->openDockWidgetsCount() > 0 ? " " : "c") + << " DockArea " << "[hs: " << DockArea->sizePolicy().horizontalStretch() << ", vs: " << DockArea->sizePolicy().verticalStretch() << "]" << std::endl; + buf.fill(' ', (level + 1) * 4); + for (int i = 0; i < DockArea->dockWidgetsCount(); ++i) + { + std::cout << (const char*)buf << (i == DockArea->currentIndex() ? "*" : " "); + CDockWidget* DockWidget = DockArea->dockWidget(i); + std::cout << (DockWidget->isHidden() ? " " : "v"); + std::cout << (DockWidget->isClosed() ? "c" : " ") << " "; + std::cout << DockWidget->windowTitle().toStdString() << std::endl; + } +#endif + } +#else + Q_UNUSED(level); + Q_UNUSED(widget); +#endif +} + + +//============================================================================ +CDockAreaWidget* DockContainerWidgetPrivate::addDockWidgetToDockArea(DockWidgetArea area, + CDockWidget* Dockwidget, CDockAreaWidget* TargetDockArea, int Index) +{ + if (CenterDockWidgetArea == area) + { + TargetDockArea->insertDockWidget(Index, Dockwidget); + TargetDockArea->updateTitleBarVisibility(); + return TargetDockArea; + } + + CDockAreaWidget* NewDockArea = new CDockAreaWidget(DockManager, _this); + NewDockArea->addDockWidget(Dockwidget); + auto InsertParam = internal::dockAreaInsertParameters(area); + + auto TargetAreaSplitter = TargetDockArea->parentSplitter(); + int index = TargetAreaSplitter ->indexOf(TargetDockArea); + if (TargetAreaSplitter->orientation() == InsertParam.orientation()) + { + ADS_PRINT("TargetAreaSplitter->orientation() == InsertParam.orientation()"); + TargetAreaSplitter->insertWidget(index + InsertParam.insertOffset(), NewDockArea); + updateSplitterHandles(TargetAreaSplitter); + // do nothing, if flag is not enabled + if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) + { + adjustSplitterSizesOnInsertion(TargetAreaSplitter); + } + } + else + { + ADS_PRINT("TargetAreaSplitter->orientation() != InsertParam.orientation()"); + auto TargetAreaSizes = TargetAreaSplitter->sizes(); + auto NewSplitter = newSplitter(InsertParam.orientation()); + NewSplitter->addWidget(TargetDockArea); + + insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); + updateSplitterHandles(NewSplitter); + TargetAreaSplitter->insertWidget(index, NewSplitter); + updateSplitterHandles(TargetAreaSplitter); + if (CDockManager::testConfigFlag(CDockManager::EqualSplitOnInsertion)) + { + TargetAreaSplitter->setSizes(TargetAreaSizes); + adjustSplitterSizesOnInsertion(NewSplitter); + } + } + + addDockAreasToList({NewDockArea}); + return NewDockArea; +} + + +//============================================================================ +CDockContainerWidget::CDockContainerWidget(CDockManager* DockManager, QWidget *parent) : + QFrame(parent), + d(new DockContainerWidgetPrivate(this)) +{ + d->DockManager = DockManager; + d->isFloating = floatingWidget() != nullptr; + + d->Layout = new QGridLayout(); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + d->Layout->setColumnStretch(1, 1); + d->Layout->setRowStretch(1, 1); + setLayout(d->Layout); + + // The function d->newSplitter() accesses the config flags from dock + // manager which in turn requires a properly constructed dock manager. + // If this dock container is the dock manager, then it is not properly + // constructed yet because this base class constructor is called before + // the constructor of the DockManager private class + if (DockManager != this) + { + d->DockManager->registerDockContainer(this); + createRootSplitter(); + createSideTabBarWidgets(); + } +} + + +//============================================================================ +CDockContainerWidget::~CDockContainerWidget() +{ + if (d->DockManager) + { + d->DockManager->removeDockContainer(this); + } + + delete d; +} + + +//============================================================================ +CDockAreaWidget* CDockContainerWidget::addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, + CDockAreaWidget* DockAreaWidget, int Index) +{ + auto TopLevelDockWidget = topLevelDockWidget(); + CDockAreaWidget* OldDockArea = Dockwidget->dockAreaWidget(); + if (OldDockArea) + { + OldDockArea->removeDockWidget(Dockwidget); + } + + Dockwidget->setDockManager(d->DockManager); + CDockAreaWidget* DockArea; + if (DockAreaWidget) + { + DockArea = d->addDockWidgetToDockArea(area, Dockwidget, DockAreaWidget, Index); + } + else + { + DockArea = d->addDockWidgetToContainer(area, Dockwidget); + } + + if (TopLevelDockWidget) + { + auto NewTopLevelDockWidget = topLevelDockWidget(); + // If the container contained only one visible dock widget, the we need + // to emit a top level event for this widget because it is not the one and + // only visible docked widget anymore + if (!NewTopLevelDockWidget) + { + CDockWidget::emitTopLevelEventForWidget(TopLevelDockWidget, false); + } + } + return DockArea; +} + + +//============================================================================ +CAutoHideDockContainer* CDockContainerWidget::createAndSetupAutoHideContainer( + SideBarLocation area, CDockWidget* DockWidget, int TabIndex) +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + Q_ASSERT_X(false, "CDockContainerWidget::createAndInitializeDockWidgetOverlayContainer", + "Requested area does not exist in config"); + return nullptr; + } + if (d->DockManager != DockWidget->dockManager()) + { + DockWidget->setDockManager(d->DockManager); // Auto hide Dock Container needs a valid dock manager + } + + return autoHideSideBar(area)->insertDockWidget(TabIndex, DockWidget); +} + + +//============================================================================ +void CDockContainerWidget::removeDockWidget(CDockWidget* Dockwidget) +{ + CDockAreaWidget* Area = Dockwidget->dockAreaWidget(); + if (Area) + { + Area->removeDockWidget(Dockwidget); + } +} + +//============================================================================ +unsigned int CDockContainerWidget::zOrderIndex() const +{ + return d->zOrderIndex; +} + + +//============================================================================ +bool CDockContainerWidget::isInFrontOf(CDockContainerWidget* Other) const +{ + return this->zOrderIndex() > Other->zOrderIndex(); +} + + +//============================================================================ +bool CDockContainerWidget::event(QEvent *e) +{ + bool Result = QWidget::event(e); + if (e->type() == QEvent::WindowActivate) + { + d->zOrderIndex = ++zOrderCounter; + } + else if (e->type() == QEvent::Show && !d->zOrderIndex) + { + d->zOrderIndex = ++zOrderCounter; + } + + return Result; +} + + +//============================================================================ +QList CDockContainerWidget::autoHideWidgets() const +{ + return d->AutoHideWidgets; +} + + +//============================================================================ +void CDockContainerWidget::addDockArea(CDockAreaWidget* DockAreaWidget, + DockWidgetArea area) +{ + CDockContainerWidget* Container = DockAreaWidget->dockContainer(); + if (Container && Container != this) + { + Container->removeDockArea(DockAreaWidget); + } + + d->addDockArea(DockAreaWidget, area); +} + + +//============================================================================ +void CDockContainerWidget::removeDockArea(CDockAreaWidget* area) +{ + ADS_PRINT("CDockContainerWidget::removeDockArea"); + // If it is an auto hide area, then there is nothing much to do + if (area->isAutoHide()) + { + area->setAutoHideDockContainer(nullptr); + return; + } + + area->disconnect(this); + d->DockAreas.removeAll(area); + auto Splitter = area->parentSplitter(); + + // Remove are from parent splitter and recursively hide tree of parent + // splitters if it has no visible content + area->setParent(nullptr); + internal::hideEmptyParentSplitters(Splitter); + + // Remove this area from cached areas + auto p = std::find(std::begin(d->LastAddedAreaCache), std::end(d->LastAddedAreaCache), area); + if (p != std::end(d->LastAddedAreaCache)) { + *p = nullptr; + } + + // If splitter has more than 1 widgets, we are finished and can leave + if (Splitter->count() > 1) + { + goto emitAndExit; + } + + // If this is the RootSplitter we need to remove empty splitters to + // avoid too many empty splitters + if (Splitter == d->RootSplitter) + { + ADS_PRINT("Removed from RootSplitter"); + // If splitter is empty, we are finished + if (!Splitter->count()) + { + Splitter->hide(); + goto emitAndExit; + } + + QWidget* widget = Splitter->widget(0); + auto ChildSplitter = qobject_cast(widget); + // If the one and only content widget of the splitter is not a splitter + // then we are finished + if (!ChildSplitter) + { + goto emitAndExit; + } + + // We replace the superfluous RootSplitter with the ChildSplitter + ChildSplitter->setParent(nullptr); + QLayoutItem* li = d->Layout->replaceWidget(Splitter, ChildSplitter); + d->RootSplitter = ChildSplitter; + delete li; + ADS_PRINT("RootSplitter replaced by child splitter"); + } + else if (Splitter->count() == 1) + { + ADS_PRINT("Replacing splitter with content"); + QSplitter* ParentSplitter = internal::findParent(Splitter); + auto Sizes = ParentSplitter->sizes(); + QWidget* widget = Splitter->widget(0); + widget->setParent(this); + internal::replaceSplitterWidget(ParentSplitter, Splitter, widget); + ParentSplitter->setSizes(Sizes); + } + + delete Splitter; + Splitter = nullptr; + +emitAndExit: + updateSplitterHandles(Splitter); + CDockWidget* TopLevelWidget = topLevelDockWidget(); + + // Updated the title bar visibility of the dock widget if there is only + // one single visible dock widget + CDockWidget::emitTopLevelEventForWidget(TopLevelWidget, true); + dumpLayout(); + d->emitDockAreasRemoved(); +} + + +//============================================================================ +QList> CDockContainerWidget::removeAllDockAreas() +{ + auto Result = d->DockAreas; + d->DockAreas.clear(); + return Result; +} + + +//============================================================================ +CDockAreaWidget* CDockContainerWidget::dockAreaAt(const QPoint& GlobalPos) const +{ + for (const auto& DockArea : d->DockAreas) + { + if (DockArea && DockArea->isVisible() && DockArea->rect().contains(DockArea->mapFromGlobal(GlobalPos))) + { + return DockArea; + } + } + + return nullptr; +} + + +//============================================================================ +CDockAreaWidget* CDockContainerWidget::dockArea(int Index) const +{ + return (Index < dockAreaCount()) ? d->DockAreas[Index] : nullptr; +} + + +//============================================================================ +bool CDockContainerWidget::isFloating() const +{ + return d->isFloating; +} + + +//============================================================================ +int CDockContainerWidget::dockAreaCount() const +{ + return d->DockAreas.count(); +} + + +//============================================================================ +int CDockContainerWidget::visibleDockAreaCount() const +{ + int Result = 0; + for (auto DockArea : d->DockAreas) + { + Result += (!DockArea || DockArea->isHidden()) ? 0 : 1; + } + + return Result; + + // TODO Cache or precalculate this to speed it up because it is used during + // movement of floating widget + //return d->visibleDockAreaCount(); +} + + +//============================================================================ +void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer* FloatingWidget, + const QPoint& TargetPos) +{ + ADS_PRINT("CDockContainerWidget::dropFloatingWidget"); + CDockWidget* SingleDroppedDockWidget = FloatingWidget->topLevelDockWidget(); + CDockWidget* SingleDockWidget = topLevelDockWidget(); + auto dropArea = InvalidDockWidgetArea; + auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(); + bool Dropped = false; + + CDockAreaWidget* DockArea = dockAreaAt(TargetPos); + // mouse is over dock area + if (DockArea) + { + auto dropOverlay = d->DockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(DockArea->allowedAreas()); + dropArea = dropOverlay->showOverlay(DockArea); + if (ContainerDropArea != InvalidDockWidgetArea && + ContainerDropArea != dropArea) + { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) + { + ADS_PRINT("Dock Area Drop Content: " << dropArea); + int TabIndex = d->DockManager->dockAreaOverlay()->tabIndexUnderCursor(); + d->dropIntoSection(FloatingWidget, DockArea, dropArea, TabIndex); + Dropped = true; + } + } + + // mouse is over container or auto hide side bar + if (InvalidDockWidgetArea == dropArea && InvalidDockWidgetArea != ContainerDropArea) + { + if (internal::isSideBarArea(ContainerDropArea)) + { + ADS_PRINT("Container Drop Content: " << ContainerDropArea); + d->dropIntoAutoHideSideBar(FloatingWidget, ContainerDropArea); + } + else + { + ADS_PRINT("Container Drop Content: " << ContainerDropArea); + d->dropIntoContainer(FloatingWidget, ContainerDropArea); + } + Dropped = true; + } + + // Remove the auto hide widgets from the FloatingWidget and insert + // them into this widget + for (auto AutohideWidget : FloatingWidget->dockContainer()->autoHideWidgets()) + { + auto SideBar = autoHideSideBar(AutohideWidget->sideBarLocation()); + SideBar->addAutoHideWidget(AutohideWidget); + } + + if (Dropped) + { + // Fix https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351 + FloatingWidget->finishDropOperation(); + + // 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 + CDockWidget::emitTopLevelEventForWidget(SingleDroppedDockWidget, false); + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + CDockWidget::emitTopLevelEventForWidget(SingleDockWidget, false); + } + + window()->activateWindow(); + if (SingleDroppedDockWidget) + { + d->DockManager->notifyWidgetOrAreaRelocation(SingleDroppedDockWidget); + } + d->DockManager->notifyFloatingWidgetDrop(FloatingWidget); +} + + +//============================================================================ +void CDockContainerWidget::dropWidget(QWidget* Widget, DockWidgetArea DropArea, CDockAreaWidget* TargetAreaWidget, + int TabIndex) +{ + CDockWidget* SingleDockWidget = topLevelDockWidget(); + if (TargetAreaWidget) + { + d->moveToNewSection(Widget, TargetAreaWidget, DropArea, TabIndex); + } + else if (internal::isSideBarArea(DropArea)) + { + d->moveToAutoHideSideBar(Widget, DropArea, TabIndex); + } + else + { + d->moveToContainer(Widget, DropArea); + } + + // If there was a top level widget before the drop, then it is not top + // level widget anymore + CDockWidget::emitTopLevelEventForWidget(SingleDockWidget, false); + + window()->activateWindow(); + d->DockManager->notifyWidgetOrAreaRelocation(Widget); +} + + +//============================================================================ +QList CDockContainerWidget::openedDockAreas() const +{ + QList Result; + for (auto DockArea : d->DockAreas) + { + if (DockArea && !DockArea->isHidden()) + { + Result.append(DockArea); + } + } + + return Result; +} + + +//============================================================================ +QList CDockContainerWidget::openedDockWidgets() const +{ + QList DockWidgetList; + for (auto DockArea : d->DockAreas) + { + if (DockArea && !DockArea->isHidden()) + { + DockWidgetList.append(DockArea->openedDockWidgets()); + } + } + + return DockWidgetList; +} + + +//============================================================================ +bool CDockContainerWidget::hasOpenDockAreas() const +{ + for (auto DockArea : d->DockAreas) + { + if (DockArea && !DockArea->isHidden()) + { + return true; + } + } + + return false; +} + + +//============================================================================ +void CDockContainerWidget::saveState(QXmlStreamWriter& s) const +{ + ADS_PRINT("CDockContainerWidget::saveState isFloating " + << isFloating()); + + s.writeStartElement("Container"); + s.writeAttribute("Floating", QString::number(isFloating() ? 1 : 0)); + if (isFloating()) + { + CFloatingDockContainer* FloatingWidget = floatingWidget(); + QByteArray Geometry = FloatingWidget->saveGeometry(); +#if QT_VERSION < 0x050900 + s.writeTextElement("Geometry", qByteArrayToHex(Geometry, ' ')); +#else + s.writeTextElement("Geometry", Geometry.toHex(' ')); +#endif + } + d->saveChildNodesState(s, d->RootSplitter); + d->saveAutoHideWidgetsState(s); + s.writeEndElement(); +} + + +//============================================================================ +bool CDockContainerWidget::restoreState(CDockingStateReader& s, bool Testing) +{ + bool IsFloating = s.attributes().value("Floating").toInt(); + ADS_PRINT("Restore CDockContainerWidget Floating" << IsFloating); + + QWidget* NewRootSplitter {}; + if (!Testing) + { + d->VisibleDockAreaCount = -1;// invalidate the dock area count + d->DockAreas.clear(); + std::fill(std::begin(d->LastAddedAreaCache),std::end(d->LastAddedAreaCache), nullptr); + } + + if (IsFloating) + { + ADS_PRINT("Restore floating widget"); + if (!s.readNextStartElement() || s.name() != QLatin1String("Geometry")) + { + return false; + } + + QByteArray GeometryString = s.readElementText(CDockingStateReader::ErrorOnUnexpectedElement).toLocal8Bit(); + QByteArray Geometry = QByteArray::fromHex(GeometryString); + if (Geometry.isEmpty()) + { + return false; + } + + if (!Testing) + { + CFloatingDockContainer* FloatingWidget = floatingWidget(); + if (FloatingWidget) + { + FloatingWidget->restoreGeometry(Geometry); + } + } + } + + if (!d->restoreChildNodes(s, NewRootSplitter, Testing)) + { + return false; + } + + if (Testing) + { + return true; + } + + // If the root splitter is empty, rostoreChildNodes returns a 0 pointer + // and we need to create a new empty root splitter + if (!NewRootSplitter) + { + NewRootSplitter = d->newSplitter(Qt::Horizontal); + } + + QLayoutItem* li = d->Layout->replaceWidget(d->RootSplitter, NewRootSplitter); + auto OldRoot = d->RootSplitter; + d->RootSplitter = qobject_cast(NewRootSplitter); + OldRoot->deleteLater(); + delete li; + + return true; +} + + +//============================================================================ +CDockSplitter* CDockContainerWidget::rootSplitter() const +{ + return d->RootSplitter; +} + + +//============================================================================ +void CDockContainerWidget::createRootSplitter() +{ + if (d->RootSplitter) + { + return; + } + d->RootSplitter = d->newSplitter(Qt::Horizontal); + d->Layout->addWidget(d->RootSplitter, 1, 1); // Add it to the center - the 0 and 2 indexes are used for the SideTabBar widgets +} + + +//============================================================================ +void CDockContainerWidget::createSideTabBarWidgets() +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + return; + } + + { + auto Area = SideBarLocation::SideBarLeft; + d->SideTabBarWidgets[Area] = new CAutoHideSideBar(this, Area); + d->Layout->addWidget(d->SideTabBarWidgets[Area], 1, 0); + } + + { + auto Area = SideBarLocation::SideBarRight; + d->SideTabBarWidgets[Area] = new CAutoHideSideBar(this, Area); + d->Layout->addWidget(d->SideTabBarWidgets[Area], 1, 2); + } + + { + auto Area = SideBarLocation::SideBarBottom; + d->SideTabBarWidgets[Area] = new CAutoHideSideBar(this, Area); + d->Layout->addWidget(d->SideTabBarWidgets[Area], 2, 1); + } + + { + auto Area = SideBarLocation::SideBarTop; + d->SideTabBarWidgets[Area] = new CAutoHideSideBar(this, Area); + d->Layout->addWidget(d->SideTabBarWidgets[Area], 0, 1); + } +} + + +//============================================================================ +void CDockContainerWidget::dumpLayout() +{ +#if (ADS_DEBUG_LEVEL > 0) + qDebug("\n\nDumping layout --------------------------"); + std::cout << "\n\nDumping layout --------------------------" << std::endl; + d->dumpRecursive(0, d->RootSplitter); + qDebug("--------------------------\n\n"); + std::cout << "--------------------------\n\n" << std::endl; +#endif +} + + +//============================================================================ +CDockAreaWidget* CDockContainerWidget::lastAddedDockAreaWidget(DockWidgetArea area) const +{ + return d->LastAddedAreaCache[areaIdToIndex(area)]; +} + + +//============================================================================ +bool CDockContainerWidget::hasTopLevelDockWidget() const +{ + auto DockAreas = openedDockAreas(); + if (DockAreas.count() != 1) + { + return false; + } + + return DockAreas[0]->openDockWidgetsCount() == 1; +} + + +//============================================================================ +CDockWidget* CDockContainerWidget::topLevelDockWidget() const +{ + auto TopLevelDockArea = topLevelDockArea(); + if (!TopLevelDockArea) + { + return nullptr; + } + + auto DockWidgets = TopLevelDockArea->openedDockWidgets(); + if (DockWidgets.count() != 1) + { + return nullptr; + } + + return DockWidgets[0]; + +} + + +//============================================================================ +CDockAreaWidget* CDockContainerWidget::topLevelDockArea() const +{ + auto DockAreas = openedDockAreas(); + if (DockAreas.count() != 1) + { + return nullptr; + } + + return DockAreas[0]; +} + + +//============================================================================ +QList CDockContainerWidget::dockWidgets() const +{ + QList Result; + for (const auto& DockArea : d->DockAreas) + { + if (!DockArea) + { + continue; + } + Result.append(DockArea->dockWidgets()); + } + + return Result; +} + +//============================================================================ +void CDockContainerWidget::updateSplitterHandles(QSplitter* splitter) +{ + d->updateSplitterHandles(splitter); +} + +//============================================================================ +void CDockContainerWidget::registerAutoHideWidget(CAutoHideDockContainer* AutohideWidget) +{ + d->AutoHideWidgets.append(AutohideWidget); + Q_EMIT autoHideWidgetCreated(AutohideWidget); + ADS_PRINT("d->AutoHideWidgets.count() " << d->AutoHideWidgets.count()); +} + +//============================================================================ +void CDockContainerWidget::removeAutoHideWidget(CAutoHideDockContainer* AutohideWidget) +{ + d->AutoHideWidgets.removeAll(AutohideWidget); +} + +//============================================================================ +CDockWidget::DockWidgetFeatures CDockContainerWidget::features() const +{ + CDockWidget::DockWidgetFeatures Features(CDockWidget::AllDockWidgetFeatures); + for (const auto& DockArea : d->DockAreas) + { + if (!DockArea) + { + continue; + } + Features &= DockArea->features(); + } + + return Features; +} + + +//============================================================================ +CFloatingDockContainer* CDockContainerWidget::floatingWidget() const +{ + return internal::findParent(this); +} + + +//============================================================================ +void CDockContainerWidget::closeOtherAreas(CDockAreaWidget* KeepOpenArea) +{ + for (const auto& DockArea : d->DockAreas) + { + if (!DockArea || DockArea == KeepOpenArea) + { + continue; + } + + if (!DockArea->features(BitwiseAnd).testFlag(CDockWidget::DockWidgetClosable)) + { + continue; + } + + // We do not close areas with widgets with custom close handling + if (DockArea->features(BitwiseOr).testFlag(CDockWidget::CustomCloseHandling)) + { + continue; + } + + DockArea->closeArea(); + } +} + +//============================================================================ +CAutoHideSideBar* CDockContainerWidget::autoHideSideBar(SideBarLocation area) const +{ + return d->SideTabBarWidgets[area]; +} + + +//============================================================================ +QRect CDockContainerWidget::contentRect() const +{ + if (!d->RootSplitter) + { + return QRect(); + } + + if (d->RootSplitter->hasVisibleContent()) + { + return d->RootSplitter->geometry(); + } + else + { + auto ContentRect = this->rect(); + ContentRect.adjust( + autoHideSideBar(SideBarLeft)->sizeHint().width(), + autoHideSideBar(SideBarTop)->sizeHint().height(), + -autoHideSideBar(SideBarRight)->sizeHint().width(), + -autoHideSideBar(SideBarBottom)->sizeHint().height()); + + return ContentRect; + } +} + + +//=========================================================================== +QRect CDockContainerWidget::contentRectGlobal() const +{ + if (!d->RootSplitter) + { + return QRect(); + } + return internal::globalGeometry(d->RootSplitter); +} + + +//=========================================================================== +CDockManager* CDockContainerWidget::dockManager() const +{ + return d->DockManager; +} + + +//=========================================================================== +void CDockContainerWidget::handleAutoHideWidgetEvent(QEvent* e, QWidget* w) +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideShowOnMouseOver)) + { + return; + } + + if (dockManager()->isRestoringState()) + { + return; + } + + auto AutoHideTab = qobject_cast(w); + if (AutoHideTab) + { + switch (e->type()) + { + case QEvent::Enter: + if (!AutoHideTab->dockWidget()->isVisible()) + { + d->DelayedAutoHideTab = AutoHideTab; + d->DelayedAutoHideShow = true; + d->DelayedAutoHideTimer.start(); + } + else + { + d->DelayedAutoHideTimer.stop(); + } + break; + + case QEvent::MouseButtonPress: + d->DelayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (AutoHideTab->dockWidget()->isVisible()) + { + d->DelayedAutoHideTab = AutoHideTab; + d->DelayedAutoHideShow = false; + d->DelayedAutoHideTimer.start(); + } + else + { + d->DelayedAutoHideTimer.stop(); + } + break; + + default: + break; + } + return; + } + + auto AutoHideContainer = qobject_cast(w); + if (AutoHideContainer) + { + switch (e->type()) + { + case QEvent::Enter: + case QEvent::Hide: + d->DelayedAutoHideTimer.stop(); + break; + + case QEvent::Leave: + if (AutoHideContainer->isVisible()) + { + d->DelayedAutoHideTab = AutoHideContainer->autoHideTab(); + d->DelayedAutoHideShow = false; + d->DelayedAutoHideTimer.start(); + } + break; + + default: + break; + } + return; + return; + } +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockContainerWidget.cpp diff --git a/src/DockContainerWidget.h b/src/DockContainerWidget.h new file mode 100644 index 000000000..4825f2917 --- /dev/null +++ b/src/DockContainerWidget.h @@ -0,0 +1,385 @@ +#ifndef DockContainerWidgetH +#define DockContainerWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockContainerWidget.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of CDockContainerWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" +#include "AutoHideTab.h" +#include "DockWidget.h" + +QT_FORWARD_DECLARE_CLASS(QXmlStreamWriter) + + +namespace ads +{ +class DockContainerWidgetPrivate; +class CDockAreaWidget; +class CDockWidget; +class CDockManager; +struct DockManagerPrivate; +class CFloatingDockContainer; +struct FloatingDockContainerPrivate; +class CFloatingDragPreview; +struct FloatingDragPreviewPrivate; +class CDockingStateReader; +class CAutoHideSideBar; +class CAutoHideTab; +class CDockSplitter; +struct AutoHideTabPrivate; +struct AutoHideDockContainerPrivate; + + +/** + * Container that manages a number of dock areas with single dock widgets + * or tabyfied dock widgets in each area. + * Each window that support docking has a DockContainerWidget. That means + * the main application window and all floating windows contain a + * DockContainerWidget instance. + */ +class ADS_EXPORT CDockContainerWidget : public QFrame +{ + Q_OBJECT +private: + DockContainerWidgetPrivate* d; ///< private data (pimpl) + friend class DockContainerWidgetPrivate; + friend class CDockManager; + friend struct DockManagerPrivate; + friend class CDockAreaWidget; + friend struct DockAreaWidgetPrivate; + friend class CFloatingDockContainer; + friend struct FloatingDockContainerPrivate; + friend class CDockWidget; + friend class CFloatingDragPreview; + friend struct FloatingDragPreviewPrivate; + friend CAutoHideDockContainer; + friend CAutoHideTab; + friend AutoHideTabPrivate; + friend AutoHideDockContainerPrivate; + friend CAutoHideSideBar; + +protected: + /** + * Handles activation events to update zOrderIndex + */ + virtual bool event(QEvent *e) override; + + /** + * Access function for the internal root splitter + */ + CDockSplitter* rootSplitter() const; + + /** + * Creates and initializes a dockwidget auto hide container into the given area. + * Initializing inserts the tabs into the side tab widget and hides it + * Returns nullptr if you try and insert into an area where the configuration is not enabled + */ + CAutoHideDockContainer* createAndSetupAutoHideContainer(SideBarLocation area, CDockWidget* DockWidget, int TabIndex = -1); + + /** + * Helper function for creation of the root splitter + */ + void createRootSplitter(); + + /** + * Helper function for creation of the side tab bar widgets + */ + void createSideTabBarWidgets(); + + /** + * Drop floating widget into the container + */ + void dropFloatingWidget(CFloatingDockContainer* FloatingWidget, const QPoint& TargetPos); + + /** + * Drop a dock area or a dock widget given in widget parameter. + * If the TargetAreaWidget is a nullptr, then the DropArea indicates + * the drop area for the container. If the given TargetAreaWidget is not + * a nullptr, then the DropArea indicates the drop area in the given + * TargetAreaWidget + */ + void dropWidget(QWidget* Widget, DockWidgetArea DropArea, CDockAreaWidget* TargetAreaWidget, + int TabIndex = -1); + + /** + * Adds the given dock area to this container widget + */ + void addDockArea(CDockAreaWidget* DockAreaWidget, DockWidgetArea area = ads::CenterDockWidgetArea); + + /** + * Removes the given dock area from this container + */ + void removeDockArea(CDockAreaWidget* area); + + /** + * Remove all dock areas and returns the list of removed dock areas + */ + QList> removeAllDockAreas(); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter& Stream) const; + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(CDockingStateReader& Stream, bool Testing); + + /** + * This function returns the last added dock area widget for the given + * area identifier or 0 if no dock area widget has been added for the given + * area + */ + CDockAreaWidget* lastAddedDockAreaWidget(DockWidgetArea area) const; + + /** + * If hasSingleVisibleDockWidget() returns true, this function returns the + * one and only visible dock widget. Otherwise it returns a nullptr. + */ + CDockWidget* topLevelDockWidget() const; + + /** + * If the container has only one single visible dock area, then this + * functions returns this top level dock area + */ + CDockAreaWidget* topLevelDockArea() const; + + /** + * This function returns a list of all dock widgets in this floating widget. + * It may be possible, depending on the implementation, that dock widgets, + * that are not visible to the user have no parent widget. Therefore simply + * calling findChildren() would not work here. Therefore this function + * iterates over all dock areas and creates a list that contains all + * dock widgets returned from all dock areas. + */ + QList dockWidgets() const; + + /** + * This function forces the dock container widget to update handles of splitters + * based on resize modes of dock widgets contained in the container. + */ + void updateSplitterHandles(QSplitter* splitter); + + /** + * Registers the given floating widget in the internal list of + * auto hide widgets + */ + void registerAutoHideWidget(CAutoHideDockContainer* AutoHideWidget); + + /** + * Remove the given auto hide widget from the list of registered auto hide + * widgets + */ + void removeAutoHideWidget(CAutoHideDockContainer* AutoHideWidget); + + /** + * Handles widget events of auto hide widgets to trigger delayed show + * or hide actions for auto hide container on auto hide tab mouse over + */ + void handleAutoHideWidgetEvent(QEvent* e, QWidget* w); + +public: + /** + * Default Constructor + */ + CDockContainerWidget(CDockManager* DockManager, QWidget* parent = 0); + + /** + * Virtual Destructor + */ + virtual ~CDockContainerWidget(); + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. + * \return Returns the dock area widget that contains the new DockWidget + */ + CDockAreaWidget* addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, + CDockAreaWidget* DockAreaWidget = nullptr, int Index = -1); + + /** + * Removes dockwidget + */ + void removeDockWidget(CDockWidget* Dockwidget); + + /** + * Returns the current zOrderIndex + */ + virtual unsigned int zOrderIndex() const; + + /** + * This function returns true if this container widgets z order index is + * higher than the index of the container widget given in Other parameter + */ + bool isInFrontOf(CDockContainerWidget* Other) const; + + /** + * Returns the dock area at the given global position or 0 if there is no + * dock area at this position + */ + CDockAreaWidget* dockAreaAt(const QPoint& GlobalPos) const; + + /** + * Returns the dock area at the given Index or 0 if the index is out of + * range + */ + CDockAreaWidget* dockArea(int Index) const; + + /** + * Returns the list of dock areas that are not closed + * If all dock widgets in a dock area are closed, the dock area will be closed + */ + QList openedDockAreas() const; + + /** + * Returns a list for all open dock widgets in all open dock areas + */ + QList 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. + * A top level widget is a real floating widget. Only the isFloating() + * function of top level widgets may returns true. + */ + bool hasTopLevelDockWidget() const; + + /** + * Returns the number of dock areas in this container + */ + int dockAreaCount() const; + + /** + * Returns the number of visible dock areas + */ + int visibleDockAreaCount() const; + + /** + * This function returns true, if this container is in a floating widget + */ + bool isFloating() const; + + /** + * Dumps the layout for debugging purposes + */ + void dumpLayout(); + + /** + * This functions returns the dock widget features of all dock widget in + * this container. + * A bitwise and is used to combine the flags of all dock widgets. That + * means, if only dock widget does not support a certain flag, the whole + * dock are does not support the flag. + */ + CDockWidget::DockWidgetFeatures features() const; + + /** + * If this dock container is in a floating widget, this function returns + * the floating widget. + * Else, it returns a nullptr. + */ + CFloatingDockContainer* floatingWidget() const; + + /** + * Call this function to close all dock areas except the KeepOpenArea + */ + void closeOtherAreas(CDockAreaWidget* KeepOpenArea); + + /** + * Returns the side tab widget for the given area + */ + CAutoHideSideBar* autoHideSideBar(SideBarLocation area) const; + + + /** + * Access function for auto hide widgets + */ + QList autoHideWidgets() const; + + /** + * Returns the content rectangle without the autohide side bars + */ + QRect contentRect() const; + + /** + * Returns the content rectangle mapped to global space. + * The content rectangle is the rectangle of the dock manager widget minus + * the auto hide side bars. So that means, it is the geometry of the + * internal root splitter mapped to global space. + */ + QRect contentRectGlobal() const; + + /** + * Returns the dock manager that owns this container + */ + CDockManager* dockManager() const; + + +Q_SIGNALS: + /** + * This signal is emitted if one or multiple dock areas has been added to + * the internal list of dock areas. + * If multiple dock areas are inserted, this signal is emitted only once + */ + void dockAreasAdded(); + + /** + * This signal is emitted, if a new auto hide widget has been created. + */ + void autoHideWidgetCreated(ads::CAutoHideDockContainer* AutoHideWidget); + + /** + * This signal is emitted if one or multiple dock areas has been removed + */ + void dockAreasRemoved(); + + /** + * This signal is emitted if a dock area is opened or closed via + * toggleView() function + */ + void dockAreaViewToggled(ads::CDockAreaWidget* DockArea, bool Open); +}; // class DockContainerWidget +} // namespace ads +//----------------------------------------------------------------------------- +#endif // DockContainerWidgetH diff --git a/src/DockFocusController.cpp b/src/DockFocusController.cpp new file mode 100644 index 000000000..27acc4a4d --- /dev/null +++ b/src/DockFocusController.cpp @@ -0,0 +1,434 @@ +//============================================================================ +/// \file DockFocusController.cpp +/// \author Uwe Kindler +/// \date 05.06.2020 +/// \brief Implementation of CDockFocusController class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "DockFocusController.h" + +#include +#include + +#include +#include +#include +#include + +#include "DockWidget.h" +#include "DockAreaWidget.h" +#include "DockWidgetTab.h" +#include "DockContainerWidget.h" +#include "FloatingDockContainer.h" +#include "DockManager.h" +#include "DockAreaTitleBar.h" + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include "linux/FloatingWidgetTitleBar.h" +#endif + +namespace ads +{ +static const char* const FocusedDockWidgetProperty = "FocusedDockWidget"; + +/** + * Private data class of CDockFocusController class (pimpl) + */ +struct DockFocusControllerPrivate +{ + CDockFocusController *_this; + QPointer FocusedDockWidget = nullptr; + QPointer FocusedArea = nullptr; + QPointer OldFocusedDockWidget = nullptr; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QPointer FloatingWidget = nullptr; +#endif + CDockManager* DockManager; + bool ForceFocusChangedSignal = false; + bool TabPressed = false; + + /** + * Private data constructor + */ + DockFocusControllerPrivate(CDockFocusController *_public); + + /** + * This function updates the focus style of the given dock widget and + * the dock area that it belongs to + */ + void updateDockWidgetFocus(CDockWidget* DockWidget); +}; // struct DockFocusControllerPrivate + + + +//=========================================================================== +static void updateDockWidgetFocusStyle(CDockWidget* DockWidget, bool Focused) +{ + DockWidget->setProperty("focused", Focused); + DockWidget->tabWidget()->setProperty("focused", Focused); + DockWidget->tabWidget()->updateStyle(); + internal::repolishStyle(DockWidget); +} + + +//=========================================================================== +static void updateDockAreaFocusStyle(CDockAreaWidget* DockArea, bool Focused) +{ + DockArea->setProperty("focused", Focused); + internal::repolishStyle(DockArea); + internal::repolishStyle(DockArea->titleBar()); +} + + +//=========================================================================== +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +static void updateFloatingWidgetFocusStyle(CFloatingDockContainer* FloatingWidget, bool Focused) +{ + if (FloatingWidget->hasNativeTitleBar()) + { + return; + } + auto TitleBar = qobject_cast(FloatingWidget->titleBarWidget()); + if (!TitleBar) + { + return; + } + TitleBar->setProperty("focused", Focused); + TitleBar->updateStyle(); +} +#endif + + +//============================================================================ +DockFocusControllerPrivate::DockFocusControllerPrivate( + CDockFocusController *_public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockFocusControllerPrivate::updateDockWidgetFocus(CDockWidget* DockWidget) +{ + if (!DockWidget) return; + if (!DockWidget->features().testFlag(CDockWidget::DockWidgetFocusable)) + { + return; + } + + QWindow* Window = nullptr; + auto DockContainer = DockWidget->dockContainer(); + if (DockContainer) + { + Window = DockContainer->window()->windowHandle(); + } + + if (Window) + { + Window->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer(DockWidget))); + } + CDockAreaWidget* NewFocusedDockArea = nullptr; + if (FocusedDockWidget) + { + updateDockWidgetFocusStyle(FocusedDockWidget, false); + } + + CDockWidget* old = FocusedDockWidget; + FocusedDockWidget = DockWidget; + updateDockWidgetFocusStyle(FocusedDockWidget, true); + NewFocusedDockArea = FocusedDockWidget->dockAreaWidget(); + if (NewFocusedDockArea && (FocusedArea != NewFocusedDockArea)) + { + if (FocusedArea) + { + QObject::disconnect(FocusedArea, SIGNAL(viewToggled(bool)), _this, SLOT(onFocusedDockAreaViewToggled(bool))); + updateDockAreaFocusStyle(FocusedArea, false); + } + + FocusedArea = NewFocusedDockArea; + updateDockAreaFocusStyle(FocusedArea, true); + QObject::connect(FocusedArea, SIGNAL(viewToggled(bool)), _this, SLOT(onFocusedDockAreaViewToggled(bool))); + } + + + + CFloatingDockContainer* NewFloatingWidget = nullptr; + DockContainer = FocusedDockWidget->dockContainer(); + if (DockContainer) + { + NewFloatingWidget = DockContainer->floatingWidget(); + } + + if (NewFloatingWidget) + { + NewFloatingWidget->setProperty(FocusedDockWidgetProperty, QVariant::fromValue(QPointer(DockWidget))); + } + + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // This code is required for styling the floating widget titlebar for linux + // depending on the current focus state + if (FloatingWidget != NewFloatingWidget) + { + if (FloatingWidget) + { + updateFloatingWidgetFocusStyle(FloatingWidget, false); + } + FloatingWidget = NewFloatingWidget; + + if (FloatingWidget) + { + updateFloatingWidgetFocusStyle(FloatingWidget, true); + } + } +#endif + + if (old == DockWidget && !ForceFocusChangedSignal) + { + return; + } + + ForceFocusChangedSignal = false; + if (DockWidget->isVisible()) + { + Q_EMIT DockManager->focusedDockWidgetChanged(old, DockWidget); + } + else + { + OldFocusedDockWidget = old; + QObject::connect(DockWidget, SIGNAL(visibilityChanged(bool)), _this, SLOT(onDockWidgetVisibilityChanged(bool))); + } +} + + + +//============================================================================ +void CDockFocusController::onDockWidgetVisibilityChanged(bool Visible) +{ + auto Sender = sender(); + auto DockWidget = qobject_cast(Sender); + disconnect(Sender, SIGNAL(visibilityChanged(bool)), this, SLOT(onDockWidgetVisibilityChanged(bool))); + if (DockWidget && Visible) + { + Q_EMIT d->DockManager->focusedDockWidgetChanged(d->OldFocusedDockWidget, DockWidget); + } +} + + +//============================================================================ +CDockFocusController::CDockFocusController(CDockManager* DockManager) : + Super(DockManager), + d(new DockFocusControllerPrivate(this)) +{ + 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())); +} + +//============================================================================ +CDockFocusController::~CDockFocusController() +{ + delete d; +} + + +//============================================================================ +void CDockFocusController::onFocusWindowChanged(QWindow *focusWindow) +{ + if (!focusWindow) + { + return; + } + + auto vDockWidget = focusWindow->property(FocusedDockWidgetProperty); + if (!vDockWidget.isValid()) + { + return; + } + + auto DockWidget = vDockWidget.value>(); + if (!DockWidget) + { + return; + } + + d->updateDockWidgetFocus(DockWidget); +} + + +//=========================================================================== +void CDockFocusController::onApplicationFocusChanged(QWidget* focusedOld, QWidget* focusedNow) +{ + Q_UNUSED(focusedOld); + + // Ignore focus changes if we are restoring state, or if user clicked + // a tab which in turn caused the focus change + if (d->DockManager->isRestoringState() || d->TabPressed) + { + return; + } + + ADS_PRINT("CDockFocusController::onApplicationFocusChanged " + << " old: " << focusedOld << " new: " << focusedNow); + if (!focusedNow) + { + return; + } + + CDockWidget* DockWidget = qobject_cast(focusedNow); + if (!DockWidget) + { + DockWidget = internal::findParent(focusedNow); + } + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!DockWidget) + { + return; + } +#else + if (!DockWidget || DockWidget->tabWidget()->isHidden()) + { + return; + } +#endif + + d->updateDockWidgetFocus(DockWidget); +} + + +//=========================================================================== +void CDockFocusController::setDockWidgetTabFocused(CDockWidgetTab* Tab) +{ + auto DockWidget = Tab->dockWidget(); + if (DockWidget) + { + d->updateDockWidgetFocus(DockWidget); + } +} + + +//=========================================================================== +void CDockFocusController::clearDockWidgetFocus(CDockWidget* dockWidget) +{ + dockWidget->clearFocus(); + updateDockWidgetFocusStyle(dockWidget, false); +} + + +//=========================================================================== +void CDockFocusController::setDockWidgetFocused(CDockWidget* focusedNow) +{ + d->updateDockWidgetFocus(focusedNow); +} + + +//=========================================================================== +void CDockFocusController::onFocusedDockAreaViewToggled(bool Open) +{ + if (d->DockManager->isRestoringState()) + { + return; + } + + CDockAreaWidget* DockArea = qobject_cast(sender()); + if (!DockArea || Open) + { + return; + } + auto Container = DockArea->dockContainer(); + auto OpenedDockAreas = Container->openedDockAreas(); + if (OpenedDockAreas.isEmpty()) + { + return; + } + + d->updateDockWidgetFocus(OpenedDockAreas[0]->currentDockWidget()); +} + + +//=========================================================================== +void CDockFocusController::notifyWidgetOrAreaRelocation(QWidget* DroppedWidget) +{ + if (d->DockManager->isRestoringState()) + { + return; + } + + CDockWidget* DockWidget = qobject_cast(DroppedWidget); + if (!DockWidget) + { + CDockAreaWidget* DockArea = qobject_cast(DroppedWidget); + if (DockArea) + { + DockWidget = DockArea->currentDockWidget(); + } + } + + if (!DockWidget) + { + return; + } + + d->ForceFocusChangedSignal = true; + CDockManager::setWidgetFocus(DockWidget); +} + + +//=========================================================================== +void CDockFocusController::notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget) +{ + if (!FloatingWidget || d->DockManager->isRestoringState()) + { + return; + } + + auto vDockWidget = FloatingWidget->property(FocusedDockWidgetProperty); + if (!vDockWidget.isValid()) + { + return; + } + + auto DockWidget = vDockWidget.value>(); + if (DockWidget) + { + DockWidget->dockAreaWidget()->setCurrentDockWidget(DockWidget); + CDockManager::setWidgetFocus(DockWidget); + } +} + + +//========================================================================== +void CDockFocusController::onStateRestored() +{ + if (d->FocusedDockWidget) + { + updateDockWidgetFocusStyle(d->FocusedDockWidget, false); + } +} + + +//========================================================================== +CDockWidget* CDockFocusController::focusedDockWidget() const +{ + return d->FocusedDockWidget.data(); +} + + +//========================================================================== +void CDockFocusController::setDockWidgetTabPressed(bool Value) +{ + d->TabPressed = Value; +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockFocusController.cpp diff --git a/src/DockFocusController.h b/src/DockFocusController.h new file mode 100644 index 000000000..27d9fc143 --- /dev/null +++ b/src/DockFocusController.h @@ -0,0 +1,99 @@ +#ifndef DockFocusControllerH +#define DockFocusControllerH +//============================================================================ +/// \file DockFocusController.h +/// \author Uwe Kindler +/// \date 05.06.2020 +/// \brief Declaration of CDockFocusController class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "ads_globals.h" +#include "DockManager.h" + +namespace ads +{ +struct DockFocusControllerPrivate; +class CDockManager; +class CFloatingDockContainer; + +/** + * Manages focus styling of dock widgets and handling of focus changes + */ +class ADS_EXPORT CDockFocusController : public QObject +{ + Q_OBJECT +private: + DockFocusControllerPrivate* d; ///< private data (pimpl) + friend struct DockFocusControllerPrivate; + +private Q_SLOTS: + void onApplicationFocusChanged(QWidget *old, QWidget *now); + void onFocusWindowChanged(QWindow *focusWindow); + void onFocusedDockAreaViewToggled(bool Open); + void onStateRestored(); + void onDockWidgetVisibilityChanged(bool Visible); + +public: + using Super = QObject; + /** + * Default Constructor + */ + CDockFocusController(CDockManager* DockManager); + + /** + * Virtual Destructor + */ + virtual ~CDockFocusController(); + + /** + * A container needs to call this function if a widget has been dropped + * into it + */ + void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); + + /** + * This function is called, if a floating widget has been dropped into + * an new position. + * When this function is called, all dock widgets of the FloatingWidget + * are already inserted into its new position + */ + void notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget); + + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + */ + CDockWidget* focusedDockWidget() const; + + /** + * Request focus highlighting for the given dock widget assigned to the tab + * given in Tab parameter + */ + void setDockWidgetTabFocused(CDockWidgetTab* Tab); + + /* + * Request clear focus for a dock widget + */ + void clearDockWidgetFocus(CDockWidget* dockWidget); + + /** + * Notifies the dock focus controller, that a the mouse is pressed or + * released + */ + void setDockWidgetTabPressed(bool Value); + +public Q_SLOTS: + /** + * Request a focus change to the given dock widget + */ + void setDockWidgetFocused(CDockWidget* focusedNow); +}; // class DockFocusController +} + // namespace ads +//----------------------------------------------------------------------------- +#endif // DockFocusControllerH + diff --git a/src/DockManager.cpp b/src/DockManager.cpp new file mode 100644 index 000000000..e7aabb3fb --- /dev/null +++ b/src/DockManager.cpp @@ -0,0 +1,1497 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockManager.cpp +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Implementation of CDockManager class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "DockWidgetTab.h" +#include "DockManager.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FloatingDockContainer.h" +#include "DockOverlay.h" +#include "DockWidget.h" +#include "ads_globals.h" +#include "DockAreaWidget.h" +#include "IconProvider.h" +#include "DockingStateReader.h" +#include "DockAreaTitleBar.h" +#include "DockFocusController.h" +#include "DockSplitter.h" + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include "linux/FloatingWidgetTitleBar.h" +#endif + + +/** + * Initializes the resources specified by the .qrc file with the specified base + * name. Normally, when resources are built as part of the application, the + * resources are loaded automatically at startup. The Q_INIT_RESOURCE() macro + * is necessary on some platforms for resources stored in a static library. + * Because GCC causes a linker error if we put Q_INIT_RESOURCE into the + * loadStyleSheet() function, we place it into a function outside of the ads + * namespace + */ +static void initResource() +{ + Q_INIT_RESOURCE(ads); +} + + +namespace ads +{ +/** + * Internal file version in case the structure changes internally + */ +enum eStateFileVersion +{ + InitialVersion = 0, //!< InitialVersion + Version1 = 1, //!< Version1 + CurrentVersion = Version1//!< CurrentVersion +}; + +static CDockManager::ConfigFlags StaticConfigFlags = CDockManager::DefaultNonOpaqueConfig; +static CDockManager::AutoHideFlags StaticAutoHideConfigFlags; // auto hide feature is disabled by default +static QVector StaticConfigParams(CDockManager::ConfigParamCount); + +static QString FloatingContainersTitle; + +/** + * Private data class of CDockManager class (pimpl) + */ +struct DockManagerPrivate +{ + CDockManager* _this; + QList> FloatingWidgets; + QList> HiddenFloatingWidgets; + QList Containers; + CDockOverlay* ContainerOverlay; + CDockOverlay* DockAreaOverlay; + QMap DockWidgetsMap; + QMap Perspectives; + QMap ViewMenuGroups; + QMenu* ViewMenu; + CDockManager::eViewMenuInsertionOrder MenuInsertionOrder = CDockManager::MenuAlphabeticallySorted; + bool RestoringState = false; + QVector UninitializedFloatingWidgets; + CDockFocusController* FocusController = nullptr; + CDockWidget* CentralWidget = nullptr; + bool IsLeavingMinimized = false; + Qt::ToolButtonStyle ToolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle ToolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize ToolBarIconSizeDocked = QSize(16, 16); + QSize ToolBarIconSizeFloating = QSize(24, 24); + CDockWidget::DockWidgetFeatures LockedDockWidgetFeatures; + + /** + * Private data constructor + */ + DockManagerPrivate(CDockManager* _public); + + /** + * Checks if the given data stream is a valid docking system state + * file. + */ + bool checkFormat(const QByteArray &state, int version); + + /** + * Restores the state + */ + bool restoreStateFromXml(const QByteArray &state, int version, bool Testing = internal::Restore); + + /** + * Restore state + */ + bool restoreState(const QByteArray &state, int version); + + void restoreDockWidgetsOpenState(); + void restoreDockAreasIndices(); + void emitTopLevelEvents(); + + void hideFloatingWidgets() + { + // Hide updates of floating widgets from user + for (auto FloatingWidget : FloatingWidgets) + { + if (FloatingWidget) + { + FloatingWidget->hide(); + } + } + } + + void markDockWidgetsDirty() + { + for (auto DockWidget : DockWidgetsMap) + { + DockWidget->setProperty(internal::DirtyProperty, true); + } + } + + /** + * Restores the container with the given index + */ + bool restoreContainer(int Index, CDockingStateReader& stream, bool Testing); + + /** + * Loads the stylesheet + */ + void loadStylesheet(); + + /** + * Adds action to menu - optionally in sorted order + */ + void addActionToMenu(QAction* Action, QMenu* Menu, bool InsertSorted); +}; +// struct DockManagerPrivate + +//============================================================================ +DockManagerPrivate::DockManagerPrivate(CDockManager* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockManagerPrivate::loadStylesheet() +{ + initResource(); + QString Result; + QString FileName = ":ads/stylesheets/"; + FileName += CDockManager::testConfigFlag(CDockManager::FocusHighlighting) + ? "focus_highlighting" : "default"; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + FileName += "_linux"; +#endif + FileName += ".css"; + QFile StyleSheetFile(FileName); + StyleSheetFile.open(QIODevice::ReadOnly); + QTextStream StyleSheetStream(&StyleSheetFile); + Result = StyleSheetStream.readAll(); + StyleSheetFile.close(); + _this->setStyleSheet(Result); +} + + +//============================================================================ +bool DockManagerPrivate::restoreContainer(int Index, CDockingStateReader& stream, bool Testing) +{ + if (Testing) + { + Index = 0; + } + + bool Result = false; + if (Index >= Containers.count()) + { + CFloatingDockContainer* FloatingWidget = new CFloatingDockContainer(_this); + Result = FloatingWidget->restoreState(stream, Testing); + } + else + { + ADS_PRINT("d->Containers[i]->restoreState "); + auto Container = Containers[Index]; + if (Container->isFloating()) + { + Result = Container->floatingWidget()->restoreState(stream, Testing); + } + else + { + Result = Container->restoreState(stream, Testing); + } + } + + return Result; +} + + +//============================================================================ +bool DockManagerPrivate::checkFormat(const QByteArray &state, int version) +{ + return restoreStateFromXml(state, version, internal::RestoreTesting); +} + + +//============================================================================ +bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, + bool Testing) +{ + Q_UNUSED(version); + + if (state.isEmpty()) + { + return false; + } + CDockingStateReader s(state); + s.readNextStartElement(); + if (s.name() != QLatin1String("QtAdvancedDockingSystem")) + { + return false; + } + ADS_PRINT(s.attributes().value("Version")); + bool ok; + int v = s.attributes().value("Version").toInt(&ok); + if (!ok || v > CurrentVersion) + { + return false; + } + s.setFileVersion(v); + + ADS_PRINT(s.attributes().value("UserVersion")); + // Older files do not support UserVersion but we still want to load them so + // we first test if the attribute exists + if (!s.attributes().value("UserVersion").isEmpty()) + { + v = s.attributes().value("UserVersion").toInt(&ok); + if (!ok || v != version) + { + return false; + } + } + + bool Result = true; +#ifdef ADS_DEBUG_PRINT + int DockContainers = s.attributes().value("Containers").toInt(); +#endif + ADS_PRINT(DockContainers); + + if (CentralWidget) + { + const auto CentralWidgetAttribute = s.attributes().value("CentralWidget"); + // If we have a central widget but a state without central widget, then + // something is wrong. + if (CentralWidgetAttribute.isEmpty()) + { + qWarning() << "Dock manager has central widget but saved state does not have central widget."; + return false; + } + + // If the object name of the central widget does not match the name of the + // saved central widget, the something is wrong + if (CentralWidget->objectName() != CentralWidgetAttribute.toString()) + { + qWarning() << "Object name of central widget does not match name of central widget in saved state."; + return false; + } + } + + int DockContainerCount = 0; + while (s.readNextStartElement()) + { + if (s.name() == QLatin1String("Container")) + { + Result = restoreContainer(DockContainerCount, s, Testing); + if (!Result) + { + break; + } + DockContainerCount++; + } + } + + if (!Testing) + { + // Delete remaining empty floating widgets + int FloatingWidgetIndex = DockContainerCount - 1; + for (int i = FloatingWidgetIndex; i < FloatingWidgets.count(); ++i) + { + CFloatingDockContainer* floatingWidget = FloatingWidgets[i]; + if (!floatingWidget) continue; + _this->removeDockContainer(floatingWidget->dockContainer()); + floatingWidget->deleteLater(); + } + } + + return Result; +} + + +//============================================================================ +void DockManagerPrivate::restoreDockWidgetsOpenState() +{ + // All dock widgets, that have not been processed in the restore state + // function are invisible to the user now and have no assigned dock area + // They do not belong to any dock container, until the user toggles the + // toggle view action the next time + for (auto DockWidget : DockWidgetsMap) + { + if (DockWidget->property(internal::DirtyProperty).toBool()) + { + // If the DockWidget is an auto hide widget that is not assigned yet, + // then we need to delete the auto hide container now + if (DockWidget->isAutoHide()) + { + DockWidget->autoHideDockContainer()->cleanupAndDelete(); + } + DockWidget->flagAsUnassigned(); + Q_EMIT DockWidget->viewToggled(false); + } + else + { + DockWidget->toggleViewInternal(!DockWidget->property(internal::ClosedProperty).toBool()); + } + } +} + + +//============================================================================ +void DockManagerPrivate::restoreDockAreasIndices() +{ + // Now all dock areas are properly restored and we setup the index of + // The dock areas because the previous toggleView() action has changed + // the dock area index + int Count = 0; + for (auto DockContainer : Containers) + { + Count++; + for (int i = 0; i < DockContainer->dockAreaCount(); ++i) + { + CDockAreaWidget* DockArea = DockContainer->dockArea(i); + QString DockWidgetName = DockArea->property("currentDockWidget").toString(); + CDockWidget* DockWidget = nullptr; + if (!DockWidgetName.isEmpty()) + { + DockWidget = _this->findDockWidget(DockWidgetName); + } + + if (!DockWidget || DockWidget->isClosed()) + { + int Index = DockArea->indexOfFirstOpenDockWidget(); + if (Index < 0) + { + continue; + } + DockArea->setCurrentIndex(Index); + } + else + { + DockArea->internalSetCurrentDockWidget(DockWidget); + } + } + } +} + +//============================================================================ +void DockManagerPrivate::emitTopLevelEvents() +{ + // Finally we need to send the topLevelChanged() signals for all dock + // widgets if top level changed + for (auto DockContainer : Containers) + { + CDockWidget* TopLevelDockWidget = DockContainer->topLevelDockWidget(); + if (TopLevelDockWidget) + { + TopLevelDockWidget->emitTopLevelChanged(true); + } + else + { + for (int i = 0; i < DockContainer->dockAreaCount(); ++i) + { + auto DockArea = DockContainer->dockArea(i); + for (auto DockWidget : DockArea->dockWidgets()) + { + DockWidget->emitTopLevelChanged(false); + } + } + } + } +} + + +//============================================================================ +bool DockManagerPrivate::restoreState(const QByteArray& State, int version) +{ + QByteArray state = State.startsWith("dumpLayout(); + + return true; +} + + +//============================================================================ +void DockManagerPrivate::addActionToMenu(QAction* Action, QMenu* Menu, bool InsertSorted) +{ + if (InsertSorted) + { + auto Actions = Menu->actions(); + auto it = std::find_if(Actions.begin(), Actions.end(), + [&Action](const QAction* a) + { + return a->text().compare(Action->text(), Qt::CaseInsensitive) > 0; + }); + + if (it == Actions.end()) + { + Menu->addAction(Action); + } + else + { + Menu->insertAction(*it, Action); + } + } + else + { + Menu->addAction(Action); + } +} + + +//============================================================================ +CDockManager::CDockManager(QWidget *parent) : + CDockContainerWidget(this, parent), + d(new DockManagerPrivate(this)) +{ + createRootSplitter(); + createSideTabBarWidgets(); + QMainWindow* MainWindow = qobject_cast(parent); + if (MainWindow) + { + MainWindow->setCentralWidget(this); + } + + d->ViewMenu = new QMenu(tr("Show View"), this); + d->DockAreaOverlay = new CDockOverlay(this, CDockOverlay::ModeDockAreaOverlay); + d->ContainerOverlay = new CDockOverlay(this, CDockOverlay::ModeContainerOverlay); + d->Containers.append(this); + d->loadStylesheet(); + + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + d->FocusController = new CDockFocusController(this); + } + + + window()->installEventFilter(this); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + connect(qApp, &QApplication::focusWindowChanged, [](QWindow* focusWindow) + { + // bring modal dialogs to foreground to ensure that they are in front of any + // floating dock widget + if (focusWindow && focusWindow->isModal()) + { + focusWindow->raise(); + } + }); +#endif +} + +//============================================================================ +CDockManager::~CDockManager() +{ + // fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307 + std::vector> areas; + for (int i = 0; i != dockAreaCount(); ++i) + { + areas.push_back( dockArea(i) ); + } + for ( auto area : areas ) + { + if (!area || area->dockManager() != this) continue; + + // QPointer delete safety - just in case some dock wigdet in destruction + // deletes another related/twin or child dock widget. + std::vector> deleteWidgets; + for ( auto widget : area->dockWidgets() ) + { + deleteWidgets.push_back(widget); + } + for ( auto ptrWdg : deleteWidgets) + { + delete ptrWdg; + } + } + + auto FloatingWidgets = d->FloatingWidgets; + for (auto FloatingWidget : FloatingWidgets) + { + FloatingWidget->deleteContent(); + delete FloatingWidget; + } + + // Delete Dock Widgets before Areas so widgets can access them late (like dtor) + for ( auto area : areas ) + { + delete area; + } + + delete d; +} + +//============================================================================ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +bool CDockManager::eventFilter(QObject *obj, QEvent *e) +{ + // Emulate Qt:Tool behaviour. + // Required because on some WMs Tool windows can't be maximized. + + // Window always on top of the MainWindow. + if (e->type() == QEvent::WindowActivate) + { + for (auto _window : d->FloatingWidgets) + { + if (!_window->isVisible() || window()->isMinimized()) + { + continue; + } + // 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. + 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) + { + for (auto _window : d->FloatingWidgets) + { + if (!_window->isVisible() || window()->isMinimized()) + { + continue; + } + + 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(); + } + } + + // Sync minimize with MainWindow + if (e->type() == QEvent::WindowStateChange) + { + for (auto _window : d->FloatingWidgets) + { + if (! _window->isVisible()) + { + continue; + } + + if (window()->isMinimized()) + { + _window->showMinimized(); + } + else + { + _window->setWindowState(_window->windowState() & (~Qt::WindowMinimized)); + } + } + if (!window()->isMinimized()) + { + QApplication::setActiveWindow(window()); + } + } + return Super::eventFilter(obj, e); +} +#else +//============================================================================ +bool CDockManager::eventFilter(QObject *obj, QEvent *e) +{ + if (e->type() == QEvent::WindowStateChange) + { + QWindowStateChangeEvent* ev = static_cast(e); + if (ev->oldState().testFlag(Qt::WindowMinimized)) + { + d->IsLeavingMinimized = true; + QMetaObject::invokeMethod(this, "endLeavingMinimizedState", Qt::QueuedConnection); + } + } + return Super::eventFilter(obj, e); +} +#endif + + +//============================================================================ +void CDockManager::endLeavingMinimizedState() +{ + d->IsLeavingMinimized = false; + this->activateWindow(); +} + + +//============================================================================ +bool CDockManager::isLeavingMinimizedState() const +{ + return d->IsLeavingMinimized; +} + + +//============================================================================ +void CDockManager::registerFloatingWidget(CFloatingDockContainer* FloatingWidget) +{ + d->FloatingWidgets.append(FloatingWidget); + Q_EMIT floatingWidgetCreated(FloatingWidget); + ADS_PRINT("d->FloatingWidgets.count() " << d->FloatingWidgets.count()); +} + + +//============================================================================ +void CDockManager::removeFloatingWidget(CFloatingDockContainer* FloatingWidget) +{ + d->FloatingWidgets.removeAll(FloatingWidget); +} + +//============================================================================ +void CDockManager::registerDockContainer(CDockContainerWidget* DockContainer) +{ + d->Containers.append(DockContainer); +} + + +//============================================================================ +void CDockManager::removeDockContainer(CDockContainerWidget* DockContainer) +{ + if (this != DockContainer) + { + d->Containers.removeAll(DockContainer); + } +} + + +//============================================================================ +CDockOverlay* CDockManager::containerOverlay() const +{ + return d->ContainerOverlay; +} + + +//============================================================================ +CDockOverlay* CDockManager::dockAreaOverlay() const +{ + return d->DockAreaOverlay; +} + + +//============================================================================ +const QList CDockManager::dockContainers() const +{ + return d->Containers; +} + + +//============================================================================ +const QList CDockManager::floatingWidgets() const +{ + QList res; + for (auto &fl : d->FloatingWidgets) + { + if (fl) res.append(fl); + } + return res; +} + + +//============================================================================ +unsigned int CDockManager::zOrderIndex() const +{ + return 0; +} + + +//============================================================================ +QByteArray CDockManager::saveState(int version) const +{ + QByteArray xmldata; + QXmlStreamWriter s(&xmldata); + auto ConfigFlags = CDockManager::configFlags(); + s.setAutoFormatting(ConfigFlags.testFlag(XmlAutoFormattingEnabled)); + s.writeStartDocument(); + s.writeStartElement("QtAdvancedDockingSystem"); + s.writeAttribute("Version", QString::number(CurrentVersion)); + s.writeAttribute("UserVersion", QString::number(version)); + s.writeAttribute("Containers", QString::number(d->Containers.count())); + if (d->CentralWidget) + { + s.writeAttribute("CentralWidget", d->CentralWidget->objectName()); + } + for (auto Container : d->Containers) + { + Container->saveState(s); + } + + s.writeEndElement(); + s.writeEndDocument(); + + return ConfigFlags.testFlag(XmlCompressionEnabled) + ? qCompress(xmldata, 9) : xmldata; +} + + +//============================================================================ +bool CDockManager::restoreState(const QByteArray &state, int version) +{ + // Prevent multiple calls as long as state is not restore. This may + // happen, if QApplication::processEvents() is called somewhere + if (d->RestoringState) + { + return false; + } + + // We hide the complete dock manager here. Restoring the state means + // that DockWidgets are removed from the DockArea internal stack layout + // which in turn means, that each time a widget is removed the stack + // will show and raise the next available widget which in turn + // triggers show events for the dock widgets. To avoid this we hide the + // dock manager. Because there will be no processing of application + // events until this function is finished, the user will not see this + // hiding + bool IsHidden = this->isHidden(); + if (!IsHidden) + { + hide(); + } + d->RestoringState = true; + Q_EMIT restoringState(); + bool Result = d->restoreState(state, version); + d->RestoringState = false; + if (!IsHidden) + { + show(); + } + Q_EMIT stateRestored(); + return Result; +} + + +//============================================================================ +CFloatingDockContainer* CDockManager::addDockWidgetFloating(CDockWidget* Dockwidget) +{ + d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); + CDockAreaWidget* OldDockArea = Dockwidget->dockAreaWidget(); + if (OldDockArea) + { + OldDockArea->removeDockWidget(Dockwidget); + } + + Dockwidget->setDockManager(this); + CFloatingDockContainer* FloatingWidget = new CFloatingDockContainer(Dockwidget); + FloatingWidget->resize(Dockwidget->size()); + if (isVisible()) + { + FloatingWidget->show(); + } + else + { + d->UninitializedFloatingWidgets.append(FloatingWidget); + } + Q_EMIT dockWidgetAdded(Dockwidget); + return FloatingWidget; +} + + +//============================================================================ +void CDockManager::showEvent(QShowEvent *event) +{ + Super::showEvent(event); + + // Fix Issue #380 + restoreHiddenFloatingWidgets(); + if (d->UninitializedFloatingWidgets.empty()) + { + return; + } + + for (auto FloatingWidget : d->UninitializedFloatingWidgets) + { + // 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, int Index) +{ + d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); + auto Container = DockAreaWidget ? DockAreaWidget->dockContainer() : this; + auto AreaOfAddedDockWidget = Container->addDockWidget(area, Dockwidget, DockAreaWidget, Index); + Q_EMIT dockWidgetAdded(Dockwidget); + 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; +} + +//============================================================================ +CAutoHideDockContainer* CDockManager::addAutoHideDockWidget(SideBarLocation area, CDockWidget* Dockwidget) +{ + return addAutoHideDockWidgetToContainer(area, Dockwidget, this); +} + +//============================================================================ +CAutoHideDockContainer* CDockManager::addAutoHideDockWidgetToContainer(SideBarLocation area, CDockWidget* Dockwidget, + CDockContainerWidget* DockContainerWidget) +{ + d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); + auto container = DockContainerWidget->createAndSetupAutoHideContainer(area, Dockwidget); + container->collapseView(true); + + Q_EMIT dockWidgetAdded(Dockwidget); + return container; +} + + +//============================================================================ +CDockAreaWidget* CDockManager::addDockWidgetTab(DockWidgetArea area, + CDockWidget* Dockwidget) +{ + CDockAreaWidget* AreaWidget = lastAddedDockAreaWidget(area); + if (AreaWidget) + { + return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, AreaWidget); + } + else + { + return addDockWidget(area, Dockwidget, nullptr); + } +} + + +//============================================================================ +CDockAreaWidget* CDockManager::addDockWidgetTabToArea(CDockWidget* Dockwidget, + CDockAreaWidget* DockAreaWidget, int Index) +{ + return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, DockAreaWidget, Index); +} + + +//============================================================================ +CDockWidget* CDockManager::findDockWidget(const QString& ObjectName) const +{ + return d->DockWidgetsMap.value(ObjectName, nullptr); +} + +//============================================================================ +void CDockManager::removeDockWidget(CDockWidget* Dockwidget) +{ + Q_EMIT dockWidgetAboutToBeRemoved(Dockwidget); + d->DockWidgetsMap.remove(Dockwidget->objectName()); + CDockContainerWidget::removeDockWidget(Dockwidget); + Dockwidget->setDockManager(nullptr); + Q_EMIT dockWidgetRemoved(Dockwidget); +} + +//============================================================================ +QMap CDockManager::dockWidgetsMap() const +{ + return d->DockWidgetsMap; +} + + +//============================================================================ +void CDockManager::addPerspective(const QString& UniquePrespectiveName) +{ + d->Perspectives.insert(UniquePrespectiveName, saveState()); + Q_EMIT perspectiveListChanged(); +} + + +//============================================================================ +void CDockManager::removePerspective(const QString& Name) +{ + removePerspectives({Name}); +} + + +//============================================================================ +void CDockManager::removePerspectives(const QStringList& Names) +{ + int Count = 0; + for (const auto& Name : Names) + { + Count += d->Perspectives.remove(Name); + } + + if (Count) + { + Q_EMIT perspectivesRemoved(); + Q_EMIT perspectiveListChanged(); + } +} + + +//============================================================================ +QStringList CDockManager::perspectiveNames() const +{ + return d->Perspectives.keys(); +} + + +//============================================================================ +void CDockManager::openPerspective(const QString& PerspectiveName) +{ + const auto Iterator = d->Perspectives.find(PerspectiveName); + if (d->Perspectives.end() == Iterator) + { + return; + } + + Q_EMIT openingPerspective(PerspectiveName); + restoreState(Iterator.value()); + Q_EMIT perspectiveOpened(PerspectiveName); +} + + +//============================================================================ +void CDockManager::savePerspectives(QSettings& Settings) const +{ + Settings.beginWriteArray("Perspectives", d->Perspectives.size()); + int i = 0; + for (auto it = d->Perspectives.constBegin(); it != d->Perspectives.constEnd(); ++it) + { + Settings.setArrayIndex(i); + Settings.setValue("Name", it.key()); + Settings.setValue("State", it.value()); + ++i; + } + Settings.endArray(); +} + + +//============================================================================ +void CDockManager::loadPerspectives(QSettings& Settings) +{ + d->Perspectives.clear(); + int Size = Settings.beginReadArray("Perspectives"); + if (!Size) + { + Settings.endArray(); + return; + } + + for (int i = 0; i < Size; ++i) + { + Settings.setArrayIndex(i); + QString Name = Settings.value("Name").toString(); + QByteArray Data = Settings.value("State").toByteArray(); + if (Name.isEmpty() || Data.isEmpty()) + { + continue; + } + + d->Perspectives.insert(Name, Data); + } + + Settings.endArray(); + Q_EMIT perspectiveListChanged(); + Q_EMIT perspectiveListLoaded(); +} + + +//============================================================================ +CDockWidget* CDockManager::centralWidget() const +{ + return d->CentralWidget; +} + + +//============================================================================ +CDockAreaWidget* CDockManager::setCentralWidget(CDockWidget* widget) +{ + if (!widget) + { + d->CentralWidget = nullptr; + return nullptr; + } + + // Setting a new central widget is now allowed if there is already a central + // widget or if there are already other dock widgets + if (d->CentralWidget) + { + qWarning("Setting a central widget not possible because there is already a central widget."); + return nullptr; + } + + // Setting a central widget is now allowed if there are already other + // dock widgets. + if (!d->DockWidgetsMap.isEmpty()) + { + qWarning("Setting a central widget not possible - the central widget need to be the first " + "dock widget that is added to the dock manager."); + return nullptr; + } + + + widget->setFeature(CDockWidget::DockWidgetClosable, false); + widget->setFeature(CDockWidget::DockWidgetMovable, false); + widget->setFeature(CDockWidget::DockWidgetFloatable, false); + widget->setFeature(CDockWidget::DockWidgetPinnable, false); + d->CentralWidget = widget; + CDockAreaWidget* CentralArea = addDockWidget(CenterDockWidgetArea, widget); + CentralArea->setDockAreaFlag(CDockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true); + return CentralArea; +} + +//============================================================================ +QAction* CDockManager::addToggleViewActionToMenu(QAction* ToggleViewAction, + const QString& Group, const QIcon& GroupIcon) +{ + bool AlphabeticallySorted = (MenuAlphabeticallySorted == d->MenuInsertionOrder); + if (!Group.isEmpty()) + { + QMenu* GroupMenu = d->ViewMenuGroups.value(Group, nullptr); + if (!GroupMenu) + { + GroupMenu = new QMenu(Group, this); + GroupMenu->setIcon(GroupIcon); + d->addActionToMenu(GroupMenu->menuAction(), d->ViewMenu, AlphabeticallySorted); + d->ViewMenuGroups.insert(Group, GroupMenu); + } + else if (GroupMenu->icon().isNull() && !GroupIcon.isNull()) + { + GroupMenu->setIcon(GroupIcon); + } + + d->addActionToMenu(ToggleViewAction, GroupMenu, AlphabeticallySorted); + return GroupMenu->menuAction(); + } + else + { + d->addActionToMenu(ToggleViewAction, d->ViewMenu, AlphabeticallySorted); + return ToggleViewAction; + } +} + + +//============================================================================ +QMenu* CDockManager::viewMenu() const +{ + return d->ViewMenu; +} + + +//============================================================================ +void CDockManager::setViewMenuInsertionOrder(eViewMenuInsertionOrder Order) +{ + d->MenuInsertionOrder = Order; +} + + +//=========================================================================== +bool CDockManager::isRestoringState() const +{ + return d->RestoringState; +} + + +//=========================================================================== +int CDockManager::startDragDistance() +{ + return QApplication::startDragDistance() * 1.5; +} + + +//=========================================================================== +CDockManager::ConfigFlags CDockManager::configFlags() +{ + return StaticConfigFlags; +} + +CDockManager::AutoHideFlags CDockManager::autoHideConfigFlags() +{ + return StaticAutoHideConfigFlags; +} + + +//=========================================================================== +void CDockManager::setConfigFlags(const ConfigFlags Flags) +{ + StaticConfigFlags = Flags; +} + + +//=========================================================================== +void CDockManager::setAutoHideConfigFlags(const AutoHideFlags Flags) +{ + StaticAutoHideConfigFlags = Flags; +} + + +//=========================================================================== +void CDockManager::setConfigFlag(eConfigFlag Flag, bool On) +{ + internal::setFlag(StaticConfigFlags, Flag, On); +} + + +//=========================================================================== +void CDockManager::setAutoHideConfigFlag(eAutoHideFlag Flag, bool On) +{ + internal::setFlag(StaticAutoHideConfigFlags, Flag, On); +} + +//=========================================================================== +bool CDockManager::testConfigFlag(eConfigFlag Flag) +{ + return configFlags().testFlag(Flag); +} + + +//=========================================================================== +bool CDockManager::testAutoHideConfigFlag(eAutoHideFlag Flag) +{ + return autoHideConfigFlags().testFlag(Flag); +} + + +//=========================================================================== +CIconProvider& CDockManager::iconProvider() +{ + static CIconProvider Instance; + return Instance; +} + + +//=========================================================================== +void CDockManager::notifyWidgetOrAreaRelocation(QWidget* DroppedWidget) +{ + if (d->FocusController) + { + d->FocusController->notifyWidgetOrAreaRelocation(DroppedWidget); + } +} + + +//=========================================================================== +void CDockManager::notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget) +{ + if (d->FocusController) + { + d->FocusController->notifyFloatingWidgetDrop(FloatingWidget); + } +} + + +//=========================================================================== +void CDockManager::setDockWidgetFocused(CDockWidget* DockWidget) +{ + if (d->FocusController) + { + d->FocusController->setDockWidgetFocused(DockWidget); + } +} + +//=========================================================================== +void CDockManager::hideManagerAndFloatingWidgets() +{ + hide(); + + d->HiddenFloatingWidgets.clear(); + // Hide updates of floating widgets from user + for (auto FloatingWidget : d->FloatingWidgets) + { + if ( FloatingWidget->isVisible() ) + { + QList 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(); + + // hiding 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 +{ + if (!d->FocusController) + { + return nullptr; + } + else + { + return d->FocusController->focusedDockWidget(); + } +} + +//=========================================================================== +QList CDockManager::splitterSizes(CDockAreaWidget *ContainedArea) const +{ + if (ContainedArea) + { + auto Splitter = ContainedArea->parentSplitter(); + if (Splitter) + { + return Splitter->sizes(); + } + } + return QList(); +} + +//=========================================================================== +void CDockManager::setSplitterSizes(CDockAreaWidget *ContainedArea, const QList& sizes) +{ + if (!ContainedArea) + { + return; + } + + auto Splitter = ContainedArea->parentSplitter(); + if (Splitter && Splitter->count() == sizes.count()) + { + Splitter->setSizes(sizes); + } +} + + +//=========================================================================== +CDockFocusController* CDockManager::dockFocusController() const +{ + return d->FocusController; +} + +//=========================================================================== +void CDockManager::setFloatingContainersTitle(const QString& Title) +{ + FloatingContainersTitle = Title; +} + + +//=========================================================================== +QString CDockManager::floatingContainersTitle() +{ + if (FloatingContainersTitle.isEmpty()) + return qApp->applicationDisplayName(); + + return FloatingContainersTitle; +} + + +//=========================================================================== +void CDockManager::setDockWidgetToolBarStyle(Qt::ToolButtonStyle Style, CDockWidget::eState State) +{ + if (CDockWidget::StateFloating == State) + { + d->ToolBarStyleFloating = Style; + } + else + { + d->ToolBarStyleDocked = Style; + } +} + + +//=========================================================================== +Qt::ToolButtonStyle CDockManager::dockWidgetToolBarStyle(CDockWidget::eState State) const +{ + if (CDockWidget::StateFloating == State) + { + return d->ToolBarStyleFloating; + } + else + { + return d->ToolBarStyleDocked; + } +} + + +//=========================================================================== +void CDockManager::setDockWidgetToolBarIconSize(const QSize& IconSize, CDockWidget::eState State) +{ + if (CDockWidget::StateFloating == State) + { + d->ToolBarIconSizeFloating = IconSize; + } + else + { + d->ToolBarIconSizeDocked = IconSize; + } +} + + +//=========================================================================== +QSize CDockManager::dockWidgetToolBarIconSize(CDockWidget::eState State) const +{ + if (CDockWidget::StateFloating == State) + { + return d->ToolBarIconSizeFloating; + } + else + { + return d->ToolBarIconSizeDocked; + } +} + + +//=========================================================================== +void CDockManager::lockDockWidgetFeaturesGlobally(CDockWidget::DockWidgetFeatures Value) +{ + // Limit the features to CDockWidget::GloballyLockableFeatures + Value &= CDockWidget::GloballyLockableFeatures; + if (d->LockedDockWidgetFeatures == Value) + { + return; + } + + d->LockedDockWidgetFeatures = Value; + // Call the notifyFeaturesChanged() function for all dock widgets to update + // the state of the close and detach buttons + for (auto DockWidget : d->DockWidgetsMap) + { + DockWidget->notifyFeaturesChanged(); + } +} + + +//=========================================================================== +CDockWidget::DockWidgetFeatures CDockManager::globallyLockedDockWidgetFeatures() const +{ + return d->LockedDockWidgetFeatures; +} + + +//=========================================================================== +void CDockManager::setConfigParam(CDockManager::eConfigParam Param, QVariant Value) +{ + StaticConfigParams[Param] = Value; +} + + +//=========================================================================== +QVariant CDockManager::configParam(eConfigParam Param, QVariant Default) +{ + return StaticConfigParams[Param].isValid() ? StaticConfigParams[Param] : Default; +} + + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockManager.cpp diff --git a/src/DockManager.h b/src/DockManager.h new file mode 100644 index 000000000..9edb8e8b1 --- /dev/null +++ b/src/DockManager.h @@ -0,0 +1,828 @@ +#ifndef DockManagerH +#define DockManagerH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockManager.h +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Declaration of CDockManager class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" +#include "DockContainerWidget.h" +#include "DockWidget.h" +#include "FloatingDockContainer.h" + + +QT_FORWARD_DECLARE_CLASS(QSettings) +QT_FORWARD_DECLARE_CLASS(QMenu) + +namespace ads +{ +struct DockManagerPrivate; +class CFloatingDockContainer; +struct FloatingDockContainerPrivate; +class CDockContainerWidget; +class DockContainerWidgetPrivate; +class CDockOverlay; +class CDockAreaTabBar; +class CDockWidgetTab; +struct DockWidgetTabPrivate; +struct DockAreaWidgetPrivate; +class CIconProvider; +class CDockComponentsFactory; +class CDockFocusController; +class CAutoHideSideBar; +class CAutoHideTab; +struct AutoHideTabPrivate; + +/** + * The central dock manager that maintains the complete docking system. + * With the configuration flags you can globally control the functionality + * of the docking system. The dock manager uses an internal stylesheet to + * style its components like splitters, tabs and buttons. If you want to + * disable this stylesheet because your application uses its own, + * just call the function for settings the stylesheet with an empty + * string. + * \code + * DockManager->setStyleSheet(""); + * \endcode + **/ +class ADS_EXPORT CDockManager : public CDockContainerWidget +{ + Q_OBJECT +private: + DockManagerPrivate* d; ///< private data (pimpl) + friend struct DockManagerPrivate; + friend class CFloatingDockContainer; + friend struct FloatingDockContainerPrivate; + friend class CDockContainerWidget; + friend class DockContainerWidgetPrivate; + friend class CDockAreaTabBar; + friend class CDockWidgetTab; + friend struct DockAreaWidgetPrivate; + friend struct DockWidgetTabPrivate; + friend class CFloatingDragPreview; + friend struct FloatingDragPreviewPrivate; + friend class CDockAreaTitleBar; + friend class CAutoHideDockContainer; + friend CAutoHideSideBar; + friend CAutoHideTab; + friend AutoHideTabPrivate; + +public Q_SLOTS: + /** + * Ends the isRestoringFromMinimizedState + */ + void endLeavingMinimizedState(); + + +protected: + /** + * Registers the given floating widget in the internal list of + * floating widgets + */ + void registerFloatingWidget(CFloatingDockContainer* FloatingWidget); + + /** + * Remove the given floating widget from the list of registered floating + * widgets + */ + void removeFloatingWidget(CFloatingDockContainer* FloatingWidget); + + /** + * Registers the given dock container widget + */ + void registerDockContainer(CDockContainerWidget* DockContainer); + + /** + * Remove dock container from the internal list of registered dock + * containers + */ + void removeDockContainer(CDockContainerWidget* DockContainer); + + /** + * Overlay for containers + */ + CDockOverlay* containerOverlay() const; + + /** + * Overlay for dock areas + */ + CDockOverlay* dockAreaOverlay() const; + + + /** + * A container needs to call this function if a widget has been dropped + * into it + */ + void notifyWidgetOrAreaRelocation(QWidget* RelocatedWidget); + + /** + * This function is called, if a floating widget has been dropped into + * an new position. + * When this function is called, all dock widgets of the FloatingWidget + * are already inserted into its new position + */ + void notifyFloatingWidgetDrop(CFloatingDockContainer* FloatingWidget); + + /** + * Show the floating widgets that has been created floating + */ + virtual void showEvent(QShowEvent *event) override; + + /** + * Access 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; + + enum eViewMenuInsertionOrder + { + MenuSortedByInsertion, + MenuAlphabeticallySorted + }; + + /** + * These global configuration flags configure some global dock manager + * settings. + * Set the dock manager flags, before you create the dock manager instance. + */ + enum eConfigFlag + { + ActiveTabHasCloseButton = 0x0001, //!< If this flag is set, the active tab in a tab area has a close button + DockAreaHasCloseButton = 0x0002, //!< If the flag is set each dock area has a close button + DockAreaCloseButtonClosesTab = 0x0004,//!< If the flag is set, the dock area close button closes the active tab, if not set, it closes the complete dock area + OpaqueSplitterResize = 0x0008, //!< See QSplitter::setOpaqueResize() documentation + XmlAutoFormattingEnabled = 0x0010,//!< If enabled, the XML writer automatically adds line-breaks and indentation to empty sections between elements (ignorable whitespace). + XmlCompressionEnabled = 0x0020,//!< If enabled, the XML output will be compressed and is not human readable anymore + TabCloseButtonIsToolButton = 0x0040,//! If enabled the tab close buttons will be QToolButtons instead of QPushButtons - disabled by default + AllTabsHaveCloseButton = 0x0080, //!< if this flag is set, then all tabs that are closable show a close button + RetainTabSizeWhenCloseButtonHidden = 0x0100, //!< if this flag is set, the space for the close button is reserved even if the close button is not visible + DragPreviewIsDynamic = 0x0400,///< If opaque undocking is disabled, this flag defines the behavior of the drag preview window, if this flag is enabled, the preview will be adjusted dynamically to the drop area + DragPreviewShowsContentPixmap = 0x0800,///< If opaque undocking is disabled, the created drag preview window shows a copy of the content of the dock widget / dock are that is dragged + DragPreviewHasWindowFrame = 0x1000,///< If opaque undocking is disabled, then this flag configures if the drag preview is frameless or looks like a real window + AlwaysShowTabs = 0x2000,///< If this option is enabled, the tab of a dock widget is always displayed - even if it is the only visible dock widget in a floating widget. + DockAreaHasUndockButton = 0x4000, //!< If the flag is set each dock area has an undock button + DockAreaHasTabsMenuButton = 0x8000, //!< If the flag is set each dock area has a tabs menu button + DockAreaHideDisabledButtons = 0x10000, //!< If the flag is set disabled dock area buttons will not appear on the toolbar at all (enabling them will bring them back) + DockAreaDynamicTabsMenuButtonVisibility = 0x20000, //!< If the flag is set, the tabs menu button will be shown only when it is required - that means, if the tabs are elided. If the tabs are not elided, it is hidden + FloatingContainerHasWidgetTitle = 0x40000, //!< If set, the Floating Widget window title reflects the title of the current dock widget otherwise it displays the title set with `CDockManager::setFloatingContainersTitle` or application name as window title + FloatingContainerHasWidgetIcon = 0x80000, //!< If set, the Floating Widget icon reflects the icon of the current dock widget otherwise it displays application icon + HideSingleCentralWidgetTitleBar = 0x100000, //!< If there is only one single visible dock widget in the main dock container (the dock manager) and if this flag is set, then the titlebar of this dock widget will be hidden + //!< this only makes sense for non draggable and non floatable widgets and enables the creation of some kind of "central" widget + + FocusHighlighting = 0x200000, //!< enables styling of focused dock widget tabs or floating widget titlebar + EqualSplitOnInsertion = 0x400000, ///!< if enabled, the space is equally distributed to all widgets in a splitter + + FloatingContainerForceNativeTitleBar = 0x800000, //!< Linux only ! Forces all FloatingContainer to use the native title bar. This might break docking for FloatinContainer on some Window Managers (like Kwin/KDE). + //!< If neither this nor FloatingContainerForceCustomTitleBar 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". + 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 + DisableTabTextEliding = 0x4000000, //! Set this flag to disable eliding of tab texts in dock area tabs + ShowTabTextOnlyForActiveTab =0x8000000, //! Set this flag to show label texts in dock area tabs only for active tabs + + DefaultDockAreaButtons = DockAreaHasCloseButton + | DockAreaHasUndockButton + | DockAreaHasTabsMenuButton, ///< default configuration of dock area title bar buttons + + DefaultBaseConfig = DefaultDockAreaButtons + | ActiveTabHasCloseButton + | XmlCompressionEnabled + | FloatingContainerHasWidgetTitle, ///< default base configuration settings + + DefaultOpaqueConfig = DefaultBaseConfig + | OpaqueSplitterResize + | DragPreviewShowsContentPixmap, ///< the default configuration for non opaque operations + + DefaultNonOpaqueConfig = DefaultBaseConfig + | DragPreviewShowsContentPixmap, ///< the default configuration for non opaque operations + + NonOpaqueWithWindowFrame = DefaultNonOpaqueConfig + | DragPreviewHasWindowFrame ///< the default configuration for non opaque operations that show a real window with frame + }; + Q_DECLARE_FLAGS(ConfigFlags, eConfigFlag) + + + /** + * These global configuration flags configure some dock manager auto hide + * settings + * Set the dock manager flags, before you create the dock manager instance. + */ + enum eAutoHideFlag + { + AutoHideFeatureEnabled = 0x01, //!< enables / disables auto hide feature + DockAreaHasAutoHideButton = 0x02, //!< If the flag is set each dock area has a auto hide menu button + AutoHideButtonTogglesArea = 0x04, //!< If the flag is set, the auto hide button enables auto hiding for all dock widgets in an area, if disabled, only the current dock widget will be toggled + AutoHideButtonCheckable = 0x08, //!< If the flag is set, the auto hide button will be checked and unchecked depending on the auto hide state. Mainly for styling purposes. + AutoHideSideBarsIconOnly = 0x10,///< show only icons in auto hide side tab - if a tab has no icon, then the text will be shown + AutoHideShowOnMouseOver = 0x20, ///< show the auto hide window on mouse over tab and hide it if mouse leaves auto hide container + AutoHideCloseButtonCollapsesDock = 0x40, ///< Close button of an auto hide container collapses the dock instead of hiding it completely + AutoHideHasCloseButton = 0x80, //< If the flag is set an auto hide title bar has a close button + AutoHideHasMinimizeButton = 0x100, ///< if this flag is set, the auto hide title bar has a minimize button to collapse the dock widget + AutoHideOpenOnDragHover = 0x200, ///< if this flag is set, dragging hover the tab bar will open the dock + + DefaultAutoHideConfig = AutoHideFeatureEnabled + | DockAreaHasAutoHideButton + | AutoHideHasMinimizeButton + + }; + Q_DECLARE_FLAGS(AutoHideFlags, eAutoHideFlag) + + /** + * Global configuration parameters that you can set via setConfigParam() + */ + enum eConfigParam + { + AutoHideOpenOnDragHoverDelay_ms, ///< Delay in ms before the dock opens on drag hover if AutoHideOpenOnDragHover flag is set + ConfigParamCount // just a delimiter to count number of config params + }; + + + /** + * Default Constructor. + * If the given parent is a QMainWindow, the dock manager sets itself as the + * central widget. + * Before you create any dock widgets, you should properly setup the + * configuration flags via setConfigFlags(). + */ + CDockManager(QWidget* parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~CDockManager() override; + + /** + * This function returns the global configuration flags + */ + static ConfigFlags configFlags(); + + /** + * This function returns the auto hide configuration flags + */ + static AutoHideFlags autoHideConfigFlags(); + + /** + * Sets the global configuration flags for the whole docking system. + * Call this function before you create the dock manager and before + * your create the first dock widget. + */ + static void setConfigFlags(const ConfigFlags Flags); + + /** + * Sets the global configuration flags for the whole docking system. + * Call this function before you create the dock manager and before + * your create the first dock widget. + */ + static void setAutoHideConfigFlags(const AutoHideFlags Flags); + + /** + * Set a certain config flag. + * \see setConfigFlags() + */ + static void setConfigFlag(eConfigFlag Flag, bool On = true); + + /** + * Set a certain overlay config flag. + * \see setConfigFlags() + */ + static void setAutoHideConfigFlag(eAutoHideFlag Flag, bool On = true); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(eConfigFlag Flag); + + /** + * Returns true if the given overlay config flag is set + */ + static bool testAutoHideConfigFlag(eAutoHideFlag Flag); + + /** + * Sets the value for the given config parameter + */ + static void setConfigParam(eConfigParam Param, QVariant Value); + + /** + * Returns the value for the given config parameter or the default value + * if the parameter is not set. + */ + static QVariant configParam(eConfigParam Param, QVariant Default); + + /** + * Returns the global icon provider. + * The icon provider enables the use of custom icons in case using + * styleheets for icons is not an option. + */ + static CIconProvider& iconProvider(); + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. If you would like to add a dock widget + * tabified, then you need to add it to an existing dock area object + * into the CenterDockWidgetArea. The following code shows this: + * \code + * DockManager->addDockWidget(ads::CenterDockWidgetArea, NewDockWidget, + * ExisitingDockArea); + * \endcode + * \return Returns the dock area widget that contains the new DockWidget + */ + CDockAreaWidget* addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, + CDockAreaWidget* DockAreaWidget = nullptr, int Index = -1); + + /** + * 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); + + /** + * Adds an Auto-Hide widget to the dock manager container pinned to + * the given side bar location. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + CAutoHideDockContainer* addAutoHideDockWidget(SideBarLocation Location, CDockWidget* Dockwidget); + + /** + * Adds an Auto-Hide widget to the given DockContainerWidget pinned to + * the given side bar location in this container. + * \return Returns the CAutoHideDockContainer that contains the new DockWidget + */ + CAutoHideDockContainer* addAutoHideDockWidgetToContainer(SideBarLocation Location, + CDockWidget* Dockwidget, CDockContainerWidget* DockContainerWidget); + + /** + * This function will add the given Dockwidget to the given dock area as + * a new tab. + * If no dock area widget exists for the given area identifier, a new + * dock area widget is created. + */ + CDockAreaWidget* addDockWidgetTab(DockWidgetArea area, + CDockWidget* Dockwidget); + + /** + * This function will add the given Dockwidget to the given DockAreaWidget + * as a new tab. + * If index is out of range, the tab is simply appended. Otherwise it is + * inserted at the specified position. + */ + CDockAreaWidget* addDockWidgetTabToArea(CDockWidget* Dockwidget, + CDockAreaWidget* DockAreaWidget, int Index = -1); + + /** + * Adds the given DockWidget floating and returns the created + * CFloatingDockContainer instance. + */ + CFloatingDockContainer* addDockWidgetFloating(CDockWidget* Dockwidget); + + /** + * Searches for a registered doc widget with the given ObjectName + * \return Return the found dock widget or nullptr if a dock widget with the + * given name is not registered + */ + CDockWidget* findDockWidget(const QString& ObjectName) const; + + /** + * Remove the given Dock from the dock manager + */ + void removeDockWidget(CDockWidget* Dockwidget); + + /** + * This function returns a readable reference to the internal dock + * widgets map so that it is possible to iterate over all dock widgets + */ + QMap dockWidgetsMap() const; + + /** + * Returns the list of all active and visible dock containers + * Dock containers are the main dock manager and all floating widgets + */ + const QList dockContainers() const; + + /** + * Returns the list of all floating widgets + */ + const QList floatingWidgets() const; + + /** + * This function always return 0 because the main window is always behind + * any floating widget + */ + unsigned int zOrderIndex() const override; + + /** + * Saves the current state of the dockmanger and all its dock widgets + * into the returned QByteArray. + * The XmlMode enables / disables the auto formatting for the XmlStreamWriter. + * If auto formatting is enabled, the output is intended and line wrapped. + * The XmlMode XmlAutoFormattingDisabled is better if you would like to have + * a more compact XML output - i.e. for storage in ini files. + * The version number is stored as part of the data. + * To restore the saved state, pass the return value and version number + * to restoreState(). + * \see restoreState() + */ + QByteArray saveState(int version = 0) const; + + /** + * Restores the state of this dockmanagers dockwidgets. + * The version number is compared with that stored in state. If they do + * not match, the dockmanager's state is left unchanged, and this function + * returns false; otherwise, the state is restored, and this function + * returns true. + * \see saveState() + */ + bool restoreState(const QByteArray &state, int version = 0); + + /** + * Saves the current perspective to the internal list of perspectives. + * A perspective is the current state of the dock manager assigned + * with a certain name. This makes it possible for the user, + * to switch between different perspectives quickly. + * If a perspective with the given name already exists, then + * it will be overwritten with the new state. + */ + void addPerspective(const QString& UniquePrespectiveName); + + /** + * Removes the perspective with the given name from the list of perspectives + */ + void removePerspective(const QString& Name); + + /** + * Removes the given perspectives from the dock manager + */ + void removePerspectives(const QStringList& Names); + + /** + * Returns the names of all available perspectives + */ + QStringList perspectiveNames() const; + + /** + * Saves the perspectives to the given settings file. + */ + void savePerspectives(QSettings& Settings) const; + + /** + * Loads the perspectives from the given settings file + */ + void loadPerspectives(QSettings& Settings); + + /** + * This function returns managers central widget or nullptr if no central widget is set. + */ + CDockWidget* centralWidget() const; + + /** + * Adds dockwidget widget into the central area and marks it as central widget. + * If central widget is set, it will be the only dock widget + * that will resize with the dock container. A central widget if not + * movable, floatable or closable and the titlebar of the central + * dock area is not visible. + * If the given widget could be set as central widget, the function returns + * the created dock area. If the widget could not be set, because there + * is already a central widget, this function returns a nullptr. + * To clear the central widget, pass a nullptr to the function. + * \note Setting a central widget is only possible if no other dock widgets + * have been registered before. That means, this function should be the + * first function that you call before you add other dock widgets. + * \retval != 0 The dock area that contains the central widget + * \retval nullptr Indicates that the given widget can not be set as central + * widget because there is already a central widget. + */ + CDockAreaWidget* setCentralWidget(CDockWidget* widget); + + /** + * Adds a toggle view action to the the internal view menu. + * You can either manage the insertion of the toggle view actions in your + * application or you can add the actions to the internal view menu and + * then simply insert the menu object into your. + * \param[in] ToggleViewAction The action to insert. If no group is provided + * the action is directly inserted into the menu. If a group + * is provided, the action is inserted into the group and the + * group is inserted into the menu if it is not existing yet. + * \param[in] Group This is the text used for the group menu item + * \param[in] GroupIcon The icon used for grouping the workbenches in the + * view menu. I.e. if there is a workbench for each device + * like for spectrometer devices, it is good to group all these + * workbenches under a menu item + * \return If Group is not empty, this function returns the GroupAction + * for this group. If the group is empty, the function returns + * the given ToggleViewAction. + */ + QAction* addToggleViewActionToMenu(QAction* ToggleViewAction, + const QString& Group = QString(), const QIcon& GroupIcon = QIcon()); + + /** + * This function returns the internal view menu. + * To fill the view menu, you can use the addToggleViewActionToMenu() + * function. + */ + QMenu* viewMenu() const; + + /** + * Define the insertion order for toggle view menu items. + * The order defines how the actions are added to the view menu. + * The default insertion order is MenuAlphabeticallySorted to make it + * easier for users to find the menu entry for a certain dock widget. + * You need to call this function before you insert the first menu item + * into the view menu. + */ + void setViewMenuInsertionOrder(eViewMenuInsertionOrder Order); + + /** + * This function returns true between the restoringState() and + * stateRestored() signals. + */ + bool isRestoringState() const; + + /** + * This function returns true, if the DockManager window is restoring from + * minimized state. + * The DockManager is in this state starting from the QWindowStateChangeEvent + * that signals the state change from minimized to normal until + * endLeavingMinimizedState() function is called. + */ + bool isLeavingMinimizedState() const; + + /** + * The distance the user needs to move the mouse with the left button + * hold down before a dock widget start floating + */ + static int startDragDistance(); + + /** + * Helper function to set focus depending on the configuration of the + * FocusStyling flag + */ + template + static void setWidgetFocus(QWidgetPtr widget) + { + if (!CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + return; + } + + widget->setFocus(Qt::OtherFocusReason); + } + + bool eventFilter(QObject *obj, QEvent *e) override; + + /** + * Returns the dock widget that has focus style in the ui or a nullptr if + * not dock widget is painted focused. + * If the flag FocusHighlighting is disabled, this function always returns + * nullptr. + */ + CDockWidget* focusedDockWidget() const; + + /** + * Returns the sizes of the splitter that contains the dock area. + * + * If there is no splitter that contains the area, an empty list will be + * returned. + */ + QList splitterSizes(CDockAreaWidget *ContainedArea) const; + + /** + * Update the sizes of a splitter + * Programmatically updates the sizes of a given splitter by calling + * QSplitter::setSizes(). The splitter will be the splitter that + * contains the supplied dock area widget. If there is not splitter + * that contains the dock area, or the sizes supplied does not match + * the number of children of the splitter, this method will have no + * effect. + */ + void setSplitterSizes(CDockAreaWidget *ContainedArea, const QList& sizes); + + /** + * Set a custom title for all FloatingContainer that does not reflect + * the title of the current dock widget. + */ + static void setFloatingContainersTitle(const QString& Title); + + /** + * Returns the title used by all FloatingContainer that does not + * reflect the title of the current dock widget. + * + * If not title was set with setFloatingContainersTitle(), it returns + * QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle(); + + /** + * This function sets the tool button style for the given dock widget state. + * It is possible to switch the tool button style depending on the state. + * If a dock widget is floating, then here are more space and it is + * possible to select a style that requires more space like + * Qt::ToolButtonTextUnderIcon. For the docked state Qt::ToolButtonIconOnly + * might be better. + */ + void setDockWidgetToolBarStyle(Qt::ToolButtonStyle Style, CDockWidget::eState State); + + /** + * Returns the tool button style for the given docking state. + * \see setToolBarStyle() + */ + Qt::ToolButtonStyle dockWidgetToolBarStyle(CDockWidget::eState State) const; + + /** + * This function sets the tool button icon size for the given state. + * If a dock widget is floating, there is more space and increasing the + * icon size is possible. For docked widgets, small icon sizes, eg. 16 x 16 + * might be better. + */ + void setDockWidgetToolBarIconSize(const QSize& IconSize, CDockWidget::eState State); + + /** + * Returns the icon size for a given docking state. + * \see setToolBarIconSize() + */ + QSize dockWidgetToolBarIconSize(CDockWidget::eState State) const; + + /** + * Returns all dock widget features that are globally locked by the dock + * manager. + * Globally locked features are removed from the features of all dock + * widgets. + */ + CDockWidget::DockWidgetFeatures globallyLockedDockWidgetFeatures() const; + + /** + * Globally Lock features of all dock widgets to "freeze" the current + * workspace layout. + * For example, it is now possible to lock the workspace to avoid + * accidentally dragging a docked view. Locking wasn’t possible before. + * So, users had to manually dock it back to the desired place after + * each accidental undock. + * You can use a combination of the following feature flags: + * - CDockWidget::DockWidgetClosable + * - CDockWidget::DockWidgetMovable + * - CDockWidget::DockWidgetFloatable + * - CDockWidget::DockWidgetPinable + * + * To clear the locked features, you can use CDockWidget::NoDockWidgetFeatures + * The following code shows how to lock and unlock dock widget features + * globally. + * + * \code + * DockManager->lockDockWidgetFeaturesGlobally(); + * DockManager->lockDockWidgetFeaturesGlobally(CDockWidget::NoDockWidgetFeatures); + * \code + */ + void lockDockWidgetFeaturesGlobally(CDockWidget::DockWidgetFeatures Features = CDockWidget::GloballyLockableFeatures); + +public Q_SLOTS: + /** + * Opens the perspective with the given name. + */ + void openPerspective(const QString& PerspectiveName); + + /** + * Request a focus change to the given dock widget. + * This function only has an effect, if the flag CDockManager::FocusStyling + * is enabled + */ + 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. + * The list of perspectives changes if perspectives are added, removed + * or if the perspective list has been loaded + */ + void perspectiveListChanged(); + + /** + * This signal is emitted if the perspective list has been loaded + */ + void perspectiveListLoaded(); + + /** + * This signal is emitted if perspectives have been removed + */ + void perspectivesRemoved(); + + /** + * This signal is emitted, if the restore function is called, just before + * the dock manager starts restoring the state. + * If this function is called, nothing has changed yet + */ + void restoringState(); + + /** + * This signal is emitted if the state changed in restoreState. + * The signal is emitted if the restoreState() function is called or + * if the openPerspective() function is called + */ + void stateRestored(); + + /** + * This signal is emitted, if the dock manager starts opening a + * perspective. + * Opening a perspective may take more than a second if there are + * many complex widgets. The application may use this signal + * to show some progress indicator or to change the mouse cursor + * into a busy cursor. + */ + void openingPerspective(const QString& PerspectiveName); + + /** + * This signal is emitted if the dock manager finished opening a + * perspective. + */ + void perspectiveOpened(const QString& PerspectiveName); + + /** + * This signal is emitted, if a new floating widget has been created. + * An application can use this signal to e.g. subscribe to events of + * the newly created window. + */ + void floatingWidgetCreated(ads::CFloatingDockContainer* FloatingWidget); + + /** + * This signal is emitted, if a new DockArea has been created. + * An application can use this signal to set custom icons or custom + * tooltips for the DockArea buttons. + */ + void dockAreaCreated(ads::CDockAreaWidget* DockArea); + + /** + * This signal is emitted if a dock widget has been added to this + * dock manager instance. + */ + void dockWidgetAdded(ads::CDockWidget* DockWidget); + + /** + * This signal is emitted just before the given dock widget is removed + * from the dock manager + */ + void dockWidgetAboutToBeRemoved(ads::CDockWidget* DockWidget); + + /** + * This signal is emitted if a dock widget has been removed with the remove + * removeDockWidget() function. + * If this signal is emitted, the dock widget has been removed from the + * docking system but it is not deleted yet. + */ + void dockWidgetRemoved(ads::CDockWidget* DockWidget); + + /** + * This signal is emitted if the focused dock widget changed. + * Both old and now can be nullptr. + * The focused dock widget is the one that is highlighted in the GUI + */ + void focusedDockWidgetChanged(ads::CDockWidget* old, ads::CDockWidget* now); +}; // class DockManager +} // namespace ads + +Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockManager::ConfigFlags) +//----------------------------------------------------------------------------- +#endif // DockManagerH diff --git a/src/DockOverlay.cpp b/src/DockOverlay.cpp new file mode 100644 index 000000000..3ae43a2d7 --- /dev/null +++ b/src/DockOverlay.cpp @@ -0,0 +1,984 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockOverlay.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockAreaTitleBar.h" +#include "DockContainerWidget.h" +#include "AutoHideSideBar.h" +#include "DockManager.h" +#include "DockAreaTabBar.h" + +#include + +namespace ads +{ +static const int AutoHideAreaWidth = 32; +static const int AutoHideAreaMouseZone = 8; +static const int InvalidTabIndex = -2; + +/** + * Private data class of CDockOverlay + */ +struct DockOverlayPrivate +{ + CDockOverlay* _this; + DockWidgetAreas AllowedAreas = InvalidDockWidgetArea; + CDockOverlayCross* Cross; + QPointer TargetWidget; + DockWidgetArea LastLocation = InvalidDockWidgetArea; + bool DropPreviewEnabled = true; + CDockOverlay::eMode Mode = CDockOverlay::ModeDockAreaOverlay; + QRect DropAreaRect; + int TabIndex = InvalidTabIndex; + + /** + * Private data constructor + */ + DockOverlayPrivate(CDockOverlay* _public) : _this(_public) {} + + /** + * Returns the overlay width / height depending on the visibility + * of the sidebar + */ + int sideBarOverlaySize(SideBarLocation sideBarLocation); + + /** + * The area where the mouse is considered in the sidebar + */ + int sideBarMouseZone(SideBarLocation sideBarLocation); +}; + +/** + * Private data of CDockOverlayCross class + */ +struct DockOverlayCrossPrivate +{ + CDockOverlayCross* _this; + CDockOverlay::eMode Mode = CDockOverlay::ModeDockAreaOverlay; + CDockOverlay* DockOverlay; + QHash DropIndicatorWidgets; + QGridLayout* GridLayout; + QColor IconColors[5]; + bool UpdateRequired = false; + double LastDevicePixelRatio = 0.1; + + /** + * Private data constructor + */ + DockOverlayCrossPrivate(CDockOverlayCross* _public) : _this(_public) {} + + /** + * + * @param area + * @return + */ + QPoint areaGridPosition(const DockWidgetArea area); + + + /** + * Palette based default icon colors + */ + QColor defaultIconColor(CDockOverlayCross::eIconColor ColorIndex) + { + QPalette pal = _this->palette(); + switch (ColorIndex) + { + case CDockOverlayCross::FrameColor: return pal.color(QPalette::Active, QPalette::Highlight); + case CDockOverlayCross::WindowBackgroundColor: return pal.color(QPalette::Active, QPalette::Base); + case CDockOverlayCross::OverlayColor: + { + QColor Color = pal.color(QPalette::Active, QPalette::Highlight); + Color.setAlpha(64); + return Color; + } + break; + + case CDockOverlayCross::ArrowColor: return pal.color(QPalette::Active, QPalette::Base); + case CDockOverlayCross::ShadowColor: return QColor(0, 0, 0, 64); + default: + return QColor(); + } + + return QColor(); + } + + /** + * Stylehseet based icon colors + */ + QColor iconColor(CDockOverlayCross::eIconColor ColorIndex) + { + QColor Color = IconColors[ColorIndex]; + if (!Color.isValid()) + { + Color = defaultIconColor(ColorIndex); + IconColors[ColorIndex] = Color; + } + return Color; + } + + //============================================================================ + /** + * Helper function that returns the drop indicator width depending on the + * operating system + */ + qreal dropIndicatiorWidth(QLabel* l) const + { + #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + Q_UNUSED(l) + return 40; + #else + return static_cast(l->fontMetrics().height()) * 3.f; + #endif + } + + + //============================================================================ + QWidget* createDropIndicatorWidget(DockWidgetArea DockWidgetArea, + CDockOverlay::eMode Mode) + { + QLabel* l = new QLabel(); + l->setObjectName("DockWidgetAreaLabel"); + + qreal metric = dropIndicatiorWidth(l); + QSizeF size(metric, metric); + if (internal::isSideBarArea(DockWidgetArea)) + { + auto SideBarLocation = internal::toSideBarLocation(DockWidgetArea); + if (internal::isHorizontalSideBarLocation(SideBarLocation)) + { + size.setHeight(size.height() / 2); + } + else + { + size.setWidth(size.width() / 2); + } + } + + l->setPixmap(createHighDpiDropIndicatorPixmap(size, DockWidgetArea, Mode)); + l->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + l->setAttribute(Qt::WA_TranslucentBackground); + l->setProperty("dockWidgetArea", DockWidgetArea); + return l; + } + + //============================================================================ + void updateDropIndicatorIcon(QWidget* DropIndicatorWidget) + { + QLabel* l = qobject_cast(DropIndicatorWidget); + const qreal metric = dropIndicatiorWidth(l); + const QSizeF size(metric, metric); + + int Area = l->property("dockWidgetArea").toInt(); + l->setPixmap(createHighDpiDropIndicatorPixmap(size, (DockWidgetArea)Area, Mode)); + } + + //============================================================================ + QPixmap createHighDpiDropIndicatorPixmap(const QSizeF& size, DockWidgetArea DockWidgetArea, + CDockOverlay::eMode Mode) + { + QColor borderColor = iconColor(CDockOverlayCross::FrameColor); + QColor backgroundColor = iconColor(CDockOverlayCross::WindowBackgroundColor); + QColor overlayColor = iconColor(CDockOverlayCross::OverlayColor); + if (overlayColor.alpha() == 255) + { + overlayColor.setAlpha(64); + } + +#if QT_VERSION >= 0x050600 + double DevicePixelRatio = _this->window()->devicePixelRatioF(); +#else + double DevicePixelRatio = _this->window()->devicePixelRatio(); +#endif + QSizeF PixmapSize = size * DevicePixelRatio; + QPixmap pm(PixmapSize.toSize()); + pm.fill(QColor(0, 0, 0, 0)); + + QPainter p(&pm); + QPen pen = p.pen(); + QRectF ShadowRect(pm.rect()); + QRectF baseRect; + baseRect.setSize(ShadowRect.size() * 0.7); + baseRect.moveCenter(ShadowRect.center()); + + // Fill + QColor ShadowColor = iconColor(CDockOverlayCross::ShadowColor); + if (ShadowColor.alpha() == 255) + { + ShadowColor.setAlpha(64); + } + p.fillRect(ShadowRect, ShadowColor); + + // Drop area rect. + p.save(); + QRectF areaRect; + QLineF areaLine; + QRectF nonAreaRect; + switch (DockWidgetArea) + { + case TopDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * .5f); + nonAreaRect = QRectF(baseRect.x(), ShadowRect.height() * .5f, baseRect.width(), baseRect.height() * .5f); + areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); + break; + case RightDockWidgetArea: + areaRect = QRectF(ShadowRect.width() * .5f, baseRect.y(), baseRect.width() * .5f, baseRect.height()); + nonAreaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * .5f, baseRect.height()); + areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); + break; + case BottomDockWidgetArea: + areaRect = QRectF(baseRect.x(), ShadowRect.height() * .5f, baseRect.width(), baseRect.height() * .5f); + nonAreaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * .5f); + areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); + break; + case LeftDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * .5f, baseRect.height()); + nonAreaRect = QRectF(ShadowRect.width() * .5f, baseRect.y(), baseRect.width() * .5f, baseRect.height()); + areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); + break; + default: + break; + } + + QSizeF baseSize = baseRect.size(); + bool IsOuterContainerArea = (CDockOverlay::ModeContainerOverlay == Mode) + && (DockWidgetArea != CenterDockWidgetArea) + && !internal::isSideBarArea(DockWidgetArea); + + if (IsOuterContainerArea) + { + baseRect = areaRect; + } + + p.fillRect(baseRect, backgroundColor); + + if (areaRect.isValid()) + { + pen = p.pen(); + pen.setColor(borderColor); + p.setBrush(overlayColor); + p.setPen(Qt::NoPen); + p.drawRect(areaRect); + + pen = p.pen(); + pen.setWidth(1); + pen.setColor(borderColor); + pen.setStyle(Qt::DashLine); + p.setPen(pen); + p.drawLine(areaLine); + } + p.restore(); + + + p.save(); + // Draw outer border + pen = p.pen(); + pen.setColor(borderColor); + pen.setWidth(1); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRect(baseRect); + + // draw window title bar + p.setBrush(borderColor); + QRectF FrameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); + p.drawRect(FrameRect); + p.restore(); + + + // Draw arrow for outer container drop indicators + if (IsOuterContainerArea) + { + QRectF ArrowRect; + ArrowRect.setSize(baseSize); + ArrowRect.setWidth(ArrowRect.width() / 4.6); + ArrowRect.setHeight(ArrowRect.height() / 2); + ArrowRect.moveCenter(QPointF(0, 0)); + QPolygonF Arrow; + Arrow << ArrowRect.topLeft() + << QPointF( ArrowRect.right(), ArrowRect.center().y()) + << ArrowRect.bottomLeft(); + p.setPen(Qt::NoPen); + p.setBrush(iconColor(CDockOverlayCross::ArrowColor)); + p.setRenderHint(QPainter::Antialiasing, true); + p.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); + + switch (DockWidgetArea) + { + case TopDockWidgetArea: + p.rotate(-90); + break; + case RightDockWidgetArea: + break; + case BottomDockWidgetArea: + p.rotate(90); + break; + case LeftDockWidgetArea: + p.rotate(180); + break; + default: + break; + } + + p.drawPolygon(Arrow); + } + + pm.setDevicePixelRatio(DevicePixelRatio); + return pm; + } + +}; + + +//============================================================================ +int DockOverlayPrivate::sideBarOverlaySize(SideBarLocation sideBarLocation) +{ + auto Container = qobject_cast(TargetWidget.data()); + auto SideBar = Container->autoHideSideBar(sideBarLocation); + if (!SideBar || !SideBar->isVisibleTo(Container)) + { + return AutoHideAreaWidth; + } + else + { + return (SideBar->orientation() == Qt::Horizontal) ? SideBar->height() : SideBar->width(); + } +} + + +//============================================================================ +int DockOverlayPrivate::sideBarMouseZone(SideBarLocation sideBarLocation) +{ + auto Container = qobject_cast(TargetWidget.data()); + auto SideBar = Container->autoHideSideBar(sideBarLocation); + if (!SideBar || !SideBar->isVisibleTo(Container)) + { + return AutoHideAreaMouseZone; + } + else + { + return (SideBar->orientation() == Qt::Horizontal) ? SideBar->height() : SideBar->width(); + } +} + + +//============================================================================ +CDockOverlay::CDockOverlay(QWidget* parent, eMode Mode) : + QFrame(parent), + d(new DockOverlayPrivate(this)) +{ + d->Mode = Mode; + d->Cross = new CDockOverlayCross(this); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); +#else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); +#endif + setWindowOpacity(1); + setWindowTitle("DockOverlay"); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + + d->Cross->setVisible(false); + setVisible(false); +} + + +//============================================================================ +CDockOverlay::~CDockOverlay() +{ + delete d; +} + + +//============================================================================ +void CDockOverlay::setAllowedAreas(DockWidgetAreas areas) +{ + if (areas == d->AllowedAreas) + { + return; + } + d->AllowedAreas = areas; + d->Cross->reset(); +} + + +//============================================================================ +void CDockOverlay::setAllowedArea(DockWidgetArea area, bool Enable) +{ + auto AreasOld = d->AllowedAreas; + d->AllowedAreas.setFlag(area, Enable); + if (AreasOld != d->AllowedAreas) + { + d->Cross->reset(); + } +} + + +//============================================================================ +DockWidgetAreas CDockOverlay::allowedAreas() const +{ + return d->AllowedAreas; +} + + +//============================================================================ +DockWidgetArea CDockOverlay::dropAreaUnderCursor() const +{ + d->TabIndex = InvalidTabIndex; + if (!d->TargetWidget) + { + return InvalidDockWidgetArea; + } + + DockWidgetArea Result = d->Cross->cursorLocation(); + if (Result != InvalidDockWidgetArea) + { + return Result; + } + + auto CursorPos = QCursor::pos(); + auto DockArea = qobject_cast(d->TargetWidget.data()); + if (!DockArea && CDockManager::autoHideConfigFlags().testFlag(CDockManager::AutoHideFeatureEnabled)) + { + auto Rect = rect(); + const QPoint pos = mapFromGlobal(QCursor::pos()); + if ((pos.x() < d->sideBarMouseZone(SideBarLeft)) + && d->AllowedAreas.testFlag(LeftAutoHideArea)) + { + Result = LeftAutoHideArea; + } + else if (pos.x() > (Rect.width() - d->sideBarMouseZone(SideBarRight)) + && d->AllowedAreas.testFlag(RightAutoHideArea)) + { + Result = RightAutoHideArea; + } + else if (pos.y() < d->sideBarMouseZone(SideBarTop) + && d->AllowedAreas.testFlag(TopAutoHideArea)) + { + Result = TopAutoHideArea; + } + else if (pos.y() > (Rect.height() - d->sideBarMouseZone(SideBarBottom)) + && d->AllowedAreas.testFlag(BottomAutoHideArea)) + { + Result = BottomAutoHideArea; + } + + auto SideBarLocation = ads::internal::toSideBarLocation(Result); + if (SideBarLocation != SideBarNone) + { + auto Container = qobject_cast(d->TargetWidget.data()); + auto SideBar = Container->autoHideSideBar(SideBarLocation); + if (SideBar->isVisible()) + { + d->TabIndex = SideBar->tabInsertIndexAt(SideBar->mapFromGlobal(CursorPos)); + } + } + return Result; + } + else if (!DockArea) + { + return Result; + } + + if (DockArea->allowedAreas().testFlag(CenterDockWidgetArea) + && !DockArea->titleBar()->isHidden() + && DockArea->titleBarGeometry().contains(DockArea->mapFromGlobal(CursorPos))) + { + auto TabBar = DockArea->titleBar()->tabBar(); + d->TabIndex = TabBar->tabInsertIndexAt(TabBar->mapFromGlobal(CursorPos)); + return CenterDockWidgetArea; + } + + return Result; +} + + +//============================================================================ +int CDockOverlay::tabIndexUnderCursor() const +{ + return d->TabIndex; +} + + +//============================================================================ +DockWidgetArea CDockOverlay::visibleDropAreaUnderCursor() const +{ + if (isHidden() || !d->DropPreviewEnabled) + { + return InvalidDockWidgetArea; + } + else + { + return dropAreaUnderCursor(); + } +} + + +//============================================================================ +DockWidgetArea CDockOverlay::showOverlay(QWidget* target) +{ + if (d->TargetWidget == target) + { + // Hint: We could update geometry of overlay here. + DockWidgetArea da = dropAreaUnderCursor(); + if (da != d->LastLocation) + { + repaint(); + d->LastLocation = da; + } + return da; + } + + d->TargetWidget = target; + d->LastLocation = InvalidDockWidgetArea; + + // Move it over the target. + hide(); + resize(target->size()); + QPoint TopLeft = target->mapToGlobal(target->rect().topLeft()); + move(TopLeft); + show(); + d->Cross->updatePosition(); + d->Cross->updateOverlayIcons(); + return dropAreaUnderCursor(); +} + + +//============================================================================ +void CDockOverlay::hideOverlay() +{ + hide(); + d->TargetWidget.clear(); + d->LastLocation = InvalidDockWidgetArea; + d->DropAreaRect = QRect(); +} + + +//============================================================================ +void CDockOverlay::enableDropPreview(bool Enable) +{ + d->DropPreviewEnabled = Enable; + update(); +} + + +//============================================================================ +bool CDockOverlay::dropPreviewEnabled() const +{ + return d->DropPreviewEnabled; +} + + +//============================================================================ +void CDockOverlay::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + + // Draw rect based on location + if (!d->DropPreviewEnabled) + { + d->DropAreaRect = QRect(); + return; + } + + QRect r = rect(); + const DockWidgetArea da = dropAreaUnderCursor(); + double Factor = (CDockOverlay::ModeContainerOverlay == d->Mode) ? + 3 : 2; + + switch (da) + { + case TopDockWidgetArea: r.setHeight(r.height() / Factor); break; + case RightDockWidgetArea: r.setX(r.width() * (1 - 1 / Factor)); break; + case BottomDockWidgetArea: r.setY(r.height() * (1 - 1 / Factor)); break; + case LeftDockWidgetArea: r.setWidth(r.width() / Factor); break; + case CenterDockWidgetArea: r = rect();break; + case LeftAutoHideArea: r.setWidth(d->sideBarOverlaySize(SideBarLeft)); break; + case RightAutoHideArea: r.setX(r.width() - d->sideBarOverlaySize(SideBarRight)); break; + case TopAutoHideArea: r.setHeight(d->sideBarOverlaySize(SideBarTop)); break; + case BottomAutoHideArea: r.setY(r.height() - d->sideBarOverlaySize(SideBarBottom)); break; + default: return; + } + + QPainter painter(this); + QColor Color = palette().color(QPalette::Active, QPalette::Highlight); + QPen Pen = painter.pen(); + Pen.setColor(Color.darker(120)); + Pen.setStyle(Qt::SolidLine); + Pen.setWidth(1); + Pen.setCosmetic(true); + painter.setPen(Pen); + Color = Color.lighter(130); + Color.setAlpha(64); + painter.setBrush(Color); + painter.drawRect(r.adjusted(0, 0, -1, -1)); + d->DropAreaRect = r; +} + + +//============================================================================ +QRect CDockOverlay::dropOverlayRect() const +{ + return d->DropAreaRect; +} + + +//============================================================================ +void CDockOverlay::showEvent(QShowEvent* e) +{ + d->Cross->show(); + QFrame::showEvent(e); +} + + +//============================================================================ +void CDockOverlay::hideEvent(QHideEvent* e) +{ + d->Cross->hide(); + QFrame::hideEvent(e); +} + + +//============================================================================ +bool CDockOverlay::event(QEvent *e) +{ + bool Result = Super::event(e); + if (e->type() == QEvent::Polish) + { + d->Cross->setupOverlayCross(d->Mode); + } + return Result; +} + + +//============================================================================ +static int areaAlignment(const DockWidgetArea area) +{ + switch (area) + { + case TopDockWidgetArea: return (int) Qt::AlignHCenter | Qt::AlignBottom; + case RightDockWidgetArea: return (int) Qt::AlignLeft | Qt::AlignVCenter; + case BottomDockWidgetArea: return (int) Qt::AlignHCenter | Qt::AlignTop; + case LeftDockWidgetArea: return (int) Qt::AlignRight | Qt::AlignVCenter; + case CenterDockWidgetArea: return (int) Qt::AlignCenter; + default: return Qt::AlignCenter; + } +} + +//============================================================================ +// DockOverlayCrossPrivate +//============================================================================ +QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) +{ + if (CDockOverlay::ModeDockAreaOverlay == Mode) + { + switch (area) + { + case TopDockWidgetArea: return QPoint(1, 2); + case RightDockWidgetArea: return QPoint(2, 3); + case BottomDockWidgetArea: return QPoint(3, 2); + case LeftDockWidgetArea: return QPoint(2, 1); + case CenterDockWidgetArea: return QPoint(2, 2); + default: return QPoint(); + } + } + else + { + switch (area) + { + case TopDockWidgetArea: return QPoint(0, 2); + case RightDockWidgetArea: return QPoint(2, 4); + case BottomDockWidgetArea: return QPoint(4, 2); + case LeftDockWidgetArea: return QPoint(2, 0); + case CenterDockWidgetArea: return QPoint(2, 2); + default: return QPoint(); + } + } +} + + +//============================================================================ +CDockOverlayCross::CDockOverlayCross(CDockOverlay* overlay) : + QWidget(overlay->parentWidget()), + d(new DockOverlayCrossPrivate(this)) +{ + d->DockOverlay = overlay; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); +#else + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); +#endif + setWindowTitle("DockOverlayCross"); + setAttribute(Qt::WA_TranslucentBackground); + + d->GridLayout = new QGridLayout(); + d->GridLayout->setSpacing(0); + setLayout(d->GridLayout); +} + + +//============================================================================ +CDockOverlayCross::~CDockOverlayCross() +{ + delete d; +} + + +//============================================================================ +void CDockOverlayCross::setupOverlayCross(CDockOverlay::eMode Mode) +{ + d->Mode = Mode; + + QHash areaWidgets; + areaWidgets.insert(TopDockWidgetArea, d->createDropIndicatorWidget(TopDockWidgetArea, Mode)); + areaWidgets.insert(RightDockWidgetArea, d->createDropIndicatorWidget(RightDockWidgetArea, Mode)); + areaWidgets.insert(BottomDockWidgetArea, d->createDropIndicatorWidget(BottomDockWidgetArea, Mode)); + areaWidgets.insert(LeftDockWidgetArea, d->createDropIndicatorWidget(LeftDockWidgetArea, Mode)); + areaWidgets.insert(CenterDockWidgetArea, d->createDropIndicatorWidget(CenterDockWidgetArea, Mode)); + +#if QT_VERSION >= 0x050600 + d->LastDevicePixelRatio = devicePixelRatioF(); +#else + d->LastDevicePixelRatio = devicePixelRatio(); +#endif + setAreaWidgets(areaWidgets); + d->UpdateRequired = false; +} + + +//============================================================================ +void CDockOverlayCross::updateOverlayIcons() +{ + if (windowHandle()->devicePixelRatio() == d->LastDevicePixelRatio) + { + return; + } + + for (auto Widget : d->DropIndicatorWidgets) + { + d->updateDropIndicatorIcon(Widget); + } +#if QT_VERSION >= 0x050600 + d->LastDevicePixelRatio = devicePixelRatioF(); +#else + d->LastDevicePixelRatio = devicePixelRatio(); +#endif +} + + +//============================================================================ +void CDockOverlayCross::setIconColor(eIconColor ColorIndex, const QColor& Color) +{ + d->IconColors[ColorIndex] = Color; + d->UpdateRequired = true; +} + + +//============================================================================ +QColor CDockOverlayCross::iconColor(eIconColor ColorIndex) const +{ + return d->IconColors[ColorIndex]; +} + + +//============================================================================ +void CDockOverlayCross::setAreaWidgets(const QHash& widgets) +{ + // Delete old widgets. + QMutableHashIterator i(d->DropIndicatorWidgets); + while (i.hasNext()) + { + i.next(); + QWidget* widget = i.value(); + d->GridLayout->removeWidget(widget); + delete widget; + i.remove(); + } + + // Insert new widgets into grid. + d->DropIndicatorWidgets = widgets; + QHashIterator i2(d->DropIndicatorWidgets); + while (i2.hasNext()) + { + i2.next(); + const DockWidgetArea area = i2.key(); + QWidget* widget = i2.value(); + QPoint p = d->areaGridPosition(area); + d->GridLayout->addWidget(widget, p.x(), p.y(), (Qt::Alignment) areaAlignment(area)); + } + + if (CDockOverlay::ModeDockAreaOverlay == d->Mode) + { + d->GridLayout->setContentsMargins(0, 0, 0, 0); + d->GridLayout->setRowStretch(0, 1); + d->GridLayout->setRowStretch(1, 0); + d->GridLayout->setRowStretch(2, 0); + d->GridLayout->setRowStretch(3, 0); + d->GridLayout->setRowStretch(4, 1); + + d->GridLayout->setColumnStretch(0, 1); + d->GridLayout->setColumnStretch(1, 0); + d->GridLayout->setColumnStretch(2, 0); + d->GridLayout->setColumnStretch(3, 0); + d->GridLayout->setColumnStretch(4, 1); + } + else + { + d->GridLayout->setContentsMargins(4, 4, 4, 4); + d->GridLayout->setRowStretch(0, 0); + d->GridLayout->setRowStretch(1, 1); + d->GridLayout->setRowStretch(2, 1); + d->GridLayout->setRowStretch(3, 1); + d->GridLayout->setRowStretch(4, 0); + + d->GridLayout->setColumnStretch(0, 0); + d->GridLayout->setColumnStretch(1, 1); + d->GridLayout->setColumnStretch(2, 1); + d->GridLayout->setColumnStretch(3, 1); + d->GridLayout->setColumnStretch(4, 0); + } + reset(); +} + + +//============================================================================ +DockWidgetArea CDockOverlayCross::cursorLocation() const +{ + const QPoint pos = mapFromGlobal(QCursor::pos()); + QHashIterator i(d->DropIndicatorWidgets); + while (i.hasNext()) + { + i.next(); + if (d->DockOverlay->allowedAreas().testFlag(i.key()) + && i.value() + && i.value()->isVisible() + && i.value()->geometry().contains(pos)) + { + return i.key(); + } + } + return InvalidDockWidgetArea; +} + + +//============================================================================ +void CDockOverlayCross::showEvent(QShowEvent*) +{ + if (d->UpdateRequired) + { + setupOverlayCross(d->Mode); + } + this->updatePosition(); +} + + +//============================================================================ +void CDockOverlayCross::updatePosition() +{ + resize(d->DockOverlay->size()); + QPoint TopLeft = d->DockOverlay->pos(); + QPoint Offest((this->width() - d->DockOverlay->width()) / 2, + (this->height() - d->DockOverlay->height()) / 2); + QPoint CrossTopLeft = TopLeft - Offest; + move(CrossTopLeft); +} + + +//============================================================================ +void CDockOverlayCross::reset() +{ + QList allAreas; + allAreas << TopDockWidgetArea << RightDockWidgetArea + << BottomDockWidgetArea << LeftDockWidgetArea << CenterDockWidgetArea; + const DockWidgetAreas allowedAreas = d->DockOverlay->allowedAreas(); + + // Update visibility of area widgets based on allowedAreas. + for (int i = 0; i < allAreas.count(); ++i) + { + QPoint p = d->areaGridPosition(allAreas.at(i)); + QLayoutItem* item = d->GridLayout->itemAtPosition(p.x(), p.y()); + QWidget* w = nullptr; + if (item && (w = item->widget()) != nullptr) + { + w->setVisible(allowedAreas.testFlag(allAreas.at(i))); + } + } +} + + +//============================================================================ +void CDockOverlayCross::setIconColors(const QString& Colors) +{ + static const QMap ColorCompenentStringMap{ + {"Frame", CDockOverlayCross::FrameColor}, + {"Background", CDockOverlayCross::WindowBackgroundColor}, + {"Overlay", CDockOverlayCross::OverlayColor}, + {"Arrow", CDockOverlayCross::ArrowColor}, + {"Shadow", CDockOverlayCross::ShadowColor}}; + +#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0)) + auto SkipEmptyParts = QString::SkipEmptyParts; +#else + auto SkipEmptyParts = Qt::SkipEmptyParts; +#endif + auto ColorList = Colors.split(' ', SkipEmptyParts); + for (const auto& ColorListEntry : ColorList) + { + auto ComponentColor = ColorListEntry.split('=', SkipEmptyParts); + int Component = ColorCompenentStringMap.value(ComponentColor[0], -1); + if (Component < 0) + { + continue; + } + d->IconColors[Component] = QColor(ComponentColor[1]); + } + + d->UpdateRequired = true; +} + +//============================================================================ +QString CDockOverlayCross::iconColors() const +{ + return QString(); +} + + + +} // namespace ads +//---------------------------------------------------------------------------- + diff --git a/src/DockOverlay.h b/src/DockOverlay.h new file mode 100644 index 000000000..36376f591 --- /dev/null +++ b/src/DockOverlay.h @@ -0,0 +1,280 @@ +#ifndef DockOverlayH +#define DockOverlayH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include +#include + +#include "ads_globals.h" + +QT_FORWARD_DECLARE_CLASS(QGridLayout) + +namespace ads +{ +struct DockOverlayPrivate; +class CDockOverlayCross; + +/*! + * DockOverlay paints a translucent rectangle over another widget. The geometry + * of the rectangle is based on the mouse location. + */ +class ADS_EXPORT CDockOverlay : public QFrame +{ + Q_OBJECT +private: + DockOverlayPrivate* d; //< private data class + friend struct DockOverlayPrivate; + friend class DockOverlayCross; + +public: + using Super = QFrame; + + enum eMode + { + ModeDockAreaOverlay, + ModeContainerOverlay + }; + + /** + * Creates a dock overlay + */ + CDockOverlay(QWidget* parent, eMode Mode = ModeDockAreaOverlay); + + /** + * Virtual destructor + */ + virtual ~CDockOverlay(); + + /** + * Configures the areas that are allowed for docking + */ + void setAllowedAreas(DockWidgetAreas areas); + + /** + * Enable / disable a certain area + */ + void setAllowedArea(DockWidgetArea area, bool Enable); + + /** + * Returns flags with all allowed drop areas + */ + DockWidgetAreas allowedAreas() const; + + /** + * Returns the drop area under the current cursor location + */ + DockWidgetArea dropAreaUnderCursor() const; + + /** + * If the drop area is the CenterDockWidgetArea or a sidebar area, + * then this function returns the index of the tab under cursor. + * Call this function after call to dropAreaUnderCursor() because this + * function updates the tab index. + * A value of -1 indicates a position before the first tab and a value of + * tabCount() indicates a position behind the last tab. + * A value of -2 indicates an valid value + */ + int tabIndexUnderCursor() const; + + /** + * This function returns the same like dropAreaUnderCursor() if this + * overlay is not hidden and if drop preview is enabled and returns + * InvalidDockWidgetArea if it is hidden or drop preview is disabled. + */ + DockWidgetArea visibleDropAreaUnderCursor() const; + + /** + * Show the drop overly for the given target widget + */ + DockWidgetArea showOverlay(QWidget* target); + + /** + * Hides the overlay + */ + void hideOverlay(); + + /** + * Enables / disables the semi transparent overlay rectangle that represents + * the future area of the dropped widget + */ + void enableDropPreview(bool Enable); + + /** + * Returns true if drop preview is enabled + */ + bool dropPreviewEnabled() const; + + /** + * The drop overlay rectangle for the target area + */ + QRect dropOverlayRect() const; + + /** + * Handle polish events + */ + virtual bool event(QEvent *e) override; + +protected: + virtual void paintEvent(QPaintEvent *e) override; + virtual void showEvent(QShowEvent* e) override; + virtual void hideEvent(QHideEvent* e) override; +}; + + +struct DockOverlayCrossPrivate; +/*! + * DockOverlayCross shows a cross with 5 different drop area possibilities. + * I could have handled everything inside DockOverlay, but because of some + * styling issues it's better to have a separate class for the cross. + * You can style the cross icon using the property system. + * \code + * ads--CDockOverlayCross + { + qproperty-iconFrameColor: palette(highlight); + qproperty-iconBackgroundColor: palette(base); + qproperty-iconOverlayColor: palette(highlight); + qproperty-iconArrowColor: rgb(227, 227, 227); + qproperty-iconShadowColor: rgb(0, 0, 0); + } + * \endcode + * Or you can use the iconColors property to pass in AARRGGBB values as + * hex string like shown in the example below. + * \code + * ads--CDockOverlayCross + * { + * qproperty-iconColors: "Frame=#ff3d3d3d Background=#ff929292 Overlay=#1f3d3d3d Arrow=#ffb4b4b4 Shadow=#40474747"; + * } + * \endcode + */ +class CDockOverlayCross : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString iconColors READ iconColors WRITE setIconColors) + Q_PROPERTY(QColor iconFrameColor READ iconColor WRITE setIconFrameColor) + Q_PROPERTY(QColor iconBackgroundColor READ iconColor WRITE setIconBackgroundColor) + Q_PROPERTY(QColor iconOverlayColor READ iconColor WRITE setIconOverlayColor) + Q_PROPERTY(QColor iconArrowColor READ iconColor WRITE setIconArrowColor) + Q_PROPERTY(QColor iconShadowColor READ iconColor WRITE setIconShadowColor) + +public: + enum eIconColor + { + FrameColor,///< the color of the frame of the small window icon + WindowBackgroundColor,///< the background color of the small window in the icon + OverlayColor,///< the color that shows the overlay (the dock side) in the icon + ArrowColor,///< the arrow that points into the direction + ShadowColor///< the color of the shadow rectangle that is painted below the icons + }; + +private: + DockOverlayCrossPrivate* d; + friend struct DockOverlayCrossPrivate; + friend class CDockOverlay; + +protected: + /** + * This function returns an empty string and is only here to silence + * moc + */ + QString iconColors() const; + + /** + * This is a dummy function for the property system + */ + QColor iconColor() const {return QColor();} + void setIconFrameColor(const QColor& Color) {setIconColor(FrameColor, Color);} + void setIconBackgroundColor(const QColor& Color) {setIconColor(WindowBackgroundColor, Color);} + void setIconOverlayColor(const QColor& Color) {setIconColor(OverlayColor, Color);} + void setIconArrowColor(const QColor& Color) {setIconColor(ArrowColor, Color);} + void setIconShadowColor(const QColor& Color) {setIconColor(ShadowColor, Color);} + +public: + /** + * Creates an overlay cross for the given overlay + */ + CDockOverlayCross(CDockOverlay* overlay); + + /** + * Virtual destructor + */ + virtual ~CDockOverlayCross(); + + /** + * Sets a certain icon color + */ + void setIconColor(eIconColor ColorIndex, const QColor& Color); + + /** + * Returns the icon color given by ColorIndex + */ + QColor iconColor(eIconColor ColorIndex) const; + + /** + * Returns the dock widget area depending on the current cursor location. + * The function checks, if the mouse cursor is inside of any drop indicator + * widget and returns the corresponding DockWidgetArea. + */ + DockWidgetArea cursorLocation() const; + + /** + * Sets up the overlay cross for the given overlay mode + */ + void setupOverlayCross(CDockOverlay::eMode Mode); + + /** + * Recreates the overlay icons. + */ + void updateOverlayIcons(); + + /** + * Resets and updates the + */ + void reset(); + + /** + * Updates the current position + */ + void updatePosition(); + + /** + * A string with all icon colors to set. + * You can use this property to style the overly icon via CSS stylesheet + * file. The colors are set via a color identifier and a hex AARRGGBB value like + * in the example below. + * \code + * ads--CDockOverlayCross + * { + * qproperty-iconColors: "Frame=#ff3d3d3d Background=#ff929292 Overlay=#1f3d3d3d Arrow=#ffb4b4b4 Shadow=#40474747"; + * } + */ + void setIconColors(const QString& Colors); + +protected: + virtual void showEvent(QShowEvent* e) override; + void setAreaWidgets(const QHash& widgets); +}; // CDockOverlayCross + +} // namespace ads +#endif // DockOverlayH diff --git a/src/DockSplitter.cpp b/src/DockSplitter.cpp new file mode 100644 index 000000000..33f617236 --- /dev/null +++ b/src/DockSplitter.cpp @@ -0,0 +1,122 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockSplitter.cpp +/// \author Uwe Kindler +/// \date 24.03.2017 +/// \brief Implementation of CDockSplitter +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockSplitter.h" + +#include +#include +#include +#include "DockAreaWidget.h" + +namespace ads +{ +/** + * Private dock splitter data + */ +struct DockSplitterPrivate +{ + CDockSplitter* _this; + int VisibleContentCount = 0; + + DockSplitterPrivate(CDockSplitter* _public) : _this(_public) {} +}; + +//============================================================================ +CDockSplitter::CDockSplitter(QWidget *parent) + : QSplitter(parent), + d(new DockSplitterPrivate(this)) +{ + setProperty("ads-splitter", QVariant(true)); + setChildrenCollapsible(false); +} + + +//============================================================================ +CDockSplitter::CDockSplitter(Qt::Orientation orientation, QWidget *parent) + : QSplitter(orientation, parent), + d(new DockSplitterPrivate(this)) +{ + +} + +//============================================================================ +CDockSplitter::~CDockSplitter() +{ + ADS_PRINT("~CDockSplitter"); + delete d; +} + + +//============================================================================ +bool CDockSplitter::hasVisibleContent() const +{ + // TODO Cache or precalculate this to speed up + for (int i = 0; i < count(); ++i) + { + if (!widget(i)->isHidden()) + { + return true; + } + } + + return false; +} + + +//============================================================================ +QWidget* CDockSplitter::firstWidget() const +{ + return (count() > 0) ? widget(0) : nullptr; +} + + +//============================================================================ +QWidget* CDockSplitter::lastWidget() const +{ + return (count() > 0) ? widget(count() - 1) : nullptr; +} + +//============================================================================ +bool CDockSplitter::isResizingWithContainer() const +{ + for (auto area : findChildren()) + { + if(area->isCentralWidgetArea()) + { + return true; + } + } + + return false; +} + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockSplitter.cpp diff --git a/src/DockSplitter.h b/src/DockSplitter.h new file mode 100644 index 000000000..498a507ad --- /dev/null +++ b/src/DockSplitter.h @@ -0,0 +1,84 @@ +#ifndef DockSplitterH +#define DockSplitterH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockSplitter.h +/// \author Uwe Kindler +/// \date 24.03.2017 +/// \brief Declaration of CDockSplitter +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +namespace ads +{ +struct DockSplitterPrivate; + +/** + * Splitter used internally instead of QSplitter with some additional + * functionality. + */ +class ADS_EXPORT CDockSplitter : public QSplitter +{ + Q_OBJECT +private: + DockSplitterPrivate* d; + friend struct DockSplitterPrivate; + +public: + CDockSplitter(QWidget *parent = Q_NULLPTR); + CDockSplitter(Qt::Orientation orientation, QWidget *parent = Q_NULLPTR); + + /** + * Prints debug info + */ + virtual ~CDockSplitter(); + + /** + * Returns true, if any of the internal widgets is visible + */ + bool hasVisibleContent() const; + + /** + * Returns first widget or nullptr if splitter is empty + */ + QWidget* firstWidget() const; + + /** + * Returns last widget of nullptr is splitter is empty + */ + QWidget* lastWidget() const; + + /** + * Returns true if the splitter contains central widget of dock manager. + */ + bool isResizingWithContainer() const; +}; // class CDockSplitter + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // DockSplitterH diff --git a/src/DockWidget.cpp b/src/DockWidget.cpp new file mode 100644 index 000000000..4bb8ea416 --- /dev/null +++ b/src/DockWidget.cpp @@ -0,0 +1,1357 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockWidget.cpp +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Implementation of CDockWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include +#include "DockWidgetTab.h" +#include "DockWidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "DockContainerWidget.h" +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "FloatingDockContainer.h" +#include "DockSplitter.h" +#include "DockComponentsFactory.h" +#include "ads_globals.h" + + +namespace ads +{ +/** + * Private data class of CDockWidget class (pimpl) + */ +struct DockWidgetPrivate +{ + struct WidgetFactory + { + CDockWidget::FactoryFunc createWidget; + CDockWidget::eInsertMode insertMode; + }; + + CDockWidget* _this = nullptr; + QBoxLayout* Layout = nullptr; + QWidget* Widget = nullptr; + CDockWidgetTab* TabWidget = nullptr; + CDockWidget::DockWidgetFeatures Features = CDockWidget::DefaultDockWidgetFeatures; + QPointer DockManager; + QPointer DockArea; + QAction* ToggleViewAction = nullptr; + bool Closed = false; + QScrollArea* ScrollArea = nullptr; + QToolBar* ToolBar = nullptr; + Qt::ToolButtonStyle ToolBarStyleDocked = Qt::ToolButtonIconOnly; + Qt::ToolButtonStyle ToolBarStyleFloating = Qt::ToolButtonTextUnderIcon; + QSize ToolBarIconSizeDocked = QSize(16, 16); + QSize ToolBarIconSizeFloating = QSize(24, 24); + bool IsFloatingTopLevel = false; + QList TitleBarActions; + CDockWidget::eMinimumSizeHintMode MinimumSizeHintMode = CDockWidget::MinimumSizeHintFromDockWidget; + WidgetFactory* Factory = nullptr; + QPointer SideTabWidget; + CDockWidget::eToolBarStyleSource ToolBarStyleSource = CDockWidget::ToolBarStyleFromDockManager; + + /** + * Private data constructor + */ + DockWidgetPrivate(CDockWidget* _public); + + /** + * Show dock widget + */ + void showDockWidget(); + + /** + * Hide dock widget. + */ + void hideDockWidget(); + + /** + * Hides a dock area if all dock widgets in the area are closed. + * This function updates the current selected tab and hides the parent + * dock area if it is empty + */ + void updateParentDockArea(); + + /** + * Closes all auto hide dock widgets if there are no more opened dock areas + * This prevents the auto hide dock widgets from being pinned to an empty dock area + */ + void closeAutoHideDockWidgetsIfNeeded(); + + /** + * Setup the top tool bar + */ + void setupToolBar(); + + /** + * Setup the main scroll area + */ + void setupScrollArea(); + + /** + * Creates the content widget with the registered widget factory and + * returns true on success. + */ + bool createWidgetFromFactory(); + + /** + * Use the dock manager toolbar style and icon size for the different states + */ + void setToolBarStyleFromDockManager(); +}; +// struct DockWidgetPrivate + +//============================================================================ +DockWidgetPrivate::DockWidgetPrivate(CDockWidget* _public) : + _this(_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); + // 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(); + } + else + { + DockArea->setCurrentDockWidget(_this); + DockArea->toggleView(true); + TabWidget->show(); + auto Splitter = DockArea->parentSplitter(); + while (Splitter && !Splitter->isVisible() && !DockArea->isAutoHide()) + { + Splitter->show(); + Splitter = internal::findParent(Splitter); + } + + CDockContainerWidget* Container = DockArea->dockContainer(); + if (Container->isFloating()) + { + CFloatingDockContainer* FloatingWidget = internal::findParent< + CFloatingDockContainer*>(Container); + FloatingWidget->show(); + } + + // If this widget is pinned and there are no opened dock widgets, unpin the auto hide widget by moving it's contents to parent container + // While restoring state, opened dock widgets are not valid + if (Container->openedDockWidgets().count() == 0 && DockArea->isAutoHide() && !DockManager->isRestoringState()) + { + DockArea->autoHideDockContainer()->moveContentsToParent(); + } + } +} + + +//============================================================================ +void DockWidgetPrivate::hideDockWidget() +{ + TabWidget->hide(); + updateParentDockArea(); + + closeAutoHideDockWidgetsIfNeeded(); + + if (Features.testFlag(CDockWidget::DeleteContentOnClose)) + { + if (ScrollArea) + { + ScrollArea->takeWidget(); + delete ScrollArea; + ScrollArea = nullptr; + } + Widget->deleteLater(); + Widget = nullptr; + } +} + + +//============================================================================ +void DockWidgetPrivate::updateParentDockArea() +{ + if (!DockArea) + { + return; + } + + // we don't need to change the current tab if the + // current dock widget is not the one being closed + if (DockArea->currentDockWidget() != _this){ + return; + } + + auto NextDockWidget = DockArea->nextOpenDockWidget(_this); + if (NextDockWidget) + { + DockArea->setCurrentDockWidget(NextDockWidget); + } + else + { + DockArea->hideAreaWithNoVisibleContent(); + } +} + + +//============================================================================ +void DockWidgetPrivate::closeAutoHideDockWidgetsIfNeeded() +{ + auto DockContainer = _this->dockContainer(); + if (!DockContainer) + { + return; + } + + if (_this->dockManager()->isRestoringState()) + { + return; + } + + // If the dock container is the dock manager, or if it is not empty, then we + // don't need to do anything + if ((DockContainer == _this->dockManager()) + || !DockContainer->openedDockWidgets().isEmpty()) + { + return; + } + + for (auto autoHideWidget : DockContainer->autoHideWidgets()) + { + auto DockWidget = autoHideWidget->dockWidget(); + if (DockWidget == _this) + { + continue; + } + + DockWidget->toggleView(false); + } +} + + +//============================================================================ +void DockWidgetPrivate::setupToolBar() +{ + ToolBar = new QToolBar(_this); + ToolBar->setObjectName("dockWidgetToolBar"); + Layout->insertWidget(0, ToolBar); + ToolBar->setIconSize(QSize(16, 16)); + ToolBar->toggleViewAction()->setEnabled(false); + ToolBar->toggleViewAction()->setVisible(false); + _this->connect(_this, SIGNAL(topLevelChanged(bool)), SLOT(setToolbarFloatingStyle(bool))); +} + + + +//============================================================================ +void DockWidgetPrivate::setupScrollArea() +{ + ScrollArea = new QScrollArea(_this); + ScrollArea->setObjectName("dockWidgetScrollArea"); + ScrollArea->setWidgetResizable(true); + Layout->addWidget(ScrollArea); +} + + +//============================================================================ +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; +} + + +//============================================================================ +void DockWidgetPrivate::setToolBarStyleFromDockManager() +{ + if (!DockManager) + { + return; + } + auto State = CDockWidget::StateDocked; + _this->setToolBarIconSize(DockManager->dockWidgetToolBarIconSize(State), State); + _this->setToolBarStyle(DockManager->dockWidgetToolBarStyle(State), State); + State = CDockWidget::StateFloating; + _this->setToolBarIconSize(DockManager->dockWidgetToolBarIconSize(State), State); + _this->setToolBarStyle(DockManager->dockWidgetToolBarStyle(State), State); +} + + +//============================================================================ +CDockWidget::CDockWidget(const QString &title, QWidget *parent) : + QFrame(parent), + d(new DockWidgetPrivate(this)) +{ + d->Layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + setWindowTitle(title); + setObjectName(title); + + d->TabWidget = componentsFactory()->createDockWidgetTab(this); + + d->ToggleViewAction = new QAction(title, this); + d->ToggleViewAction->setCheckable(true); + connect(d->ToggleViewAction, SIGNAL(triggered(bool)), this, + SLOT(toggleView(bool))); + setToolbarFloatingStyle(false); + + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + setFocusPolicy(Qt::ClickFocus); + } +} + +//============================================================================ +CDockWidget::~CDockWidget() +{ + ADS_PRINT("~CDockWidget(): " << this->windowTitle()); + delete d; +} + +//============================================================================ +void CDockWidget::setToggleViewAction(QAction* action) +{ + if (!action) + { + return; + } + + d->ToggleViewAction->setParent(nullptr); + delete d->ToggleViewAction; + d->ToggleViewAction = action; + d->ToggleViewAction->setParent(this); + connect(d->ToggleViewAction, &QAction::triggered, this, &CDockWidget::toggleView); +} + +//============================================================================ +void CDockWidget::setToggleViewActionChecked(bool Checked) +{ + QAction* Action = d->ToggleViewAction; + Action->blockSignals(true); + Action->setChecked(Checked); + Action->blockSignals(false); +} + + +//============================================================================ +void CDockWidget::setWidget(QWidget* widget, eInsertMode InsertMode) +{ + if (d->Widget) + { + takeWidget(); + } + + auto ScrollAreaWidget = qobject_cast(widget); + if (ScrollAreaWidget || ForceNoScrollArea == InsertMode) + { + d->Layout->addWidget(widget); + if (ScrollAreaWidget && ScrollAreaWidget->viewport()) + { + ScrollAreaWidget->viewport()->setProperty("dockWidgetContent", true); + } + } + else + { + d->setupScrollArea(); + d->ScrollArea->setWidget(widget); + } + + d->Widget = widget; + 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() +{ + QWidget* w = nullptr; + if (d->ScrollArea) + { + d->Layout->removeWidget(d->ScrollArea); + w = d->ScrollArea->takeWidget(); + delete d->ScrollArea; + d->ScrollArea = nullptr; + d->Widget = nullptr; + } + else if (d->Widget) + { + d->Layout->removeWidget(d->Widget); + w = d->Widget; + d->Widget = nullptr; + } + + if (w) + { + w->setParent(nullptr); + } + return w; +} + + +//============================================================================ +QWidget* CDockWidget::widget() const +{ + return d->Widget; +} + + +//============================================================================ +CDockWidgetTab* CDockWidget::tabWidget() const +{ + return d->TabWidget; +} + + +//============================================================================ +CAutoHideDockContainer* CDockWidget::autoHideDockContainer() const +{ + if (!d->DockArea) + { + return nullptr; + } + + return d->DockArea->autoHideDockContainer(); +} + + +//============================================================================ +void CDockWidget::setFeatures(DockWidgetFeatures features) +{ + if (d->Features == features) + { + return; + } + d->Features = features; + notifyFeaturesChanged(); +} + + +//============================================================================ +void CDockWidget::notifyFeaturesChanged() +{ + Q_EMIT featuresChanged(d->Features); + d->TabWidget->onDockWidgetFeaturesChanged(); + if(CDockAreaWidget* DockArea = dockAreaWidget()) + { + DockArea->onDockWidgetFeaturesChanged(); + } +} + + +//============================================================================ +void CDockWidget::setFeature(DockWidgetFeature flag, bool on) +{ + auto Features = features(); + internal::setFlag(Features, flag, on); + setFeatures(Features); +} + + +//============================================================================ +CDockWidget::DockWidgetFeatures CDockWidget::features() const +{ + if (d->DockManager) + { + return d->Features &~ d->DockManager->globallyLockedDockWidgetFeatures(); + } + else + { + return d->Features; + } +} + + +//============================================================================ +CDockManager* CDockWidget::dockManager() const +{ + return d->DockManager; +} + + +//============================================================================ +void CDockWidget::setDockManager(CDockManager* DockManager) +{ + d->DockManager = DockManager; + if (!DockManager) + { + return; + } + + if (ToolBarStyleFromDockManager == d->ToolBarStyleSource) + { + d->setToolBarStyleFromDockManager(); + } +} + + +//============================================================================ +CDockContainerWidget* CDockWidget::dockContainer() const +{ + if (d->DockArea) + { + return d->DockArea->dockContainer(); + } + else + { + return nullptr; + } +} + + +//============================================================================ +CFloatingDockContainer* CDockWidget::floatingDockContainer() const +{ + auto DockContainer = dockContainer(); + return DockContainer ? DockContainer->floatingWidget() : nullptr; +} + + +//============================================================================ +CDockAreaWidget* CDockWidget::dockAreaWidget() const +{ + return d->DockArea; +} + +//============================================================================ +CAutoHideTab* CDockWidget::sideTabWidget() const +{ + return d->SideTabWidget; +} + + +//============================================================================ +void CDockWidget::setSideTabWidget(CAutoHideTab* SideTab) const +{ + d->SideTabWidget = SideTab; +} + + +//============================================================================ +bool CDockWidget::isAutoHide() const +{ + return !d->SideTabWidget.isNull(); +} + + +//============================================================================ +SideBarLocation CDockWidget::autoHideLocation() const +{ + return isAutoHide() ? autoHideDockContainer()->sideBarLocation() : SideBarNone; +} + + +//============================================================================ +bool CDockWidget::isFloating() const +{ + if (!isInFloatingContainer()) + { + return false; + } + + return dockContainer()->topLevelDockWidget() == this; +} + + +//============================================================================ +bool CDockWidget::isInFloatingContainer() const +{ + auto Container = dockContainer(); + if (!Container) + { + return false; + } + + if (!Container->isFloating()) + { + return false; + } + + return true; +} + + +//============================================================================ +bool CDockWidget::isClosed() const +{ + return d->Closed; +} + + +//============================================================================ +QAction* CDockWidget::toggleViewAction() const +{ + return d->ToggleViewAction; +} + + +//============================================================================ +void CDockWidget::setToggleViewActionMode(eToggleViewActionMode Mode) +{ + if (ActionModeToggle == Mode) + { + d->ToggleViewAction->setCheckable(true); + d->ToggleViewAction->setIcon(QIcon()); + } + else + { + d->ToggleViewAction->setCheckable(false); + d->ToggleViewAction->setIcon(d->TabWidget->icon()); + } +} + + +//============================================================================ +void CDockWidget::setMinimumSizeHintMode(eMinimumSizeHintMode Mode) +{ + d->MinimumSizeHintMode = Mode; +} + + +//============================================================================ +CDockWidget::eMinimumSizeHintMode CDockWidget::minimumSizeHintMode() const +{ + return d->MinimumSizeHintMode; +} + + +//============================================================================ +bool CDockWidget::isCentralWidget() const +{ + return dockManager()->centralWidget() == this; +} + + +//============================================================================ +void CDockWidget::toggleView(bool Open) +{ + // If the toggle view action mode is ActionModeShow, then Open is always + // true if the sender is the toggle view action + QAction* Sender = qobject_cast(sender()); + if (Sender == d->ToggleViewAction && !d->ToggleViewAction->isCheckable()) + { + 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 + auto AutoHideContainer = autoHideDockContainer(); + if (d->Closed != !Open) + { + toggleViewInternal(Open); + } + else if (Open && d->DockArea && !AutoHideContainer) + { + raise(); + } + + if (Open && AutoHideContainer) + { + AutoHideContainer->collapseView(false); + } +} + + +//============================================================================ +void CDockWidget::toggleViewInternal(bool Open) +{ + CDockContainerWidget* DockContainer = dockContainer(); + CDockWidget* TopLevelDockWidgetBefore = DockContainer + ? DockContainer->topLevelDockWidget() : nullptr; + + d->Closed = !Open; + + if (Open) + { + d->showDockWidget(); + } + else + { + d->hideDockWidget(); + } + + d->ToggleViewAction->blockSignals(true); + d->ToggleViewAction->setChecked(Open); + d->ToggleViewAction->blockSignals(false); + if (d->DockArea) + { + d->DockArea->toggleDockWidgetView(this, Open); + if (d->DockArea->isAutoHide()) + { + d->DockArea->autoHideDockContainer()->toggleView(Open); + } + } + + if (Open && TopLevelDockWidgetBefore) + { + CDockWidget::emitTopLevelEventForWidget(TopLevelDockWidgetBefore, false); + } + + // Here we need to call the dockContainer() function again, because if + // this dock widget was unassigned before the call to showDockWidget() then + // it has a dock container now + DockContainer = dockContainer(); + CDockWidget* TopLevelDockWidgetAfter = DockContainer + ? DockContainer->topLevelDockWidget() : nullptr; + CDockWidget::emitTopLevelEventForWidget(TopLevelDockWidgetAfter, true); + CFloatingDockContainer* FloatingContainer = DockContainer + ? DockContainer->floatingWidget() : nullptr; + if (FloatingContainer) + { + FloatingContainer->updateWindowTitle(); + } + + if (!Open) + { + Q_EMIT closed(); + } + Q_EMIT viewToggled(Open); +} + + +//============================================================================ +void CDockWidget::setDockArea(CDockAreaWidget* DockArea) +{ + d->DockArea = DockArea; + d->ToggleViewAction->setChecked(DockArea != nullptr && !this->isClosed()); + setParent(DockArea); +} + + +//============================================================================ +void CDockWidget::saveState(QXmlStreamWriter& s) const +{ + s.writeStartElement("Widget"); + s.writeAttribute("Name", objectName()); + s.writeAttribute("Closed", QString::number(d->Closed ? 1 : 0)); + s.writeEndElement(); +} + + +//============================================================================ +void CDockWidget::flagAsUnassigned() +{ + d->Closed = true; + setParent(d->DockManager); + setVisible(false); + setDockArea(nullptr); + tabWidget()->setParent(this); +} + + +//============================================================================ +bool CDockWidget::event(QEvent *e) +{ + switch (e->type()) + { + case QEvent::Hide: + Q_EMIT visibilityChanged(false); + break; + + case QEvent::Show: + Q_EMIT visibilityChanged(geometry().right() >= 0 && geometry().bottom() >= 0); + break; + + case QEvent::WindowTitleChange : + { + const auto title = windowTitle(); + if (d->TabWidget) + { + d->TabWidget->setText(title); + } + if (d->SideTabWidget) + { + d->SideTabWidget->setText(title); + } + if (d->ToggleViewAction) + { + d->ToggleViewAction->setText(title); + } + if (d->DockArea) + { + d->DockArea->markTitleBarMenuOutdated();//update tabs menu + } + + auto FloatingWidget = floatingDockContainer(); + if (FloatingWidget) + { + FloatingWidget->updateWindowTitle(); + } + Q_EMIT titleChanged(title); + } + break; + + default: + break; + } + + return Super::event(e); +} + + +#ifndef QT_NO_TOOLTIP +//============================================================================ +void CDockWidget::setTabToolTip(const QString &text) +{ + if (d->TabWidget) + { + d->TabWidget->setToolTip(text); + } + if (d->ToggleViewAction) + { + d->ToggleViewAction->setToolTip(text); + } + if (d->DockArea) + { + d->DockArea->markTitleBarMenuOutdated();//update tabs menu + } +} +#endif + + +//============================================================================ +void CDockWidget::setIcon(const QIcon& Icon) +{ + d->TabWidget->setIcon(Icon); + + if (d->SideTabWidget) + { + d->SideTabWidget->setIcon(Icon); + } + + if (!d->ToggleViewAction->isCheckable()) + { + d->ToggleViewAction->setIcon(Icon); + } +} + + +//============================================================================ +QIcon CDockWidget::icon() const +{ + return d->TabWidget->icon(); +} + + +//============================================================================ +QToolBar* CDockWidget::toolBar() const +{ + return d->ToolBar; +} + + +//============================================================================ +QToolBar* CDockWidget::createDefaultToolBar() +{ + if (!d->ToolBar) + { + d->setupToolBar(); + } + + return d->ToolBar; +} + + +//============================================================================ +void CDockWidget::setToolBar(QToolBar* ToolBar) +{ + if (d->ToolBar) + { + delete d->ToolBar; + } + + d->ToolBar = ToolBar; + d->Layout->insertWidget(0, d->ToolBar); + this->connect(this, SIGNAL(topLevelChanged(bool)), SLOT(setToolbarFloatingStyle(bool))); + setToolbarFloatingStyle(isFloating()); +} + + +//============================================================================ +void CDockWidget::setToolBarStyle(Qt::ToolButtonStyle Style, eState State) +{ + if (StateFloating == State) + { + d->ToolBarStyleFloating = Style; + } + else + { + d->ToolBarStyleDocked = Style; + } + + setToolbarFloatingStyle(isFloating()); +} + + +//============================================================================ +Qt::ToolButtonStyle CDockWidget::toolBarStyle(eState State) const +{ + if (StateFloating == State) + { + return d->ToolBarStyleFloating; + } + else + { + return d->ToolBarStyleDocked; + } +} + + +//============================================================================ +void CDockWidget::setToolBarIconSize(const QSize& IconSize, eState State) +{ + if (StateFloating == State) + { + d->ToolBarIconSizeFloating = IconSize; + } + else + { + d->ToolBarIconSizeDocked = IconSize; + } + + setToolbarFloatingStyle(isFloating()); +} + + +//============================================================================ +QSize CDockWidget::toolBarIconSize(eState State) const +{ + if (StateFloating == State) + { + return d->ToolBarIconSizeFloating; + } + else + { + return d->ToolBarIconSizeDocked; + } +} + + +//============================================================================ +void CDockWidget::setToolbarFloatingStyle(bool Floating) +{ + if (!d->ToolBar) + { + return; + } + + auto IconSize = Floating ? d->ToolBarIconSizeFloating : d->ToolBarIconSizeDocked; + if (IconSize != d->ToolBar->iconSize()) + { + d->ToolBar->setIconSize(IconSize); + } + + auto ButtonStyle = Floating ? d->ToolBarStyleFloating : d->ToolBarStyleDocked; + if (ButtonStyle != d->ToolBar->toolButtonStyle()) + { + d->ToolBar->setToolButtonStyle(ButtonStyle); + } +} + + +//============================================================================ +void CDockWidget::emitTopLevelEventForWidget(CDockWidget* TopLevelDockWidget, bool Floating) +{ + if (TopLevelDockWidget) + { + TopLevelDockWidget->dockAreaWidget()->updateTitleBarVisibility(); + TopLevelDockWidget->emitTopLevelChanged(Floating); + } +} + + +//============================================================================ +void CDockWidget::emitTopLevelChanged(bool Floating) +{ + if (Floating != d->IsFloatingTopLevel) + { + d->IsFloatingTopLevel = Floating; + Q_EMIT topLevelChanged(d->IsFloatingTopLevel); + } +} + + +//============================================================================ +void CDockWidget::setClosedState(bool Closed) +{ + d->Closed = Closed; +} + + +//============================================================================ +QSize CDockWidget::minimumSizeHint() const +{ + if (!d->Widget) + { + return QSize(60, 40); + } + + switch (d->MinimumSizeHintMode) + { + case MinimumSizeHintFromDockWidget: return QSize(60, 40); + case MinimumSizeHintFromContent: return d->Widget->minimumSizeHint(); + case MinimumSizeHintFromDockWidgetMinimumSize: return minimumSize(); + case MinimumSizeHintFromContentMinimumSize: return d->Widget->minimumSize(); + } + + return d->Widget->minimumSizeHint(); +} + + +//============================================================================ +void CDockWidget::setFloating() +{ + if (isClosed()) + { + return; + } + + if (this->isAutoHide()) + { + dockAreaWidget()->setFloating(); + } + else + { + d->TabWidget->detachDockWidget(); + } +} + + +//============================================================================ +void CDockWidget::deleteDockWidget() +{ + auto manager=dockManager(); + if(manager){ + manager->removeDockWidget(this); + } + deleteLater(); + d->Closed = true; +} + + +//============================================================================ +void CDockWidget::closeDockWidget() +{ + closeDockWidgetInternal(true); +} + + + +//============================================================================ +void CDockWidget::requestCloseDockWidget() +{ + if (features().testFlag(CDockWidget::DockWidgetDeleteOnClose) + || features().testFlag(CDockWidget::CustomCloseHandling)) + { + closeDockWidgetInternal(false); + } + else + { + toggleView(false); + } +} + + +//============================================================================ +bool CDockWidget::closeDockWidgetInternal(bool ForceClose) +{ + if (!ForceClose) + { + Q_EMIT closeRequested(); + } + + if (!ForceClose && features().testFlag(CDockWidget::CustomCloseHandling)) + { + return false; + } + + if (features().testFlag(CDockWidget::DockWidgetDeleteOnClose)) + { + // If the dock widget is floating, then we check if we also need to + // delete the floating widget + if (isFloating()) + { + CFloatingDockContainer* FloatingWidget = internal::findParent< + CFloatingDockContainer*>(this); + if (FloatingWidget->dockWidgets().count() == 1) + { + FloatingWidget->deleteLater(); + } + else + { + FloatingWidget->hide(); + } + } + if (d->DockArea && d->DockArea->isAutoHide()) + { + d->DockArea->autoHideDockContainer()->cleanupAndDelete(); + } + deleteDockWidget(); + Q_EMIT closed(); + } + else + { + toggleView(false); + } + + return true; +} + + +//============================================================================ +void CDockWidget::setTitleBarActions(QList actions) +{ + d->TitleBarActions = actions; +} + + +//============================================================================ +QList CDockWidget::titleBarActions() const +{ + return d->TitleBarActions; +} + + +//============================================================================ +void CDockWidget::showFullScreen() +{ + if (isFloating()) + { + dockContainer()->floatingWidget()->showFullScreen(); + } + else + { + Super::showFullScreen(); + } +} + + +//============================================================================ +void CDockWidget::showNormal() +{ + if (isFloating()) + { + dockContainer()->floatingWidget()->showNormal(); + } + else + { + Super::showNormal(); + } +} + + +//============================================================================ +bool CDockWidget::isFullScreen() const +{ + if (isFloating()) + { + return dockContainer()->floatingWidget()->isFullScreen(); + } + else + { + return Super::isFullScreen(); + } +} + + +//============================================================================ +void CDockWidget::setAsCurrentTab() +{ + if (d->DockArea && !isClosed()) + { + d->DockArea->setCurrentDockWidget(this); + } +} + + +//============================================================================ +bool CDockWidget::isTabbed() const +{ + return d->DockArea && (d->DockArea->openDockWidgetsCount() > 1); +} + + + +//============================================================================ +bool CDockWidget::isCurrentTab() const +{ + return d->DockArea && (d->DockArea->currentDockWidget() == this); +} + + +//============================================================================ +void CDockWidget::raise() +{ + if (isClosed()) + { + return; + } + + setAsCurrentTab(); + if (isInFloatingContainer()) + { + auto FloatingWindow = window(); + FloatingWindow->raise(); + FloatingWindow->activateWindow(); + } +} + + +//============================================================================ +void CDockWidget::setAutoHide(bool Enable, SideBarLocation Location, int TabIndex) +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + return; + } + + // Do nothing if nothing changes + if (Enable == isAutoHide() && Location == autoHideLocation()) + { + return; + } + + auto DockArea = dockAreaWidget(); + + if (!Enable) + { + DockArea->setAutoHide(false); + } + else if (isAutoHide()) + { + autoHideDockContainer()->moveToNewSideBarLocation(Location); + } + else + { + auto area = (SideBarNone == Location) ? DockArea->calculateSideTabBarArea() : Location; + dockContainer()->createAndSetupAutoHideContainer(area, this, TabIndex); + } +} + + +//============================================================================ +void CDockWidget::toggleAutoHide(SideBarLocation Location) +{ + if (!CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + return; + } + + setAutoHide(!isAutoHide(), Location); +} + + +//============================================================================ +void CDockWidget::setToolBarStyleSource(eToolBarStyleSource Source) +{ + d->ToolBarStyleSource = Source; + if (ToolBarStyleFromDockManager == d->ToolBarStyleSource) + { + d->setToolBarStyleFromDockManager(); + } +} + + +//============================================================================ +CDockWidget::eToolBarStyleSource CDockWidget::toolBarStyleSource() const +{ + return d->ToolBarStyleSource; +} + + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockWidget.cpp diff --git a/src/DockWidget.h b/src/DockWidget.h new file mode 100644 index 000000000..0d6b88c53 --- /dev/null +++ b/src/DockWidget.h @@ -0,0 +1,708 @@ +#ifndef DockWidgetH +#define DockWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockWidget.h +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Declaration of CDockWidget class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +QT_FORWARD_DECLARE_CLASS(QToolBar) +QT_FORWARD_DECLARE_CLASS(QXmlStreamWriter) + +namespace ads +{ +struct DockWidgetPrivate; +class CDockWidgetTab; +class CDockManager; +class CDockContainerWidget; +class CDockAreaWidget; +class DockContainerWidgetPrivate; +class CFloatingDockContainer; +class CAutoHideTab; +class CAutoHideDockContainer; +class CAutoHideSideBar; + +/** + * The QDockWidget class provides a widget that can be docked inside a + * CDockManager or floated as a top-level window on the desktop. + */ +class ADS_EXPORT CDockWidget : public QFrame +{ + Q_OBJECT +private: + DockWidgetPrivate* d; ///< private data (pimpl) + friend struct DockWidgetPrivate; + +private Q_SLOTS: + /** + * Adjusts the toolbar icon sizes according to the floating state + */ + void setToolbarFloatingStyle(bool topLevel); + +protected: + friend class CDockContainerWidget; + friend class CDockAreaWidget; + friend class CFloatingDockContainer; + friend class CDockManager; + friend struct DockManagerPrivate; + friend class DockContainerWidgetPrivate; + friend class CDockAreaTabBar; + friend class CDockWidgetTab; + friend struct DockWidgetTabPrivate; + friend struct DockAreaTitleBarPrivate; + friend class CAutoHideDockContainer; + friend CAutoHideSideBar; + + /** + * Assigns the dock manager that manages this dock widget + */ + void setDockManager(CDockManager* DockManager); + + /** + * If this dock widget is inserted into a dock area, the dock area will + * be registered on this widget via this function. If a dock widget is + * removed from a dock area, this function will be called with nullptr + * value. + */ + void setDockArea(CDockAreaWidget* DockArea); + + /** + * This function changes the toggle view action without emitting any + * signal + */ + void setToggleViewActionChecked(bool Checked); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter& Stream) const; + + /** + * This is a helper function for the dock manager to flag this widget + * as unassigned. + * When calling the restore function, it may happen, that the saved state + * contains less dock widgets then currently available. All widgets whose + * data is not contained in the saved state, are flagged as unassigned + * after the restore process. If the user shows an unassigned dock widget, + * a floating widget will be created to take up the dock widget. + */ + void flagAsUnassigned(); + + /** + * Call this function to emit a topLevelChanged() signal and to update + * the dock area tool bar visibility + */ + static void emitTopLevelEventForWidget(CDockWidget* TopLevelDockWidget, bool Floating); + + /** + * Use this function to emit a top level changed event. + * Do never use emit topLevelChanged(). Always use this function because + * it only emits a signal if the floating state has really changed + */ + void emitTopLevelChanged(bool Floating); + + /** + * Internal function for modifying the closed state when restoring + * a saved docking state + */ + void setClosedState(bool Closed); + + /** + * Internal toggle view function that does not check if the widget + * already is in the given state + */ + void toggleViewInternal(bool Open); + + /** + * Internal close dock widget implementation. + * The function returns true if the dock widget has been closed or hidden + */ + bool closeDockWidgetInternal(bool ForceClose = false); + +public: + using Super = QFrame; + + enum DockWidgetFeature + { + 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. + DockWidgetPinnable = 0x200, ///< dock widget can be pinned and added to an auto hide dock container + DefaultDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable | DockWidgetFocusable | DockWidgetPinnable, + AllDockWidgetFeatures = DefaultDockWidgetFeatures | DockWidgetDeleteOnClose | CustomCloseHandling, + DockWidgetAlwaysCloseAndDelete = DockWidgetForceCloseWithArea | DockWidgetDeleteOnClose, + GloballyLockableFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable | DockWidgetPinnable, + NoDockWidgetFeatures = 0x000 + }; + Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) + + enum eState + { + StateHidden, + StateDocked, + StateFloating + }; + + enum eToolBarStyleSource + { + ToolBarStyleFromDockManager, + ToolBarStyleFromDockWidget + }; + + /** + * 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 + * 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 + * area or that contains a scroll area. + * If the InsertMode is AutoScrollArea, the DockWidget tries to automatically + * detect how to insert the given widget. If the widget is derived from + * QScrollArea (i.e. an QAbstractItemView), then the widget is inserted + * directly. If the given widget is not a scroll area, the widget will be + * inserted into a scroll area. + * To force insertion into a scroll area, you can also provide the InsertMode + * ForceScrollArea. To prevent insertion into a scroll area, you can + * provide the InsertMode ForceNoScrollArea + */ + enum eInsertMode + { + AutoScrollArea, + ForceScrollArea, + ForceNoScrollArea + }; + + + /** + * The mode of the minimumSizeHint() that is returned by the DockWidget + * minimumSizeHint() function. + * To ensure, that a dock widget does not block resizing, the dock widget + * reimplements minimumSizeHint() function to return a very small minimum + * size hint. If you would like to adhere the minimumSizeHint() from the + * content widget, then set the minimumSizeHintMode() to + * MinimumSizeHintFromContent. If you would like to use the minimumSize() + * value of the content widget or the dock widget, then you can use the + * MinimumSizeHintFromDockWidgetMinimumSize or + * MinimumSizeHintFromContentMinimumSize modes. + */ + enum eMinimumSizeHintMode + { + MinimumSizeHintFromDockWidget, + MinimumSizeHintFromContent, + MinimumSizeHintFromDockWidgetMinimumSize, + MinimumSizeHintFromContentMinimumSize, + }; + + + /** + * This mode configures the behavior of the toggle view action. + * If the mode if ActionModeToggle, then the toggle view action is + * a checkable action to show / hide the dock widget. If the mode + * is ActionModeShow, then the action is not checkable an it will + * always show the dock widget if clicked. If the mode is ActionModeShow, + * the user can only close the DockWidget with the close button. + */ + enum eToggleViewActionMode + { + ActionModeToggle,//!< ActionModeToggle + ActionModeShow //!< ActionModeShow + }; + + + /** + * This constructor creates a dock widget with the given title. + * The title is the text that is shown in the window title when the dock + * widget is floating and it is the title that is shown in the titlebar + * or the tab of this dock widget if it is tabified. + * The object name of the dock widget is also set to the title. The + * object name is required by the dock manager to properly save and restore + * the state of the dock widget. That means, the title needs to be unique. + * If your title is not unique or if you would like to change the title + * during runtime, you need to set a unique object name explicitly + * by calling setObjectName() after construction. + * Use the layoutFlags to configure the layout of the dock widget. + */ + CDockWidget(const QString &title, QWidget* parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~CDockWidget(); + + /** + * We return a fixed minimum size hint or the size hint of the content + * widget if minimum size hint mode is MinimumSizeHintFromContent + */ + virtual QSize minimumSizeHint() const override; + + /** + * 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 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 + * area or that contains a scroll area. + * If the InsertMode is AutoScrollArea, the DockWidget tries to automatically + * detect how to insert the given widget. If the widget is derived from + * QScrollArea (i.e. an QAbstractItemView), then the widget is inserted + * directly. If the given widget is not a scroll area, the widget will be + * inserted into a scroll area. + * To force insertion into a scroll area, you can also provide the InsertMode + * ForceScrollArea. To prevent insertion into a scroll area, you can + * 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; + void setWidgetFactory(FactoryFunc createWidget, eInsertMode InsertMode = AutoScrollArea); + + /** + * Remove the widget from the dock and give ownership back to the caller + */ + QWidget* takeWidget(); + + /** + * Returns the widget for the dock widget. This function returns zero if + * the widget has not been set. + */ + QWidget* widget() const; + + /** + * Returns the tab widget of this dock widget that is shown in the dock + * area title bar + */ + CDockWidgetTab* tabWidget() const; + + /** + * Sets, whether the dock widget is movable, closable, and floatable. + */ + void setFeatures(DockWidgetFeatures features); + + /** + * Sets the feature flag for this dock widget if on is true; otherwise + * clears the flag. + */ + void setFeature(DockWidgetFeature flag, bool on); + + /** + * This property holds whether the dock widget is movable, closable, and + * floatable. + * By default, this property is set to a combination of DockWidgetClosable, + * DockWidgetMovable and DockWidgetFloatable. + */ + DockWidgetFeatures features() const; + + /** + * Triggers notification of feature change signals and functions + */ + void notifyFeaturesChanged(); + + /** + * Returns the dock manager that manages the dock widget or 0 if the widget + * has not been assigned to any dock manager yet + */ + CDockManager* dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if this dock widget has not been docked yet + */ + 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 + */ + CDockAreaWidget* dockAreaWidget() const; + + /** + * Returns the side tab widget for this dock, if this dock widget is in + * a auto hide container. If it is not in a auto hide container, then this + * function returns a nullptr, + */ + CAutoHideTab* sideTabWidget() const; + + /** + * Assign a side tab widget if this dock widget is an auto hide container + */ + void setSideTabWidget(CAutoHideTab* SideTab) const; + + /** + * Returns true, if this dock widget is in an auto hide container + */ + bool isAutoHide() const; + + /** + * Returns the auto hide dock container of this dock widget + * or 0 if there is none + */ + CAutoHideDockContainer* autoHideDockContainer() const; + + /** + * Returns the auto hide side bar location or SideBarNone if, this is not + * an autohide dock widget + */ + SideBarLocation autoHideLocation() const; + + /** + * This property holds whether the dock widget is floating. + * A dock widget is only floating, if it is the one and only widget inside + * of a floating container. If there are more than one dock widget in a + * floating container, the all dock widgets are docked and not floating. + */ + bool isFloating() const; + + /** + * This function returns true, if this dock widget is in a floating. + * The function returns true, if the dock widget is floating and it also + * returns true if it is docked inside of a floating container. + */ + bool isInFloatingContainer() const; + + /** + * Returns true, if this dock widget is closed. + */ + bool isClosed() const; + + /** + * Returns a checkable action that can be used to show or close this dock widget. + * The action's text is set to the dock widget's window title. + */ + QAction* toggleViewAction() const; + + /** + * Use provided action to be the default toggle view action for this dock widget. + * This dock widget now owns the action. + */ + void setToggleViewAction(QAction* action); + + /** + * Configures the behavior of the toggle view action. + * \see eToggleViewActionMode for a detailed description + */ + void setToggleViewActionMode(eToggleViewActionMode Mode); + + /** + * Configures the minimum size hint that is returned by the + * minimumSizeHint() function. + * \see eMinimumSizeHintMode for a detailed description + */ + void setMinimumSizeHintMode(eMinimumSizeHintMode Mode); + + /** + * Get the minimum size hint mode configured by setMinimumSizeHintMode + */ + eMinimumSizeHintMode minimumSizeHintMode() const; + + /** + * Returns true if the dock widget is set as central widget of it's dock manager + */ + bool isCentralWidget() const; + + /** + * Sets the dock widget icon that is shown in tabs and in toggle view + * actions + */ + void setIcon(const QIcon& Icon); + + /** + * Returns the icon that has been assigned to the dock widget + */ + QIcon icon() const; + + /** + * This function returns the dock widget top tool bar. + * If no toolbar is assigned, this function returns nullptr. To get a valid + * toolbar you either need to create a default empty toolbar via + * createDefaultToolBar() function or you need to assign your custom + * toolbar via setToolBar(). + */ + QToolBar* toolBar() const; + + /** + * If you would like to use the default top tool bar, then call this + * function to create the default tool bar. + * After this function the toolBar() function will return a valid toolBar() + * object. + */ + QToolBar* createDefaultToolBar(); + + /** + * Assign a new tool bar that is shown above the content widget. + * The dock widget will become the owner of the tool bar and deletes it + * on destruction + */ + void setToolBar(QToolBar* ToolBar); + + /** + * Configures, if the dock widget uses the global tool bar styles from + * dock manager or if it uses its own tool bar style + */ + void setToolBarStyleSource(eToolBarStyleSource Source); + + /** + * Returns the configured tool bar style source + */ + eToolBarStyleSource toolBarStyleSource() const; + + /** + * This function sets the tool button style for the given dock widget state. + * It is possible to switch the tool button style depending on the state. + * If a dock widget is floating, then here are more space and it is + * possible to select a style that requires more space like + * Qt::ToolButtonTextUnderIcon. For the docked state Qt::ToolButtonIconOnly + * might be better. + */ + void setToolBarStyle(Qt::ToolButtonStyle Style, eState State); + + /** + * Returns the tool button style for the given docking state. + * \see setToolBarStyle() + */ + Qt::ToolButtonStyle toolBarStyle(eState State) const; + + /** + * This function sets the tool button icon size for the given state. + * If a dock widget is floating, there is more space an increasing the + * icon size is possible. For docked widgets, small icon sizes, eg. 16 x 16 + * might be better. + */ + void setToolBarIconSize(const QSize& IconSize, eState State); + + /** + * Returns the icon size for a given docking state. + * \see setToolBarIconSize() + */ + QSize toolBarIconSize(eState State) const; + + /** + * Set the actions that will be shown in the dock area title bar + * if this dock widget is the active tab. + * You should not add to many actions to the title bar, because this + * will remove the available space for the tabs. If you have a number + * of actions, just add an action with a menu to show a popup menu + * button in the title bar. + */ + void setTitleBarActions(QList actions); + + /** + * Returns a list of actions that will be inserted into the dock area title + * bar if this dock widget becomes the current widget + */ + virtual QList titleBarActions() const; + + +#ifndef QT_NO_TOOLTIP + /** + * This is function sets text tooltip for title bar widget + * and tooltip for toggle view action + */ + void setTabToolTip(const QString &text); +#endif + + /** + * Returns true if the dock widget is floating and if the floating dock + * container is full screen + */ + bool isFullScreen() const; + + /** + * Returns true if this dock widget is in a dock area, that contains at + * least 2 opened dock widgets + */ + bool isTabbed() const; + + /** + * Returns true if this dock widget is the current one in the dock + * area widget that contains it. + * If the dock widget is the only opened dock widget in a dock area, + * the true is returned + */ + bool isCurrentTab() const; + +public: // reimplements QFrame ----------------------------------------------- + /** + * Emits titleChanged signal if title change event occurs + */ + virtual bool event(QEvent *e) override; + +public Q_SLOTS: + /** + * This property controls whether the dock widget is open or closed. + * The toogleViewAction triggers this slot + */ + void toggleView(bool Open = true); + + /** + * Makes this dock widget the current tab in its dock area. + * The function only has an effect, if the dock widget is open. A call + * to this function will not toggle the view, so if it is closed, + * nothing will happen + */ + void setAsCurrentTab(); + + /** + * Brings the dock widget to the front + * This means: + * - If the dock widget is tabbed with other dock widgets but its tab is not current, it's made current. + * - If the dock widget is floating, QWindow::raise() is called. + * This only applies if the dock widget is already open. If closed, does nothing. + */ + void raise(); + + /** + * This function will make a docked widget floating + */ + void setFloating(); + + /** + * This function will delete the dock widget and its content from the + * docking system + */ + void deleteDockWidget(); + + /** + * Closes the dock widget. + * The function forces closing of the dock widget even for CustomCloseHandling. + */ + void closeDockWidget(); + + /** + * Request closing of the dock widget. + * For DockWidget with default close handling, the function does the same + * like clodeDockWidget() but if the flas CustomCloseHandling is set, + * the function only emits the closeRequested() signal. + */ + void requestCloseDockWidget(); + + /** + * Shows the widget in full-screen mode. + * Normally this function only affects windows. To make the interface + * compatible to QDockWidget, this function also maximizes a floating + * dock widget. + * + * \note Full-screen mode works fine under Windows, but has certain + * problems (doe not work) under X (Linux). These problems are due to + * limitations of the ICCCM protocol that specifies the communication + * between X11 clients and the window manager. ICCCM simply does not + * understand the concept of non-decorated full-screen windows. + */ + void showFullScreen(); + + /** + * This function complements showFullScreen() to restore the widget + * after it has been in full screen mode. + */ + void showNormal(); + + /** + * Sets the dock widget into auto hide mode if this feature is enabled + * via CDockManager::setAutoHideFlags(CDockManager::AutoHideFeatureEnabled) + */ + void setAutoHide(bool Enable, SideBarLocation Location = SideBarNone, int TabIndex = -1); + + /** + * Switches the dock widget to auto hide mode or vice versa depending on its + * current state. + */ + void toggleAutoHide(SideBarLocation Location = SideBarNone); + + +Q_SIGNALS: + /** + * This signal is emitted if the dock widget is opened or closed + */ + void viewToggled(bool Open); + + /** + * This signal is emitted if the dock widget is closed + */ + void closed(); + + /** + * This signal is emitted if the window title of this dock widget + * changed + */ + void titleChanged(const QString& Title); + + /** + * This signal is emitted when the floating property changes. + * The topLevel parameter is true if the dock widget is now floating; + * otherwise it is false. + */ + void topLevelChanged(bool topLevel); + + /** + * This signal is emitted, if close is requested + */ + void closeRequested(); + + /** + * This signal is emitted when the dock widget becomes visible (or invisible). + * This happens when the widget is hidden or shown, as well as when it is + * docked in a tabbed dock area and its tab becomes selected or unselected. + */ + void visibilityChanged(bool visible); + + /** + * This signal is emitted when the features property changes. + * The features parameter gives the new value of the property. + */ + void featuresChanged(ads::CDockWidget::DockWidgetFeatures features); +}; // class DockWidget +} // namespace ads + +Q_DECLARE_OPERATORS_FOR_FLAGS(ads::CDockWidget::DockWidgetFeatures) +//----------------------------------------------------------------------------- +#endif // DockWidgetH diff --git a/src/DockWidgetTab.cpp b/src/DockWidgetTab.cpp new file mode 100644 index 000000000..1380288b3 --- /dev/null +++ b/src/DockWidgetTab.cpp @@ -0,0 +1,843 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockWidgetTab.cpp +/// \author Uwe Kindler +/// \date 27.02.2017 +/// \brief Implementation of CDockWidgetTab class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "FloatingDragPreview.h" +#include "ElidingLabel.h" +#include "DockWidgetTab.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ads_globals.h" +#include "DockWidget.h" +#include "DockAreaWidget.h" +#include "FloatingDockContainer.h" +#include "DockOverlay.h" +#include "DockManager.h" +#include "IconProvider.h" +#include "DockFocusController.h" + + +namespace ads +{ +using tTabLabel = CElidingLabel; + +/** + * Private data class of CDockWidgetTab class (pimpl) + */ +struct DockWidgetTabPrivate +{ + CDockWidgetTab* _this; + CDockWidget* DockWidget; + QLabel* IconLabel = nullptr; + tTabLabel* TitleLabel; + QPoint GlobalDragStartMousePosition; + QPoint DragStartMousePosition; + bool IsActiveTab = false; + CDockAreaWidget* DockArea = nullptr; + eDragState DragState = DraggingInactive; + IFloatingWidget* FloatingWidget = nullptr; + QIcon Icon; + QAbstractButton* CloseButton = nullptr; + QSpacerItem* IconTextSpacer; + QPoint TabDragStartPosition; + QSize IconSize; + + /** + * Private data constructor + */ + DockWidgetTabPrivate(CDockWidgetTab* _public); + + /** + * Creates the complete layout including all controls + */ + void createLayout(); + + /** + * Moves the tab depending on the position in the given mouse event + */ + void moveTab(QMouseEvent* ev); + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) const + { + return this->DragState == dragState; + } + + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(eDragState DraggingState = DraggingFloatingWidget); + + /** + * Returns true if the given config flag is set + */ + bool testConfigFlag(CDockManager::eConfigFlag Flag) const + { + return CDockManager::testConfigFlag(Flag); + } + + /** + * Creates the close button as QPushButton or as QToolButton + */ + QAbstractButton* createCloseButton() const + { + if (testConfigFlag(CDockManager::TabCloseButtonIsToolButton)) + { + auto Button = new QToolButton(); + Button->setAutoRaise(true); + return Button; + } + else + { + return new QPushButton(); + } + } + + /** + * Update the close button visibility from current feature/config + */ + void updateCloseButtonVisibility(bool active) + { + bool DockWidgetClosable = DockWidget->features().testFlag(CDockWidget::DockWidgetClosable); + bool ActiveTabHasCloseButton = testConfigFlag(CDockManager::ActiveTabHasCloseButton); + bool AllTabsHaveCloseButton = testConfigFlag(CDockManager::AllTabsHaveCloseButton); + bool TabHasCloseButton = (ActiveTabHasCloseButton && active) | AllTabsHaveCloseButton; + CloseButton->setVisible(DockWidgetClosable && TabHasCloseButton); + } + + /** + * Update the size policy of the close button depending on the + * RetainTabSizeWhenCloseButtonHidden feature + */ + void updateCloseButtonSizePolicy() + { + auto Features = DockWidget->features(); + auto SizePolicy = CloseButton->sizePolicy(); + SizePolicy.setRetainSizeWhenHidden(Features.testFlag(CDockWidget::DockWidgetClosable) + && testConfigFlag(CDockManager::RetainTabSizeWhenCloseButtonHidden)); + CloseButton->setSizePolicy(SizePolicy); + } + + template + IFloatingWidget* createFloatingWidget(T* Widget, bool CreateContainer) + { + if (CreateContainer) + { + return new CFloatingDockContainer(Widget); + } + else + { + auto w = new CFloatingDragPreview(Widget); + _this->connect(w, &CFloatingDragPreview::draggingCanceled, [=]() + { + DragState = DraggingInactive; + }); + return w; + } + } + + /** + * Saves the drag start position in global and local coordinates + */ + void saveDragStartMousePosition(const QPoint& GlobalPos) + { + GlobalDragStartMousePosition = GlobalPos; + DragStartMousePosition = _this->mapFromGlobal(GlobalPos); + } + + /** + * Update the icon in case the icon size changed + */ + void updateIcon() + { + if (!IconLabel || Icon.isNull()) + { + return; + } + + if (IconSize.isValid()) + { + IconLabel->setPixmap(Icon.pixmap(IconSize)); + } + else + { + IconLabel->setPixmap(Icon.pixmap(_this->style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, _this))); + } + IconLabel->setVisible(true); + } + + /** + * Convenience function for access to the dock manager dock focus controller + */ + CDockFocusController* focusController() const + { + return DockWidget->dockManager()->dockFocusController(); + } + + /** + * Helper function to create and initialize the menu entries for + * the "Auto Hide Group To..." menu + */ + QAction* createAutoHideToAction(const QString& Title, SideBarLocation Location, + QMenu* Menu) + { + auto Action = Menu->addAction(Title); + Action->setProperty(internal::LocationProperty, Location); + QObject::connect(Action, &QAction::triggered, _this, &CDockWidgetTab::onAutoHideToActionClicked); + return Action; + } +}; +// struct DockWidgetTabPrivate + + +//============================================================================ +DockWidgetTabPrivate::DockWidgetTabPrivate(CDockWidgetTab* _public) : + _this(_public) +{ + +} + + +//============================================================================ +void DockWidgetTabPrivate::createLayout() +{ + TitleLabel = new tTabLabel(); + if (CDockManager::testConfigFlag(CDockManager::DisableTabTextEliding)) + { + TitleLabel->setElideMode(Qt::ElideNone); + } + else + { + TitleLabel->setElideMode(Qt::ElideRight); + } + TitleLabel->setText(DockWidget->windowTitle()); + TitleLabel->setObjectName("dockWidgetTabLabel"); + TitleLabel->setAlignment(Qt::AlignCenter); + _this->connect(TitleLabel, SIGNAL(elidedChanged(bool)), SIGNAL(elidedChanged(bool))); + + + CloseButton = createCloseButton(); + 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())); + + QFontMetrics fm(TitleLabel->font()); + int Spacing = qRound(fm.height() / 4.0); + + // Fill the layout + QBoxLayout* Layout = new QBoxLayout(QBoxLayout::LeftToRight); + Layout->setContentsMargins(2 * Spacing,0,0,0); + Layout->setSpacing(0); + _this->setLayout(Layout); + Layout->addWidget(TitleLabel, 1); + Layout->addSpacing(Spacing); + Layout->addWidget(CloseButton); + Layout->addSpacing(qRound(Spacing * 4.0 / 3.0)); + Layout->setAlignment(Qt::AlignCenter); + + TitleLabel->setVisible(true); +} + +//============================================================================ +void DockWidgetTabPrivate::moveTab(QMouseEvent* ev) +{ + ev->accept(); + QPoint Distance = internal::globalPositionOf(ev) - GlobalDragStartMousePosition; + Distance.setY(0); + auto TargetPos = Distance + TabDragStartPosition; + TargetPos.rx() = qMax(TargetPos.x(), 0); + TargetPos.rx() = qMin(_this->parentWidget()->rect().right() - _this->width() + 1, TargetPos.rx()); + _this->move(TargetPos); + _this->raise(); +} + + +//============================================================================ +bool DockWidgetTabPrivate::startFloating(eDragState DraggingState) +{ + auto dockContainer = DockWidget->dockContainer(); + ADS_PRINT("isFloating " << dockContainer->isFloating()); + ADS_PRINT("areaCount " << dockContainer->dockAreaCount()); + ADS_PRINT("widgetCount " << DockWidget->dockAreaWidget()->dockWidgetsCount()); + // if this is the last dock widget inside of this floating widget, + // then it does not make any sense, to make it floating because + // it is already floating + if (dockContainer->isFloating() + && (dockContainer->visibleDockAreaCount() == 1) + && (DockWidget->dockAreaWidget()->dockWidgetsCount() == 1)) + { + return false; + } + + ADS_PRINT("startFloating"); + DragState = DraggingState; + IFloatingWidget* FloatingWidget = nullptr; + bool CreateContainer = (DraggingFloatingWidget != DraggingState); + + // If section widget has multiple tabs, we take only one tab + // If it has only one single tab, we can move the complete + // dock area into floating widget + QSize Size; + if (DockArea->dockWidgetsCount() > 1) + { + FloatingWidget = createFloatingWidget(DockWidget, CreateContainer); + Size = DockWidget->size(); + } + else + { + FloatingWidget = createFloatingWidget(DockArea, CreateContainer); + Size = DockArea->size(); + } + + if (DraggingFloatingWidget == DraggingState) + { + FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingFloatingWidget, _this); + auto DockManager = DockWidget->dockManager(); + auto Overlay = DockManager->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + this->FloatingWidget = FloatingWidget; + qApp->postEvent(DockWidget, new QEvent((QEvent::Type)internal::DockedWidgetDragStartEvent)); + } + else + { + FloatingWidget->startFloating(DragStartMousePosition, Size, DraggingInactive, nullptr); + } + + return true; +} + + +//============================================================================ +CDockWidgetTab::CDockWidgetTab(CDockWidget* DockWidget, QWidget *parent) : + QFrame(parent), + d(new DockWidgetTabPrivate(this)) +{ + setAttribute(Qt::WA_NoMousePropagation, true); + d->DockWidget = DockWidget; + d->createLayout(); + setFocusPolicy(Qt::NoFocus); +} + +//============================================================================ +CDockWidgetTab::~CDockWidgetTab() +{ + ADS_PRINT("~CDockWidgetTab()"); + delete d; +} + + +//============================================================================ +void CDockWidgetTab::mousePressEvent(QMouseEvent* ev) +{ + if (ev->button() == Qt::LeftButton) + { + ev->accept(); + d->saveDragStartMousePosition(internal::globalPositionOf(ev)); + d->DragState = DraggingMousePressed; + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + d->focusController()->setDockWidgetTabPressed(true); + d->focusController()->setDockWidgetTabFocused(this); + } + Q_EMIT clicked(); + return; + } + Super::mousePressEvent(ev); +} + + + +//============================================================================ +void CDockWidgetTab::mouseReleaseEvent(QMouseEvent* ev) +{ + if (ev->button() == Qt::LeftButton) + { + auto CurrentDragState = d->DragState; + d->GlobalDragStartMousePosition = QPoint(); + d->DragStartMousePosition = QPoint(); + d->DragState = DraggingInactive; + + switch (CurrentDragState) + { + case DraggingTab: + // 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: + break; + } + + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + d->focusController()->setDockWidgetTabPressed(false); + } + } + 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); +} + + +//============================================================================ +void CDockWidgetTab::mouseMoveEvent(QMouseEvent* ev) +{ + if (!(ev->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) + { + d->DragState = DraggingInactive; + Super::mouseMoveEvent(ev); + return; + } + + // move floating window + if (d->isDraggingState(DraggingFloatingWidget)) + { + d->FloatingWidget->moveFloating(); + Super::mouseMoveEvent(ev); + return; + } + + // move tab + if (d->isDraggingState(DraggingTab)) + { + // Moving the tab is always allowed because it does not mean moving the + // dock widget around + d->moveTab(ev); + } + + auto MappedPos = mapToParent(ev->pos()); + bool MouseOutsideBar = (MappedPos.x() < 0) || (MappedPos.x() > parentWidget()->rect().right()); + // Maybe a fixed drag distance is better here ? + int DragDistanceY = qAbs(d->GlobalDragStartMousePosition.y() - internal::globalPositionOf(ev).y()); + if (DragDistanceY >= CDockManager::startDragDistance() || MouseOutsideBar) + { + // If this is the last dock area in a dock container with only + // one single dock widget 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->openDockWidgetsCount() == 1 + && d->DockArea->dockContainer()->visibleDockAreaCount() == 1) + { + return; + } + + + // Floating is only allowed for widgets that are floatable + // We can create the drag preview if the widget is movable. + auto Features = d->DockWidget->features(); + if (Features.testFlag(CDockWidget::DockWidgetFloatable) || (Features.testFlag(CDockWidget::DockWidgetMovable))) + { + // If we undock, we need to restore the initial position of this + // tab because it looks strange if it remains on its dragged position + if (d->isDraggingState(DraggingTab)) + { + parentWidget()->layout()->update(); + } + d->startFloating(); + } + return; + } + else if (d->DockArea->openDockWidgetsCount() > 1 + && (internal::globalPositionOf(ev) - d->GlobalDragStartMousePosition).manhattanLength() >= QApplication::startDragDistance()) // Wait a few pixels before start moving + { + // If we start dragging the tab, we save its initial position to + // restore it later + if (DraggingTab != d->DragState) + { + d->TabDragStartPosition = this->pos(); + } + d->DragState = DraggingTab; + return; + } + + Super::mouseMoveEvent(ev); +} + + +//============================================================================ +void CDockWidgetTab::contextMenuEvent(QContextMenuEvent* ev) +{ + ev->accept(); + if (d->isDraggingState(DraggingFloatingWidget)) + { + return; + } + + auto Menu = buildContextMenu(nullptr); + d->saveDragStartMousePosition(ev->globalPos()); + Menu->exec(ev->globalPos()); +} + +QMenu* CDockWidgetTab::buildContextMenu(QMenu *Menu) +{ + if (Menu == nullptr) { + Menu = new QMenu(this); + } + + const bool isFloatable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable); + const bool isNotOnlyTabInContainer = !d->DockArea->dockContainer()->hasTopLevelDockWidget(); + const bool isTopLevelArea = d->DockArea->isTopLevelArea(); + const bool isDetachable = isFloatable && isNotOnlyTabInContainer; + QAction* Action; + + if (!isTopLevelArea) + { + Action = Menu->addAction(tr("Detach"), this, SLOT(detachDockWidget())); + Action->setEnabled(isDetachable); + if (CDockManager::testAutoHideConfigFlag(CDockManager::AutoHideFeatureEnabled)) + { + Action = Menu->addAction(tr("Pin"), this, SLOT(autoHideDockWidget())); + auto IsPinnable = d->DockWidget->features().testFlag(CDockWidget::DockWidgetPinnable); + Action->setEnabled(IsPinnable); + + auto menu = Menu->addMenu(tr("Pin To...")); + menu->setEnabled(IsPinnable); + d->createAutoHideToAction(tr("Top"), SideBarTop, menu); + d->createAutoHideToAction(tr("Left"), SideBarLeft, menu); + d->createAutoHideToAction(tr("Right"), SideBarRight, menu); + d->createAutoHideToAction(tr("Bottom"), SideBarBottom, menu); + } + } + + Menu->addSeparator(); + Action = Menu->addAction(tr("Close"), this, SIGNAL(closeRequested())); + Action->setEnabled(isClosable()); + if (d->DockArea->openDockWidgetsCount() > 1) + { + Action = Menu->addAction(tr("Close Others"), this, SIGNAL(closeOtherTabsRequested())); + } + + return Menu; +} +//============================================================================ +bool CDockWidgetTab::isActiveTab() const +{ + return d->IsActiveTab; +} + + +//============================================================================ +void CDockWidgetTab::setActiveTab(bool active) +{ + d->updateCloseButtonVisibility(active); + + if(CDockManager::testConfigFlag(CDockManager::ShowTabTextOnlyForActiveTab) && !d->Icon.isNull()) + { + if(active) + d->TitleLabel->setVisible(true); + else + d->TitleLabel->setVisible(false); + } + + // Focus related stuff + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting) && !d->DockWidget->dockManager()->isRestoringState()) + { + bool UpdateFocusStyle = false; + if (active && !hasFocus()) + { + //setFocus(Qt::OtherFocusReason); + d->focusController()->setDockWidgetTabFocused(this); + UpdateFocusStyle = true; + } + + if (d->IsActiveTab == active) + { + if (UpdateFocusStyle) + { + updateStyle(); + } + return; + } + } + else if (d->IsActiveTab == active) + { + return; + } + + d->IsActiveTab = active; + updateStyle(); + update(); + updateGeometry(); + + Q_EMIT activeTabChanged(); +} + + +//============================================================================ +CDockWidget* CDockWidgetTab::dockWidget() const +{ + return d->DockWidget; +} + + +//============================================================================ +void CDockWidgetTab::setDockAreaWidget(CDockAreaWidget* DockArea) +{ + d->DockArea = DockArea; +} + + +//============================================================================ +CDockAreaWidget* CDockWidgetTab::dockAreaWidget() const +{ + return d->DockArea; +} + + +//============================================================================ +void CDockWidgetTab::setIcon(const QIcon& Icon) +{ + QBoxLayout* Layout = qobject_cast(layout()); + if (!d->IconLabel && Icon.isNull()) + { + return; + } + + if (!d->IconLabel) + { + d->IconLabel = new QLabel(); + d->IconLabel->setAlignment(Qt::AlignVCenter); + d->IconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + internal::setToolTip(d->IconLabel, d->TitleLabel->toolTip()); + Layout->insertWidget(0, d->IconLabel, Qt::AlignVCenter); + Layout->insertSpacing(1, qRound(1.5 * Layout->contentsMargins().left() / 2.0)); + } + else if (Icon.isNull()) + { + // Remove icon label and spacer item + Layout->removeWidget(d->IconLabel); + Layout->removeItem(Layout->itemAt(0)); + delete d->IconLabel; + d->IconLabel = nullptr; + } + + d->Icon = Icon; + d->updateIcon(); +} + + +//============================================================================ +const QIcon& CDockWidgetTab::icon() const +{ + return d->Icon; +} + + +//============================================================================ +QString CDockWidgetTab::text() const +{ + return d->TitleLabel->text(); +} + + +//============================================================================ +void CDockWidgetTab::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + // 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); +} + + +//============================================================================ +void CDockWidgetTab::setVisible(bool visible) +{ + visible &= !d->DockWidget->features().testFlag(CDockWidget::NoTab); + Super::setVisible(visible); +} + + +//============================================================================ +void CDockWidgetTab::setText(const QString& title) +{ + d->TitleLabel->setText(title); +} + + +//============================================================================ +bool CDockWidgetTab::isTitleElided() const +{ + return d->TitleLabel->isElided(); +} + + + +//============================================================================ +bool CDockWidgetTab::isClosable() const +{ + return d->DockWidget && d->DockWidget->features().testFlag(CDockWidget::DockWidgetClosable); +} + + +//=========================================================================== +void CDockWidgetTab::detachDockWidget() +{ + if (!d->DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + return; + } + + d->saveDragStartMousePosition(QCursor::pos()); + d->startFloating(DraggingInactive); +} + + +//=========================================================================== +void CDockWidgetTab::autoHideDockWidget() +{ + d->DockWidget->setAutoHide(true); +} + + +//=========================================================================== +void CDockWidgetTab::onAutoHideToActionClicked() +{ + int Location = sender()->property(internal::LocationProperty).toInt(); + d->DockWidget->toggleAutoHide((SideBarLocation)Location); +} + + +//============================================================================ +bool CDockWidgetTab::event(QEvent *e) +{ +#ifndef QT_NO_TOOLTIP + if (e->type() == QEvent::ToolTipChange) + { + const auto text = toolTip(); + d->TitleLabel->setToolTip(text); + if (d->IconLabel) { + d->IconLabel->setToolTip(text); + } + } +#endif + if (e->type() == QEvent::StyleChange) + { + d->updateIcon(); + } + return Super::event(e); +} + + +//============================================================================ +eDragState CDockWidgetTab::dragState() const +{ + return d->DragState; +} + + +//============================================================================ +void CDockWidgetTab::onDockWidgetFeaturesChanged() +{ + d->updateCloseButtonSizePolicy(); + d->updateCloseButtonVisibility(isActiveTab()); +} + + +//============================================================================ +void CDockWidgetTab::setElideMode(Qt::TextElideMode mode) +{ + d->TitleLabel->setElideMode(mode); +} + + +//============================================================================ +void CDockWidgetTab::updateStyle() +{ + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + + +//============================================================================ +QSize CDockWidgetTab::iconSize() const +{ + return d->IconSize; +} + +//============================================================================ +void CDockWidgetTab::setIconSize(const QSize& Size) +{ + d->IconSize = Size; + d->updateIcon(); +} + +} // namespace ads +//--------------------------------------------------------------------------- +// EOF DockWidgetTab.cpp diff --git a/src/DockWidgetTab.h b/src/DockWidgetTab.h new file mode 100644 index 000000000..02781affd --- /dev/null +++ b/src/DockWidgetTab.h @@ -0,0 +1,217 @@ +#ifndef DockWidgetTabH +#define DockWidgetTabH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file DockWidgetTab.h +/// \author Uwe Kindler +/// \date 27.02.2017 +/// \brief Declaration of CDockWidgetTab class +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include + +#include "ads_globals.h" + +QT_FORWARD_DECLARE_CLASS(QMenu) + +namespace ads +{ +class CDockWidget; +class CDockAreaWidget; +struct DockWidgetTabPrivate; +class CDockManager; + +/** + * A dock widget tab that shows a title and an icon. + * The dock widget tab is shown in the dock area title bar to switch between + * tabbed dock widgets + */ +class ADS_EXPORT CDockWidgetTab : public QFrame +{ + Q_OBJECT + Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) + Q_PROPERTY(QSize iconSize READ iconSize WRITE setIconSize) + +private: + DockWidgetTabPrivate* d; ///< private data (pimpl) + friend struct DockWidgetTabPrivate; + friend class CDockWidget; + friend class CDockManager; + friend class CAutoHideDockContainer; + void onDockWidgetFeaturesChanged(); + +private Q_SLOTS: + void detachDockWidget(); + void autoHideDockWidget(); + void onAutoHideToActionClicked(); + +protected: + virtual void mousePressEvent(QMouseEvent* ev) override; + virtual void mouseReleaseEvent(QMouseEvent* ev) override; + virtual void mouseMoveEvent(QMouseEvent* ev) override; + virtual void contextMenuEvent(QContextMenuEvent* ev) override; + + /** + * Double clicking the tab widget makes the assigned dock widget floating + */ + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + +public: + using Super = QFrame; + /** + * Default Constructor + * param[in] DockWidget The dock widget this title bar belongs to + * param[in] parent The parent widget of this title bar + */ + CDockWidgetTab(CDockWidget* DockWidget, QWidget* parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~CDockWidgetTab(); + + /** + * Returns true, if this is the active tab + */ + bool isActiveTab() const; + + /** + * Set this true to make this tab the active tab + */ + void setActiveTab(bool active); + + /** + * Sets the dock area widget the dockWidget returned by dockWidget() + * function belongs to. + */ + void setDockAreaWidget(CDockAreaWidget* DockArea); + + /** + * Returns the dock area widget this title bar belongs to. + * \return This function returns 0 if the dock widget that owns this title + * bar widget has not been added to any dock area yet. + */ + CDockAreaWidget* dockAreaWidget() const; + + /** + * Returns the dock widget this title widget belongs to + */ + CDockWidget* dockWidget() const; + + /** + * Sets the icon to show in title bar + */ + void setIcon(const QIcon& Icon); + + /** + * Returns the icon + */ + const QIcon& icon() const; + + /** + * Returns the tab text + */ + QString text() const; + + /** + * Sets the tab text + */ + void setText(const QString& title); + + /** + * Returns true if text is elided on the tab's title + */ + bool isTitleElided() const; + + /** + * This function returns true if the assigned dock widget is closable + */ + bool isClosable() const; + + /** + * Track event ToolTipChange and set child ToolTip + */ + virtual bool event(QEvent *e) override; + + /** + * Sets the text elide mode + */ + void setElideMode(Qt::TextElideMode mode); + + /** + * Update stylesheet style if a property changes + */ + void updateStyle(); + + /** + * Returns the icon size. + * If no explicit icon size has been set, the function returns an invalid + * QSize + */ + QSize iconSize() const; + + /** + * Set an explicit icon size. + * If no icon size has been set explicitly, than the tab sets the icon size + * depending on the style + */ + void setIconSize(const QSize& Size); + + /** + * Returns the current drag state of this tab. + * Use this function to determine if the tab is currently being dragged + */ + eDragState dragState() const; + + /** + * Fills the provided menu with standard entries. If a nullptr is passed, a + * new menu is created and filled with standard entries. + * This function is called from the actual version of contextMenuEvent, but + * can be called from any code. Caller is responsible of deleting the created + * object. + * + * @param menu The QMenu to fill with standard entries. If nullptr, a new + * QMenu will be created. + * @return The filled QMenu, either the provided one or a newly created one if + * nullptr was passed. + */ + virtual QMenu *buildContextMenu(QMenu *); + +public Q_SLOTS: + virtual void setVisible(bool visible) override; + +Q_SIGNALS: + void activeTabChanged(); + void clicked(); + void closeRequested(); + void closeOtherTabsRequested(); + void moved(const QPoint& GlobalPos); + void elidedChanged(bool elided); +}; // class DockWidgetTab +} + // namespace ads +//----------------------------------------------------------------------------- +#endif // DockWidgetTabH diff --git a/src/DockingStateReader.cpp b/src/DockingStateReader.cpp new file mode 100644 index 000000000..4ae0f3c07 --- /dev/null +++ b/src/DockingStateReader.cpp @@ -0,0 +1,30 @@ +//============================================================================ +/// \file DockingStateReader.cpp +/// \author Uwe Kindler +/// \date 29.11.2019 +/// \brief Implementation of CDockingStateReader +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockingStateReader.h" + +namespace ads +{ + +//============================================================================ +void CDockingStateReader::setFileVersion(int FileVersion) +{ + m_FileVersion = FileVersion; +} + +//============================================================================ +int CDockingStateReader::fileVersion() const +{ + return m_FileVersion; +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockingStateReader.cpp diff --git a/src/DockingStateReader.h b/src/DockingStateReader.h new file mode 100644 index 000000000..5c59dc204 --- /dev/null +++ b/src/DockingStateReader.h @@ -0,0 +1,43 @@ +#ifndef DockingStateReaderH +#define DockingStateReaderH +//============================================================================ +/// \file DockingStateReader.h +/// \author Uwe Kindler +/// \date 29.11.2019 +/// \brief Declaration of CDockingStateReader +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +namespace ads +{ + +/** + * Extends QXmlStreamReader with file version information + */ +class CDockingStateReader : public QXmlStreamReader +{ +private: + int m_FileVersion; + +public: + using QXmlStreamReader::QXmlStreamReader; + + /** + * Set the file version for this state reader + */ + void setFileVersion(int FileVersion); + + /** + * Returns the file version set via setFileVersion + */ + int fileVersion() const; +}; + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // DockingStateReaderH diff --git a/src/ElidingLabel.cpp b/src/ElidingLabel.cpp new file mode 100644 index 000000000..ac521228f --- /dev/null +++ b/src/ElidingLabel.cpp @@ -0,0 +1,231 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file ElidingLabel.cpp +/// \author Uwe Kindler +/// \date 05.11.2018 +/// \brief Implementation of CElidingLabel +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ElidingLabel.h" +#include + + +namespace ads +{ +/** + * Private data of public CClickableLabel + */ +struct ElidingLabelPrivate +{ + CElidingLabel* _this; + Qt::TextElideMode ElideMode = Qt::ElideNone; + QString Text; + bool IsElided = false; + + ElidingLabelPrivate(CElidingLabel* _public) : _this(_public) {} + + void elideText(int Width); + + /** + * Convenience function to check if the + */ + bool isModeElideNone() const + { + return Qt::ElideNone == ElideMode; + } +}; + + +//============================================================================ +void ElidingLabelPrivate::elideText(int Width) +{ + if (isModeElideNone()) + { + return; + } + QFontMetrics fm = _this->fontMetrics(); + QString str = fm.elidedText(Text, ElideMode, Width - _this->margin() * 2 - _this->indent()); + if (str == "…") + { + str = Text.at(0); + } + bool WasElided = IsElided; + IsElided = str != Text; + if(IsElided != WasElided) + { + Q_EMIT _this->elidedChanged(IsElided); + } + _this->QLabel::setText(str); +} + + +//============================================================================ +CElidingLabel::CElidingLabel(QWidget* parent, Qt::WindowFlags f) + : QLabel(parent, f), + d(new ElidingLabelPrivate(this)) +{ + +} + + +//============================================================================ +CElidingLabel::CElidingLabel(const QString& text, QWidget* parent, Qt::WindowFlags f) + : QLabel(text, parent,f), + d(new ElidingLabelPrivate(this)) +{ + d->Text = text; + internal::setToolTip(this, text); +} + + +//============================================================================ +CElidingLabel::~CElidingLabel() +{ + delete d; +} + + +//============================================================================ +Qt::TextElideMode CElidingLabel::elideMode() const +{ + return d->ElideMode; +} + + +//============================================================================ +void CElidingLabel::setElideMode(Qt::TextElideMode mode) +{ + d->ElideMode = mode; + d->elideText(size().width()); +} + +//============================================================================ +bool CElidingLabel::isElided() const +{ + return d->IsElided; +} + + +//============================================================================ +void CElidingLabel::mouseReleaseEvent(QMouseEvent* event) +{ + Super::mouseReleaseEvent(event); + if (event->button() != Qt::LeftButton) + { + return; + } + + Q_EMIT clicked(); +} + + +//============================================================================ +void CElidingLabel::mouseDoubleClickEvent( QMouseEvent *ev ) +{ + Q_UNUSED(ev) + Q_EMIT doubleClicked(); + Super::mouseDoubleClickEvent(ev); +} + + +//============================================================================ +void CElidingLabel::resizeEvent(QResizeEvent *event) +{ + if (!d->isModeElideNone()) + { + d->elideText(event->size().width()); + } + Super::resizeEvent(event); +} + + +//============================================================================ +QSize CElidingLabel::minimumSizeHint() const +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + bool HasPixmap = !pixmap(Qt::ReturnByValue).isNull(); +#else + bool HasPixmap = (pixmap() != nullptr); +#endif + if (HasPixmap || d->isModeElideNone()) + { + return QLabel::minimumSizeHint(); + } + const QFontMetrics &fm = fontMetrics(); + #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + QSize size(fm.horizontalAdvance(d->Text.left(2) + "…"), fm.height()); + #else + QSize size(fm.width(d->Text.left(2) + "…"), fm.height()); + #endif + return size; +} + + +//============================================================================ +QSize CElidingLabel::sizeHint() const +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) + bool HasPixmap = !pixmap(Qt::ReturnByValue).isNull(); +#else + bool HasPixmap = (pixmap() != nullptr); +#endif + if (HasPixmap || d->isModeElideNone()) + { + return QLabel::sizeHint(); + } + const QFontMetrics& fm = fontMetrics(); + #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) + QSize size(fm.horizontalAdvance(d->Text), QLabel::sizeHint().height()); + #else + QSize size(fm.width(d->Text), QLabel::sizeHint().height()); + #endif + return size; +} + + +//============================================================================ +void CElidingLabel::setText(const QString &text) +{ + d->Text = text; + if (d->isModeElideNone()) + { + Super::setText(text); + } + else + { + internal::setToolTip(this, text); + d->elideText(this->size().width()); + } +} + + +//============================================================================ +QString CElidingLabel::text() const +{ + return d->Text; +} +} // namespace QtLabb + +//--------------------------------------------------------------------------- +// EOF ClickableLabel.cpp diff --git a/src/ElidingLabel.h b/src/ElidingLabel.h new file mode 100644 index 000000000..e2330e68b --- /dev/null +++ b/src/ElidingLabel.h @@ -0,0 +1,108 @@ +#ifndef ElidingLabelH +#define ElidingLabelH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file ElidingLabel.h +/// \author Uwe Kindler +/// \date 05.11.2018 +/// \brief Declaration of CElidingLabel +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +namespace ads +{ +struct ElidingLabelPrivate; + +/** + * A QLabel that supports eliding text. + * Because the functions setText() and text() are no virtual functions setting + * and reading the text via a pointer to the base class QLabel does not work + * properly + */ +class ADS_EXPORT CElidingLabel : public QLabel +{ + Q_OBJECT +private: + ElidingLabelPrivate* d; + friend struct ElidingLabelPrivate; + +protected: + virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void resizeEvent( QResizeEvent *event ) override; + virtual void mouseDoubleClickEvent( QMouseEvent *ev ) override; + +public: + using Super = QLabel; + + CElidingLabel(QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags ()); + CElidingLabel(const QString& text, QWidget* parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags ()); + virtual ~CElidingLabel(); + + /** + * Returns the text elide mode. + * The default mode is ElideNone + */ + Qt::TextElideMode elideMode() const; + + /** + * Sets the text elide mode + */ + void setElideMode(Qt::TextElideMode mode); + + /** + * This function indicates whether the text on this label is currently elided + */ + bool isElided() const; + +public: // reimplements QLabel ---------------------------------------------- + virtual QSize minimumSizeHint() const override; + virtual QSize sizeHint() const override; + void setText(const QString &text); + QString text() const; + +Q_SIGNALS: + /** + * This signal is emitted if the user clicks on the label (i.e. pressed + * down then released while the mouse cursor is inside the label) + */ + void clicked(); + + /** + * This signal is emitted if the user does a double click on the label + */ + void doubleClicked(); + + /** + * This signal is emitted when isElided() state of this label is changed + */ + void elidedChanged(bool elided); +}; //class CElidingLabel + +} // namespace QtLabb + +//--------------------------------------------------------------------------- +#endif // ElidingLabelH diff --git a/src/FloatingDockContainer.cpp b/src/FloatingDockContainer.cpp new file mode 100644 index 000000000..81a80fb7f --- /dev/null +++ b/src/FloatingDockContainer.cpp @@ -0,0 +1,1428 @@ +/******************************************************************************* + ** Qt Advanced Docking System + ** Copyright (C) 2017 Uwe Kindler + ** + ** This library is free software; you can redistribute it and/or + ** modify it under the terms of the GNU Lesser General Public + ** License as published by the Free Software Foundation; either + ** version 2.1 of the License, or (at your option) any later version. + ** + ** This library is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + ** Lesser General Public License for more details. + ** + ** You should have received a copy of the GNU Lesser General Public + ** License along with this library; If not, see . + ******************************************************************************/ + +//============================================================================ +/// \file FloatingDockContainer.cpp +/// \author Uwe Kindler +/// \date 01.03.2017 +/// \brief Implementation of CFloatingDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "FloatingDockContainer.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockContainerWidget.h" +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "DockWidget.h" +#include "DockOverlay.h" + +#ifdef Q_OS_WIN +#include +#ifdef _MSC_VER +#pragma comment(lib, "User32.lib") +#endif +#endif +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include "linux/FloatingWidgetTitleBar.h" +#include +#endif + +namespace ads +{ +#ifdef Q_OS_WIN +#if 0 // set to 1 if you need this function for debugging +/** + * Just for debugging to convert windows message identifiers to strings + */ +static const char* windowsMessageString(int MessageId) +{ + switch (MessageId) + { + case 0: return "WM_NULL"; + case 1: return "WM_CREATE"; + case 2: return "WM_DESTROY"; + case 3: return "WM_MOVE"; + case 5: return "WM_SIZE"; + case 6: return "WM_ACTIVATE"; + case 7: return "WM_SETFOCUS"; + case 8: return "WM_KILLFOCUS"; + case 10: return "WM_ENABLE"; + case 11: return "WM_SETREDRAW"; + case 12: return "WM_SETTEXT"; + case 13: return "WM_GETTEXT"; + case 14: return "WM_GETTEXTLENGTH"; + case 15: return "WM_PAINT"; + case 16: return "WM_CLOSE"; + case 17: return "WM_QUERYENDSESSION"; + case 18: return "WM_QUIT"; + case 19: return "WM_QUERYOPEN"; + case 20: return "WM_ERASEBKGND"; + case 21: return "WM_SYSCOLORCHANGE"; + case 22: return "WM_ENDSESSION"; + case 24: return "WM_SHOWWINDOW"; + case 25: return "WM_CTLCOLOR"; + case 26: return "WM_WININICHANGE"; + case 27: return "WM_DEVMODECHANGE"; + case 28: return "WM_ACTIVATEAPP"; + case 29: return "WM_FONTCHANGE"; + case 30: return "WM_TIMECHANGE"; + case 31: return "WM_CANCELMODE"; + case 32: return "WM_SETCURSOR"; + case 33: return "WM_MOUSEACTIVATE"; + case 34: return "WM_CHILDACTIVATE"; + case 35: return "WM_QUEUESYNC"; + case 36: return "WM_GETMINMAXINFO"; + case 38: return "WM_PAINTICON"; + case 39: return "WM_ICONERASEBKGND"; + case 40: return "WM_NEXTDLGCTL"; + case 42: return "WM_SPOOLERSTATUS"; + case 43: return "WM_DRAWITEM"; + case 44: return "WM_MEASUREITEM"; + case 45: return "WM_DELETEITEM"; + case 46: return "WM_VKEYTOITEM"; + case 47: return "WM_CHARTOITEM"; + case 48: return "WM_SETFONT"; + case 49: return "WM_GETFONT"; + case 50: return "WM_SETHOTKEY"; + case 51: return "WM_GETHOTKEY"; + case 55: return "WM_QUERYDRAGICON"; + case 57: return "WM_COMPAREITEM"; + case 61: return "WM_GETOBJECT"; + case 65: return "WM_COMPACTING"; + case 68: return "WM_COMMNOTIFY"; + case 70: return "WM_WINDOWPOSCHANGING"; + case 71: return "WM_WINDOWPOSCHANGED"; + case 72: return "WM_POWER"; + case 73: return "WM_COPYGLOBALDATA"; + case 74: return "WM_COPYDATA"; + case 75: return "WM_CANCELJOURNAL"; + case 78: return "WM_NOTIFY"; + case 80: return "WM_INPUTLANGCHANGEREQUEST"; + case 81: return "WM_INPUTLANGCHANGE"; + case 82: return "WM_TCARD"; + case 83: return "WM_HELP"; + case 84: return "WM_USERCHANGED"; + case 85: return "WM_NOTIFYFORMAT"; + case 123: return "WM_CONTEXTMENU"; + case 124: return "WM_STYLECHANGING"; + case 125: return "WM_STYLECHANGED"; + case 126: return "WM_DISPLAYCHANGE"; + case 127: return "WM_GETICON"; + case 128: return "WM_SETICON"; + case 129: return "WM_NCCREATE"; + case 130: return "WM_NCDESTROY"; + case 131: return "WM_NCCALCSIZE"; + case 132: return "WM_NCHITTEST"; + case 133: return "WM_NCPAINT"; + case 134: return "WM_NCACTIVATE"; + case 135: return "WM_GETDLGCODE"; + case 136: return "WM_SYNCPAINT"; + case 160: return "WM_NCMOUSEMOVE"; + case 161: return "WM_NCLBUTTONDOWN"; + case 162: return "WM_NCLBUTTONUP"; + case 163: return "WM_NCLBUTTONDBLCLK"; + case 164: return "WM_NCRBUTTONDOWN"; + case 165: return "WM_NCRBUTTONUP"; + case 166: return "WM_NCRBUTTONDBLCLK"; + case 167: return "WM_NCMBUTTONDOWN"; + case 168: return "WM_NCMBUTTONUP"; + case 169: return "WM_NCMBUTTONDBLCLK"; + case 171: return "WM_NCXBUTTONDOWN"; + case 172: return "WM_NCXBUTTONUP"; + case 173: return "WM_NCXBUTTONDBLCLK"; + case 176: return "EM_GETSEL"; + case 177: return "EM_SETSEL"; + case 178: return "EM_GETRECT"; + case 179: return "EM_SETRECT"; + case 180: return "EM_SETRECTNP"; + case 181: return "EM_SCROLL"; + case 182: return "EM_LINESCROLL"; + case 183: return "EM_SCROLLCARET"; + case 185: return "EM_GETMODIFY"; + case 187: return "EM_SETMODIFY"; + case 188: return "EM_GETLINECOUNT"; + case 189: return "EM_LINEINDEX"; + case 190: return "EM_SETHANDLE"; + case 191: return "EM_GETHANDLE"; + case 192: return "EM_GETTHUMB"; + case 193: return "EM_LINELENGTH"; + case 194: return "EM_REPLACESEL"; + case 195: return "EM_SETFONT"; + case 196: return "EM_GETLINE"; + case 197: return "EM_LIMITTEXT / EM_SETLIMITTEXT"; + case 198: return "EM_CANUNDO"; + case 199: return "EM_UNDO"; + case 200: return "EM_FMTLINES"; + case 201: return "EM_LINEFROMCHAR"; + case 202: return "EM_SETWORDBREAK"; + case 203: return "EM_SETTABSTOPS"; + case 204: return "EM_SETPASSWORDCHAR"; + case 205: return "EM_EMPTYUNDOBUFFER"; + case 206: return "EM_GETFIRSTVISIBLELINE"; + case 207: return "EM_SETREADONLY"; + case 209: return "EM_SETWORDBREAKPROC / EM_GETWORDBREAKPROC"; + case 210: return "EM_GETPASSWORDCHAR"; + case 211: return "EM_SETMARGINS"; + case 212: return "EM_GETMARGINS"; + case 213: return "EM_GETLIMITTEXT"; + case 214: return "EM_POSFROMCHAR"; + case 215: return "EM_CHARFROMPOS"; + case 216: return "EM_SETIMESTATUS"; + case 217: return "EM_GETIMESTATUS"; + case 224: return "SBM_SETPOS"; + case 225: return "SBM_GETPOS"; + case 226: return "SBM_SETRANGE"; + case 227: return "SBM_GETRANGE"; + case 228: return "SBM_ENABLE_ARROWS"; + case 230: return "SBM_SETRANGEREDRAW"; + case 233: return "SBM_SETSCROLLINFO"; + case 234: return "SBM_GETSCROLLINFO"; + case 235: return "SBM_GETSCROLLBARINFO"; + case 240: return "BM_GETCHECK"; + case 241: return "BM_SETCHECK"; + case 242: return "BM_GETSTATE"; + case 243: return "BM_SETSTATE"; + case 244: return "BM_SETSTYLE"; + case 245: return "BM_CLICK"; + case 246: return "BM_GETIMAGE"; + case 247: return "BM_SETIMAGE"; + case 248: return "BM_SETDONTCLICK"; + case 255: return "WM_INPUT"; + case 256: return "WM_KEYDOWN"; + case 257: return "WM_KEYUP"; + case 258: return "WM_CHAR"; + case 259: return "WM_DEADCHAR"; + case 260: return "WM_SYSKEYDOWN"; + case 261: return "WM_SYSKEYUP"; + case 262: return "WM_SYSCHAR"; + case 263: return "WM_SYSDEADCHAR"; + case 265: return "WM_UNICHAR / WM_WNT_CONVERTREQUESTEX"; + case 266: return "WM_CONVERTREQUEST"; + case 267: return "WM_CONVERTRESULT"; + case 268: return "WM_INTERIM"; + case 269: return "WM_IME_STARTCOMPOSITION"; + case 270: return "WM_IME_ENDCOMPOSITION"; + case 272: return "WM_INITDIALOG"; + case 273: return "WM_COMMAND"; + case 274: return "WM_SYSCOMMAND"; + case 275: return "WM_TIMER"; + case 276: return "WM_HSCROLL"; + case 277: return "WM_VSCROLL"; + case 278: return "WM_INITMENU"; + case 279: return "WM_INITMENUPOPUP"; + case 280: return "WM_SYSTIMER"; + case 287: return "WM_MENUSELECT"; + case 288: return "WM_MENUCHAR"; + case 289: return "WM_ENTERIDLE"; + case 290: return "WM_MENURBUTTONUP"; + case 291: return "WM_MENUDRAG"; + case 292: return "WM_MENUGETOBJECT"; + case 293: return "WM_UNINITMENUPOPUP"; + case 294: return "WM_MENUCOMMAND"; + case 295: return "WM_CHANGEUISTATE"; + case 296: return "WM_UPDATEUISTATE"; + case 297: return "WM_QUERYUISTATE"; + case 306: return "WM_CTLCOLORMSGBOX"; + case 307: return "WM_CTLCOLOREDIT"; + case 308: return "WM_CTLCOLORLISTBOX"; + case 309: return "WM_CTLCOLORBTN"; + case 310: return "WM_CTLCOLORDLG"; + case 311: return "WM_CTLCOLORSCROLLBAR"; + case 312: return "WM_CTLCOLORSTATIC"; + case 512: return "WM_MOUSEMOVE"; + case 513: return "WM_LBUTTONDOWN"; + case 514: return "WM_LBUTTONUP"; + case 515: return "WM_LBUTTONDBLCLK"; + case 516: return "WM_RBUTTONDOWN"; + case 517: return "WM_RBUTTONUP"; + case 518: return "WM_RBUTTONDBLCLK"; + case 519: return "WM_MBUTTONDOWN"; + case 520: return "WM_MBUTTONUP"; + case 521: return "WM_MBUTTONDBLCLK"; + case 522: return "WM_MOUSEWHEEL"; + case 523: return "WM_XBUTTONDOWN"; + case 524: return "WM_XBUTTONUP"; + case 525: return "WM_XBUTTONDBLCLK"; + case 528: return "WM_PARENTNOTIFY"; + case 529: return "WM_ENTERMENULOOP"; + case 530: return "WM_EXITMENULOOP"; + case 531: return "WM_NEXTMENU"; + case 532: return "WM_SIZING"; + case 533: return "WM_CAPTURECHANGED"; + case 534: return "WM_MOVING"; + case 536: return "WM_POWERBROADCAST"; + case 537: return "WM_DEVICECHANGE"; + case 544: return "WM_MDICREATE"; + case 545: return "WM_MDIDESTROY"; + case 546: return "WM_MDIACTIVATE"; + case 547: return "WM_MDIRESTORE"; + case 548: return "WM_MDINEXT"; + case 549: return "WM_MDIMAXIMIZE"; + case 550: return "WM_MDITILE"; + case 551: return "WM_MDICASCADE"; + case 552: return "WM_MDIICONARRANGE"; + case 553: return "WM_MDIGETACTIVE"; + case 560: return "WM_MDISETMENU"; + case 561: return "WM_ENTERSIZEMOVE"; + case 562: return "WM_EXITSIZEMOVE"; + case 563: return "WM_DROPFILES"; + case 564: return "WM_MDIREFRESHMENU"; + case 640: return "WM_IME_REPORT"; + case 641: return "WM_IME_SETCONTEXT"; + case 642: return "WM_IME_NOTIFY"; + case 643: return "WM_IME_CONTROL"; + case 644: return "WM_IME_COMPOSITIONFULL"; + case 645: return "WM_IME_SELECT"; + case 646: return "WM_IME_CHAR"; + case 648: return "WM_IME_REQUEST"; + case 656: return "WM_IME_KEYDOWN"; + case 657: return "WM_IME_KEYUP"; + case 672: return "WM_NCMOUSEHOVER"; + case 673: return "WM_MOUSEHOVER"; + case 674: return "WM_NCMOUSELEAVE"; + case 675: return "WM_MOUSELEAVE"; + case 768: return "WM_CUT"; + case 769: return "WM_COPY"; + case 770: return "WM_PASTE"; + case 771: return "WM_CLEAR"; + case 772: return "WM_UNDO"; + case 773: return "WM_RENDERFORMAT"; + case 774: return "WM_RENDERALLFORMATS"; + case 775: return "WM_DESTROYCLIPBOARD"; + case 776: return "WM_DRAWCLIPBOARD"; + case 777: return "WM_PAINTCLIPBOARD"; + case 778: return "WM_VSCROLLCLIPBOARD"; + case 779: return "WM_SIZECLIPBOARD"; + case 780: return "WM_ASKCBFORMATNAME"; + case 781: return "WM_CHANGECBCHAIN"; + case 782: return "WM_HSCROLLCLIPBOARD"; + case 783: return "WM_QUERYNEWPALETTE"; + case 784: return "WM_PALETTEISCHANGING"; + case 785: return "WM_PALETTECHANGED"; + case 786: return "WM_HOTKEY"; + case 791: return "WM_PRINT"; + case 792: return "WM_PRINTCLIENT"; + case 793: return "WM_APPCOMMAND"; + case 856: return "WM_HANDHELDFIRST"; + case 863: return "WM_HANDHELDLAST"; + case 864: return "WM_AFXFIRST"; + case 895: return "WM_AFXLAST"; + case 896: return "WM_PENWINFIRST"; + case 897: return "WM_RCRESULT"; + case 898: return "WM_HOOKRCRESULT"; + case 899: return "WM_GLOBALRCCHANGE / WM_PENMISCINFO"; + case 900: return "WM_SKB"; + case 901: return "WM_HEDITCTL / WM_PENCTL"; + case 902: return "WM_PENMISC"; + case 903: return "WM_CTLINIT"; + case 904: return "WM_PENEVENT"; + case 911: return "WM_PENWINLAST"; + default: + return "unknown WM_ message"; + } + + return "unknown WM_ message"; +} +#endif +#endif + + +static unsigned int zOrderCounterFloating = 0; +/** + * Private data class of CFloatingDockContainer class (pimpl) + */ +struct FloatingDockContainerPrivate +{ + CFloatingDockContainer *_this; + CDockContainerWidget *DockContainer; + unsigned int zOrderIndex = ++zOrderCounterFloating; + QPointer DockManager; + eDragState DraggingState = DraggingInactive; + QPoint DragStartMousePosition; + CDockContainerWidget *DropContainer = nullptr; + CDockAreaWidget *SingleDockArea = nullptr; + QPoint DragStartPos; + bool Hiding = false; + bool AutoHideChildren = true; +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QWidget* MouseEventHandler = nullptr; + CFloatingWidgetTitleBar* TitleBar = nullptr; + bool IsResizing = false; + bool MousePressed = false; +#endif + + /** + * Private data constructor + */ + FloatingDockContainerPrivate(CFloatingDockContainer *_public); + + void titleMouseReleaseEvent(); + void updateDropOverlays(const QPoint &GlobalPos); + + /** + * Returns true if the given config flag is set + */ + static bool testConfigFlag(CDockManager::eConfigFlag Flag) + { + return CDockManager::testConfigFlag(Flag); + } + + /** + * Tests is a certain state is active + */ + bool isState(eDragState StateId) const + { + return StateId == DraggingState; + } + + /** + * Sets the dragging state and posts a FloatingWidgetDragStartEvent + * if dragging starts + */ + void setState(eDragState StateId) + { + if (DraggingState == StateId) + { + return; + } + + DraggingState = StateId; + if (DraggingFloatingWidget == DraggingState) + { + qApp->postEvent(_this, new QEvent((QEvent::Type)internal::FloatingWidgetDragStartEvent)); + } + } + + void setWindowTitle(const QString &Text) + { +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (TitleBar) + { + TitleBar->setTitle(Text); + } +#endif + _this->setWindowTitle(Text); + } + + /** + * Reflect the current dock widget title in the floating widget windowTitle() + * depending on the CDockManager::FloatingContainerHasWidgetTitle flag + */ + void reflectCurrentWidget(CDockWidget* CurrentWidget) + { + // reflect CurrentWidget's title if configured to do so, otherwise display application name as window title + if (testConfigFlag(CDockManager::FloatingContainerHasWidgetTitle)) + { + setWindowTitle(CurrentWidget->windowTitle()); + } + else + { + setWindowTitle(floatingContainersTitle()); + } + + // reflect CurrentWidget's icon if configured to do so, otherwise display application icon as window icon + QIcon CurrentWidgetIcon = CurrentWidget->icon(); + if (testConfigFlag(CDockManager::FloatingContainerHasWidgetIcon) + && !CurrentWidgetIcon.isNull()) + { + _this->setWindowIcon(CurrentWidget->icon()); + } + else + { + _this->setWindowIcon(QApplication::windowIcon()); + } + } + + /** + * Handles escape key press when dragging around the floating widget + */ + void handleEscapeKey(); + + /** + * Returns the title used by all FloatingContainer that does not + * reflect the title of the current dock widget. + * + * If not title was set with CDockManager::setFloatingContainersTitle(), + * it returns QGuiApplication::applicationDisplayName(). + */ + static QString floatingContainersTitle() + { + return CDockManager::floatingContainersTitle(); + } +}; +// struct FloatingDockContainerPrivate + +//============================================================================ +FloatingDockContainerPrivate::FloatingDockContainerPrivate( + CFloatingDockContainer *_public) : + _this(_public) +{ + +} + +//============================================================================ +void FloatingDockContainerPrivate::titleMouseReleaseEvent() +{ + setState(DraggingInactive); + if (!DropContainer) + { + return; + } + + if (DockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea + || DockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) + { + CDockOverlay *Overlay = DockManager->containerOverlay(); + if (!Overlay->dropOverlayRect().isValid()) + { + Overlay = DockManager->dockAreaOverlay(); + } + + // Do not resize if we drop into an autohide sidebar area to preserve + // the dock area size for the initial size of the auto hide area + if (!ads::internal::isSideBarArea(Overlay->dropAreaUnderCursor())) + { + // Resize the floating widget to the size of the highlighted drop area + // rectangle + QRect Rect = Overlay->dropOverlayRect(); + int FrameWidth = (_this->frameSize().width() - _this->rect().width()) + / 2; + int TitleBarHeight = _this->frameSize().height() + - _this->rect().height() - FrameWidth; + if (Rect.isValid()) + { + QPoint TopLeft = Overlay->mapToGlobal(Rect.topLeft()); + TopLeft.ry() += TitleBarHeight; + _this->setGeometry( + QRect(TopLeft, + QSize(Rect.width(), Rect.height() - TitleBarHeight))); + QApplication::processEvents(); + } + } + DropContainer->dropFloatingWidget(_this, QCursor::pos()); + } + + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); +} + + +//============================================================================ +void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &GlobalPos) +{ + if (!_this->isVisible() || !DockManager) + { + return; + } + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + // Prevent display of drop overlays and docking as long as a model dialog + // is active + if (qApp->activeModalWidget()) + { + return; + } +#endif + + auto Containers = DockManager->dockContainers(); + CDockContainerWidget *TopContainer = nullptr; + for (auto ContainerWidget : Containers) + { + if (!ContainerWidget->isVisible()) + { + continue; + } + + if (DockContainer == ContainerWidget) + { + continue; + } + + QPoint MappedPos = ContainerWidget->mapFromGlobal(GlobalPos); + if (ContainerWidget->rect().contains(MappedPos)) + { + if (!TopContainer || ContainerWidget->isInFrontOf(TopContainer)) + { + TopContainer = ContainerWidget; + } + } + } + + DropContainer = TopContainer; + auto ContainerOverlay = DockManager->containerOverlay(); + auto DockAreaOverlay = DockManager->dockAreaOverlay(); + + if (!TopContainer) + { + ContainerOverlay->hideOverlay(); + DockAreaOverlay->hideOverlay(); + return; + } + + int VisibleDockAreas = TopContainer->visibleDockAreaCount(); + DockWidgetAreas AllowedContainerAreas = (VisibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; + auto DockArea = TopContainer->dockAreaAt(GlobalPos); + // If the dock container contains only one single DockArea, then we need + // to respect the allowed areas - only the center area is relevant here because + // all other allowed areas are from the container + if (VisibleDockAreas == 1 && DockArea) + { + AllowedContainerAreas.setFlag(CenterDockWidgetArea, DockArea->allowedAreas().testFlag(CenterDockWidgetArea)); + } + + if (DockContainer->features().testFlag(CDockWidget::DockWidgetPinnable)) + { + AllowedContainerAreas |= AutoHideDockAreas; + } + + ContainerOverlay->setAllowedAreas(AllowedContainerAreas); + + DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer); + ContainerOverlay->enableDropPreview(ContainerArea != InvalidDockWidgetArea); + if (DockArea && DockArea->isVisible() && VisibleDockAreas > 0) + { + DockAreaOverlay->enableDropPreview(true); + DockAreaOverlay->setAllowedAreas( + (VisibleDockAreas == 1) ? NoDockWidgetArea : DockArea->allowedAreas()); + DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that + // the mouse is in the title bar. If the ContainerArea is valid + // then we ignore the dock area of the dockAreaOverlay() and disable + // the drop preview + if ((Area == CenterDockWidgetArea) + && (ContainerArea != InvalidDockWidgetArea)) + { + DockAreaOverlay->enableDropPreview(false); + ContainerOverlay->enableDropPreview(true); + } + else + { + ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area); + } + } + else + { + DockAreaOverlay->hideOverlay(); + } +} + + +//============================================================================ +void FloatingDockContainerPrivate::handleEscapeKey() +{ + ADS_PRINT("FloatingDockContainerPrivate::handleEscapeKey()"); + setState(DraggingInactive); + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); +} + + +//============================================================================ +CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) : + tFloatingWidgetBase(DockManager), + d(new FloatingDockContainerPrivate(this)) +{ + d->DockManager = DockManager; + d->DockContainer = new CDockContainerWidget(DockManager, this); + connect(d->DockContainer, SIGNAL(dockAreasAdded()), this, + SLOT(onDockAreasAddedOrRemoved())); + connect(d->DockContainer, SIGNAL(dockAreasRemoved()), this, + SLOT(onDockAreasAddedOrRemoved())); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + QDockWidget::setWidget(d->DockContainer); + QDockWidget::setFeatures(QDockWidget::DockWidgetClosable + | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + + bool native_window = true; + + // FloatingContainerForce*TitleBar is overwritten by the "ADS_UseNativeTitle" environment variable if set. + auto env = qgetenv("ADS_UseNativeTitle").toUpper(); + if (env == "1") + { + native_window = true; + } + else if (env == "0") + { + native_window = false; + } + else if (DockManager->testConfigFlag(CDockManager::FloatingContainerForceNativeTitleBar)) + { + native_window = true; + } + else if (DockManager->testConfigFlag(CDockManager::FloatingContainerForceQWidgetTitleBar)) + { + native_window = false; + } + else + { + // KDE doesn't seem to fire MoveEvents while moving windows, so for now no native titlebar for everything using KWin. + QString window_manager = internal::windowManager().toUpper().split(" ")[0]; + native_window = window_manager != "KWIN"; + } + + if (native_window) + { + // Native windows do not work if wayland is used. Ubuntu 22.04 uses wayland by default. To use + // native windows, switch to Xorg + QString XdgSessionType = qgetenv("XDG_SESSION_TYPE").toLower(); + if ("wayland" == XdgSessionType) + { + native_window = false; + } + } + + if (native_window) + { + setTitleBarWidget(new QWidget()); + setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint); + } + else + { + d->TitleBar = new CFloatingWidgetTitleBar(this); + setTitleBarWidget(d->TitleBar); + setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint); + d->TitleBar->enableCloseButton(isClosable()); + connect(d->TitleBar, SIGNAL(closeRequested()), SLOT(close())); + connect(d->TitleBar, &CFloatingWidgetTitleBar::maximizeRequested, + this, &CFloatingDockContainer::onMaximizeRequest); + } +#else + setWindowFlags( + Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom); + l->setContentsMargins(0, 0, 0, 0); + l->setSpacing(0); + setLayout(l); + l->addWidget(d->DockContainer); +#endif + + DockManager->registerFloatingWidget(this); +} + +//============================================================================ +CFloatingDockContainer::CFloatingDockContainer(CDockAreaWidget *DockArea) : + CFloatingDockContainer(DockArea->dockManager()) +{ + d->DockContainer->addDockArea(DockArea); + + auto TopLevelDockWidget = topLevelDockWidget(); + if (TopLevelDockWidget) + { + TopLevelDockWidget->emitTopLevelChanged(true); + } + + d->DockManager->notifyWidgetOrAreaRelocation(DockArea); +} + +//============================================================================ +CFloatingDockContainer::CFloatingDockContainer(CDockWidget *DockWidget) : + CFloatingDockContainer(DockWidget->dockManager()) +{ + d->DockContainer->addDockWidget(CenterDockWidgetArea, DockWidget); + auto TopLevelDockWidget = topLevelDockWidget(); + if (TopLevelDockWidget) + { + TopLevelDockWidget->emitTopLevelChanged(true); + } + + d->DockManager->notifyWidgetOrAreaRelocation(DockWidget); +} + + +//============================================================================ +CFloatingDockContainer::~CFloatingDockContainer() +{ + ADS_PRINT("~CFloatingDockContainer"); + if (d->DockManager) + { + d->DockManager->removeFloatingWidget(this); + } + delete d; +} + + +//============================================================================ +void CFloatingDockContainer::deleteContent() +{ + std::vector> areas; + for (int i = 0; i != dockContainer()->dockAreaCount(); ++i) + { + areas.push_back( dockContainer()->dockArea(i) ); + } + for (auto area : areas) + { + if (!area) + { + continue; + } + + // QPointer delete safety - just in case some dock widget in destruction + // deletes another related/twin or child dock widget. + std::vector> deleteWidgets; + for (auto widget : area->dockWidgets()) + { + deleteWidgets.push_back(widget); + } + for (auto ptrWdg : deleteWidgets) + { + delete ptrWdg; + } + } +} + +//============================================================================ +CDockContainerWidget* CFloatingDockContainer::dockContainer() const +{ + return d->DockContainer; +} + +//============================================================================ +void CFloatingDockContainer::changeEvent(QEvent *event) +{ + Super::changeEvent(event); + switch (event->type()) + { + case QEvent::ActivationChange: + if (isActiveWindow()) + { + ADS_PRINT("FloatingWidget::changeEvent QEvent::ActivationChange "); + d->zOrderIndex = ++zOrderCounterFloating; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (d->DraggingState == DraggingFloatingWidget) + { + d->titleMouseReleaseEvent(); + d->DraggingState = DraggingInactive; + } +#endif + } + break; + + case QEvent::WindowStateChange: + // If the DockManager window is restored from minimized on Windows + // then the FloatingWidgets are not properly restored to maximized but + // to normal state. + // We simply check here, if the FloatingWidget was maximized before + // and if the DockManager is just leaving the minimized state. In this + // case, we restore the maximized state of this floating widget + if (d->DockManager->isLeavingMinimizedState()) + { + QWindowStateChangeEvent* ev = static_cast(event); + if (ev->oldState().testFlag(Qt::WindowMaximized)) + { + this->showMaximized(); + } + } + break; + + default: + break; // do nothing + } +} + + +#ifdef Q_OS_WIN +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) +bool CFloatingDockContainer::nativeEvent(const QByteArray &eventType, void *message, long *result) +#else +bool CFloatingDockContainer::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) +#endif +{ + QWidget::nativeEvent(eventType, message, result); + MSG *msg = static_cast(message); + switch (msg->message) + { + case WM_MOVING: + { + if (d->isState(DraggingFloatingWidget)) + { + d->updateDropOverlays(QCursor::pos()); + } + } + break; + + case WM_NCLBUTTONDOWN: + if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) + { + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_NCLBUTTONDOWN"); + d->DragStartPos = pos(); + d->setState(DraggingMousePressed); + } + break; + + case WM_NCLBUTTONDBLCLK: + d->setState(DraggingInactive); + break; + + case WM_ENTERSIZEMOVE: + if (d->isState(DraggingMousePressed)) + { + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_ENTERSIZEMOVE"); + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + break; + + case WM_EXITSIZEMOVE: + if (d->isState(DraggingFloatingWidget)) + { + ADS_PRINT("CFloatingDockContainer::nativeEvent WM_EXITSIZEMOVE"); + if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) + { + d->handleEscapeKey(); + } + else + { + d->titleMouseReleaseEvent(); + } + } + break; + } + return false; +} +#endif + + +//============================================================================ +void CFloatingDockContainer::closeEvent(QCloseEvent *event) +{ + ADS_PRINT("CFloatingDockContainer closeEvent"); + d->setState(DraggingInactive); + event->ignore(); + if (!isClosable()) + { + return; + } + + bool HasOpenDockWidgets = false; + for (auto DockWidget : d->DockContainer->openedDockWidgets()) + { + if (DockWidget->features().testFlag(CDockWidget::DockWidgetDeleteOnClose) || DockWidget->features().testFlag(CDockWidget::CustomCloseHandling)) + { + bool Closed = DockWidget->closeDockWidgetInternal(); + if (!Closed) + { + HasOpenDockWidgets = true; + } + } + 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(); +} + +//============================================================================ +void CFloatingDockContainer::hideEvent(QHideEvent *event) +{ + Super::hideEvent(event); + if (event->spontaneous()) + { + return; + } + + // Prevent toogleView() events during restore state + if (d->DockManager->isRestoringState()) + { + return; + } + + if ( d->AutoHideChildren ) + { + d->Hiding = true; + for ( auto DockArea : d->DockContainer->openedDockAreas() ) + { + for ( auto DockWidget : DockArea->openedDockWidgets() ) + { + DockWidget->toggleView( false ); + } + } + d->Hiding = false; + } +} + + +//============================================================================ +void CFloatingDockContainer::showEvent(QShowEvent *event) +{ + Super::showEvent(event); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (CDockManager::testConfigFlag(CDockManager::FocusHighlighting)) + { + this->window()->activateWindow(); + } +#endif +} + + +//============================================================================ +void CFloatingDockContainer::startFloating(const QPoint &DragStartMousePos, + const QSize &Size, eDragState DragState, QWidget *MouseEventHandler) +{ +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if (!isMaximized()) + { + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + } + d->setState(DragState); + if (DraggingFloatingWidget == DragState) + { + d->MouseEventHandler = MouseEventHandler; + if (d->MouseEventHandler) + { + d->MouseEventHandler->grabMouse(); + } + } + + if (!isMaximized()) + { + moveFloating(); + } + show(); +#else + Q_UNUSED(MouseEventHandler) + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + d->setState(DragState); + moveFloating(); + show(); +#endif +} + +//============================================================================ +void CFloatingDockContainer::moveFloating() +{ + int BorderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition + - QPoint(BorderSize, 0); + move(moveToPos); + switch (d->DraggingState) + { + case DraggingMousePressed: + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + break; + + case DraggingFloatingWidget: + d->updateDropOverlays(QCursor::pos()); +#ifdef Q_OS_MACOS + // In OSX when hiding the DockAreaOverlay the application would set + // the main window as the active window for some reason. This fixes + // that by resetting the active window to the floating widget after + // updating the overlays. + activateWindow(); +#endif + break; + default: + break; + } +} + +//============================================================================ +bool CFloatingDockContainer::isClosable() const +{ + return d->DockContainer->features().testFlag( + CDockWidget::DockWidgetClosable); +} + +//============================================================================ +void CFloatingDockContainer::onDockAreasAddedOrRemoved() +{ + ADS_PRINT("CFloatingDockContainer::onDockAreasAddedOrRemoved()"); + auto TopLevelDockArea = d->DockContainer->topLevelDockArea(); + if (TopLevelDockArea) + { + d->SingleDockArea = TopLevelDockArea; + CDockWidget* CurrentWidget = d->SingleDockArea->currentDockWidget(); + d->reflectCurrentWidget(CurrentWidget); + connect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, + SLOT(onDockAreaCurrentChanged(int))); + } + else + { + if (d->SingleDockArea) + { + disconnect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, + SLOT(onDockAreaCurrentChanged(int))); + d->SingleDockArea = nullptr; + } + d->setWindowTitle(d->floatingContainersTitle()); + setWindowIcon(QApplication::windowIcon()); + } +} + +//============================================================================ +void CFloatingDockContainer::updateWindowTitle() +{ + // If this floating container will be hidden, then updating the window + // tile is not required anymore + if (d->Hiding) + { + return; + } + + + auto TopLevelDockArea = d->DockContainer->topLevelDockArea(); + if (TopLevelDockArea) + { + CDockWidget* CurrentWidget = TopLevelDockArea->currentDockWidget(); + if (CurrentWidget) + { + d->reflectCurrentWidget(CurrentWidget); + } + } + else + { + d->setWindowTitle(d->floatingContainersTitle()); + setWindowIcon(QApplication::windowIcon()); + } +} + +//============================================================================ +void CFloatingDockContainer::onDockAreaCurrentChanged(int Index) +{ + Q_UNUSED(Index); + CDockWidget* CurrentWidget = d->SingleDockArea->currentDockWidget(); + d->reflectCurrentWidget(CurrentWidget); +} + +//============================================================================ +bool CFloatingDockContainer::restoreState(CDockingStateReader &Stream, + bool Testing) +{ + if (!d->DockContainer->restoreState(Stream, Testing)) + { + return false; + } + onDockAreasAddedOrRemoved(); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + if(d->TitleBar) + { + d->TitleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); + } +#endif + return true; +} + + +//============================================================================ +bool CFloatingDockContainer::hasTopLevelDockWidget() const +{ + return d->DockContainer->hasTopLevelDockWidget(); +} + +//============================================================================ +CDockWidget* CFloatingDockContainer::topLevelDockWidget() const +{ + return d->DockContainer->topLevelDockWidget(); +} + +//============================================================================ +QList CFloatingDockContainer::dockWidgets() const +{ + return d->DockContainer->dockWidgets(); +} + +//============================================================================ +void CFloatingDockContainer::finishDropOperation() +{ + // 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(); + // The floating widget will be deleted now. Ensure, that the destructor + // of the floating widget does not delete any dock areas that have been + // moved to a new container - simply remove all dock areas before deleting + // the floating widget + d->DockContainer->removeAllDockAreas(); + deleteLater(); + if (d->DockManager) + { + d->DockManager->removeFloatingWidget(this); + d->DockManager->removeDockContainer(this->dockContainer()); + } +} + +//============================================================================ +void CFloatingDockContainer::finishDragging() +{ + ADS_PRINT("CFloatingDockContainer::finishDragging"); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + setWindowOpacity(1); + activateWindow(); + if (d->MouseEventHandler) + { + d->MouseEventHandler->releaseMouse(); + d->MouseEventHandler = nullptr; + } +#endif + d->titleMouseReleaseEvent(); +} + +#ifdef Q_OS_MACOS +//============================================================================ +bool CFloatingDockContainer::event(QEvent *e) +{ + switch (d->DraggingState) + { + case DraggingInactive: + { + // Normally we would check here, if the left mouse button is pressed. + // But from QT version 5.12.2 on the mouse events from + // QEvent::NonClientAreaMouseButtonPress return the wrong mouse button + // The event always returns Qt::RightButton even if the left button + // is clicked. + // It is really great to work around the whole NonClientMouseArea + // bugs +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 2)) + if (e->type() == QEvent::NonClientAreaMouseButtonPress /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) +#else + if (e->type() == QEvent::NonClientAreaMouseButtonPress && QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)) +#endif + { + ADS_PRINT("FloatingWidget::event Event::NonClientAreaMouseButtonPress" << e->type()); + d->DragStartPos = pos(); + d->setState(DraggingMousePressed); + } + } + break; + + case DraggingMousePressed: + switch (e->type()) + { + case QEvent::NonClientAreaMouseButtonDblClick: + ADS_PRINT("FloatingWidget::event QEvent::NonClientAreaMouseButtonDblClick"); + d->setState(DraggingInactive); + break; + + case QEvent::Resize: + // If the first event after the mouse press is a resize event, then + // the user resizes the window instead of dragging it around. + // But there is one exception. If the window is maximized, + // then dragging the window via title bar will cause the widget to + // leave the maximized state. This in turn will trigger a resize event. + // To know, if the resize event was triggered by user via moving a + // corner of the window frame or if it was caused by a windows state + // change, we check, if we are not in maximized state. + if (!isMaximized()) + { + d->setState(DraggingInactive); + } + break; + + default: + break; + } + break; + + case DraggingFloatingWidget: + if (e->type() == QEvent::NonClientAreaMouseButtonRelease) + { + ADS_PRINT("FloatingWidget::event QEvent::NonClientAreaMouseButtonRelease"); + d->titleMouseReleaseEvent(); + } + break; + + default: + break; + } + +#if (ADS_DEBUG_LEVEL > 0) + qDebug() << QTime::currentTime() << "CFloatingDockContainer::event " << e->type(); +#endif + return QWidget::event(e); +} + + +//============================================================================ +void CFloatingDockContainer::moveEvent(QMoveEvent *event) +{ + QWidget::moveEvent(event); + switch (d->DraggingState) + { + case DraggingMousePressed: + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + break; + + case DraggingFloatingWidget: + d->updateDropOverlays(QCursor::pos()); + // In OSX when hiding the DockAreaOverlay the application would set + // the main window as the active window for some reason. This fixes + // that by resetting the active window to the floating widget after + // updating the overlays. + activateWindow(); + break; + default: + break; + } + + +} +#endif + + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +//============================================================================ +void CFloatingDockContainer::onMaximizeRequest() +{ + if (windowState() == Qt::WindowMaximized) + { + showNormal(); + } + else + { + showMaximized(); + } +} + + +//============================================================================ +void CFloatingDockContainer::showNormal(bool fixGeometry) +{ + if ( (windowState() & Qt::WindowMaximized) != 0 || + (windowState() & Qt::WindowFullScreen) != 0) + { + QRect oldNormal = normalGeometry(); + Super::showNormal(); + if(fixGeometry) + { + setGeometry(oldNormal); + } + } + if(d->TitleBar) + { + d->TitleBar->setMaximizedIcon(false); + } +} + + +//============================================================================ +void CFloatingDockContainer::showMaximized() +{ + Super::showMaximized(); + if (d->TitleBar) + { + d->TitleBar->setMaximizedIcon(true); + } +} + + +//============================================================================ +bool CFloatingDockContainer::isMaximized() const +{ + return windowState() == Qt::WindowMaximized; +} + + +//============================================================================ +void CFloatingDockContainer::show() +{ + // Prevent this window from showing in the taskbar and pager (alt+tab) + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_TASKBAR"); + internal::xcb_add_prop(true, winId(), "_NET_WM_STATE", "_NET_WM_STATE_SKIP_PAGER"); + Super::show(); +} + + +//============================================================================ +void CFloatingDockContainer::resizeEvent(QResizeEvent *event) +{ + d->IsResizing = true; + Super::resizeEvent(event); +} + + +//============================================================================ +void CFloatingDockContainer::moveEvent(QMoveEvent *event) +{ + Super::moveEvent(event); + if (!d->IsResizing && event->spontaneous() && d->MousePressed) + { + d->setState(DraggingFloatingWidget); + d->updateDropOverlays(QCursor::pos()); + } + d->IsResizing = false; +} + + +//============================================================================ +bool CFloatingDockContainer::event(QEvent *e) +{ + bool result = Super::event(e); + switch (e->type()) + { + case QEvent::WindowActivate: + d->MousePressed = false; + break; + case QEvent::WindowDeactivate: + d->MousePressed = true; + break; + default: + break; + } + return result; +} + +//============================================================================ +bool CFloatingDockContainer::hasNativeTitleBar() +{ + return d->TitleBar == nullptr; +} +#endif + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF FloatingDockContainer.cpp diff --git a/src/FloatingDockContainer.h b/src/FloatingDockContainer.h new file mode 100644 index 000000000..2dd9d499f --- /dev/null +++ b/src/FloatingDockContainer.h @@ -0,0 +1,312 @@ +#ifndef FloatingDockContainerH +#define FloatingDockContainerH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file FloatingDockContainer.h +/// \author Uwe Kindler +/// \date 01.03.2017 +/// \brief Declaration of CFloatingDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" + +#include + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include +#define tFloatingWidgetBase QDockWidget +#else +#include +#define tFloatingWidgetBase QWidget +#endif + +class CDockingStateReader; + +namespace ads +{ +struct FloatingDockContainerPrivate; +class CDockManager; +struct DockManagerPrivate; +class CDockAreaWidget; +class CDockContainerWidget; +class CDockWidget; +class CDockManager; +class CDockAreaTabBar; +class CDockWidgetTab; +struct DockWidgetTabPrivate; +class CDockAreaTitleBar; +struct DockAreaTitleBarPrivate; +class CFloatingWidgetTitleBar; +class CDockingStateReader; + +/** + * Pure virtual interface for floating widgets. + * This interface is used for opaque and non-opaque undocking. If opaque + * undocking is used, the a real CFloatingDockContainer widget will be created + */ +class ADS_EXPORT IFloatingWidget +{ +public: + virtual ~IFloatingWidget() = default; + + /** + * Starts floating. + * This function should get called typically from a mouse press event + * handler + */ + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + eDragState DragState, QWidget* MouseEventHandler) = 0; + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called. + * This function should be called from a mouse mouve event handler to + * move the floating widget on mouse move events. + */ + virtual void moveFloating() = 0; + + /** + * Tells the widget that to finish dragging if the mouse is released. + * This function should be called from a mouse release event handler + * to finish the dragging + */ + virtual void finishDragging() = 0; +}; + + +/** + * This implements a floating widget that is a dock container that accepts + * docking of dock widgets like the main window and that can be docked into + * another dock container. + * Every floating window of the docking system is a FloatingDockContainer. + */ +class ADS_EXPORT CFloatingDockContainer : public tFloatingWidgetBase, public IFloatingWidget +{ + Q_OBJECT +private: + FloatingDockContainerPrivate* d; ///< private data (pimpl) + friend struct FloatingDockContainerPrivate; + friend class CDockManager; + friend struct DockManagerPrivate; + friend class CDockAreaTabBar; + friend struct DockWidgetTabPrivate; + friend class CDockWidgetTab; + friend class CDockAreaTitleBar; + friend struct DockAreaTitleBarPrivate; + friend class CDockWidget; + friend class CDockAreaWidget; + friend class CFloatingWidgetTitleBar; + +private Q_SLOTS: + void onDockAreasAddedOrRemoved(); + void onDockAreaCurrentChanged(int Index); + +protected: + /** + * Starts floating at the given global position. + * Use moveToGlobalPos() to move the widget to a new position + * depending on the start position given in Pos parameter + */ + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + eDragState DragState, QWidget* MouseEventHandler) override; + + /** + * Call this function if you explicitly want to signal that dragging has + * finished + */ + virtual void finishDragging() override; + + /** + * This function deletes all dock widgets in it. + * This functions should be called only from dock manager in its + * destructor before deleting the floating widget + */ + void deleteContent(); + + /** + * Call this function if you just want to initialize the position + * and size of the floating widget + */ + void initFloatingGeometry(const QPoint& DragStartMousePos, const QSize& Size) + { + startFloating(DragStartMousePos, Size, DraggingInactive, nullptr); + } + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called + */ + void moveFloating() override; + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(CDockingStateReader& Stream, bool Testing); + + /** + * Call this function to update the window title + */ + void updateWindowTitle(); + +protected: // reimplements QWidget + virtual void changeEvent(QEvent *event) override; + virtual void closeEvent(QCloseEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; + virtual void showEvent(QShowEvent *event) override; + +#ifdef Q_OS_MACOS + virtual bool event(QEvent *e) override; + virtual void moveEvent(QMoveEvent *event) override; +#elif defined(Q_OS_UNIX) + virtual void moveEvent(QMoveEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; + virtual bool event(QEvent *e) override; +#endif + +#ifdef Q_OS_WIN + /** + * Native event filter for handling WM_MOVING messages on Windows + */ +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; +#else + virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; +#endif +#endif + + +public: + using Super = tFloatingWidgetBase; + + /** + * Create empty floating widget - required for restore state + */ + CFloatingDockContainer(CDockManager* DockManager); + + /** + * Create floating widget with the given dock area + */ + CFloatingDockContainer(CDockAreaWidget* DockArea); + + /** + * Create floating widget with the given dock widget + */ + CFloatingDockContainer(CDockWidget* DockWidget); + + /** + * Virtual Destructor + */ + virtual ~CFloatingDockContainer(); + + /** + * Access function for the internal dock container + */ + CDockContainerWidget* dockContainer() const; + + /** + * Call this function to start dragging the floating widget + */ + void startDragging(const QPoint& DragStartMousePos, const QSize& Size, + QWidget* MouseEventHandler) + { + startFloating(DragStartMousePos, Size, DraggingFloatingWidget, MouseEventHandler); + } + + /** + * This function returns true, if it can be closed. + * It can be closed, if all dock widgets in all dock areas can be closed + */ + bool isClosable() const; + + /** + * This function returns true, if this floating widget has only one single + * visible dock widget in a single visible dock area. + * The single dock widget is a real top level floating widget because no + * other widgets are docked. + */ + bool hasTopLevelDockWidget() const; + + /** + * This function returns the first dock widget in the first dock area. + * If the function hasSingleDockWidget() returns true, then this function + * returns this single dock widget. + */ + CDockWidget* topLevelDockWidget() const; + + /** + * This function returns a list of all dock widget in this floating widget. + * This is a simple convenience function that simply calls the dockWidgets() + * function of the internal container widget. + */ + QList dockWidgets() const; + + /** + * This function hides the floating widget instantly and delete it later. + */ + void finishDropOperation(); + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + /** + * This is a function that responds to FloatingWidgetTitleBar::maximizeRequest() + * Maximize or normalize the container size. + */ + void onMaximizeRequest(); + + /** + * Normalize (Unmaximize) the window. + * fixGeometry parameter fixes a "bug" in QT where immediately after calling showNormal + * geometry is not set properly. + * Set this true when moving the window immediately after normalizing. + */ + void showNormal(bool fixGeometry=false); + + /** + * Maximizes the window. + */ + void showMaximized(); + + /** + * Returns if the window is currently maximized or not. + */ + bool isMaximized() const; + + /** + * Patched show to prevent the window from appearing in the taskbar. + */ + void show(); + + /** + * Returns true if the floating widget has a native titlebar or false if + * the floating widget has a QWidget based title bar + */ + bool hasNativeTitleBar(); +#endif +}; // class FloatingDockContainer +} + // namespace ads +//----------------------------------------------------------------------------- +#endif // FloatingDockContainerH diff --git a/src/FloatingDragPreview.cpp b/src/FloatingDragPreview.cpp new file mode 100644 index 000000000..f7a45a2b2 --- /dev/null +++ b/src/FloatingDragPreview.cpp @@ -0,0 +1,521 @@ +//============================================================================ +/// \file FloatingDragPreview.cpp +/// \author Uwe Kindler +/// \date 26.11.2019 +/// \brief Implementation of CFloatingDragPreview +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "FloatingDragPreview.h" +#include + +#include +#include +#include +#include + +#include "DockWidget.h" +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "DockContainerWidget.h" +#include "DockOverlay.h" +#include "AutoHideDockContainer.h" +#include "ads_globals.h" + +namespace ads +{ + +/** + * Private data class (pimpl) + */ +struct FloatingDragPreviewPrivate +{ + CFloatingDragPreview *_this; + QWidget* Content; + CDockWidget::DockWidgetFeatures ContentFeatures; + CDockAreaWidget* ContentSourceArea = nullptr; + QPoint DragStartMousePosition; + CDockManager* DockManager; + CDockContainerWidget *DropContainer = nullptr; + qreal WindowOpacity; + bool Hidden = false; + QPixmap ContentPreviewPixmap; + bool Canceled = false; + + + /** + * Private data constructor + */ + FloatingDragPreviewPrivate(CFloatingDragPreview *_public); + void updateDropOverlays(const QPoint &GlobalPos); + + void setHidden(bool Value) + { + Hidden = Value; + _this->update(); + } + + /** + * Cancel dragging and emit the draggingCanceled event + */ + void cancelDragging() + { + Canceled = true; + Q_EMIT _this->draggingCanceled(); + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); + _this->close(); + } + + /** + * Creates the real floating widget in case the mouse is released outside + * outside of any drop area + */ + void createFloatingWidget(); + + /** + * Returns true, if the content is floatable + */ + bool isContentFloatable() const + { + return this->ContentFeatures.testFlag(CDockWidget::DockWidgetFloatable); + } + + /** + * Returns true, if the content is pinnable + */ + bool isContentPinnable() const + { + return this->ContentFeatures.testFlag(CDockWidget::DockWidgetPinnable); + } + + /** + * Returns the content features + */ + CDockWidget::DockWidgetFeatures contentFeatures() const + { + CDockWidget* DockWidget = qobject_cast(Content); + if (DockWidget) + { + return DockWidget->features(); + } + + CDockAreaWidget* DockArea = qobject_cast(Content); + if (DockArea) + { + return DockArea->features(); + } + + return CDockWidget::DockWidgetFeatures(); + } +}; +// struct LedArrayPanelPrivate + + +//============================================================================ +void FloatingDragPreviewPrivate::updateDropOverlays(const QPoint &GlobalPos) +{ + if (!_this->isVisible() || !DockManager) + { + return; + } + + auto Containers = DockManager->dockContainers(); + CDockContainerWidget *TopContainer = nullptr; + for (auto ContainerWidget : Containers) + { + if (!ContainerWidget->isVisible()) + { + continue; + } + + QPoint MappedPos = ContainerWidget->mapFromGlobal(GlobalPos); + if (ContainerWidget->rect().contains(MappedPos)) + { + if (!TopContainer || ContainerWidget->isInFrontOf(TopContainer)) + { + TopContainer = ContainerWidget; + } + } + } + + DropContainer = TopContainer; + auto ContainerOverlay = DockManager->containerOverlay(); + auto DockAreaOverlay = DockManager->dockAreaOverlay(); + + if (!TopContainer) + { + ContainerOverlay->hideOverlay(); + DockAreaOverlay->hideOverlay(); + if (CDockManager::testConfigFlag(CDockManager::DragPreviewIsDynamic)) + { + setHidden(false); + } + return; + } + + auto DockDropArea = DockAreaOverlay->dropAreaUnderCursor(); + auto ContainerDropArea = ContainerOverlay->dropAreaUnderCursor(); + + int VisibleDockAreas = TopContainer->visibleDockAreaCount(); + + // Include the overlay widget we're dragging as a visible widget + auto dockAreaWidget = qobject_cast(Content); + if (dockAreaWidget && dockAreaWidget->isAutoHide()) + { + VisibleDockAreas++; + } + + DockWidgetAreas AllowedContainerAreas = (VisibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; + //ContainerOverlay->enableDropPreview(ContainerDropArea != InvalidDockWidgetArea); + auto DockArea = TopContainer->dockAreaAt(GlobalPos); + // If the dock container contains only one single DockArea, then we need + // to respect the allowed areas - only the center area is relevant here because + // all other allowed areas are from the container + if (VisibleDockAreas == 1 && DockArea) + { + AllowedContainerAreas.setFlag(CenterDockWidgetArea, DockArea->allowedAreas().testFlag(CenterDockWidgetArea)); + } + + if (isContentPinnable()) + { + AllowedContainerAreas |= AutoHideDockAreas; + } + ContainerOverlay->setAllowedAreas(AllowedContainerAreas); + ContainerOverlay->enableDropPreview(ContainerDropArea != InvalidDockWidgetArea); + if (DockArea && DockArea->isVisible() && VisibleDockAreas >= 0 && DockArea != ContentSourceArea) + { + DockAreaOverlay->enableDropPreview(true); + DockAreaOverlay->setAllowedAreas( (VisibleDockAreas == 1) ? NoDockWidgetArea : DockArea->allowedAreas()); + DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that + // the mouse is in the title bar. If the ContainerArea is valid + // then we ignore the dock area of the dockAreaOverlay() and disable + // the drop preview + if ((Area == CenterDockWidgetArea) && (ContainerDropArea != InvalidDockWidgetArea)) + { + DockAreaOverlay->enableDropPreview(false); + ContainerOverlay->enableDropPreview(true); + } + else + { + ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area); + } + ContainerOverlay->showOverlay(TopContainer); + } + else + { + DockAreaOverlay->hideOverlay(); + // If there is only one single visible dock area in a container, then + // it does not make sense to show a dock overlay because the dock area + // would be removed and inserted at the same position. Only auto hide + // area is allowed + if (VisibleDockAreas == 1) + { + ContainerOverlay->setAllowedAreas(AutoHideDockAreas); + } + ContainerOverlay->showOverlay(TopContainer); + + + if (DockArea == ContentSourceArea && InvalidDockWidgetArea == ContainerDropArea) + { + DropContainer = nullptr; + } + } + + if (CDockManager::testConfigFlag(CDockManager::DragPreviewIsDynamic)) + { + setHidden(DockDropArea != InvalidDockWidgetArea || ContainerDropArea != InvalidDockWidgetArea); + } +} + + +//============================================================================ +FloatingDragPreviewPrivate::FloatingDragPreviewPrivate(CFloatingDragPreview *_public) : + _this(_public) +{ + +} + + +//============================================================================ +void FloatingDragPreviewPrivate::createFloatingWidget() +{ + CDockWidget* DockWidget = qobject_cast(Content); + CDockAreaWidget* DockArea = qobject_cast(Content); + + CFloatingDockContainer* FloatingWidget = nullptr; + + if (DockWidget && DockWidget->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + FloatingWidget = new CFloatingDockContainer(DockWidget); + } + else if (DockArea && DockArea->features().testFlag(CDockWidget::DockWidgetFloatable)) + { + FloatingWidget = new CFloatingDockContainer(DockArea); + } + + if (FloatingWidget) + { + FloatingWidget->setGeometry(_this->geometry()); + FloatingWidget->show(); + if (!CDockManager::testConfigFlag(CDockManager::DragPreviewHasWindowFrame)) + { + QApplication::processEvents(); + int FrameHeight = FloatingWidget->frameGeometry().height() - FloatingWidget->geometry().height(); + QRect FixedGeometry = _this->geometry(); + FixedGeometry.adjust(0, FrameHeight, 0, 0); + FloatingWidget->setGeometry(FixedGeometry); + } + } +} + + +//============================================================================ +CFloatingDragPreview::CFloatingDragPreview(QWidget* Content, QWidget* parent) : + QWidget(parent), + d(new FloatingDragPreviewPrivate(this)) +{ + d->Content = Content; + d->ContentFeatures = d->contentFeatures(); + setAttribute(Qt::WA_DeleteOnClose); + if (CDockManager::testConfigFlag(CDockManager::DragPreviewHasWindowFrame)) + { + setWindowFlags( + Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); + } + else + { + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + } + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + auto Flags = windowFlags(); + Flags |= Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint; + setWindowFlags(Flags); +#endif + + // Create a static image of the widget that should get undocked + // This is like some kind preview image like it is uses in drag and drop + // operations + if (CDockManager::testConfigFlag(CDockManager::DragPreviewShowsContentPixmap)) + { + d->ContentPreviewPixmap = QPixmap(Content->size()); + Content->render(&d->ContentPreviewPixmap); + } + + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), + SLOT(onApplicationStateChanged(Qt::ApplicationState))); + + // The only safe way to receive escape key presses is to install an event + // filter for the application object + qApp->installEventFilter(this); +} + + +//============================================================================ +CFloatingDragPreview::CFloatingDragPreview(CDockWidget* Content) + : CFloatingDragPreview((QWidget*)Content, Content->dockManager()) +{ + d->DockManager = Content->dockManager(); + if (Content->dockAreaWidget()->openDockWidgetsCount() == 1) + { + d->ContentSourceArea = Content->dockAreaWidget(); + } + setWindowTitle(Content->windowTitle()); +} + + +//============================================================================ +CFloatingDragPreview::CFloatingDragPreview(CDockAreaWidget* Content) + : CFloatingDragPreview((QWidget*)Content, Content->dockManager()) +{ + d->DockManager = Content->dockManager(); + d->ContentSourceArea = Content; + setWindowTitle(Content->currentDockWidget()->windowTitle()); +} + + +//============================================================================ +CFloatingDragPreview::~CFloatingDragPreview() +{ + delete d; +} + + +//============================================================================ +void CFloatingDragPreview::moveFloating() +{ + int BorderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition + - QPoint(BorderSize, 0); + move(moveToPos); + d->updateDropOverlays(QCursor::pos()); +} + + +//============================================================================ +void CFloatingDragPreview::startFloating(const QPoint &DragStartMousePos, + const QSize &Size, eDragState DragState, QWidget *MouseEventHandler) +{ + Q_UNUSED(MouseEventHandler) + Q_UNUSED(DragState) + resize(Size); + d->DragStartMousePosition = DragStartMousePos; + moveFloating(); + show(); + +} + + +//============================================================================ +void CFloatingDragPreview::finishDragging() +{ + ADS_PRINT("CFloatingDragPreview::finishDragging"); + + auto DockDropArea = d->DockManager->dockAreaOverlay()->visibleDropAreaUnderCursor(); + auto ContainerDropArea = d->DockManager->containerOverlay()->visibleDropAreaUnderCursor(); + bool ValidDropArea = (DockDropArea != InvalidDockWidgetArea) || (ContainerDropArea != InvalidDockWidgetArea); + + // Non floatable auto hide widgets should stay in its current auto hide + // state if they are dragged into a floating window + if (ValidDropArea || d->isContentFloatable()) + { + cleanupAutoHideContainerWidget(ContainerDropArea); + } + + if (!d->DropContainer) + { + d->createFloatingWidget(); + } + else if (DockDropArea != InvalidDockWidgetArea) + { + d->DropContainer->dropWidget(d->Content, DockDropArea, d->DropContainer->dockAreaAt(QCursor::pos()), + d->DockManager->dockAreaOverlay()->tabIndexUnderCursor()); + } + else if (ContainerDropArea != InvalidDockWidgetArea) + { + CDockAreaWidget* DockArea = nullptr; + // If there is only one single dock area, and we drop into the center + // then we tabify the dropped widget into the only visible dock area + if (d->DropContainer->visibleDockAreaCount() <= 1 && CenterDockWidgetArea == ContainerDropArea) + { + DockArea = d->DropContainer->dockAreaAt(QCursor::pos()); + } + + d->DropContainer->dropWidget(d->Content, ContainerDropArea, DockArea, + d->DockManager->containerOverlay()->tabIndexUnderCursor()); + } + else + { + d->createFloatingWidget(); + } + + this->close(); + d->DockManager->containerOverlay()->hideOverlay(); + d->DockManager->dockAreaOverlay()->hideOverlay(); +} + + +//============================================================================ +void CFloatingDragPreview::cleanupAutoHideContainerWidget(DockWidgetArea ContainerDropArea) +{ + auto DroppedDockWidget = qobject_cast(d->Content); + auto DroppedArea = qobject_cast(d->Content); + auto AutoHideContainer = DroppedDockWidget + ? DroppedDockWidget->autoHideDockContainer() + : DroppedArea->autoHideDockContainer(); + + if (!AutoHideContainer) + { + return; + } + + // If the dropped widget is already an auto hide widget and if it is moved + // to a new side bar location in the same container, then we do not need + // to cleanup + if (ads::internal::isSideBarArea(ContainerDropArea) + && (d->DropContainer == AutoHideContainer->dockContainer())) + { + return; + } + + AutoHideContainer->cleanupAndDelete(); +} + + +//============================================================================ +void CFloatingDragPreview::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + if (d->Hidden) + { + return; + } + + QPainter painter(this); + painter.setOpacity(0.6); + if (CDockManager::testConfigFlag(CDockManager::DragPreviewShowsContentPixmap)) + { + painter.drawPixmap(QPoint(0, 0), d->ContentPreviewPixmap); + } + + // If we do not have a window frame then we paint a QRubberBand like + // frameless window + if (!CDockManager::testConfigFlag(CDockManager::DragPreviewHasWindowFrame)) + { + QColor Color = palette().color(QPalette::Active, QPalette::Highlight); + QPen Pen = painter.pen(); + Pen.setColor(Color.darker(120)); + Pen.setStyle(Qt::SolidLine); + Pen.setWidth(1); + Pen.setCosmetic(true); + painter.setPen(Pen); + Color = Color.lighter(130); + Color.setAlpha(64); + painter.setBrush(Color); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + } +} + +//============================================================================ +void CFloatingDragPreview::onApplicationStateChanged(Qt::ApplicationState state) +{ + if (state != Qt::ApplicationActive) + { + disconnect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), + this, SLOT(onApplicationStateChanged(Qt::ApplicationState))); + d->cancelDragging(); + } +} + + +//============================================================================ +bool CFloatingDragPreview::eventFilter(QObject *watched, QEvent *event) +{ + Q_UNUSED(watched); + if (!d->Canceled && event->type() == QEvent::KeyPress) + { + QKeyEvent* e = static_cast(event); + if (e->key() == Qt::Key_Escape) + { + watched->removeEventFilter(this); + d->cancelDragging(); + } + } + + return false; +} + + + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF FloatingDragPreview.cpp diff --git a/src/FloatingDragPreview.h b/src/FloatingDragPreview.h new file mode 100644 index 000000000..68e3e5cf9 --- /dev/null +++ b/src/FloatingDragPreview.h @@ -0,0 +1,113 @@ +#ifndef FloatingDragPreviewH +#define FloatingDragPreviewH +//============================================================================ +/// \file FloatingDragPreview.h +/// \author Uwe Kindler +/// \date 26.11.2019 +/// \brief Declaration of CFloatingDragPreview +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include "FloatingDockContainer.h" + +namespace ads +{ +class CDockWidget; +class CDockAreaWidget; +struct FloatingDragPreviewPrivate; + +/** + * A floating overlay is a temporary floating widget that is just used to + * indicate the floating widget movement. + * This widget is used as a placeholder for drag operations for non-opaque + * docking + */ +class CFloatingDragPreview : public QWidget, public IFloatingWidget +{ + Q_OBJECT +private: + FloatingDragPreviewPrivate* d; + friend struct FloatingDragPreviewPrivate; + +private Q_SLOTS: + /** + * Cancel non opaque undocking if application becomes inactive + */ + void onApplicationStateChanged(Qt::ApplicationState state); + +protected: + /** + * Cares about painting the + */ + virtual void paintEvent(QPaintEvent *e) override; + + /** + * The content is a DockArea or a DockWidget + */ + CFloatingDragPreview(QWidget* Content, QWidget* parent); + +public: + using Super = QWidget; + + /** + * Creates an instance for undocking the DockWidget in Content parameter + */ + CFloatingDragPreview(CDockWidget* Content); + + /** + * Creates an instance for undocking the DockArea given in Content + * parameters + */ + CFloatingDragPreview(CDockAreaWidget* Content); + + /** + * Delete private data + */ + ~CFloatingDragPreview(); + + /** + * We filter the events of the assigned content widget to receive + * escape key presses for canceling the drag operation + */ + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + +public: // implements IFloatingWidget ----------------------------------------- + virtual void startFloating(const QPoint& DragStartMousePos, const QSize& Size, + eDragState DragState, QWidget* MouseEventHandler) override; + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called + */ + virtual void moveFloating() override; + + /** + * Finishes dragging. + * Hides the dock overlays and executes the real undocking and docking + * of the assigned Content widget + */ + virtual void finishDragging() override; + + /** + * Cleanup auto hide container if the dragged widget has one + */ + void cleanupAutoHideContainerWidget(DockWidgetArea ContainerDropArea); + +Q_SIGNALS: + /** + * This signal is emitted, if dragging has been canceled by escape key + * or by active application switching via task manager + */ + void draggingCanceled(); +}; + + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // FloatingDragPreviewH + diff --git a/src/IconProvider.cpp b/src/IconProvider.cpp new file mode 100644 index 000000000..25e7be19c --- /dev/null +++ b/src/IconProvider.cpp @@ -0,0 +1,73 @@ +//============================================================================ +/// \file IconProvider.cpp +/// \author Uwe Kindler +/// \date 18.10.2019 +/// \brief Implementation of CIconProvider +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "IconProvider.h" +#include + +namespace ads +{ +/** + * Private data class (pimpl) + */ +struct IconProviderPrivate +{ + CIconProvider *_this; + QVector UserIcons{IconCount, QIcon()}; + + /** + * Private data constructor + */ + IconProviderPrivate(CIconProvider *_public); +}; +// struct LedArrayPanelPrivate + +//============================================================================ +IconProviderPrivate::IconProviderPrivate(CIconProvider *_public) : + _this(_public) +{ + +} + +//============================================================================ +CIconProvider::CIconProvider() : + d(new IconProviderPrivate(this)) +{ + +} + +//============================================================================ +CIconProvider::~CIconProvider() +{ + delete d; +} + + +//============================================================================ +QIcon CIconProvider::customIcon(eIcon IconId) const +{ + Q_ASSERT(IconId < d->UserIcons.size()); + return d->UserIcons[IconId]; +} + + +//============================================================================ +void CIconProvider::registerCustomIcon(eIcon IconId, const QIcon &icon) +{ + Q_ASSERT(IconId < d->UserIcons.size()); + d->UserIcons[IconId] = icon; +} + +} // namespace ads + + + + +//--------------------------------------------------------------------------- +// EOF IconProvider.cpp diff --git a/src/IconProvider.h b/src/IconProvider.h new file mode 100644 index 000000000..fa7d150f6 --- /dev/null +++ b/src/IconProvider.h @@ -0,0 +1,61 @@ +#ifndef IconProviderH +#define IconProviderH +//============================================================================ +/// \file IconProvider.h +/// \author Uwe Kindler +/// \date 18.10.2019 +/// \brief Declaration of CIconProvider +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +namespace ads +{ + +struct IconProviderPrivate; + +/** + * This object provides all icons that are required by the advanced docking + * system. + * The IconProvider enables the user to register custom icons in case using + * stylesheets is not an option. + */ +class ADS_EXPORT CIconProvider +{ +private: + IconProviderPrivate* d; ///< private data (pimpl) + friend struct IconProviderPrivate; + +public: + /** + * Default Constructor + */ + CIconProvider(); + + /** + * Virtual Destructor + */ + virtual ~CIconProvider(); + + /** + * The function returns a custom icon if one is registered and a null Icon + * if no custom icon is registered + */ + QIcon customIcon(eIcon IconId) const; + + /** + * Registers a custom icon for the given IconId + */ + void registerCustomIcon(eIcon IconId, const QIcon &icon); +}; // class IconProvider + +} // namespace ads + + +//--------------------------------------------------------------------------- +#endif // IconProviderH diff --git a/src/PushButton.cpp b/src/PushButton.cpp new file mode 100644 index 000000000..54883c88d --- /dev/null +++ b/src/PushButton.cpp @@ -0,0 +1,70 @@ +//============================================================================ +/// \file PushButton.cpp +/// \author Uwe Kindler +/// \date 18.10.2022 +/// \brief Implementation of CPushButton +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "PushButton.h" + +#include +#include +#include +#include + + +namespace ads +{ +QSize CPushButton::sizeHint() const +{ + QSize sh = QPushButton::sizeHint(); + + if (m_Orientation != CPushButton::Horizontal) + { + sh.transpose(); + } + + return sh; +} + +void CPushButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QStylePainter painter(this); + QStyleOptionButton option; + initStyleOption(&option); + + if (m_Orientation == CPushButton::VerticalTopToBottom) + { + painter.rotate(90); + painter.translate(0, -1 * width()); + option.rect = option.rect.transposed(); + } + else if (m_Orientation == CPushButton::VerticalBottomToTop) + { + painter.rotate(-90); + painter.translate(-1 * height(), 0); + option.rect = option.rect.transposed(); + } + + painter.drawControl(QStyle::CE_PushButton, option); +} + +CPushButton::Orientation CPushButton::buttonOrientation() const +{ + return m_Orientation; +} + +void CPushButton::setButtonOrientation(Orientation orientation) +{ + m_Orientation = orientation; + updateGeometry(); +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF PushButton.cpp diff --git a/src/PushButton.h b/src/PushButton.h new file mode 100644 index 000000000..5c4ac7e44 --- /dev/null +++ b/src/PushButton.h @@ -0,0 +1,58 @@ +#ifndef PushButtonH +#define PushButtonH +//============================================================================ +/// \file PushButton.h +/// \author Uwe Kindler +/// \date 18.10.2022 +/// \brief Declaration of CPushButton +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" + +#include + +namespace ads +{ + +/** + * ADS specific push button class with orientation support + */ +class ADS_EXPORT CPushButton : public QPushButton +{ + Q_OBJECT +public: + enum Orientation { + Horizontal, + VerticalTopToBottom, + VerticalBottomToTop + }; + + using QPushButton::QPushButton; + + virtual QSize sizeHint() const override; + + /** + * Returns the current orientation + */ + Orientation buttonOrientation() const; + + /** + * Set the orientation of this button + */ + void setButtonOrientation(Orientation orientation); + +protected: + virtual void paintEvent(QPaintEvent *event) override; + +private: + Orientation m_Orientation = Horizontal; +}; + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // PushButtonH + diff --git a/src/ResizeHandle.cpp b/src/ResizeHandle.cpp new file mode 100644 index 000000000..ae979844c --- /dev/null +++ b/src/ResizeHandle.cpp @@ -0,0 +1,326 @@ +//============================================================================ +/// \file ResizeHandle.cpp +/// \author Uwe Kindler +/// \date 24.10.2022 +/// \brief Implementation of CResizeHandle class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ResizeHandle.h" + +#include +#include +#include +#include +#include +#include + +#include "ads_globals.h" + +namespace ads +{ +/** + * Private data class of CResizeHandle class (pimpl) + */ +struct ResizeHandlePrivate +{ + CResizeHandle *_this; + Qt::Edge HandlePosition = Qt::LeftEdge; + QWidget* Target = nullptr; + int MouseOffset = 0; + bool Pressed = false; + int MinSize = 0; + int MaxSize = 1; + QPointer RubberBand; + bool OpaqueResize = false; + int HandleWidth = 4; + + /** + * Private data constructor + */ + ResizeHandlePrivate(CResizeHandle *_public); + + /** + * Pick position component from pos depending on orientation + */ + int pick(const QPoint &pos) const + { + return _this->orientation() == Qt::Horizontal ? pos.x() : pos.y(); + } + + /** + * Returns true, if orientation is horizontal + */ + bool isHorizontal() const + { + return _this->orientation() == Qt::Horizontal; + } + + /** + * Set rubberband position + */ + void setRubberBand(int Pos); + + /** + * Calculates the resize position and geometry + */ + void doResizing(QMouseEvent* e, bool ForceResize = false); +}; +// struct ResizeHandlePrivate + +//============================================================================ +ResizeHandlePrivate::ResizeHandlePrivate(CResizeHandle *_public) : + _this(_public) +{ + +} + + +//============================================================================ +void ResizeHandlePrivate::setRubberBand(int Pos) +{ + if (!RubberBand) + { + RubberBand = new QRubberBand(QRubberBand::Line, Target->parentWidget()); + } + + auto Geometry = _this->geometry(); + auto TopLeft = Target->mapTo(Target->parentWidget(), Geometry.topLeft()); + switch (HandlePosition) + { + case Qt::LeftEdge: + case Qt::RightEdge: TopLeft.rx() += Pos; break; + case Qt::TopEdge: + case Qt::BottomEdge: TopLeft.ry() += Pos; break; + } + + Geometry.moveTopLeft(TopLeft); + RubberBand->setGeometry(Geometry); + RubberBand->show(); +} + + +//============================================================================ +void ResizeHandlePrivate::doResizing(QMouseEvent* e, bool ForceResize) +{ + int pos = pick(e->pos()) - MouseOffset; + auto OldGeometry = Target->geometry(); + auto NewGeometry = OldGeometry; + switch (HandlePosition) + { + case Qt::LeftEdge: + { + NewGeometry.adjust(pos, 0, 0, 0); + int Size = qBound(MinSize, NewGeometry.width(), MaxSize); + pos += (NewGeometry.width() - Size); + NewGeometry.setWidth(Size); + NewGeometry.moveTopRight(OldGeometry.topRight()); + } + break; + + + case Qt::RightEdge: + { + NewGeometry.adjust(0, 0, pos, 0); + int Size = qBound(MinSize, NewGeometry.width(), MaxSize); + pos -= (NewGeometry.width() - Size); + NewGeometry.setWidth(Size); + } + break; + + case Qt::TopEdge: + { + NewGeometry.adjust(0, pos, 0, 0); + int Size = qBound(MinSize, NewGeometry.height(), MaxSize); + pos += (NewGeometry.height() - Size); + NewGeometry.setHeight(Size); + NewGeometry.moveBottomLeft(OldGeometry.bottomLeft()); + } + break; + + case Qt::BottomEdge: + { + NewGeometry.adjust(0, 0, 0, pos); + int Size = qBound(MinSize, NewGeometry.height(), MaxSize); + pos -= (NewGeometry.height() - Size); + NewGeometry.setHeight(Size); + } + break; + } + + if (_this->opaqueResize() || ForceResize) + { + Target->setGeometry(NewGeometry); + } + else + { + setRubberBand(pos); + } +} + + +//============================================================================ +CResizeHandle::CResizeHandle(Qt::Edge HandlePosition, QWidget* parent) : + Super(parent), + d(new ResizeHandlePrivate(this)) +{ + d->Target = parent; + setMinResizeSize(48); + setHandlePosition(HandlePosition); +} + + +//============================================================================ +CResizeHandle::~CResizeHandle() +{ + delete d; +} + + +//============================================================================ +void CResizeHandle::mouseMoveEvent(QMouseEvent* e) +{ + if (!(e->buttons() & Qt::LeftButton)) + { + return; + } + + d->doResizing(e); +} + + +//============================================================================ +void CResizeHandle::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton) + { + d->MouseOffset = d->pick(e->pos()); + d->Pressed = true; + update(); + } +} + + +//============================================================================ +void CResizeHandle::mouseReleaseEvent(QMouseEvent* e) +{ + if (!opaqueResize() && e->button() == Qt::LeftButton) + { + if (d->RubberBand) + { + d->RubberBand->deleteLater(); + } + d->doResizing(e, true); + } + if (e->button() == Qt::LeftButton) + { + d->Pressed = false; + update(); + } +} + + +//============================================================================ +void CResizeHandle::setHandlePosition(Qt::Edge HandlePosition) +{ + d->HandlePosition = HandlePosition; + switch (d->HandlePosition) + { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: setCursor(Qt::SizeHorCursor); break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: setCursor(Qt::SizeVerCursor); break; + } + + setMaxResizeSize(d->isHorizontal() ? parentWidget()->height() : parentWidget()->width()); + if (!d->isHorizontal()) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + } + else + { + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + } +} + + +//============================================================================ +Qt::Edge CResizeHandle::handlePostion() const +{ + return d->HandlePosition; +} + + +//============================================================================ +Qt::Orientation CResizeHandle::orientation() const +{ + switch (d->HandlePosition) + { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: return Qt::Horizontal; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: return Qt::Vertical; + } + + return Qt::Horizontal; +} + + +//============================================================================ +QSize CResizeHandle::sizeHint() const +{ + QSize Result; + switch (d->HandlePosition) + { + case Qt::LeftEdge: // fall through + case Qt::RightEdge: Result = QSize(d->HandleWidth, d->Target->height()); break; + + case Qt::TopEdge: // fall through + case Qt::BottomEdge: Result = QSize(d->Target->width(), d->HandleWidth); break; + } + + return Result; +} + + +//============================================================================ +bool CResizeHandle::isResizing() const +{ + return d->Pressed; +} + + +//============================================================================ +void CResizeHandle::setMinResizeSize(int MinSize) +{ + d->MinSize = MinSize; +} + + +//============================================================================ +void CResizeHandle::setMaxResizeSize(int MaxSize) +{ + d->MaxSize = MaxSize; +} + + +//============================================================================ +void CResizeHandle::setOpaqueResize(bool opaque) +{ + d->OpaqueResize = opaque; +} + + +//============================================================================ +bool CResizeHandle::opaqueResize() const +{ + return d->OpaqueResize; +} +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF ResizeHandle.cpp diff --git a/src/ResizeHandle.h b/src/ResizeHandle.h new file mode 100644 index 000000000..1b5af3216 --- /dev/null +++ b/src/ResizeHandle.h @@ -0,0 +1,102 @@ +#ifndef ResizeHandleH +#define ResizeHandleH +//============================================================================ +/// \file ResizeHandle.h +/// \author Uwe Kindler +/// \date 24.10.2022 +/// \brief Declaration of CResizeHandle class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "ads_globals.h" +#include + +namespace ads +{ +struct ResizeHandlePrivate; + +/** + * Resize handle for resizing its parent widget + */ +class ADS_EXPORT CResizeHandle : public QFrame +{ + Q_OBJECT + Q_DISABLE_COPY(CResizeHandle) + Q_PROPERTY(bool opaqueResize READ opaqueResize WRITE setOpaqueResize) +private: + ResizeHandlePrivate* d; ///< private data (pimpl) + friend struct ResizeHandlePrivate; + +protected: + void mouseMoveEvent(QMouseEvent *) override; + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + +public: + using Super = QFrame; + + /** + * Default Constructor + */ + CResizeHandle(Qt::Edge HandlePosition, QWidget* parent); + + /** + * Virtual Destructor + */ + virtual ~CResizeHandle(); + + /** + * Sets the handle position + */ + void setHandlePosition(Qt::Edge HandlePosition); + + /** + * Returns the handle position + */ + Qt::Edge handlePostion() const; + + /** + * Returns the orientation of this resize handle + */ + Qt::Orientation orientation() const; + + /** + * Returns the size hint + */ + QSize sizeHint() const override; + + /** + * Returns true, if resizing is active + */ + bool isResizing() const; + + /** + * Sets the minimum size for the widget that is going to be resized. + * The resize handle will not resize the target widget to a size smaller + * than this value + */ + void setMinResizeSize(int MinSize); + + /** + * Sets the maximum size for the widget that is going to be resized + * The resize handle will not resize the target widget to a size bigger + * than this value + */ + void setMaxResizeSize(int MaxSize); + + /** + * Enable / disable opaque resizing + */ + void setOpaqueResize(bool opaque = true); + + /** + * Returns true if widgets are resized dynamically (opaquely) while + * interactively moving the resize handle. Otherwise returns false. + */ + bool opaqueResize() const; +}; // class name +} // namespace ads +//----------------------------------------------------------------------------- +#endif // ResizeHandleH diff --git a/src/ads.qrc b/src/ads.qrc new file mode 100644 index 000000000..307e93b1f --- /dev/null +++ b/src/ads.qrc @@ -0,0 +1,27 @@ + + + stylesheets/default.css + images/close-button.svg + images/pin-button.svg + images/close-button-disabled.svg + stylesheets/default_linux.css + images/close-button-focused.svg + stylesheets/focus_highlighting.css + stylesheets/focus_highlighting_linux.css + images/tabs-menu-button.svg + images/detach-button.svg + images/detach-button-disabled.svg + images/maximize-button.svg + images/maximize-button-focused.svg + images/restore-button.svg + images/restore-button-focused.svg + images/vs-pin-button.svg + images/vs-pin-button-pinned.svg + images/vs-pin-button-pinned-focused.svg + images/vs-pin-button_45.svg + images/pin-button-big.svg + images/minimize-button.svg + images/minimize-button-focused.svg + images/vs-pin-button-disabled.svg + + diff --git a/src/ads_globals.cpp b/src/ads_globals.cpp new file mode 100644 index 000000000..7d3cf7e6f --- /dev/null +++ b/src/ads_globals.cpp @@ -0,0 +1,467 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file ads_globals.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include +#include + +#include "DockSplitter.h" +#include "DockManager.h" +#include "IconProvider.h" +#include "ads_globals.h" + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include +#include +#include +#include +#endif + +namespace ads +{ + +namespace internal +{ +const int FloatingWidgetDragStartEvent = QEvent::registerEventType(); +const int DockedWidgetDragStartEvent = QEvent::registerEventType(); +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +static QString _window_manager; +static QHash _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(connection); +} + + +//============================================================================ +xcb_atom_t xcb_get_atom(const char *name) +{ + if (!is_platform_x11()) + { + return XCB_ATOM_NONE; + } + auto key = QString(name); + if(_xcb_atom_cache.contains(key)) + { + return _xcb_atom_cache[key]; + } + 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, nullptr); + if (!reply) + { + return XCB_ATOM_NONE; + } + xcb_atom_t atom = reply->atom; + if(atom == XCB_ATOM_NONE) + { + ADS_PRINT("Unknown Atom response from XServer: " << name); + } + else + { + _xcb_atom_cache.insert(key, atom); + } + free(reply); + return atom; +} + + +//============================================================================ +void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2) +{ + 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; + event.response_type = XCB_CLIENT_MESSAGE; + event.format = 32; + event.sequence = 0; + event.window = window; + event.type = type_atom; + event.data.data32[0] = set ? 1 : 0; + event.data.data32[1] = prop_atom; + event.data.data32[2] = prop2 ? xcb_get_atom(prop2) : 0; + event.data.data32[3] = 0; + event.data.data32[4] = 0; + + xcb_send_event(connection, 0, window, + XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_PROPERTY_CHANGE, + (const char *)&event); + xcb_flush(connection); +} + + +//============================================================================ +xcb_get_property_reply_t* _xcb_get_props(WId window, const char *type, unsigned int atom_type) +{ + if (!is_platform_x11()) + { + return nullptr; + } + xcb_connection_t *connection = x11_connection(); + xcb_atom_t type_atom = xcb_get_atom(type); + if (type_atom == XCB_ATOM_NONE) + { + return nullptr; + } + xcb_get_property_cookie_t request = xcb_get_property_unchecked(connection, 0, window, type_atom, atom_type, 0, 1024); + xcb_get_property_reply_t *reply = xcb_get_property_reply(connection, request, nullptr); + if(reply && reply->type != atom_type) + { + ADS_PRINT("ATOM TYPE MISMATCH (" << type <<"). Expected: " << atom_type << " but got " << reply->type); + free(reply); + return nullptr; + } + return reply; +} + + +//============================================================================ +template +void xcb_get_prop_list(WId window, const char *type, QVector &ret, unsigned int atom_type) +{ + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, atom_type); + if (reply && reply->format == 32 && reply->type == atom_type && reply->value_len > 0) + { + const xcb_atom_t *data = static_cast(xcb_get_property_value(reply)); + ret.resize(reply->value_len); + memcpy((void *)&ret.first(), (void *)data, reply->value_len * sizeof(T)); + } + free(reply); +} + + +//============================================================================ +QString xcb_get_prop_string(WId window, const char *type) +{ + QString ret; + // try utf8 first + xcb_atom_t utf_atom = xcb_get_atom("UTF8_STRING"); + if(utf_atom != XCB_ATOM_NONE) + { + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, utf_atom); + if (reply && reply->format == 8 && reply->type == utf_atom) + { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromUtf8(value, xcb_get_property_value_length(reply)); + free(reply); + return ret; + } + free(reply); + } + // Fall back to XCB_ATOM_STRING + xcb_get_property_reply_t *reply = _xcb_get_props(window, type, XCB_ATOM_STRING); + if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) + { + const char *value = reinterpret_cast(xcb_get_property_value(reply)); + ret = QString::fromLatin1(value, xcb_get_property_value_length(reply)); + } + free(reply); + return ret; +} + + +//============================================================================ +bool xcb_dump_props(WId window, const char *type) +{ + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + qDebug() << "\n\n!!!" << type << " - " << atoms.length(); + xcb_connection_t *connection = x11_connection(); + for (auto atom : atoms) + { + auto foo = xcb_get_atom_name(connection, atom); + auto bar = xcb_get_atom_name_reply(connection, foo, nullptr); + qDebug() << "\t" << xcb_get_atom_name_name(bar); + free(bar); + } + return true; +} + + +//============================================================================ +void xcb_add_prop(bool state, WId window, const char *type, const char *prop) +{ + if (!is_platform_x11()) + { + return; + } + xcb_atom_t prop_atom = xcb_get_atom(prop); + xcb_atom_t type_atom = xcb_get_atom(type); + if (prop_atom == XCB_ATOM_NONE || type_atom == XCB_ATOM_NONE) + { + return; + } + QVector atoms; + xcb_get_prop_list(window, type, atoms, XCB_ATOM_ATOM); + int index = atoms.indexOf(prop_atom); + if (state && index == -1) + { + atoms.push_back(prop_atom); + } + else if (!state && index >= 0) + { + atoms.remove(index); + } + 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); +} + + +//============================================================================ +QString detectWindowManagerX11() +{ + // Tries to detect the windowmanager via X11. + // See: https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html#idm46018259946000 + if (!is_platform_x11()) + { + return "UNKNOWN"; + } + xcb_connection_t *connection = x11_connection(); + xcb_screen_t *first_screen = xcb_setup_roots_iterator (xcb_get_setup (connection)).data; + if(!first_screen) + { + ADS_PRINT("No screen found via XCB."); + return "UNKNOWN"; + } + // Get supporting window () + xcb_window_t root = first_screen->root; + xcb_window_t support_win = 0; + QVector sup_windows; + xcb_get_prop_list(root, "_NET_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_WINDOW); + if(sup_windows.length() == 0) + { + // This doesn't seem to be in use anymore, but wmctrl does the same so lets play safe. + // Both XCB_ATOM_CARDINAL and XCB_ATOM_WINDOW break down to a uint32_t, so reusing sup_windows should be fine. + xcb_get_prop_list(root, "_WIN_SUPPORTING_WM_CHECK", sup_windows, XCB_ATOM_CARDINAL); + } + if(sup_windows.length() == 0) + { + ADS_PRINT("Failed to get the supporting window on non EWMH comform WM."); + return "UNKNOWN"; + } + support_win = sup_windows[0]; + QString ret = xcb_get_prop_string(support_win, "_NET_WM_NAME"); + if(ret.length() == 0) + { + ADS_PRINT("Empty WM name occurred."); + return "UNKNOWN"; + } + return ret; +} + +//============================================================================ +QString windowManager() +{ + if(_window_manager.length() == 0) + { + _window_manager = detectWindowManagerX11(); + } + return _window_manager; +} +#endif + + +//============================================================================ +void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To) +{ + int index = Splitter->indexOf(From); + From->setParent(nullptr); + Splitter->insertWidget(index, To); +} + +//============================================================================ +CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area) +{ + switch (Area) + { + case TopDockWidgetArea: return CDockInsertParam(Qt::Vertical, false); + case RightDockWidgetArea: return CDockInsertParam(Qt::Horizontal, true); + case CenterDockWidgetArea: + case BottomDockWidgetArea: return CDockInsertParam(Qt::Vertical, true); + case LeftDockWidgetArea: return CDockInsertParam(Qt::Horizontal, false); + default: CDockInsertParam(Qt::Vertical, false); + } // switch (Area) + + return CDockInsertParam(Qt::Vertical, false); +} + + +//============================================================================ +SideBarLocation toSideBarLocation(DockWidgetArea Area) +{ + switch (Area) + { + case LeftAutoHideArea: return SideBarLeft; + case RightAutoHideArea: return SideBarRight; + case TopAutoHideArea: return SideBarTop; + case BottomAutoHideArea: return SideBarBottom; + default: + return SideBarNone; + } + + return SideBarNone; +} + + +//============================================================================ +bool isHorizontalSideBarLocation(SideBarLocation Location) +{ + switch (Location) + { + case SideBarTop: + case SideBarBottom: return true; + case SideBarLeft: + case SideBarRight: return false; + default: + return false; + } + + return false; +} + + +//============================================================================ +bool isSideBarArea(DockWidgetArea Area) +{ + return toSideBarLocation(Area) != SideBarNone; +} + + +//============================================================================ +QPixmap createTransparentPixmap(const QPixmap& Source, qreal Opacity) +{ + QPixmap TransparentPixmap(Source.size()); + TransparentPixmap.fill(Qt::transparent); + QPainter p(&TransparentPixmap); + p.setOpacity(Opacity); + p.drawPixmap(0, 0, Source); + return TransparentPixmap; +} + + +//============================================================================ +void hideEmptyParentSplitters(CDockSplitter* Splitter) +{ + while (Splitter && Splitter->isVisible()) + { + if (!Splitter->hasVisibleContent()) + { + Splitter->hide(); + } + Splitter = internal::findParent(Splitter); + } +} + + +//============================================================================ +void setButtonIcon(QAbstractButton* Button, QStyle::StandardPixmap StandarPixmap, + ads::eIcon CustomIconId) +{ + // First we try to use custom icons if available + QIcon Icon = CDockManager::iconProvider().customIcon(CustomIconId); + if (!Icon.isNull()) + { + Button->setIcon(Icon); + return; + } + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) + Button->setIcon(Button->style()->standardIcon(StandarPixmap)); +#else + // The standard icons does not look good on high DPI screens so we create + // our own "standard" icon here. + QPixmap normalPixmap = Button->style()->standardPixmap(StandarPixmap, 0, Button); + Icon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + Icon.addPixmap(normalPixmap, QIcon::Normal); + Button->setIcon(Icon); +#endif +} + + +//============================================================================ +void repolishStyle(QWidget* w, eRepolishChildOptions Options) +{ + if (!w) + { + return; + } + w->style()->unpolish(w); + w->style()->polish(w); + + if (RepolishIgnoreChildren == Options) + { + return; + } + + QList Children = w->findChildren(QString(), + (RepolishDirectChildren == Options) ? Qt::FindDirectChildrenOnly: Qt::FindChildrenRecursively); + for (auto Widget : Children) + { + Widget->style()->unpolish(Widget); + Widget->style()->polish(Widget); + } +} + + +//============================================================================ +QRect globalGeometry(QWidget* w) +{ + QRect g = w->geometry(); + g.moveTopLeft(w->mapToGlobal(QPoint(0, 0))); + return g; +} + +} // namespace internal +} // namespace ads + + + +//--------------------------------------------------------------------------- +// EOF ads_globals.cpp diff --git a/src/ads_globals.h b/src/ads_globals.h new file mode 100644 index 000000000..83deecc50 --- /dev/null +++ b/src/ads_globals.h @@ -0,0 +1,374 @@ +#ifndef ads_globalsH +#define ads_globalsH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file ads_globals.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of +//============================================================================ + + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QAbstractButton) + +#ifndef ADS_STATIC +#ifdef ADS_SHARED_EXPORT +#define ADS_EXPORT Q_DECL_EXPORT +#else +#define ADS_EXPORT Q_DECL_IMPORT +#endif +#else +#define ADS_EXPORT +#endif + +// Define ADS_DEBUG_PRINT to enable a lot of debug output +#ifdef ADS_DEBUG_PRINT +#define ADS_PRINT(s) qDebug() << s +#else +#define ADS_PRINT(s) +#endif + +// Set ADS_DEBUG_LEVEL to enable additional debug output and to enable layout +// dumps to qDebug and std::cout after layout changes +#define ADS_DEBUG_LEVEL 0 + +QT_FORWARD_DECLARE_CLASS(QSplitter) + +namespace ads +{ +Q_NAMESPACE +class CDockSplitter; + +enum DockWidgetArea +{ + NoDockWidgetArea = 0x00, + LeftDockWidgetArea = 0x01, + RightDockWidgetArea = 0x02, + TopDockWidgetArea = 0x04, + BottomDockWidgetArea = 0x08, + CenterDockWidgetArea = 0x10, + LeftAutoHideArea = 0x20, + RightAutoHideArea = 0x40, + TopAutoHideArea = 0x80, + BottomAutoHideArea = 0x100, + + InvalidDockWidgetArea = NoDockWidgetArea, + OuterDockAreas = TopDockWidgetArea | LeftDockWidgetArea | RightDockWidgetArea | BottomDockWidgetArea, + AutoHideDockAreas = LeftAutoHideArea | RightAutoHideArea | TopAutoHideArea | BottomAutoHideArea, + AllDockAreas = OuterDockAreas | CenterDockWidgetArea +}; +Q_DECLARE_FLAGS(DockWidgetAreas, DockWidgetArea) + + +enum eTabIndex +{ + TabDefaultInsertIndex = -1, + TabInvalidIndex = -2 +}; + + +enum TitleBarButton +{ + TitleBarButtonTabsMenu, + TitleBarButtonUndock, + TitleBarButtonClose, + TitleBarButtonAutoHide, + TitleBarButtonMinimize +}; + +/** + * The different dragging states + */ +enum eDragState +{ + DraggingInactive, //!< DraggingInactive + DraggingMousePressed, //!< DraggingMousePressed + DraggingTab, //!< DraggingTab + DraggingFloatingWidget//!< DraggingFloatingWidget +}; + +/** + * The different icons used in the UI + */ +enum eIcon +{ + TabCloseIcon, //!< TabCloseIcon + AutoHideIcon, //!< AutoHideIcon + DockAreaMenuIcon, //!< DockAreaMenuIcon + DockAreaUndockIcon,//!< DockAreaUndockIcon + DockAreaCloseIcon, //!< DockAreaCloseIcon + DockAreaMinimizeIcon, + + IconCount, //!< just a delimiter for range checks +}; + +/** + * For bitwise combination of dock wdget features + */ +enum eBitwiseOperator +{ + BitwiseAnd, + BitwiseOr +}; + + +/** + * Each dock container supports 4 sidebars + */ +enum SideBarLocation +{ + SideBarTop, + SideBarLeft, + SideBarRight, + SideBarBottom, + SideBarNone +}; +Q_ENUMS(SideBarLocation) + + +namespace internal +{ +static const bool RestoreTesting = true; +static const bool Restore = false; +static const char* const ClosedProperty = "close"; +static const char* const DirtyProperty = "dirty"; +static const char* const LocationProperty = "Location"; +extern const int FloatingWidgetDragStartEvent; +extern const int DockedWidgetDragStartEvent; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +// Utils to directly communicate with the X server +/** + * Get atom from cache or request it from the XServer. + */ +xcb_atom_t xcb_get_atom(const char *name); + +/** + * Add a property to a window. Only works on "hidden" windows. + */ +void xcb_add_prop(bool state, WId window, const char *type, const char *prop); +/** + * Updates up to two window properties. Can be set on a visible window. + */ +void xcb_update_prop(bool set, WId window, const char *type, const char *prop, const char *prop2 = nullptr); +/** + * Only for debugging purposes. + */ +bool xcb_dump_props(WId window, const char *type); +/** + * Gets the active window manager from the X11 Server. + * Requires a EWMH conform window manager (Almost all common used ones are). + * Returns "UNKNOWN" otherwise. + */ +QString windowManager(); +#endif + +/** + * Replace the from widget in the given splitter with the To widget + */ +void replaceSplitterWidget(QSplitter* Splitter, QWidget* From, QWidget* To); + +/** + * This function walks the splitter tree upwards to hides all splitters + * that do not have visible content + */ +void hideEmptyParentSplitters(CDockSplitter* FirstParentSplitter); + + +/** + * Convenience class for QPair to provide better naming than first and + * second + */ +class CDockInsertParam : public QPair +{ +public: + using QPair::QPair; + Qt::Orientation orientation() const {return this->first;} + bool append() const {return this->second;} + int insertOffset() const {return append() ? 1 : 0;} +}; + +/** + * Returns the insertion parameters for the given dock area + */ +CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area); + + +/** + * Returns the SieBarLocation for the AutoHide dock widget areas + */ +SideBarLocation toSideBarLocation(DockWidgetArea Area); + + +/** + * Returns true for the top or bottom side bar ansd false for the + * left and right side bar + */ +bool isHorizontalSideBarLocation(SideBarLocation Location); + + +/** + * Returns true, if the given dock area is a SideBar area + */ +bool isSideBarArea(DockWidgetArea Area); + +/** + * Searches for the parent widget of the given type. + * Returns the parent widget of the given widget or 0 if the widget is not + * child of any widget of type T + * + * It is not safe to use this function in in CDockWidget because only + * the current dock widget has a parent. All dock widgets that are not the + * current dock widget in a dock area have no parent. + */ +template +T findParent(const QWidget* w) +{ + QWidget* parentWidget = w->parentWidget(); + while (parentWidget) + { + T ParentImpl = qobject_cast(parentWidget); + if (ParentImpl) + { + return ParentImpl; + } + parentWidget = parentWidget->parentWidget(); + } + return nullptr; +} + +/** + * Creates a semi transparent pixmap from the given pixmap Source. + * The Opacity parameter defines the opacity from completely transparent (0.0) + * to completely opaque (1.0) + */ +QPixmap createTransparentPixmap(const QPixmap& Source, qreal Opacity); + + +/** + * Helper function for settings flags in a QFlags instance. + */ +template +void setFlag(T& Flags, typename T::enum_type flag, bool on = true) +{ +#if QT_VERSION >= 0x050700 + Flags.setFlag(flag, on); +#else + if(on) + { + Flags |= flag; + } + else + { + Flags &= ~flag; + } +#endif +} + + +/** + * Helper function for settings tooltips without cluttering the code with + * tests for preprocessor macros + */ +template +void setToolTip(QObjectPtr obj, const QString &tip) +{ +#ifndef QT_NO_TOOLTIP + obj->setToolTip(tip); +#else + Q_UNUSED(obj); + Q_UNUSED(tip); +#endif +} + + +/** + * Helper function for access to mouse event global position in Qt5 and + */ +inline QPoint globalPositionOf(QMouseEvent* ev) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) + return ev->globalPosition().toPoint(); +#else + return ev->globalPos(); +#endif +} + + +/** + * Helper function to set the icon of a certain button. + * Use this function to set the icons for the dock area and dock widget buttons. + * The function first uses the CustomIconId to get an icon from the + * CIconProvider. You can register your custom icons with the icon provider, if + * you do not want to use the default buttons and if you do not want to use + * stylesheets. + * If the IconProvider does not return a valid icon (icon is null), the function + * fetches the given standard pixmap from the QStyle. + * param[in] Button The button whose icons are to be set + * param[in] StandardPixmap The standard pixmap to be used for the button + * param[in] CustomIconId The identifier for the custom icon. + */ +void setButtonIcon(QAbstractButton* Button, QStyle::StandardPixmap StandarPixmap, + ads::eIcon CustomIconId); + + +enum eRepolishChildOptions +{ + RepolishIgnoreChildren, + RepolishDirectChildren, + RepolishChildrenRecursively +}; + +/** + * Calls unpolish() / polish for the style of the given widget to update + * stylesheet if a property changes + */ +void repolishStyle(QWidget* w, eRepolishChildOptions Options = RepolishIgnoreChildren); + + +/** + * Returns the geometry of the given widget in global space + */ +QRect globalGeometry(QWidget* w); + +} // namespace internal +} // namespace ads + +Q_DECLARE_OPERATORS_FOR_FLAGS(ads::DockWidgetAreas) +//--------------------------------------------------------------------------- +#endif // ads_globalsH diff --git a/src/images/close-button-disabled.svg b/src/images/close-button-disabled.svg new file mode 100644 index 000000000..cbcf1e5ab --- /dev/null +++ b/src/images/close-button-disabled.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/close-button-focused.svg b/src/images/close-button-focused.svg new file mode 100644 index 000000000..b9a6fad93 --- /dev/null +++ b/src/images/close-button-focused.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/close-button.svg b/src/images/close-button.svg new file mode 100644 index 000000000..c77202915 --- /dev/null +++ b/src/images/close-button.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/detach-button-disabled.svg b/src/images/detach-button-disabled.svg new file mode 100644 index 000000000..3a7c082fc --- /dev/null +++ b/src/images/detach-button-disabled.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + + + diff --git a/src/images/detach-button.svg b/src/images/detach-button.svg new file mode 100644 index 000000000..88d9bf22e --- /dev/null +++ b/src/images/detach-button.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + + diff --git a/src/images/maximize-button-focused.svg b/src/images/maximize-button-focused.svg new file mode 100644 index 000000000..cdbb56d4d --- /dev/null +++ b/src/images/maximize-button-focused.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/maximize-button.svg b/src/images/maximize-button.svg new file mode 100644 index 000000000..4084eb8fd --- /dev/null +++ b/src/images/maximize-button.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/minimize-button-focused.svg b/src/images/minimize-button-focused.svg new file mode 100644 index 000000000..c473a1333 --- /dev/null +++ b/src/images/minimize-button-focused.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/minimize-button.svg b/src/images/minimize-button.svg new file mode 100644 index 000000000..78df69e41 --- /dev/null +++ b/src/images/minimize-button.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/pin-button-big.svg b/src/images/pin-button-big.svg new file mode 100644 index 000000000..305d88029 --- /dev/null +++ b/src/images/pin-button-big.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/pin-button.svg b/src/images/pin-button.svg new file mode 100644 index 000000000..12ff7cb7a --- /dev/null +++ b/src/images/pin-button.svg @@ -0,0 +1,53 @@ + +image/svg+xml diff --git a/src/images/restore-button-focused.svg b/src/images/restore-button-focused.svg new file mode 100644 index 000000000..45b32e9b5 --- /dev/null +++ b/src/images/restore-button-focused.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/restore-button.svg b/src/images/restore-button.svg new file mode 100644 index 000000000..46175d46a --- /dev/null +++ b/src/images/restore-button.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/tabs-menu-button.svg b/src/images/tabs-menu-button.svg new file mode 100644 index 000000000..dcd5eb20b --- /dev/null +++ b/src/images/tabs-menu-button.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Jemis Mali + + + + + image/svg+xml + + + + + + diff --git a/src/images/vs-pin-button-disabled.svg b/src/images/vs-pin-button-disabled.svg new file mode 100644 index 000000000..33b4b176e --- /dev/null +++ b/src/images/vs-pin-button-disabled.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/vs-pin-button-pinned-focused.svg b/src/images/vs-pin-button-pinned-focused.svg new file mode 100644 index 000000000..6608545ec --- /dev/null +++ b/src/images/vs-pin-button-pinned-focused.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/vs-pin-button-pinned.svg b/src/images/vs-pin-button-pinned.svg new file mode 100644 index 000000000..dfb98a88d --- /dev/null +++ b/src/images/vs-pin-button-pinned.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/vs-pin-button.svg b/src/images/vs-pin-button.svg new file mode 100644 index 000000000..d93a70117 --- /dev/null +++ b/src/images/vs-pin-button.svg @@ -0,0 +1,2 @@ + + diff --git a/src/images/vs-pin-button_45.svg b/src/images/vs-pin-button_45.svg new file mode 100644 index 000000000..d8dc06116 --- /dev/null +++ b/src/images/vs-pin-button_45.svg @@ -0,0 +1,2 @@ + + diff --git a/src/linux/FloatingWidgetTitleBar.cpp b/src/linux/FloatingWidgetTitleBar.cpp new file mode 100644 index 000000000..00e6fcd4d --- /dev/null +++ b/src/linux/FloatingWidgetTitleBar.cpp @@ -0,0 +1,293 @@ +/******************************************************************************* + ** Qt Advanced Docking System + ** Copyright (C) 2017 Uwe Kindler + ** + ** This library is free software; you can redistribute it and/or + ** modify it under the terms of the GNU Lesser General Public + ** License as published by the Free Software Foundation; either + ** version 2.1 of the License, or (at your option) any later version. + ** + ** This library is distributed in the hope that it will be useful, + ** but WITHOUT ANY WARRANTY; without even the implied warranty of + ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + ** Lesser General Public License for more details. + ** + ** You should have received a copy of the GNU Lesser General Public + ** License along with this library; If not, see . + ******************************************************************************/ + +//============================================================================ +/// \file FloatingWidgetTitleBar.cpp +/// \author Uwe Kindler +/// \date 13.05.2019 +/// \brief Implementation of CFloatingWidgetTitleBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "FloatingWidgetTitleBar.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "ads_globals.h" +#include "ElidingLabel.h" +#include "FloatingDockContainer.h" + +namespace ads +{ + +using tTabLabel = CElidingLabel; +using tCloseButton = QToolButton; +using tMaximizeButton = QToolButton; + +/** + * @brief Private data class of public interface CFloatingWidgetTitleBar + */ +struct FloatingWidgetTitleBarPrivate +{ + CFloatingWidgetTitleBar *_this; ///< public interface class + QLabel *IconLabel = nullptr; + tTabLabel *TitleLabel; + tCloseButton *CloseButton = nullptr; + tMaximizeButton* MaximizeButton = nullptr; + CFloatingDockContainer *FloatingWidget = nullptr; + eDragState DragState = DraggingInactive; + QIcon MaximizeIcon; + QIcon NormalIcon; + bool Maximized = false; + + FloatingWidgetTitleBarPrivate(CFloatingWidgetTitleBar *_public) : + _this(_public) + { + } + + /** + * Creates the complete layout including all controls + */ + void createLayout(); +}; + +//============================================================================ +void FloatingWidgetTitleBarPrivate::createLayout() +{ + TitleLabel = new tTabLabel(); + TitleLabel->setElideMode(Qt::ElideRight); + TitleLabel->setText("DockWidget->windowTitle()"); + TitleLabel->setObjectName("floatingTitleLabel"); + TitleLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + CloseButton = new tCloseButton(); + CloseButton->setObjectName("floatingTitleCloseButton"); + CloseButton->setAutoRaise(true); + + MaximizeButton = new tMaximizeButton(); + MaximizeButton->setObjectName("floatingTitleMaximizeButton"); + MaximizeButton->setAutoRaise(true); + + // The standard icons do does not look good on high DPI screens + QIcon CloseIcon; + QPixmap normalPixmap = _this->style()->standardPixmap( + QStyle::SP_TitleBarCloseButton, 0, CloseButton); + CloseIcon.addPixmap(normalPixmap, QIcon::Normal); + CloseIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), + QIcon::Disabled); + CloseButton->setIcon( + _this->style()->standardIcon(QStyle::SP_TitleBarCloseButton)); + CloseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + CloseButton->setVisible(true); + CloseButton->setFocusPolicy(Qt::NoFocus); + _this->connect(CloseButton, SIGNAL(clicked()), SIGNAL(closeRequested())); + + _this->setMaximizedIcon(false); + MaximizeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + MaximizeButton->setVisible(true); + MaximizeButton->setFocusPolicy(Qt::NoFocus); + _this->connect(MaximizeButton, &QPushButton::clicked, _this, &CFloatingWidgetTitleBar::maximizeRequested); + + QFontMetrics fm(TitleLabel->font()); + int Spacing = qRound(fm.height() / 4.0); + + // Fill the layout + QBoxLayout *Layout = new QBoxLayout(QBoxLayout::LeftToRight); + Layout->setContentsMargins(6, 0, 0, 0); + Layout->setSpacing(0); + _this->setLayout(Layout); + Layout->addWidget(TitleLabel, 1); + Layout->addSpacing(Spacing); + Layout->addWidget(MaximizeButton); + Layout->addWidget(CloseButton); + Layout->setAlignment(Qt::AlignCenter); + + TitleLabel->setVisible(true); +} + +//============================================================================ +CFloatingWidgetTitleBar::CFloatingWidgetTitleBar(CFloatingDockContainer *parent) : + QFrame(parent), + d(new FloatingWidgetTitleBarPrivate(this)) +{ + d->FloatingWidget = parent; + d->createLayout(); + + auto normalPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarNormalButton, nullptr, d->MaximizeButton); + d->NormalIcon.addPixmap(normalPixmap, QIcon::Normal); + d->NormalIcon.addPixmap(internal::createTransparentPixmap(normalPixmap, 0.25), QIcon::Disabled); + + auto maxPixmap = this->style()->standardPixmap(QStyle::SP_TitleBarMaxButton, nullptr, d->MaximizeButton); + d->MaximizeIcon.addPixmap(maxPixmap, QIcon::Normal); + d->MaximizeIcon.addPixmap(internal::createTransparentPixmap(maxPixmap, 0.25), QIcon::Disabled); + setMaximizedIcon(d->Maximized); +} + +//============================================================================ +CFloatingWidgetTitleBar::~CFloatingWidgetTitleBar() +{ + delete d; +} + +//============================================================================ +void CFloatingWidgetTitleBar::mousePressEvent(QMouseEvent *ev) +{ + if (ev->button() == Qt::LeftButton) + { + d->DragState = DraggingFloatingWidget; + d->FloatingWidget->startDragging(ev->pos(), d->FloatingWidget->size(), + this); + return; + } + Super::mousePressEvent(ev); +} + + +//============================================================================ +void CFloatingWidgetTitleBar::mouseReleaseEvent(QMouseEvent *ev) +{ + d->DragState = DraggingInactive; + if (d->FloatingWidget) + { + d->FloatingWidget->finishDragging(); + } + Super::mouseReleaseEvent(ev); +} + + +//============================================================================ +void CFloatingWidgetTitleBar::mouseMoveEvent(QMouseEvent *ev) +{ + if (!(ev->buttons() & Qt::LeftButton) || DraggingInactive == d->DragState) + { + d->DragState = DraggingInactive; + Super::mouseMoveEvent(ev); + return; + } + + // move floating window + if (DraggingFloatingWidget == d->DragState) + { + if(d->FloatingWidget->isMaximized()) + { + d->FloatingWidget->showNormal(true); + } + d->FloatingWidget->moveFloating(); + Super::mouseMoveEvent(ev); + return; + } + Super::mouseMoveEvent(ev); +} + + +//============================================================================ +void CFloatingWidgetTitleBar::enableCloseButton(bool Enable) +{ + d->CloseButton->setEnabled(Enable); +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setTitle(const QString &Text) +{ + d->TitleLabel->setText(Text); +} + +//============================================================================ +void CFloatingWidgetTitleBar::updateStyle() +{ + internal::repolishStyle(this, internal::RepolishDirectChildren); +} + + +//============================================================================ +void CFloatingWidgetTitleBar::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) + { + emit maximizeRequested(); + event->accept(); + } + else + { + QWidget::mouseDoubleClickEvent(event); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setMaximizedIcon(bool maximized) +{ + d->Maximized = maximized; + if (maximized) + { + d->MaximizeButton->setIcon(d->NormalIcon); + } + else + { + d->MaximizeButton->setIcon(d->MaximizeIcon); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setMaximizeIcon(const QIcon& Icon) +{ + d->MaximizeIcon = Icon; + if (d->Maximized) + { + setMaximizedIcon(d->Maximized); + } +} + + +//============================================================================ +void CFloatingWidgetTitleBar::setNormalIcon(const QIcon& Icon) +{ + d->NormalIcon = Icon; + if (!d->Maximized) + { + setMaximizedIcon(d->Maximized); + } +} + + +//============================================================================ +QIcon CFloatingWidgetTitleBar::maximizeIcon() const +{ + return d->MaximizeIcon; +} + + +//============================================================================ +QIcon CFloatingWidgetTitleBar::normalIcon() const +{ + return d->NormalIcon; +} + + +} // namespace ads diff --git a/src/linux/FloatingWidgetTitleBar.h b/src/linux/FloatingWidgetTitleBar.h new file mode 100644 index 000000000..ad6904f65 --- /dev/null +++ b/src/linux/FloatingWidgetTitleBar.h @@ -0,0 +1,109 @@ +#ifndef FLOATINGWIDGETTITLEBAR_H +#define FLOATINGWIDGETTITLEBAR_H +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + + +//============================================================================ +/// \file FloatingWidgetTitleBar.h +/// \author Uwe Kindler +/// \date 13.05.2019 +/// \brief Declaration of CFloatingWidgetTitleBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include + +namespace ads +{ +class CFloatingDockContainer; + +struct FloatingWidgetTitleBarPrivate; + + +/** + * Titlebar for floating widgets to capture non client are mouse events. + * Linux does not support NonClieantArea mouse events like + * QEvent::NonClientAreaMouseButtonPress. Because these events are required + * for the docking system to work properly, we use our own titlebar here to + * capture the required mouse events. + */ +class CFloatingWidgetTitleBar : public QFrame +{ + Q_OBJECT + Q_PROPERTY(QIcon maximizeIcon READ maximizeIcon WRITE setMaximizeIcon) + Q_PROPERTY(QIcon normalIcon READ normalIcon WRITE setNormalIcon) +private: + FloatingWidgetTitleBarPrivate *d; ///< private data (pimpl) + +protected: + virtual void mousePressEvent(QMouseEvent *ev) override; + virtual void mouseReleaseEvent(QMouseEvent *ev) override; + virtual void mouseMoveEvent(QMouseEvent *ev) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + + void setMaximizeIcon(const QIcon& Icon); + QIcon maximizeIcon() const; + void setNormalIcon(const QIcon& Icon); + QIcon normalIcon() const; + +public: + using Super = QWidget; + explicit CFloatingWidgetTitleBar(CFloatingDockContainer *parent = nullptr); + + /** + * Virtual Destructor + */ + virtual ~CFloatingWidgetTitleBar(); + + /** + * Enables / disables the window close button. + */ + void enableCloseButton(bool Enable); + + /** + * Sets the window title, that means, the text of the internal tile label. + */ + void setTitle(const QString &Text); + + /** + * Update stylesheet style if a property changes + */ + void updateStyle(); + + /** + * Change the maximize button icon according to current windows state + */ + void setMaximizedIcon(bool maximized); + +signals: + /** + * This signal is emitted, if the close button is clicked. + */ + void closeRequested(); + + /** + * This signal is emitted, if the maximize button is clicked. + */ + void maximizeRequested(); +}; +} // namespace ads +#endif // FLOATINGWIDGETTITLEBAR_H diff --git a/src/qtadvanceddockingConfig.cmake b/src/qtadvanceddockingConfig.cmake new file mode 100644 index 000000000..65173fab2 --- /dev/null +++ b/src/qtadvanceddockingConfig.cmake @@ -0,0 +1,2 @@ +include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/adsTargets.cmake") \ No newline at end of file diff --git a/src/src.pro b/src/src.pro new file mode 100644 index 000000000..79344b124 --- /dev/null +++ b/src/src.pro @@ -0,0 +1,99 @@ +ADS_OUT_ROOT = $${OUT_PWD}/.. +CONFIG += c++14 +CONFIG += debug_and_release +TARGET = $$qtLibraryTarget(qtadvanceddocking) +DEFINES += QT_DEPRECATED_WARNINGS +TEMPLATE = lib +DESTDIR = $${ADS_OUT_ROOT}/lib +QT += core gui widgets + +!adsBuildStatic { + CONFIG += shared + DEFINES += ADS_SHARED_EXPORT +} +adsBuildStatic { + CONFIG += staticlib + DEFINES += ADS_STATIC +} + +windows { + # MinGW + *-g++* { + QMAKE_CXXFLAGS += -Wall -Wextra -pedantic + } + # MSVC + *-msvc* { + QMAKE_CXXFLAGS += /utf-8 + } +} + +RESOURCES += ads.qrc + +HEADERS += \ + ads_globals.h \ + DockAreaWidget.h \ + DockAreaTabBar.h \ + DockContainerWidget.h \ + DockManager.h \ + DockWidget.h \ + DockWidgetTab.h \ + DockingStateReader.h \ + FloatingDockContainer.h \ + FloatingDragPreview.h \ + DockOverlay.h \ + DockSplitter.h \ + DockAreaTitleBar_p.h \ + DockAreaTitleBar.h \ + ElidingLabel.h \ + IconProvider.h \ + DockComponentsFactory.h \ + DockFocusController.h \ + AutoHideDockContainer.h \ + AutoHideSideBar.h \ + AutoHideTab.h \ + PushButton.h \ + ResizeHandle.h + + +SOURCES += \ + ads_globals.cpp \ + DockAreaWidget.cpp \ + DockAreaTabBar.cpp \ + DockContainerWidget.cpp \ + DockManager.cpp \ + DockWidget.cpp \ + DockingStateReader.cpp \ + DockWidgetTab.cpp \ + FloatingDockContainer.cpp \ + FloatingDragPreview.cpp \ + DockOverlay.cpp \ + DockSplitter.cpp \ + DockAreaTitleBar.cpp \ + ElidingLabel.cpp \ + IconProvider.cpp \ + DockComponentsFactory.cpp \ + DockFocusController.cpp \ + AutoHideDockContainer.cpp \ + AutoHideSideBar.cpp \ + AutoHideTab.cpp \ + PushButton.cpp \ + ResizeHandle.cpp + + +unix:!macx { +HEADERS += linux/FloatingWidgetTitleBar.h +SOURCES += linux/FloatingWidgetTitleBar.cpp +LIBS += -lxcb +QT += gui-private +} + +isEmpty(PREFIX){ + PREFIX=../installed + warning("Install Prefix not set") +} +headers.path=$$PREFIX/include +headers.files=$$HEADERS +target.path=$$PREFIX/lib +INSTALLS += headers target + +DISTFILES += diff --git a/src/stylesheets/default.css b/src/stylesheets/default.css new file mode 100644 index 000000000..19c5ab1dd --- /dev/null +++ b/src/stylesheets/default.css @@ -0,0 +1,350 @@ +/* + * Default style sheet on Windows Platforms + */ + +/***************************************************************************** + * CDockContainerWidget + *****************************************************************************/ +ads--CDockContainerWidget { + background: palette(window); +} + + +/***************************************************************************** + * CDockSplitter + *****************************************************************************/ +ads--CDockContainerWidget > QSplitter { + padding: 1 0 1 0; +} + +ads--CDockContainerWidget ads--CDockSplitter::handle { + background: palette(dark); +} + + +/***************************************************************************** + * CDockAreaWidget + *****************************************************************************/ +ads--CDockAreaWidget { + background: palette(window); +} + +ads--CTitleBarButton { + padding: 0px 0px; +} + + +#tabsMenuButton::menu-indicator { + image: none; +} + +#tabsMenuButton { + qproperty-icon: url(:/ads/images/tabs-menu-button.svg); + qproperty-iconSize: 16px; +} + +#dockAreaCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#detachGroupButton { + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + + +/***************************************************************************** + * CDockWidgetTab and close button styling + *****************************************************************************/ +ads--CDockWidgetTab { + background: palette(window); + border-color: palette(light); + border-style: solid; + border-width: 0 1px 0 0; + padding: 0 0px; +} + +ads--CDockWidgetTab[activeTab="true"] { + background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 + palette(window), stop:1 palette(light)); + /*background: palette(highlight);*/ +} + +ads--CDockWidgetTab QLabel { + color: palette(dark); +} + +ads--CDockWidgetTab[activeTab="true"] QLabel { + color: palette(foreground); +} + + +#tabCloseButton { + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#tabCloseButton:hover { + border: 1px solid rgba(0, 0, 0, 32); + background: rgba(0, 0, 0, 16); +} + +#tabCloseButton:pressed { + background: rgba(0, 0, 0, 32); +} + + +/***************************************************************************** + * CDockWidget + *****************************************************************************/ +ads--CDockWidget { + background: palette(light); + border-color: palette(light); + border-style: solid; + border-width: 1px 0 0 0; +} + + +QScrollArea#dockWidgetScrollArea { + padding: 0px; + border: none; +} + + +/***************************************************************************** + * + * Styling of auto hide functionality + * + *****************************************************************************/ + + +/***************************************************************************** + * CAutoHideTab + *****************************************************************************/ +ads--CAutoHideTab { + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + + +ads--CAutoHideTab:hover +{ + color: palette(highlight); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/** + * Auto hide tabs with icon only + */ +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + +/** + * Auto hide tabs with icon only hover + */ +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/***************************************************************************** + * CAutoHideSideBar + *****************************************************************************/ +ads--CAutoHideSideBar{ + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + + +ads--CAutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + + +/***************************************************************************** + * CAutoHideDockContainer + *****************************************************************************/ +ads--CAutoHideDockContainer { + background: palette(window); +} + + +ads--CAutoHideDockContainer ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +/* + * This is required because the ads--CDockAreaWidget[focused="true"] will + * overwrite the ads--CAutoHideDockContainer ads--CDockAreaTitleBar rule + */ +ads--CAutoHideDockContainer ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + + +/***************************************************************************** + * CAutoHideDockContainer titlebar buttons + *****************************************************************************/ +#dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button.svg), + url(:/ads/images/vs-pin-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +ads--CAutoHideDockContainer #dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaMinimizeButton { + qproperty-icon: url(:/ads/images/minimize-button-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaCloseButton{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + + +ads--CAutoHideDockContainer ads--CTitleBarButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CAutoHideDockContainer ads--CTitleBarButton:pressed { + background: rgba(255, 255, 255, 96); +} + +/***************************************************************************** + * CAutoHideDockContainer Titlebar and Buttons + *****************************************************************************/ + + +/***************************************************************************** + * CResizeHandle + *****************************************************************************/ +ads--CResizeHandle { + background: palette(window); +} + + +ads--CAutoHideDockContainer[sideBarLocation="0"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="1"] ads--CResizeHandle { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="2"] ads--CResizeHandle { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="3"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + + diff --git a/src/stylesheets/default_linux.css b/src/stylesheets/default_linux.css new file mode 100644 index 000000000..d9bc03d99 --- /dev/null +++ b/src/stylesheets/default_linux.css @@ -0,0 +1,389 @@ +/* + * Default style sheet on Linux Platforms + */ + +/***************************************************************************** + * CDockContainerWidget + *****************************************************************************/ +ads--CDockContainerWidget { + background: palette(window); +} + + +/***************************************************************************** + * CDockSplitter + *****************************************************************************/ +ads--CDockContainerWidget > QSplitter{ + padding: 1 0 1 0; +} + +ads--CDockContainerWidget ads--CDockSplitter::handle { + background: palette(dark); +} + + +/***************************************************************************** + * CDockAreaWidget + *****************************************************************************/ +ads--CDockAreaWidget { + background: palette(window); +} + +ads--CDockAreaWidget #tabsMenuButton::menu-indicator { + image: none; +} + + +ads--CTitleBarButton { + padding: 0px 0px; +} + + +#tabsMenuButton { + qproperty-icon: url(:/ads/images/tabs-menu-button.svg); + qproperty-iconSize: 16px; +} + + +#dockAreaCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + +#detachGroupButton { + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + +/***************************************************************************** + * CDockWidgetTab and close button styling + *****************************************************************************/ +ads--CDockWidgetTab { + background: palette(window); + border-color: palette(light); + border-style: solid; + border-width: 0 1px 0 0; + padding: 0 0px; +} + + +ads--CDockWidgetTab[activeTab="true"] { + background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 + palette(window), stop:1 palette(light)); + /*background: palette(highlight);*/ +} + + +ads--CDockWidgetTab QLabel { + color: palette(dark); +} + + +ads--CDockWidgetTab[activeTab="true"] QLabel { + color: palette(foreground); +} + + +#tabCloseButton { + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#tabCloseButton:hover { + border: 1px solid rgba(0, 0, 0, 32); + background: rgba(0, 0, 0, 16); +} + +#tabCloseButton:pressed { + background: rgba(0, 0, 0, 32); +} + + +/***************************************************************************** + * CDockWidget + *****************************************************************************/ +ads--CDockWidget { + background: palette(light); + border-color: palette(light); + border-style: solid; + border-width: 1px 0 0 0; +} + + +QScrollArea#dockWidgetScrollArea { + padding: 0px; + border: none; +} + + +/***************************************************************************** + * Floating widget styling + *****************************************************************************/ +ads--CFloatingWidgetTitleBar { + background: palette(midlight); + qproperty-maximizeIcon: url(:/ads/images/maximize-button.svg); + qproperty-normalIcon: url(:/ads/images/restore-button.svg); +} + + +#floatingTitleCloseButton, #floatingTitleMaximizeButton { + qproperty-iconSize: 16px; + border: none; + margin: 3px; +} + + +#floatingTitleCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg); +} + +#floatingTitleCloseButton:hover { + background: rgba(0, 0, 0, 24); + border: none; +} + +#floatingTitleCloseButton:pressed { + background: rgba(0, 0, 0, 48); +} + + +/***************************************************************************** + * + * Styling of auto hide functionality + * + *****************************************************************************/ + + +/***************************************************************************** + * CAutoHideTab + *****************************************************************************/ +ads--CAutoHideTab { + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + + +ads--CAutoHideTab:hover +{ + color: palette(highlight); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/** + * Auto hide tabs with icon only + */ +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + +/** + * Auto hide tabs with icon only hover + */ +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/***************************************************************************** + * CAutoHideSideBar + *****************************************************************************/ +ads--CAutoHideSideBar{ + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + + +ads--CAutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + + +/***************************************************************************** + * CAutoHideDockContainer + *****************************************************************************/ +ads--CAutoHideDockContainer { + background: palette(window); +} + + +ads--CAutoHideDockContainer ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +/* + * This is required because the ads--CDockAreaWidget[focused="true"] will + * overwrite the ads--CAutoHideDockContainer ads--CDockAreaTitleBar rule + */ +ads--CAutoHideDockContainer ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + + +/***************************************************************************** + * CAutoHideDockContainer titlebar buttons + *****************************************************************************/ +#dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button.svg), + url(:/ads/images/vs-pin-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +ads--CAutoHideDockContainer #dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaMinimizeButton { + qproperty-icon: url(:/ads/images/minimize-button-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaCloseButton{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + + +ads--CAutoHideDockContainer ads--CTitleBarButton:hover { + background: rgba(255, 255, 255, 48); + border: none; +} + +ads--CAutoHideDockContainer ads--CTitleBarButton:pressed { + background: rgba(255, 255, 255, 96); + border: none; +} + +/***************************************************************************** + * CAutoHideDockContainer Titlebar and Buttons + *****************************************************************************/ + + +/***************************************************************************** + * CResizeHandle + *****************************************************************************/ +ads--CResizeHandle { + background: palette(window); +} + + +ads--CAutoHideDockContainer[sideBarLocation="0"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="1"] ads--CResizeHandle { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="2"] ads--CResizeHandle { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="3"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + + + diff --git a/src/stylesheets/focus_highlighting.css b/src/stylesheets/focus_highlighting.css new file mode 100644 index 000000000..758f58613 --- /dev/null +++ b/src/stylesheets/focus_highlighting.css @@ -0,0 +1,387 @@ +/* + * Default style sheet on Windows Platforms with focus highlighting flag enabled + */ + + +/***************************************************************************** + * CDockContainerWidget + *****************************************************************************/ +ads--CDockContainerWidget { + background: palette(window); +} + + +/***************************************************************************** + * CDockSplitter + *****************************************************************************/ +ads--CDockContainerWidget > QSplitter { + padding: 1 0 1 0; +} + + +ads--CDockSplitter::handle { + background-color: palette(dark); + /* uncomment the following line if you would like to change the size of + the splitter handles */ + /* height: 1px; */ +} + + +/***************************************************************************** + * CDockAreaWidget + *****************************************************************************/ +ads--CDockAreaWidget { + background: palette(window); +} + + +ads--CDockAreaTitleBar { + background: transparent; + border-bottom: 2px solid palette(light); + padding-bottom: 0px; +} + +ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + border-bottom: 2px solid palette(highlight); +} + +ads--CTitleBarButton { + padding: 0px 0px; +} + + +#tabsMenuButton::menu-indicator { + image: none; +} + + +#dockAreaCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#detachGroupButton { + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + + +/***************************************************************************** + * CDockWidgetTab + *****************************************************************************/ +ads--CDockWidgetTab { + background: palette(window); + border-color: palette(light); + border-style: solid; + border-width: 0 1px 0 0; + padding: 0 0px; + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ +} + +ads--CDockWidgetTab[activeTab="true"] { + background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 + palette(window), stop:1 palette(light)); + /*background: palette(highlight);*/ +} + +ads--CDockWidgetTab QLabel { + color: palette(dark); +} + +ads--CDockWidgetTab[activeTab="true"] QLabel { + color: palette(foreground); +} + + +#tabCloseButton { + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#tabCloseButton:hover { + /*border: 1px solid rgba(0, 0, 0, 32);*/ + background: rgba(0, 0, 0, 24); +} + +#tabCloseButton:pressed { + background: rgba(0, 0, 0, 48); +} + +/* Focus related styling */ +ads--CDockWidgetTab[focused="true"] { + background: palette(highlight); + border-color: palette(highlight); +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton { + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:pressed { + background: rgba(255, 255, 255, 92); +} + +ads--CDockWidgetTab[focused="true"] QLabel { + color: palette(light); +} + + + +/***************************************************************************** + * CDockWidget + *****************************************************************************/ +ads--CDockWidget { + background: palette(light); + border-color: palette(light); + border-style: solid; + border-width: 1px 0 0 0; +} + + +QScrollArea#dockWidgetScrollArea { + padding: 0px; + border: none; +} + + + +/***************************************************************************** + * + * Styling of auto hide functionality + * + *****************************************************************************/ + + +/***************************************************************************** + * CAutoHideTab + *****************************************************************************/ +ads--CAutoHideTab { + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + + +ads--CAutoHideTab:hover +{ + color: palette(highlight); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/** + * Auto hide tabs with icon only + */ +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + +/** + * Auto hide tabs with icon only hover + */ +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + + +/***************************************************************************** + * CAutoHideSideBar + *****************************************************************************/ +ads--CAutoHideSideBar{ + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + + +ads--CAutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + + +/***************************************************************************** + * CAutoHideDockContainer + *****************************************************************************/ +ads--CAutoHideDockContainer { + background: palette(window); +} + + +ads--CAutoHideDockContainer ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +/* + * This is required because the ads--CDockAreaWidget[focused="true"] will + * overwrite the ads--CAutoHideDockContainer ads--CDockAreaTitleBar rule + */ +ads--CAutoHideDockContainer ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + + +/***************************************************************************** + * CAutoHideDockContainer titlebar buttons + *****************************************************************************/ +#dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button.svg), + url(:/ads/images/vs-pin-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaMinimizeButton { + qproperty-icon: url(:/ads/images/minimize-button-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaCloseButton{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + + +ads--CAutoHideDockContainer ads--CTitleBarButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CAutoHideDockContainer ads--CTitleBarButton:pressed { + background: rgba(255, 255, 255, 96); +} + +/***************************************************************************** + * CAutoHideDockContainer Titlebar and Buttons + *****************************************************************************/ + + +/***************************************************************************** + * CResizeHandle + *****************************************************************************/ +ads--CResizeHandle { + background: palette(window); +} + + +ads--CAutoHideDockContainer[sideBarLocation="0"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="1"] ads--CResizeHandle { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="2"] ads--CResizeHandle { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="3"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} diff --git a/src/stylesheets/focus_highlighting_linux.css b/src/stylesheets/focus_highlighting_linux.css new file mode 100644 index 000000000..cfedf8f77 --- /dev/null +++ b/src/stylesheets/focus_highlighting_linux.css @@ -0,0 +1,464 @@ +/* + * Default style sheet on Linux Platforms with focus highlighting flag enabled + */ + +/***************************************************************************** + * CDockContainerWidget + *****************************************************************************/ +ads--CDockContainerWidget { + background: palette(window); +} + + +/***************************************************************************** + * CDockSplitter + *****************************************************************************/ +ads--CDockContainerWidget > QSplitter{ + padding: 1 0 1 0; +} + + +ads--CDockContainerWidget ads--CDockSplitter::handle { + background: palette(dark); +} + + +/***************************************************************************** + * CDockAreaWidget + *****************************************************************************/ +ads--CDockAreaWidget { + background: palette(window); +} + +ads--CDockAreaWidget #tabsMenuButton::menu-indicator { + image: none; +} + + +ads--CTitleBarButton { + padding: 0px 0px; +} + + +#tabsMenuButton { + qproperty-icon: url(:/ads/images/tabs-menu-button.svg); + qproperty-iconSize: 16px; +} + + +#dockAreaCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + +#detachGroupButton { + qproperty-icon: url(:/ads/images/detach-button.svg), + url(:/ads/images/detach-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + + + +/***************************************************************************** + * CDockAreaTitleBar + *****************************************************************************/ +ads--CDockAreaTitleBar { + background: transparent; + border-bottom: 2px solid palette(light); + padding-bottom: 0px; +} + +ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: transparent; + border-bottom: 2px solid palette(highlight); + padding-bottom: 0px; +} + + +/***************************************************************************** + * CDockWidgetTab and close button styling + *****************************************************************************/ +ads--CDockWidgetTab { + background: palette(window); + border-color: palette(light); + border-style: solid; + border-width: 0 1px 0 0; + padding: 0 0px; +} + +ads--CDockWidgetTab[activeTab="true"] { + background: qlineargradient(spread : pad, x1 : 0, y1 : 0, x2 : 0, y2 : 0.5, stop : 0 + palette(window), stop:1 palette(light)); + /*background: palette(highlight);*/ +} + +ads--CDockWidgetTab QLabel { + color: palette(dark); +} + +ads--CDockWidgetTab[activeTab="true"] QLabel { + color: palette(foreground); +} + + +#tabCloseButton { + margin-top: 2px; + background: none; + border: none; + padding: 0px -2px; + qproperty-icon: url(:/ads/images/close-button.svg), + url(:/ads/images/close-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +#tabCloseButton:hover { + /*border: 1px solid rgba(0, 0, 0, 32);*/ + background: rgba(0, 0, 0, 24); +} + +#tabCloseButton:pressed { + background: rgba(0, 0, 0, 48); +} + + +/* Focus related styling */ +ads--CDockWidgetTab[focused="true"] { + background: palette(highlight); + border-color: palette(highlight); +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton { + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:hover { + background: rgba(255, 255, 255, 48); +} + +ads--CDockWidgetTab[focused="true"] > #tabCloseButton:pressed { + background: rgba(255, 255, 255, 92); +} + +ads--CDockWidgetTab[focused="true"] QLabel { + color: palette(light); +} + + +/***************************************************************************** + * CDockWidget + *****************************************************************************/ +ads--CDockWidget { + background: palette(light); + border-color: palette(light); + border-style: solid; + border-width: 1px 0 0 0; +} + + +QScrollArea#dockWidgetScrollArea { + padding: 0px; + border: none; +} + + + +/***************************************************************************** + * Floating widget styling + *****************************************************************************/ +ads--CFloatingWidgetTitleBar { + qproperty-maximizeIcon: url(:/ads/images/maximize-button.svg); + qproperty-normalIcon: url(:/ads/images/restore-button.svg); +} + + +/* does not properly work on KDE +ads--CFloatingDockContainer[isActiveWindow="true"] ads--CFloatingWidgetTitleBar { + background: palette(highlight); + qproperty-maximizeIcon: url(:/ads/images/maximize-button-focused.svg); + qproperty-normalIcon: url(:/ads/images/restore-button-focused.svg); +} + +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleLabel { + color: palette(light); +} +*/ + +#floatingTitleCloseButton, #floatingTitleMaximizeButton { + qproperty-iconSize: 16px; + border: none; + margin: 6px 3px 6px 3px; +} + + +#floatingTitleCloseButton { + qproperty-icon: url(:/ads/images/close-button.svg); +} + + +#floatingTitleCloseButton:hover, #floatingTitleMaximizeButton:hover { + background: rgba(0, 0, 0, 24); + border: none; +} + + +#floatingTitleCloseButton:pressed, #floatingTitleMaximizeButton:pressed { + background: rgba(0, 0, 0, 48); +} + + +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton { + qproperty-iconSize: 16px; +} + + +/* does not properly work on KDE +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton { + qproperty-icon: url(:/ads/images/close-button-focused.svg); + qproperty-iconSize: 16px; +} + +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:hover, +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton:hover { + background: rgba(255, 255, 255, 48); +} + + +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleCloseButton:pressed, +ads--CFloatingDockContainer[isActiveWindow="true"] #floatingTitleMaximizeButton:pressed { + background: rgba(255, 255, 255, 92); +} +*/ + + +/***************************************************************************** + * + * Styling of auto hide functionality + * + *****************************************************************************/ + + +/***************************************************************************** + * CAutoHideTab + *****************************************************************************/ +ads--CAutoHideTab { + qproperty-iconSize: 16px 16px;/* this is optional in case you would like to change icon size*/ + background: none; + border: none; + padding-left: 2px; + padding-right: 0px; + text-align: center; + min-height: 20px; + padding-bottom: 2px; +} + + +ads--CAutoHideTab:hover +{ + color: palette(highlight); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + + +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="0"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="0"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="2"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + + +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="1"], +ads--CAutoHideTab:hover[iconOnly="false"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="1"][activeTab="true"], +ads--CAutoHideTab[iconOnly="false"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + +/** + * Auto hide tabs with icon only + */ +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"] { + border-top: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"] { + border-left: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"] { + border-right: 6px solid rgba(0, 0, 0, 48); +} + +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"] { + border-bottom: 6px solid rgba(0, 0, 0, 48); +} + + +/** + * Auto hide tabs with icon only hover + */ +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="0"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="0"][activeTab="true"] { + border-top: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="1"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="1"][activeTab="true"] { + border-left: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="2"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="2"][activeTab="true"] { + border-right: 6px solid palette(highlight); +} + +ads--CAutoHideTab:hover[iconOnly="true"][sideBarLocation="3"], +ads--CAutoHideTab[iconOnly="true"][sideBarLocation="3"][activeTab="true"] { + border-bottom: 6px solid palette(highlight); +} + + + +/***************************************************************************** + * CAutoHideSideBar + *****************************************************************************/ +ads--CAutoHideSideBar{ + background: palette(window); + border: none; + qproperty-spacing: 12; +} + +#sideTabsContainerWidget { + background: transparent; +} + + +ads--CAutoHideSideBar[sideBarLocation="0"] { + border-bottom: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="1"] { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="2"] { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideSideBar[sideBarLocation="3"] { + border-top: 1px solid palette(dark); +} + + +/***************************************************************************** + * CAutoHideDockContainer + *****************************************************************************/ +ads--CAutoHideDockContainer { + background: palette(window); +} + + +ads--CAutoHideDockContainer ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +/* + * This is required because the ads--CDockAreaWidget[focused="true"] will + * overwrite the ads--CAutoHideDockContainer ads--CDockAreaTitleBar rule + */ +ads--CAutoHideDockContainer ads--CDockAreaWidget[focused="true"] ads--CDockAreaTitleBar { + background: palette(highlight); + padding: 0px; + border: none; +} + + +#autoHideTitleLabel { + padding-left: 4px; + color: palette(light); +} + + +/***************************************************************************** + * CAutoHideDockContainer titlebar buttons + *****************************************************************************/ +#dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button.svg), + url(:/ads/images/vs-pin-button-disabled.svg) disabled; + qproperty-iconSize: 16px; +} + +ads--CAutoHideDockContainer #dockAreaAutoHideButton { + qproperty-icon: url(:/ads/images/vs-pin-button-pinned-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaMinimizeButton { + qproperty-icon: url(:/ads/images/minimize-button-focused.svg); + qproperty-iconSize: 16px; +} + + +ads--CAutoHideDockContainer #dockAreaCloseButton{ + qproperty-icon: url(:/ads/images/close-button-focused.svg) +} + + +ads--CAutoHideDockContainer ads--CTitleBarButton:hover { + background: rgba(255, 255, 255, 48); + border: none; +} + +ads--CAutoHideDockContainer ads--CTitleBarButton:pressed { + background: rgba(255, 255, 255, 96); + border: none; +} + +/***************************************************************************** + * CAutoHideDockContainer Titlebar and Buttons + *****************************************************************************/ + + +/***************************************************************************** + * CResizeHandle + *****************************************************************************/ +ads--CResizeHandle { + background: palette(window); +} + + +ads--CAutoHideDockContainer[sideBarLocation="0"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="1"] ads--CResizeHandle { + border-left: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="2"] ads--CResizeHandle { + border-right: 1px solid palette(dark); +} + +ads--CAutoHideDockContainer[sideBarLocation="3"] ads--CResizeHandle { + border-top: 1px solid palette(dark); +} +