├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── init.py ├── pyproject.toml ├── scripts ├── custom-auditwheel.py ├── get-recent-releases.py ├── pyside_config.py └── set-qt-constraint.py ├── setup.py ├── src ├── bindings.h └── bindings.xml └── tests └── test_qtads.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | commit-message: 8 | prefix: "ci" 9 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | jobs: 3 | build_src: 4 | name: Build sdist 5 | runs-on: ubuntu-latest 6 | outputs: 7 | rel0: ${{ steps.query.outputs.rel0 }} 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 11 | with: 12 | submodules: recursive 13 | - name: Query PyPI for recent PySide release number 14 | id: query 15 | run: python ./scripts/get-recent-releases.py 3 >> $GITHUB_OUTPUT 16 | - name: Build sdist 17 | env: 18 | PIP_EXTRA_INDEX_URL: https://download.qt.io/official_releases/QtForPython/ 19 | PYSIDE6_QTADS_NO_HARD_PYSIDE_REQUIREMENT: 1 20 | run: | 21 | python -m pip install --user build 22 | python -m build --sdist 23 | - name: Upload artifact 24 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 25 | with: 26 | name: source 27 | path: dist/*.tar.gz 28 | 29 | # FIXME: Automate minimum Python, manylinux, and custom auditwheel lib filter 30 | build_wheels: 31 | needs: build_src 32 | name: Build wheel ${{ matrix.wheel }} for PySide ${{ matrix.pyside_ver }} on ${{ matrix.os }} 33 | runs-on: ${{ matrix.os }} 34 | strategy: 35 | matrix: 36 | include: 37 | - os: windows-latest 38 | pyside_ver: ${{ needs.build_src.outputs.rel0 }} 39 | wheel: cp39-win_amd64 40 | arch: x86_64 41 | - os: ubuntu-latest 42 | pyside_ver: ${{ needs.build_src.outputs.rel0 }} 43 | wheel: cp39-manylinux_x86_64 44 | arch: x86_64 45 | - os: macos-13 46 | pyside_ver: ${{ needs.build_src.outputs.rel0 }} 47 | wheel: cp39-macosx_x86_64 48 | arch: x86_64 49 | - os: macos-latest 50 | pyside_ver: ${{ needs.build_src.outputs.rel0 }} 51 | wheel: cp39-macosx_arm64 52 | arch: arm64 53 | steps: 54 | - name: Setup Python 55 | uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5 56 | with: 57 | python-version: '3.9' 58 | - name: Download sdist 59 | uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 60 | with: 61 | name: source 62 | - name: Unpack sdist 63 | shell: bash 64 | run: tar --strip-components 1 -xvf *.tar.gz 65 | - name: Setup Qt vars 66 | shell: bash 67 | run: | 68 | QT_VERSION=$(echo ${{ matrix.pyside_ver }} | cut -d. -f-3) 69 | if [[ "$RUNNER_OS" == "Windows" ]]; then 70 | QT_BASE_DIR=${GITHUB_WORKSPACE}\\Qt 71 | QT6_DIR=$QT_BASE_DIR\\$QT_VERSION\\msvc2022_64 72 | elif [[ "$RUNNER_OS" == "macOS" ]]; then 73 | QT_BASE_DIR=${GITHUB_WORKSPACE}/Qt 74 | QT6_DIR=$QT_BASE_DIR/$QT_VERSION/macos 75 | elif [[ "$RUNNER_OS" == "Linux" ]]; then 76 | QT_BASE_DIR=${GITHUB_WORKSPACE}/Qt 77 | QT6_DIR=$QT_BASE_DIR/$QT_VERSION/gcc_64 78 | else 79 | echo "Unsupported runner OS $RUNNER_OS" 80 | exit 1 81 | fi 82 | 83 | echo "QT_VERSION=$QT_VERSION" >> $GITHUB_ENV 84 | echo "QT_BASE_DIR=$QT_BASE_DIR" >> $GITHUB_ENV 85 | echo "QT6_DIR=$QT6_DIR" >> $GITHUB_ENV 86 | - uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1 87 | if: runner.os == 'Windows' 88 | - name: Build wheels 89 | uses: pypa/cibuildwheel@ee63bf16da6cddfb925f542f2c7b59ad50e93969 # v2.22.0 90 | with: 91 | output-dir: wheelhouse 92 | env: 93 | CIBW_ENVIRONMENT_LINUX: > 94 | CMAKE_PREFIX_PATH=${{ env.QT6_DIR }}/lib/cmake 95 | LD_LIBRARY_PATH=${{ env.QT6_DIR }}/lib 96 | PIP_EXTRA_INDEX_URL=https://download.qt.io/official_releases/QtForPython/ 97 | VERBOSE=1 98 | CIBW_ENVIRONMENT_MACOS: > 99 | MACOSX_DEPLOYMENT_TARGET=12.0 100 | CMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} 101 | CMAKE_PREFIX_PATH=${QT6_DIR}/lib/cmake 102 | PIP_EXTRA_INDEX_URL=https://download.qt.io/official_releases/QtForPython/ 103 | VERBOSE=1 104 | CIBW_ENVIRONMENT_WINDOWS: > 105 | CMAKE_PREFIX_PATH="${QT6_DIR}\\lib\\cmake" 106 | PIP_EXTRA_INDEX_URL=https://download.qt.io/official_releases/QtForPython/ 107 | VERBOSE=1 108 | CIBW_BEFORE_BUILD_LINUX: > 109 | rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux 110 | && yum install -y mesa-libGL libxslt llvm clang clang-devel libxkbcommon-devel 111 | && python3 -m pip install --user "aqtinstall!=3.2.1" auditwheel 112 | && python3 -m aqt install-qt linux desktop ${{ env.QT_VERSION }} --outputdir ${{ env.QT_BASE_DIR }} --base http://mirrors.ocf.berkeley.edu/qt/ 113 | && python3 ./scripts/set-qt-constraint.py ${{ matrix.pyside_ver }} 114 | CIBW_BEFORE_BUILD_MACOS: > 115 | python3 -m pip install "aqtinstall!=3.2.1" 116 | && python3 -m aqt install-qt mac desktop ${{ env.QT_VERSION }} --outputdir ${{ env.QT_BASE_DIR }} --base http://mirrors.ocf.berkeley.edu/qt/ 117 | && python3 ./scripts/set-qt-constraint.py ${{ matrix.pyside_ver }} 118 | CIBW_BEFORE_BUILD_WINDOWS: > 119 | python3 -m pip install "aqtinstall!=3.2.1" 120 | && python3 -m aqt install-qt windows desktop ${{ env.QT_VERSION }} win64_msvc2022_64 --outputdir ${{ env.QT_BASE_DIR }} --base http://mirrors.ocf.berkeley.edu/qt/ 121 | && python3 ./scripts/set-qt-constraint.py ${{ matrix.pyside_ver }} 122 | CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 123 | CIBW_ARCHS_MACOS: x86_64 arm64 124 | CIBW_BUILD_VERBOSITY: 3 125 | CIBW_BUILD: ${{ matrix.wheel }} 126 | CIBW_REPAIR_WHEEL_COMMAND_LINUX: > 127 | python ./scripts/custom-auditwheel.py repair -w {dest_dir} --only-plat --plat manylinux_2_28_x86_64 {wheel} 128 | CIBW_REPAIR_WHEEL_COMMAND_MACOS: 129 | CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: 130 | # - name: Rename wheel to include Qt version 131 | # run: | 132 | # pushd wheelhouse 133 | # for i in *.whl; do 134 | # pkgname=$(echo $i | cut -d- -f1-2) 135 | # pkginfo=$(echo $i | cut -d- -f3-) 136 | # mv $i ${pkgname}-${{ matrix.pyside_ver }}-${pkginfo} 137 | # done 138 | # popd 139 | - name: Install test dependencies 140 | if: startsWith(matrix.os, 'ubuntu') 141 | run: | 142 | sudo apt-get update 143 | sudo apt-get install -y libxkbcommon-dev libegl-dev 144 | echo "QT_QPA_PLATFORM=minimal:enable_fonts" >> $GITHUB_ENV 145 | - name: Test wheel 146 | shell: bash 147 | run: | 148 | python -m pip install ./wheelhouse/*.whl 149 | cd tests 150 | python -m unittest discover 151 | - name: Upload artifact 152 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 153 | with: 154 | name: wheel-${{ matrix.wheel }} 155 | path: ./wheelhouse/*.whl 156 | 157 | build_aarch64_wheel: 158 | name: Build wheel ${{ matrix.wheel }} for PySide ${{ matrix.pyside_ver }} on ${{ matrix.os }} 159 | needs: build_src 160 | strategy: 161 | matrix: 162 | include: 163 | - os: ubuntu-24.04-arm 164 | pyside_ver: ${{ needs.build_src.outputs.rel0 }} 165 | wheel: cp39-manylinux_aarch64 166 | arch: aarch64 167 | runs-on: ${{ matrix.os }} 168 | steps: 169 | - name: Download sdist 170 | uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 171 | with: 172 | name: source 173 | - name: Unpack sdist 174 | shell: bash 175 | run: tar --strip-components 1 -xvf *.tar.gz 176 | - name: Install dependencies 177 | run: | 178 | sudo apt-get update 179 | sudo apt-get install -y \ 180 | build-essential \ 181 | libgl1-mesa-dev \ 182 | libxslt1-dev git \ 183 | libglib2.0-dev \ 184 | patchelf \ 185 | python-is-python3 \ 186 | python3 \ 187 | python3-venv \ 188 | python3-pip \ 189 | libxkbcommon-dev 190 | - name: Install uv 191 | uses: astral-sh/setup-uv@c7f87aa956e4c323abf06d5dec078e358f6b4d04 # v6 192 | with: 193 | python-version: "3.9" 194 | activate-environment: true 195 | - name: Install Python dependencies 196 | run: uv pip install "aqtinstall!=3.2.1" PySide6 auditwheel 197 | - name: Setup Qt vars 198 | shell: bash 199 | run: echo QT_VERSION=$(echo ${{ matrix.pyside_ver }} | cut -d. -f-3) >> $GITHUB_ENV 200 | - name: Install Qt 201 | run: aqt install-qt linux_arm64 desktop ${{ env.QT_VERSION }} --outputdir qt 202 | - name: Build 203 | env: 204 | LD_LIBRARY_PATH: ${{ github.workspace }}/qt/${{ env.QT_VERSION }}/gcc_arm64/lib 205 | CMAKE_PREFIX_PATH: ${{ github.workspace }}/qt/${{ env.QT_VERSION }}/gcc_arm64/lib/cmake 206 | PIP_EXTRA_INDEX_URL: https://download.qt.io/official_releases/QtForPython/ 207 | run: uv build --verbose --wheel . 208 | - name: Fixup wheel 209 | env: 210 | LD_LIBRARY_PATH: ${{ env.VIRTUAL_ENV }}/lib/python3.12/site-packages/PySide6/ 211 | run: | 212 | mkdir dist-repaired 213 | python ./scripts/custom-auditwheel.py repair -w dist-repaired --only-plat --plat manylinux_2_39_aarch64 ./dist/pyside6_qtads-*.whl 214 | - name: Test wheel 215 | run: | 216 | uv pip install ./dist-repaired/*.whl 217 | cd tests 218 | python -m unittest discover 219 | - name: Upload artifact 220 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 221 | with: 222 | name: wheel-${{ matrix.wheel }} 223 | path: ./dist-repaired/*.whl 224 | 225 | upload_pypi: 226 | needs: [build_wheels, build_aarch64_wheel, build_src] 227 | runs-on: ubuntu-latest 228 | # upload to PyPI on every tag starting with 'v' 229 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') 230 | steps: 231 | - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 232 | with: 233 | name: source 234 | path: dist 235 | - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 236 | with: 237 | pattern: wheel-* 238 | path: dist 239 | merge-multiple: true 240 | - uses: pypa/gh-action-pypi-publish@release/v1 241 | with: 242 | user: __token__ 243 | password: ${{ secrets.PYPI_TOKEN }} 244 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | *.whl 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Qt-Advanced-Docking-System"] 2 | path = Qt-Advanced-Docking-System 3 | url = https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | cmake_policy(VERSION 3.16) 3 | 4 | # Enable policy to run automoc on generated files. 5 | if(POLICY CMP0071) 6 | cmake_policy(SET CMP0071 NEW) 7 | endif() 8 | 9 | 10 | # ================================ General configuration ====================================== 11 | 12 | project(PySide6QtAds) 13 | 14 | # Find Python 15 | find_package (Python3 COMPONENTS Interpreter) 16 | if(NOT Python3_Interpreter_FOUND) 17 | message(FATAL_ERROR "Python3 not found") 18 | endif() 19 | message(STATUS "Using python: ${Python3_EXECUTABLE}") 20 | 21 | # https://bugreports.qt.io/browse/QTBUG-89754 22 | set(OpenGL_GL_PREFERENCE "LEGACY") 23 | 24 | # Find the required Qt packages 25 | find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) 26 | 27 | # Set CPP standard to C++11 minimum. 28 | set(CMAKE_CXX_STANDARD 11) 29 | 30 | # The C++ project library for which we will create bindings. 31 | set(qtads_subdir "Qt-Advanced-Docking-System") 32 | set(qtads_dir ${CMAKE_CURRENT_SOURCE_DIR}/${qtads_subdir}) 33 | add_subdirectory(${qtads_subdir} EXCLUDE_FROM_ALL) 34 | set(project_library "qtadvanceddocking-qt6") 35 | 36 | # The name of the generated bindings module (as imported in Python). 37 | set(bindings_library "PySide6QtAds") 38 | 39 | # The header file with all the types and functions for which bindings will be generated. 40 | # Usually it simply includes other headers of the library you are creating bindings for. 41 | set(wrapped_header ${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.h) 42 | 43 | # The typesystem xml file which defines the relationships between the C++ types / functions 44 | # and the corresponding Python equivalents. 45 | set(typesystem_file ${CMAKE_CURRENT_SOURCE_DIR}/src/bindings.xml) 46 | 47 | # Specify which C++ files will be generated by shiboken. This includes the module wrapper 48 | # and a '.cpp' file per C++ type. These are needed for generating the module shared 49 | # library. 50 | set(generated_sources 51 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cautohidedockcontainer_wrapper.cpp 52 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cautohidesidebar_wrapper.cpp 53 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cautohidetab_wrapper.cpp 54 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareatabbar_wrapper.cpp 55 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareatitlebar_wrapper.cpp 56 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockareawidget_wrapper.cpp 57 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockcomponentsfactory_wrapper.cpp 58 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockcontainerwidget_wrapper.cpp 59 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockfocuscontroller_wrapper.cpp 60 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockingstatereader_wrapper.cpp 61 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockmanager_wrapper.cpp 62 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockoverlay_wrapper.cpp 63 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockoverlaycross_wrapper.cpp 64 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdocksplitter_wrapper.cpp 65 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockwidget_wrapper.cpp 66 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cdockwidgettab_wrapper.cpp 67 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_celidinglabel_wrapper.cpp 68 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cfloatingdockcontainer_wrapper.cpp 69 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cfloatingdragpreview_wrapper.cpp 70 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ciconprovider_wrapper.cpp 71 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cspacerwidget_wrapper.cpp 72 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ctitlebarbutton_wrapper.cpp 73 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_ifloatingwidget_wrapper.cpp 74 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cpushbutton_wrapper.cpp 75 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_cresizehandle_wrapper.cpp 76 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/ads_wrapper.cpp 77 | ${CMAKE_CURRENT_BINARY_DIR}/${bindings_library}/pyside6qtads_module_wrapper.cpp 78 | ) 79 | 80 | 81 | # ================================== Shiboken detection ====================================== 82 | 83 | 84 | # Macro to get various pyside / python include / link flags and paths. 85 | # Uses the not entirely supported utils/pyside_config.py file. 86 | macro(pyside_config option output_var) 87 | if(${ARGC} GREATER 2) 88 | set(is_list ${ARGV2}) 89 | else() 90 | set(is_list "") 91 | endif() 92 | 93 | execute_process( 94 | COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pyside_config.py" 95 | ${option} 96 | OUTPUT_VARIABLE ${output_var} 97 | OUTPUT_STRIP_TRAILING_WHITESPACE) 98 | 99 | if ("${${output_var}}" STREQUAL "") 100 | message(FATAL_ERROR "Error: Calling ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/scripts/pyside_config.py ${option} returned no output.") 101 | endif() 102 | if(is_list) 103 | string (REPLACE " " ";" ${output_var} "${${output_var}}") 104 | endif() 105 | endmacro() 106 | 107 | # Query for the shiboken generator path, Python path, include paths and linker flags. 108 | pyside_config(--shiboken-module-path shiboken_module_path) 109 | pyside_config(--shiboken-generator-path shiboken_generator_path) 110 | pyside_config(--pyside-path pyside_path) 111 | pyside_config(--pyside-include-path pyside_include_dir 1) 112 | pyside_config(--python-include-path python_include_dir) 113 | pyside_config(--shiboken-generator-include-path shiboken_include_dir 1) 114 | pyside_config(--shiboken-module-shared-libraries-cmake shiboken_shared_libraries 0) 115 | pyside_config(--python-link-flags-cmake python_linking_data 0) 116 | pyside_config(--pyside-shared-libraries-cmake pyside_shared_libraries 0) 117 | 118 | set(shiboken_path "${shiboken_generator_path}/shiboken6${CMAKE_EXECUTABLE_SUFFIX}") 119 | if(NOT EXISTS ${shiboken_path}) 120 | message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}") 121 | endif() 122 | 123 | 124 | # ==================================== RPATH configuration ==================================== 125 | 126 | set(CMAKE_SKIP_BUILD_RPATH TRUE) 127 | 128 | if(UNIX AND NOT APPLE) 129 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib:$ORIGIN/../PySide6/:$ORIGIN/../PySide6/Qt/lib:$ORIGIN/../shiboken6") 130 | endif() 131 | 132 | if(APPLE) 133 | set(CMAKE_INSTALL_RPATH "@loader_path/lib;@loader_path/../PySide6/;@loader_path/../PySide6/Qt/lib;@loader_path/../shiboken6") 134 | set(MACOSX_RPATH TRUE) 135 | endif() 136 | 137 | # ============================== Qt Includes for project_library ============================== 138 | 139 | # Get the relevant Qt include dirs, to pass them on to shiboken. 140 | set(QT_INCLUDE_DIR "") 141 | get_target_property(QT_INCLUDE_DIR_LIST Qt6::Core INTERFACE_INCLUDE_DIRECTORIES) 142 | foreach(_Q ${QT_INCLUDE_DIR_LIST}) 143 | if(NOT "${_Q}" MATCHES "QtCore$") 144 | set(QT_INCLUDE_DIR "${_Q}") 145 | endif() 146 | endforeach() 147 | if(QT_INCLUDE_DIR STREQUAL "") 148 | message(FATAL_ERROR "Unable to obtain the Qt include directory") 149 | endif() 150 | 151 | set(QT_INCLUDES "") 152 | list(APPEND QT_INCLUDES "-I${QT_INCLUDE_DIR}") 153 | 154 | get_property(QT_CORE_INCLUDE_DIRS TARGET Qt6::Core PROPERTY INTERFACE_INCLUDE_DIRECTORIES) 155 | foreach(INCLUDE_DIR ${QT_CORE_INCLUDE_DIRS}) 156 | list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}") 157 | endforeach() 158 | 159 | get_property(QT_GUI_INCLUDE_DIRS TARGET Qt6::Gui PROPERTY INTERFACE_INCLUDE_DIRECTORIES) 160 | foreach(INCLUDE_DIR ${QT_GUI_INCLUDE_DIRS}) 161 | list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}") 162 | endforeach() 163 | 164 | get_property(QT_WIDGETS_INCLUDE_DIRS TARGET Qt6::Widgets PROPERTY INTERFACE_INCLUDE_DIRECTORIES) 165 | foreach(INCLUDE_DIR ${QT_WIDGETS_INCLUDE_DIRS}) 166 | list(APPEND QT_INCLUDES "-I${INCLUDE_DIR}") 167 | endforeach() 168 | 169 | # Check if Qt is a framework build on macOS. This affects how include paths should be handled. 170 | get_target_property(QtCore_is_framework Qt6::Core FRAMEWORK) 171 | if (QtCore_is_framework) 172 | # Get the path to the Qt framework dir. 173 | set(QT_FRAMEWORK_INCLUDE_DIR "${QT6_INSTALL_PREFIX}/${QT6_INSTALL_LIBS}") 174 | message(STATUS "*** QT_FRAMEWORK_INCLUDE_DIR is ${QT_FRAMEWORK_INCLUDE_DIR}") 175 | list(APPEND QT_INCLUDES "--framework-include-paths=${QT_FRAMEWORK_INCLUDE_DIR}") 176 | endif() 177 | 178 | # We need to include the headers for the module bindings that we use 179 | set(pyside_additional_includes "") 180 | foreach(INCLUDE_DIR ${pyside_include_dir}) 181 | list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtCore") 182 | list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtGui") 183 | list(APPEND pyside_additional_includes "${INCLUDE_DIR}/QtWidgets") 184 | endforeach() 185 | 186 | 187 | # ====================== Shiboken target for generating binding C++ files ==================== 188 | 189 | set(implicit_includes) 190 | foreach(_current ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) 191 | set(implicit_includes ${implicit_includes} "-I${_current}") 192 | endforeach() 193 | 194 | # Set up the options to pass to shiboken. 195 | set(shiboken_options --generator-set=shiboken --enable-parent-ctor-heuristic 196 | --enable-pyside-extensions --enable-return-value-heuristic --use-isnull-as-nb_nonzero 197 | --avoid-protected-hack 198 | ${QT_INCLUDES} 199 | ${implicit_includes} 200 | -I${qtads_dir}/src 201 | -T${CMAKE_CURRENT_SOURCE_DIR} 202 | -T${pyside_path}/typesystems 203 | --output-directory=${CMAKE_CURRENT_BINARY_DIR} 204 | ) 205 | 206 | set(generated_sources_dependencies ${wrapped_header} ${typesystem_file}) 207 | 208 | # Add custom target to run shiboken to generate the binding cpp files. 209 | add_custom_command(OUTPUT ${generated_sources} 210 | COMMAND ${shiboken_path} 211 | ${shiboken_options} ${wrapped_header} ${typesystem_file} 212 | DEPENDS ${generated_sources_dependencies} 213 | IMPLICIT_DEPENDS CXX ${wrapped_header} 214 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 215 | COMMENT "Running generator for ${typesystem_file}.") 216 | 217 | 218 | # =============================== CMake target - bindings_library ============================= 219 | 220 | # Set the cpp files which will be used for the bindings library. 221 | set(${bindings_library}_sources ${generated_sources}) 222 | 223 | # Define and build the bindings library. 224 | add_library(${bindings_library} MODULE ${${bindings_library}_sources}) 225 | 226 | # Apply relevant include and link flags. 227 | target_include_directories(${bindings_library} PRIVATE ${pyside_additional_includes}) 228 | target_include_directories(${bindings_library} PRIVATE ${pyside_include_dir}) 229 | target_include_directories(${bindings_library} PRIVATE ${python_include_dir}) 230 | target_include_directories(${bindings_library} PRIVATE ${shiboken_include_dir}) 231 | target_include_directories(${bindings_library} PRIVATE "${CMAKE_SOURCE_DIR}/src") 232 | 233 | target_link_libraries(${bindings_library} PRIVATE Qt6::Widgets) 234 | target_link_libraries(${bindings_library} PRIVATE ${project_library}) 235 | target_link_libraries(${bindings_library} PRIVATE ${pyside_shared_libraries}) 236 | target_link_libraries(${bindings_library} PRIVATE ${shiboken_shared_libraries}) 237 | 238 | target_compile_options(qtadvanceddocking-qt6 PRIVATE -Og -fPIC) 239 | target_compile_options(${bindings_library} PRIVATE -Og -fPIC) 240 | target_compile_definitions(${bindings_library} PRIVATE "-DPy_LIMITED_API=0x03090000") 241 | 242 | # Adjust the name of generated module. 243 | set_property(TARGET ${bindings_library} PROPERTY PREFIX "") 244 | set_property(TARGET ${bindings_library} PROPERTY OUTPUT_NAME 245 | "${bindings_library}${PYTHON_EXTENSION_SUFFIX}") 246 | if(WIN32) 247 | set_property(TARGET ${bindings_library} PROPERTY SUFFIX ".pyd") 248 | endif() 249 | 250 | # Make sure the linker doesn't complain about not finding Python symbols on macOS. 251 | if(APPLE) 252 | set_target_properties(${bindings_library} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") 253 | endif(APPLE) 254 | 255 | if (WIN32) 256 | list(GET python_linking_data 0 python_libdir) 257 | target_link_directories(${bindings_library} PRIVATE ${python_libdir}) 258 | endif() 259 | 260 | 261 | # ================================= Dubious deployment section ================================ 262 | 263 | install(TARGETS ${bindings_library} 264 | LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} 265 | RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} 266 | ) 267 | 268 | if(NOT BUILD_STATIC) 269 | install(TARGETS ${project_library} 270 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 271 | RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} 272 | ) 273 | endif() 274 | 275 | install(FILES 276 | "${qtads_dir}/LICENSE" 277 | "${qtads_dir}/gnu-lgpl-v2.1.md" 278 | DESTINATION license/ads 279 | COMPONENT license 280 | ) 281 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | PySide6-QtAds is licensed under GNU LGPL v2.1. 2 | 3 | Based on PySide6 binding work by CJ Niemira via https://github.com/cniemira/pyside6_qtads 4 | 5 | MIT License 6 | 7 | Copyright (c) 2022 cniemira 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | With bindings.xml improvements via https://github.com/metgem/PySide2Ads also under LGPL v2.1 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft Qt-Advanced-Docking-System 2 | global-exclude .git* .appveyor.yml .project .travis.yml 3 | recursive-exclude .git* * 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PySide6-QtAds 2 | [![Latest Release](https://img.shields.io/pypi/v/Pyside6-QtAds.svg)](https://pypi.python.org/pypi/Pyside6-QtAds/) 3 | [![License](https://img.shields.io/pypi/l/PySide6-QtAds)](https://github.com/mborgerson/pyside6_qtads/blob/main/LICENSE) 4 | ![Monthly Downloads](https://img.shields.io/pypi/dm/PySide6-QtAds) 5 | 6 | Python/PySide6 bindings to the [Qt Advanced Docking System](https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System) library. 7 | 8 | Pre-built wheels are available for Windows, macOS, and Linux. You can install with: 9 | 10 | ``` 11 | pip install PySide6-QtAds 12 | ``` 13 | 14 | You may also build from source. Example build from source on Ubuntu 24.04: 15 | 16 | ```bash 17 | # Install Qt (for example, using aqtinstall) 18 | pip install aqtinstall 19 | aqt install-qt linux desktop 6.9.0 --outputdir qt 20 | 21 | # Build PySide6-QtAds 22 | LD_LIBRARY_PATH=$PWD/qt/6.9.0/gcc_64/lib \ 23 | CMAKE_PREFIX_PATH=$PWD/qt/6.9.0/gcc_64/lib/cmake/ \ 24 | PIP_EXTRA_INDEX_URL=https://download.qt.io/official_releases/QtForPython/ \ 25 | pip install -v . 26 | ``` 27 | 28 | Note: `shiboken6-generator` is required when building from source. It will be downloaded automatically in the command above from Qt's package index, as it is not available on PyPI. 29 | 30 | # Examples 31 | https://github.com/mborgerson/Qt-Advanced-Docking-System/tree/pyside6 32 | 33 | # Credits 34 | - Original PySide6 binding work by CJ Niemira via https://github.com/cniemira/pyside6_qtads 35 | - With bindings.xml improvements via https://github.com/metgem/PySide2Ads 36 | -------------------------------------------------------------------------------- /init.py: -------------------------------------------------------------------------------- 1 | import platform 2 | 3 | if platform.system() == 'Windows': 4 | import os, PySide6, shiboken6 5 | with os.add_dll_directory(os.path.dirname(PySide6.__file__)), \ 6 | os.add_dll_directory(os.path.dirname(shiboken6.__file__)): 7 | from .PySide6QtAds import ads 8 | else: 9 | # Runtime library dependencies resolved via rpath 10 | from .PySide6QtAds import ads 11 | 12 | # DockWidgetArea 13 | DockWidgetArea = ads.DockWidgetArea 14 | NoDockWidgetArea = ads.NoDockWidgetArea 15 | LeftDockWidgetArea = ads.LeftDockWidgetArea 16 | RightDockWidgetArea = ads.RightDockWidgetArea 17 | TopDockWidgetArea = ads.TopDockWidgetArea 18 | BottomDockWidgetArea = ads.BottomDockWidgetArea 19 | CenterDockWidgetArea = ads.CenterDockWidgetArea 20 | InvalidDockWidgetArea = ads.InvalidDockWidgetArea 21 | OuterDockAreas = ads.OuterDockAreas 22 | AllDockAreas = ads.AllDockAreas 23 | 24 | # eTabIndex 25 | TabDefaultInsertIndex = ads.TabDefaultInsertIndex 26 | TabInvalidIndex = ads.TabInvalidIndex 27 | 28 | # SideBarLocation 29 | SideBarLeft = ads.SideBarLeft 30 | SideBarTop = ads.SideBarTop 31 | SideBarBottom = ads.SideBarBottom 32 | SideBarRight = ads.SideBarRight 33 | SideBarNone = ads.SideBarNone 34 | 35 | # eBitwiseOperator 36 | BitwiseAnd = ads.BitwiseAnd 37 | BitwiseOr = ads.BitwiseOr 38 | 39 | # eDragState 40 | DraggingInactive = ads.DraggingInactive 41 | DraggingMousePressed = ads.DraggingMousePressed 42 | DraggingTab = ads.DraggingTab 43 | DraggingFloatingWidget = ads.DraggingFloatingWidget 44 | 45 | # eIcon 46 | TabCloseIcon = ads.TabCloseIcon 47 | AutoHideIcon = ads.AutoHideIcon 48 | DockAreaMenuIcon = ads.DockAreaMenuIcon 49 | DockAreaUndockIcon = ads.DockAreaUndockIcon 50 | DockAreaCloseIcon = ads.DockAreaCloseIcon 51 | DockAreaMinimizeIcon = ads.DockAreaMinimizeIcon 52 | IconCount = ads.IconCount 53 | 54 | # TitleBarButton 55 | TitleBarButtonTabsMenu = ads.TitleBarButtonTabsMenu 56 | TitleBarButtonUndock = ads.TitleBarButtonUndock 57 | TitleBarButtonClose = ads.TitleBarButtonClose 58 | TitleBarButtonAutoHide = ads.TitleBarButtonAutoHide 59 | TitleBarButtonMinimize = ads.TitleBarButtonMinimize 60 | 61 | # Classes 62 | CDockAreaTabBar = ads.CDockAreaTabBar 63 | CDockAreaTitleBar = ads.CDockAreaTitleBar 64 | CDockAreaWidget = ads.CDockAreaWidget 65 | CDockComponentsFactory = ads.CDockComponentsFactory 66 | CDockContainerWidget = ads.CDockContainerWidget 67 | CDockFocusController = ads.CDockFocusController 68 | CDockManager = ads.CDockManager 69 | CDockSplitter = ads.CDockSplitter 70 | CDockOverlay = ads.CDockOverlay 71 | CDockOverlayCross = ads.CDockOverlayCross 72 | CDockWidget = ads.CDockWidget 73 | CDockWidgetTab = ads.CDockWidgetTab 74 | CDockingStateReader = ads.CDockingStateReader 75 | CElidingLabel = ads.CElidingLabel 76 | CFloatingDockContainer = ads.CFloatingDockContainer 77 | CFloatingDragPreview = ads.CFloatingDragPreview 78 | IFloatingWidget = ads.IFloatingWidget 79 | CIconProvider = ads.CIconProvider 80 | CSpacerWidget = ads.CSpacerWidget 81 | CTitleBarButton = ads.CTitleBarButton 82 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "wheel", 4 | "setuptools>=45", 5 | "setuptools_scm[toml]>=6.0", 6 | "cmake_build_extension", 7 | "PySide6-Essentials", 8 | "shiboken6", 9 | "shiboken6_generator" 10 | ] 11 | build-backend = "setuptools.build_meta" 12 | 13 | [project] 14 | name = "PySide6-QtAds" 15 | version = "4.4.0.1.dev0" 16 | description = "PySide6 bindings to Qt Advanced Docking System" 17 | license = { file = "LICENSE.txt" } 18 | readme = { file = "README.md", content-type = "text/markdown" } 19 | classifiers = [ 20 | "Development Status :: 3 - Alpha", 21 | "Intended Audience :: Developers", 22 | "Programming Language :: Python :: 3", 23 | ] 24 | requires-python = ">=3.9" 25 | dynamic = ["dependencies"] 26 | 27 | [project.urls] 28 | Homepage = "https://github.com/mborgerson/pyside6_qtads/" 29 | -------------------------------------------------------------------------------- /scripts/custom-auditwheel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import pathlib 3 | import tempfile 4 | import json 5 | from auditwheel.main import main 6 | from auditwheel.policy import _POLICY_JSON_MAP as POLICY_JSON_MAP 7 | 8 | 9 | if __name__ == "__main__": 10 | tmpdir = tempfile.TemporaryDirectory() 11 | tmppath = pathlib.Path(tmpdir.name) 12 | for libc in POLICY_JSON_MAP: 13 | policies = json.loads(POLICY_JSON_MAP[libc].read_text()) 14 | for p in policies: 15 | p['lib_whitelist'].extend([ 16 | 'libpyside6.abi3.so.6.9', 17 | 'libpyside6.abi3.so.6.8', 18 | 'libpyside6.abi3.so.6.7', 19 | 'libpyside6.abi3.so.6.6', 20 | 'libpyside6.abi3.so.6.5', 21 | 'libpyside6.abi3.so.6.4', 22 | 'libpyside6.abi3.so.6.3', 23 | 'libshiboken6.abi3.so.6.9', 24 | 'libshiboken6.abi3.so.6.8', 25 | 'libshiboken6.abi3.so.6.7', 26 | 'libshiboken6.abi3.so.6.6', 27 | 'libshiboken6.abi3.so.6.5', 28 | 'libshiboken6.abi3.so.6.4', 29 | 'libshiboken6.abi3.so.6.3', 30 | 'libQt6Widgets.so.6', 31 | 'libQt6Gui.so.6', 32 | 'libpyside6qml.abi3.so.6.9', 33 | 'libpyside6qml.abi3.so.6.8', 34 | 'libpyside6qml.abi3.so.6.7', 35 | 'libpyside6qml.abi3.so.6.6', 36 | 'libpyside6qml.abi3.so.6.5', 37 | 'libpyside6qml.abi3.so.6.4', 38 | 'libpyside6qml.abi3.so.6.3', 39 | 'libGLX.so.0', 40 | 'libOpenGL.so.0', 41 | 'libQt6Core.so.6', 42 | 'libxcb.so.1', 43 | ]) 44 | fname = tmppath / (libc.name + "-policy.json") 45 | with open(fname, "w") as f: 46 | json.dump(policies, f) 47 | POLICY_JSON_MAP[libc] = fname 48 | 49 | main() 50 | tmpdir.cleanup() 51 | -------------------------------------------------------------------------------- /scripts/get-recent-releases.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import requests 3 | import argparse 4 | 5 | def main(): 6 | ap = argparse.ArgumentParser() 7 | ap.add_argument('num', type=int) 8 | args = ap.parse_args() 9 | 10 | endpoint = 'https://pypi.org/pypi/PySide6/json' 11 | all_releases = list(requests.get(endpoint).json()['releases'].keys()) 12 | for i, ver in enumerate(all_releases[0-args.num:]): 13 | print(f'rel{args.num-i-1}={ver}') 14 | 15 | 16 | if __name__ == '__main__': 17 | main() 18 | -------------------------------------------------------------------------------- /scripts/pyside_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 The Qt Company Ltd. 2 | # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause 3 | 4 | import sysconfig 5 | from enum import Enum 6 | import glob 7 | import os 8 | import re 9 | import sys 10 | 11 | 12 | PYSIDE = 'pyside6' 13 | PYSIDE_MODULE = 'PySide6' 14 | SHIBOKEN = 'shiboken6' 15 | 16 | 17 | class Package(Enum): 18 | SHIBOKEN_MODULE = 1 19 | SHIBOKEN_GENERATOR = 2 20 | PYSIDE_MODULE = 3 21 | 22 | 23 | generic_error = ('Did you forget to activate your virtualenv? Or perhaps' 24 | f' you forgot to build / install {PYSIDE_MODULE} into your currently active Python' 25 | ' environment?') 26 | pyside_error = f'Unable to locate {PYSIDE_MODULE}. {generic_error}' 27 | shiboken_module_error = f'Unable to locate {SHIBOKEN}-module. {generic_error}' 28 | shiboken_generator_error = f'Unable to locate shiboken-generator. {generic_error}' 29 | pyside_libs_error = f'Unable to locate the PySide shared libraries. {generic_error}' 30 | python_link_error = 'Unable to locate the Python library for linking.' 31 | python_include_error = 'Unable to locate the Python include headers directory.' 32 | 33 | options = [] 34 | 35 | # option, function, error, description 36 | options.append(("--shiboken-module-path", 37 | lambda: find_shiboken_module(), 38 | shiboken_module_error, 39 | "Print shiboken module location")) 40 | options.append(("--shiboken-generator-path", 41 | lambda: find_shiboken_generator(), 42 | shiboken_generator_error, 43 | "Print shiboken generator location")) 44 | options.append(("--pyside-path", lambda: find_pyside(), pyside_error, 45 | f"Print {PYSIDE_MODULE} location")) 46 | 47 | options.append(("--python-include-path", 48 | lambda: get_python_include_path(), 49 | python_include_error, 50 | "Print Python include path")) 51 | options.append(("--shiboken-generator-include-path", 52 | lambda: get_package_include_path(Package.SHIBOKEN_GENERATOR), 53 | pyside_error, 54 | "Print shiboken generator include paths")) 55 | options.append(("--pyside-include-path", 56 | lambda: get_package_include_path(Package.PYSIDE_MODULE), 57 | pyside_error, 58 | "Print PySide6 include paths")) 59 | 60 | options.append(("--python-link-flags-qmake", lambda: python_link_flags_qmake(), python_link_error, 61 | "Print python link flags for qmake")) 62 | options.append(("--python-link-flags-cmake", lambda: python_link_flags_cmake(), python_link_error, 63 | "Print python link flags for cmake")) 64 | 65 | options.append(("--shiboken-module-qmake-lflags", 66 | lambda: get_package_qmake_lflags(Package.SHIBOKEN_MODULE), pyside_error, 67 | "Print shiboken6 shared library link flags for qmake")) 68 | options.append(("--pyside-qmake-lflags", 69 | lambda: get_package_qmake_lflags(Package.PYSIDE_MODULE), pyside_error, 70 | "Print PySide6 shared library link flags for qmake")) 71 | 72 | options.append(("--shiboken-module-shared-libraries-qmake", 73 | lambda: get_shared_libraries_qmake(Package.SHIBOKEN_MODULE), pyside_libs_error, 74 | "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for qmake")) 75 | options.append(("--shiboken-module-shared-libraries-cmake", 76 | lambda: get_shared_libraries_cmake(Package.SHIBOKEN_MODULE), pyside_libs_error, 77 | "Print paths of shiboken shared libraries (.so's, .dylib's, .dll's) for cmake")) 78 | 79 | options.append(("--pyside-shared-libraries-qmake", 80 | lambda: get_shared_libraries_qmake(Package.PYSIDE_MODULE), pyside_libs_error, 81 | "Print paths of f{PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for qmake")) 82 | options.append(("--pyside-shared-libraries-cmake", 83 | lambda: get_shared_libraries_cmake(Package.PYSIDE_MODULE), pyside_libs_error, 84 | f"Print paths of {PYSIDE_MODULE} shared libraries (.so's, .dylib's, .dll's) for cmake")) 85 | 86 | options_usage = '' 87 | for i, (flag, _, _, description) in enumerate(options): 88 | options_usage += f' {flag:<45} {description}' 89 | if i < len(options) - 1: 90 | options_usage += '\n' 91 | 92 | usage = f""" 93 | Utility to determine include/link options of shiboken/PySide and Python for qmake/CMake projects 94 | that would like to embed or build custom shiboken/PySide bindings. 95 | 96 | Usage: pyside_config.py [option] 97 | Options: 98 | {options_usage} 99 | -a Print all options and their values 100 | --help/-h Print this help 101 | """ 102 | 103 | option = sys.argv[1] if len(sys.argv) == 2 else '-a' 104 | if option == '-h' or option == '--help': 105 | print(usage) 106 | sys.exit(0) 107 | 108 | 109 | def clean_path(path): 110 | return path if sys.platform != 'win32' else path.replace('\\', '/') 111 | 112 | 113 | def shared_library_suffix(): 114 | if sys.platform == 'win32': 115 | return 'lib' 116 | elif sys.platform == 'darwin': 117 | return 'dylib' 118 | # Linux 119 | else: 120 | return 'so.*' 121 | 122 | 123 | def import_suffixes(): 124 | import importlib.machinery 125 | return importlib.machinery.EXTENSION_SUFFIXES 126 | 127 | 128 | def is_debug(): 129 | debug_suffix = '_d.pyd' if sys.platform == 'win32' else '_d.so' 130 | return any([s.endswith(debug_suffix) for s in import_suffixes()]) 131 | 132 | 133 | def shared_library_glob_pattern(): 134 | glob = '*.' + shared_library_suffix() 135 | return glob if sys.platform == 'win32' else 'lib' + glob 136 | 137 | 138 | def filter_shared_libraries(libs_list): 139 | def predicate(lib_name): 140 | basename = os.path.basename(lib_name) 141 | if 'shiboken' in basename or 'pyside6' in basename: 142 | return True 143 | return False 144 | result = [lib for lib in libs_list if predicate(lib)] 145 | return result 146 | 147 | 148 | # Return qmake link option for a library file name 149 | def link_option(lib): 150 | # On Linux: 151 | # Since we cannot include symlinks with wheel packages 152 | # we are using an absolute path for the libpyside and libshiboken 153 | # libraries when compiling the project 154 | baseName = os.path.basename(lib) 155 | link = ' -l' 156 | if sys.platform in ['linux', 'linux2']: # Linux: 'libfoo.so' -> '/absolute/path/libfoo.so' 157 | link = lib 158 | elif sys.platform in ['darwin']: # Darwin: 'libfoo.so' -> '-lfoo' 159 | link += os.path.splitext(baseName[3:])[0] 160 | else: # Windows: 'libfoo.dll' -> 'libfoo.dll' 161 | link += os.path.splitext(baseName)[0] 162 | return link 163 | 164 | 165 | # Locate PySide6 via sys.path package path. 166 | def find_pyside(): 167 | return find_package_path(PYSIDE_MODULE) 168 | 169 | 170 | def find_shiboken_module(): 171 | return find_package_path(SHIBOKEN) 172 | 173 | 174 | def find_shiboken_generator(): 175 | return find_package_path(f"{SHIBOKEN}_generator") 176 | 177 | 178 | def find_package(which_package): 179 | if which_package == Package.SHIBOKEN_MODULE: 180 | return find_shiboken_module() 181 | if which_package == Package.SHIBOKEN_GENERATOR: 182 | return find_shiboken_generator() 183 | if which_package == Package.PYSIDE_MODULE: 184 | return find_pyside() 185 | return None 186 | 187 | 188 | def find_package_path(dir_name): 189 | for p in sys.path: 190 | if 'site-' in p: 191 | package = os.path.join(p, dir_name) 192 | if os.path.exists(package): 193 | return clean_path(os.path.realpath(package)) 194 | return None 195 | 196 | 197 | # Return version as "3.6" 198 | def python_version(): 199 | return str(sys.version_info[0]) + '.' + str(sys.version_info[1]) 200 | 201 | 202 | def get_python_include_path(): 203 | return sysconfig.get_path('include') 204 | 205 | 206 | def python_link_flags_qmake(): 207 | flags = python_link_data() 208 | if sys.platform == 'win32': 209 | libdir = flags['libdir'] 210 | # This will add the "~1" shortcut for directories that 211 | # contain white spaces 212 | # e.g.: "Program Files" to "Progra~1" 213 | for d in libdir.split("\\"): 214 | if " " in d: 215 | libdir = libdir.replace(d, d.split(" ")[0][:-1]+"~1") 216 | lib_flags = flags['lib'] 217 | return f'-L{libdir} -l{lib_flags}' 218 | elif sys.platform == 'darwin': 219 | libdir = flags['libdir'] 220 | lib_flags = flags['lib'] 221 | return f'-L{libdir} -l{lib_flags}' 222 | else: 223 | # Linux and anything else 224 | libdir = flags['libdir'] 225 | lib_flags = flags['lib'] 226 | return f'-L{libdir} -l{lib_flags}' 227 | 228 | 229 | def python_link_flags_cmake(): 230 | flags = python_link_data() 231 | libdir = flags['libdir'] 232 | lib = re.sub(r'.dll$', '.lib', flags['lib']) 233 | return f'{libdir};{lib}' 234 | 235 | 236 | def python_link_data(): 237 | # @TODO Fix to work with static builds of Python 238 | libdir = sysconfig.get_config_var('LIBDIR') 239 | if libdir is None: 240 | libdir = os.path.abspath(os.path.join( 241 | sysconfig.get_config_var('LIBDEST'), "..", "libs")) 242 | version = python_version() 243 | version_no_dots = version.replace('.', '') 244 | 245 | flags = {} 246 | flags['libdir'] = libdir 247 | if sys.platform == 'win32': 248 | suffix = '_d' if is_debug() else '' 249 | flags['lib'] = f'python{version_no_dots}{suffix}' 250 | 251 | elif sys.platform == 'darwin': 252 | flags['lib'] = f'python{version}' 253 | 254 | # Linux and anything else 255 | else: 256 | flags['lib'] = f'python{version}{sys.abiflags}' 257 | 258 | return flags 259 | 260 | 261 | def get_package_include_path(which_package): 262 | package_path = find_package(which_package) 263 | if package_path is None: 264 | return None 265 | 266 | includes = f"{package_path}/include" 267 | 268 | return includes 269 | 270 | 271 | def get_package_qmake_lflags(which_package): 272 | package_path = find_package(which_package) 273 | if package_path is None: 274 | return None 275 | 276 | link = f"-L{package_path}" 277 | glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) 278 | for lib in filter_shared_libraries(glob_result): 279 | link += ' ' 280 | link += link_option(lib) 281 | return link 282 | 283 | 284 | def get_shared_libraries_data(which_package): 285 | package_path = find_package(which_package) 286 | if package_path is None: 287 | return None 288 | 289 | glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) 290 | filtered_libs = filter_shared_libraries(glob_result) 291 | libs = [] 292 | if sys.platform == 'win32': 293 | for lib in filtered_libs: 294 | libs.append(os.path.realpath(lib)) 295 | else: 296 | for lib in filtered_libs: 297 | libs.append(lib) 298 | return libs 299 | 300 | 301 | def get_shared_libraries_qmake(which_package): 302 | libs = get_shared_libraries_data(which_package) 303 | if libs is None: 304 | return None 305 | 306 | if sys.platform == 'win32': 307 | if not libs: 308 | return '' 309 | dlls = '' 310 | for lib in libs: 311 | dll = os.path.splitext(lib)[0] + '.dll' 312 | dlls += dll + ' ' 313 | 314 | return dlls 315 | else: 316 | libs_string = '' 317 | for lib in libs: 318 | libs_string += lib + ' ' 319 | return libs_string 320 | 321 | 322 | def get_shared_libraries_cmake(which_package): 323 | libs = get_shared_libraries_data(which_package) 324 | result = ';'.join(libs) 325 | return result 326 | 327 | 328 | print_all = option == "-a" 329 | for argument, handler, error, _ in options: 330 | if option == argument or print_all: 331 | handler_result = handler() 332 | if handler_result is None: 333 | sys.exit(error) 334 | 335 | line = handler_result 336 | if print_all: 337 | line = f"{argument:<40}: {line}" 338 | print(line) 339 | -------------------------------------------------------------------------------- /scripts/set-qt-constraint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | 4 | def main(): 5 | ap = argparse.ArgumentParser() 6 | ap.add_argument('minver') 7 | args = ap.parse_args() 8 | 9 | p = 'pyproject.toml' 10 | 11 | with open(p, encoding='utf-8') as f: 12 | d = f.read() 13 | 14 | for pkg in ('PySide6-Essentials', 'shiboken6', 'shiboken6_generator'): 15 | d = d.replace(f'"{pkg}"', f'"{pkg}=={args.minver}"') 16 | 17 | with open(p, 'w', encoding='utf-8') as f: 18 | f.write(d) 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import inspect 3 | import os 4 | import sys 5 | from pathlib import Path 6 | from typing import List 7 | 8 | import cmake_build_extension 9 | import setuptools 10 | from wheel.bdist_wheel import bdist_wheel 11 | 12 | import PySide6 13 | import shiboken6 14 | 15 | 16 | if os.getenv('PYSIDE6_QTADS_NO_HARD_PYSIDE_REQUIREMENT'): 17 | install_requirements = [ 18 | 'PySide6-Essentials', 'shiboken6' 19 | ] 20 | else: 21 | install_requirements = [ 22 | f'PySide6-Essentials=={PySide6.__version__}', 23 | f'shiboken6=={shiboken6.__version__}' 24 | ] 25 | 26 | 27 | class bdist_wheel_abi3(bdist_wheel): 28 | def get_tag(self): 29 | python, abi, plat = super().get_tag() 30 | 31 | if python.startswith("cp"): 32 | # on CPython, our wheels are abi3 and compatible back to 3.8 33 | return "cp39", "abi3", plat 34 | 35 | return python, abi, plat 36 | 37 | 38 | 39 | class CustomCMakeExtension(cmake_build_extension.CMakeExtension): 40 | """XXX: Override CMakeExtension to support extra kwargs""" 41 | def __init__( 42 | self, 43 | name: str, 44 | install_prefix: str = "", 45 | disable_editable: bool = False, 46 | write_top_level_init: str = None, 47 | cmake_configure_options: List[str] = (), 48 | source_dir: str = str(Path(".").absolute()), 49 | cmake_build_type: str = "Release", 50 | cmake_component: str = None, 51 | cmake_depends_on: List[str] = (), 52 | expose_binaries: List[str] = (), 53 | cmake_generator: str = "Ninja", 54 | **kwargs 55 | ): 56 | setuptools.Extension.__init__(self, name=name, sources=[], **kwargs) 57 | 58 | if not Path(source_dir).is_absolute(): 59 | source_dir = str(Path(".").absolute() / source_dir) 60 | 61 | if not Path(source_dir).absolute().is_dir(): 62 | raise ValueError(f"Directory '{source_dir}' does not exist") 63 | 64 | self.install_prefix = install_prefix 65 | self.cmake_build_type = cmake_build_type 66 | self.disable_editable = disable_editable 67 | self.write_top_level_init = write_top_level_init 68 | self.cmake_depends_on = cmake_depends_on 69 | self.source_dir = str(Path(source_dir).absolute()) 70 | self.cmake_configure_options = cmake_configure_options 71 | self.cmake_component = cmake_component 72 | self.expose_binaries = expose_binaries 73 | self.cmake_generator = cmake_generator 74 | 75 | 76 | init_py = Path("init.py").read_text() 77 | 78 | 79 | setuptools.setup( 80 | ext_modules=[ 81 | CustomCMakeExtension( 82 | name="PySide6-QtAds", 83 | install_prefix="PySide6QtAds", 84 | write_top_level_init=init_py, 85 | source_dir=str(Path(__file__).parent.absolute()), 86 | cmake_configure_options=[ 87 | "-DBUILD_EXAMPLES:BOOL=OFF", 88 | "-DBUILD_STATIC:BOOL=ON", 89 | "-DADS_VERSION=4.3.0", 90 | f"-DPython3_ROOT_DIR={Path(sys.prefix)}", 91 | f"-DPython_EXECUTABLE={Path(sys.executable)}" 92 | ], 93 | py_limited_api=True 94 | ), 95 | ], 96 | cmdclass=dict( 97 | build_ext=cmake_build_extension.BuildExtension, 98 | bdist_wheel=bdist_wheel_abi3 99 | ), 100 | install_requires=install_requirements, 101 | ) 102 | -------------------------------------------------------------------------------- /src/bindings.h: -------------------------------------------------------------------------------- 1 | #ifndef BINDINGS_H 2 | #define BINDINGS_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #endif // BINDINGS_H 27 | -------------------------------------------------------------------------------- /src/bindings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | //this function is static we need keep ref to default value, to be able to call python virtual functions 120 | static PyObject* _defaultValue = 0; 121 | %CPPSELF.%FUNCTION_NAME(%1); 122 | Py_INCREF(%PYARG_1); 123 | if (_defaultValue) 124 | Py_DECREF(_defaultValue); 125 | 126 | _defaultValue = %PYARG_1; 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | -------------------------------------------------------------------------------- /tests/test_qtads.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import unittest 3 | 4 | from PySide6.QtCore import Qt 5 | from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QMenuBar, QMenu, QStatusBar 6 | 7 | import PySide6QtAds as QtAds 8 | 9 | 10 | class SimpleWindow(QMainWindow): 11 | """ 12 | Creates a main window with a dock manager. 13 | """ 14 | 15 | def __init__(self, parent=None): 16 | super().__init__(parent) 17 | 18 | self.setGeometry(0, 0, 400, 21) 19 | 20 | self.menu_bar = QMenuBar(self) 21 | self.menu_view = QMenu(self.menu_bar) 22 | self.menu_view.setTitle("View") 23 | 24 | self.menu_bar.addAction(self.menu_view.menuAction()) 25 | self.setMenuBar(self.menu_bar) 26 | 27 | self.statusBar = QStatusBar(self) 28 | self.setStatusBar(self.statusBar) 29 | 30 | # Create the dock manager. Because the parent parameter is a QMainWindow 31 | # the dock manager registers itself as the central widget. 32 | self.dock_manager = QtAds.CDockManager(self) 33 | 34 | # Create example content label - this can be any application specific 35 | # widget 36 | dock_inner_widget = QLabel() 37 | dock_inner_widget.setWordWrap(True) 38 | dock_inner_widget.setAlignment(Qt.AlignTop | Qt.AlignLeft) 39 | dock_inner_widget.setText("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. ") 40 | 41 | # Create a dock widget with the title Label 1 and set the created label 42 | # as the dock widget content 43 | dock_widget = QtAds.CDockWidget("Label 1") 44 | dock_widget.setWidget(dock_inner_widget) 45 | 46 | # Add the toggleViewAction of the dock widget to the menu to give 47 | # the user the possibility to show the dock widget if it has been closed 48 | self.menu_view.addAction(dock_widget.toggleViewAction()) 49 | 50 | # Add the dock widget to the top dock widget area 51 | self.dock_manager.addDockWidget(QtAds.TopDockWidgetArea, dock_widget) 52 | 53 | 54 | class TestSimpleWindow(unittest.TestCase): 55 | """ 56 | Basic QtAds tests. 57 | """ 58 | 59 | @classmethod 60 | def setUpClass(cls): 61 | cls.app = QApplication(sys.argv) 62 | 63 | def setUp(self): 64 | self.window = SimpleWindow() 65 | self.window.show() 66 | 67 | def tearDown(self): 68 | self.window.close() 69 | 70 | def test_window_is_visible(self): 71 | assert self.window.isVisible() 72 | 73 | 74 | if __name__ == '__main__': 75 | unittest.main() 76 | --------------------------------------------------------------------------------