diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b5867f44..a60690c4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,21 +1,21 @@ ---- -version: 2 -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - # Match pre-commit.ci automated update time. - interval: "weekly" - time: "18:00" - day: "monday" - ignore: - - dependency-name: "python" - commit-message: - prefix: ⬆ - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - commit-message: - prefix: ⬆ +--- +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + # Match pre-commit.ci automated update time. + interval: "weekly" + time: "18:00" + day: "monday" + ignore: + - dependency-name: "python" + commit-message: + prefix: ⬆ + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: ⬆ diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml index 01897cc4..74cb6f53 100644 --- a/.github/workflows/code-checks.yml +++ b/.github/workflows/code-checks.yml @@ -1,48 +1,48 @@ ---- -name: Code Checks - -on: # yamllint disable-line rule:truthy - pull_request: - push: - branches: [main] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: '3.11' - poetry-install--only: dev - poetry-install--no-root: 'true' - poetry-virtualenvs-in-project: 'true' - # Hack to make pre-commit/action to use virtual env pre-commit. - # pre-commit/action installs pre-commit using following command: - # `python -m pip install pre-commit` - # But `python` doesn't run virtual env interpreter when PATH is default. - # Override PATH to use virtual env interpreter. - # This change force run the virtual env pre-commit. - - name: Set Poetry virtual env path to PATH - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - - uses: pre-commit/action@v3.0.0 - - uses: pre-commit-ci/lite-action@v1.0.0 - if: always() - - pyright: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: '3.11' - poetry-install--only: main,test - poetry-virtualenvs-in-project: 'true' - - run: poetry run pip install PyQt6 - # Pyright uses dependencies of the interpreter run with the `python`. - # But `python` doesn't run virtual env interpreter when PATH is default. - # Override PATH to use virtual env interpreter. - # This change makes pyright use dependencies of virtual env interpreter. - - name: Set Poetry virtual env path to PATH - run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH - - uses: jakebailey/pyright-action@v1 +--- +name: Code Checks + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: '3.11' + poetry-install--only: dev + poetry-install--no-root: 'true' + poetry-virtualenvs-in-project: 'true' + # Hack to make pre-commit/action to use virtual env pre-commit. + # pre-commit/action installs pre-commit using following command: + # `python -m pip install pre-commit` + # But `python` doesn't run virtual env interpreter when PATH is default. + # Override PATH to use virtual env interpreter. + # This change force run the virtual env pre-commit. + - name: Set Poetry virtual env path to PATH + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - uses: pre-commit/action@v3.0.0 + - uses: pre-commit-ci/lite-action@v1.0.0 + if: always() + + pyright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: '3.11' + poetry-install--only: main,test + poetry-virtualenvs-in-project: 'true' + - run: poetry run pip install PyQt6 + # Pyright uses dependencies of the interpreter run with the `python`. + # But `python` doesn't run virtual env interpreter when PATH is default. + # Override PATH to use virtual env interpreter. + # This change makes pyright use dependencies of virtual env interpreter. + - name: Set Poetry virtual env path to PATH + run: echo ${{ github.workspace }}/.venv/bin >> $GITHUB_PATH + - uses: jakebailey/pyright-action@v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 641fbd8d..4492777e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,65 +1,65 @@ ---- -name: Release - -on: # yamllint disable-line rule:truthy - push: - tags: - - 'v*.*.*' - -jobs: - Release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: tlambert03/setup-qt-libs@v1 - - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - uses: snok/install-poetry@v1 - - - name: Get tag - id: tag - run: echo version=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT - - - name: Build project for distribution - run: poetry build - - - name: Get wheel file path - id: wheel-file-path - run: echo path=$(ls dist/*.whl) >> $GITHUB_OUTPUT - - - - name: Test build package - run: | - python -m pip install ${{ steps.wheel-file-path.outputs.path }} - python tools/check_build_package.py \ - --tag-version ${{ steps.tag.outputs.version }} - python -m pip uninstall -y pyqtdarktheme - - - name: Publish to testPyPI - run: | - poetry config repositories.testpypi https://test.pypi.org/legacy/ - poetry config pypi-token.testpypi ${{ secrets.TEST_PYPI_TOKEN }} - poetry publish -r testpypi - - - name: Waite until package is published - run: sleep 120 - - - name: Test testPyPI package - run: | - pip install -i https://test.pypi.org/simple/ pyqtdarktheme - python tools/check_build_package.py \ - --tag-version ${{ steps.tag.outputs.version }} - - - name: Create Release - uses: ncipollo/release-action@v1 - with: - artifacts: "dist/*" - token: ${{ secrets.GITHUB_TOKEN }} - draft: false - - - name: Publish to PyPI - run: | - poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} - poetry publish +--- +name: Release + +on: # yamllint disable-line rule:truthy + push: + tags: + - 'v*.*.*' + +jobs: + Release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: tlambert03/setup-qt-libs@v1 + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - uses: snok/install-poetry@v1 + + - name: Get tag + id: tag + run: echo version=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT + + - name: Build project for distribution + run: poetry build + + - name: Get wheel file path + id: wheel-file-path + run: echo path=$(ls dist/*.whl) >> $GITHUB_OUTPUT + + + - name: Test build package + run: | + python -m pip install ${{ steps.wheel-file-path.outputs.path }} + python tools/check_build_package.py \ + --tag-version ${{ steps.tag.outputs.version }} + python -m pip uninstall -y pyqtdarktheme + + - name: Publish to testPyPI + run: | + poetry config repositories.testpypi https://test.pypi.org/legacy/ + poetry config pypi-token.testpypi ${{ secrets.TEST_PYPI_TOKEN }} + poetry publish -r testpypi + + - name: Waite until package is published + run: sleep 120 + + - name: Test testPyPI package + run: | + pip install -i https://test.pypi.org/simple/ pyqtdarktheme + python tools/check_build_package.py \ + --tag-version ${{ steps.tag.outputs.version }} + + - name: Create Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + draft: false + + - name: Publish to PyPI + run: | + poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }} + poetry publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b421fbbe..8deabc67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,105 +1,105 @@ ---- -name: tests - -on: # yamllint disable-line rule:truthy - pull_request: - push: - branches: [main] - -jobs: - pytest: - strategy: - fail-fast: false - matrix: # Test each python and Qt-lib only once on each os. - os: [ubuntu-latest, macOS-latest, windows-latest] - py: ['3.7', '3.8', '3.9', '3.10', '3.11'] - include: - - {py: '3.7', qt: PyQt5~=5.15.0} - - {py: '3.8', qt: PySide2~=5.15.0} - - {py: '3.9', qt: PyQt6} - - {py: '3.10', qt: PySide6-Essentials} - - {py: '3.11', qt: PyQt6} - runs-on: ${{ matrix.os }} - name: pytest (${{ matrix.os }}, ${{ matrix.py }}, ${{ matrix.qt }}) - - steps: - - uses: actions/checkout@v3 - - uses: tlambert03/setup-qt-libs@v1 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: ${{ matrix.py }} - additional-dependency-cache-key: ${{ matrix.qt }} - poetry-install--only: github-actions,test,main - - run: poetry run pip install -U ${{ matrix.qt }} - - - run: >- - poetry run pytest tests --color=yes - --cov=qdarktheme - --cov=tests - --cov-report=term-missing - --cov-report=xml - - - uses: codecov/codecov-action@v3 - with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} - - - name: Capture WidgetGallery img - run: >- - poetry run python ./tools/capture.py - --name ${{runner.os}}-py${{matrix.py}}-${{matrix.qt}} - - - uses: actions/upload-artifact@v3 - with: - path: ./*.png - - pytest-without-qt: - strategy: - fail-fast: false - matrix: # Python version does not affect tests without Qt. - os: [ubuntu-latest, macOS-latest, windows-latest] - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v3 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: '3.11' - poetry-install--only: github-actions,test,main - - - run: >- - poetry run pytest --color=yes - --cov=qdarktheme - --cov=tests - --cov-report=term-missing - --cov-report=xml - --ignore=tests/test_widget_gallery.py - --ignore=tests/test_qdarktheme_with_qt.py - -p no:pytest-qt - - - uses: codecov/codecov-action@v3 - with: - fail_ci_if_error: true - token: ${{ secrets.CODECOV_TOKEN }} - - PyInstaller: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: '3.11' - poetry-install--only: main - additional-dependency-cache-key: PyInstaller - - run: poetry run pip install -U PyInstaller - - - name: Make test file - run: | - cat <<'EOL' >> test.py - import qdarktheme - qdarktheme.load_stylesheet('auto') - EOL - cat -n test.py - - - run: poetry run python -m PyInstaller test.py -n test_app -F - - run: ./dist/test_app +--- +name: tests + +on: # yamllint disable-line rule:truthy + pull_request: + push: + branches: [main] + +jobs: + pytest: + strategy: + fail-fast: false + matrix: # Test each python and Qt-lib only once on each os. + os: [ubuntu-latest, macOS-latest, windows-latest] + py: ['3.7', '3.8', '3.9', '3.10', '3.11'] + include: + - {py: '3.7', qt: PyQt5~=5.15.0} + - {py: '3.8', qt: PySide2~=5.15.0} + - {py: '3.9', qt: PyQt6} + - {py: '3.10', qt: PySide6-Essentials} + - {py: '3.11', qt: PyQt6} + runs-on: ${{ matrix.os }} + name: pytest (${{ matrix.os }}, ${{ matrix.py }}, ${{ matrix.qt }}) + + steps: + - uses: actions/checkout@v3 + - uses: tlambert03/setup-qt-libs@v1 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: ${{ matrix.py }} + additional-dependency-cache-key: ${{ matrix.qt }} + poetry-install--only: github-actions,test,main + - run: poetry run pip install -U ${{ matrix.qt }} + + - run: >- + poetry run pytest tests --color=yes + --cov=qdarktheme + --cov=tests + --cov-report=term-missing + --cov-report=xml + + - uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Capture WidgetGallery img + run: >- + poetry run python ./tools/capture.py + --name ${{runner.os}}-py${{matrix.py}}-${{matrix.qt}} + + - uses: actions/upload-artifact@v3 + with: + path: ./*.png + + pytest-without-qt: + strategy: + fail-fast: false + matrix: # Python version does not affect tests without Qt. + os: [ubuntu-latest, macOS-latest, windows-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: '3.11' + poetry-install--only: github-actions,test,main + + - run: >- + poetry run pytest --color=yes + --cov=qdarktheme + --cov=tests + --cov-report=term-missing + --cov-report=xml + --ignore=tests/test_widget_gallery.py + --ignore=tests/test_qdarktheme_with_qt.py + -p no:pytest-qt + + - uses: codecov/codecov-action@v3 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + + PyInstaller: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: '3.11' + poetry-install--only: main + additional-dependency-cache-key: PyInstaller + - run: poetry run pip install -U PyInstaller + + - name: Make test file + run: | + cat <<'EOL' >> test.py + import qdarktheme + qdarktheme.load_stylesheet('auto') + EOL + cat -n test.py + + - run: poetry run python -m PyInstaller test.py -n test_app -F + - run: ./dist/test_app diff --git a/.github/workflows/update-icon.yml b/.github/workflows/update-icon.yml index e0e56289..3eff0d1d 100644 --- a/.github/workflows/update-icon.yml +++ b/.github/workflows/update-icon.yml @@ -1,29 +1,29 @@ ---- -name: Automated icon update - -on: # yamllint disable-line rule:truthy - schedule: - - cron: '23 1 * * *' # Runs at 01:23 UTC every day - workflow_dispatch: - -jobs: - update-material-design-icons: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: 5yutan5/setup-poetry-env@v1.1.0 - with: - python-version: '3.11' - poetry-install--only: main - - name: Download latest svg and update style - run: | - poetry run python -c \ - "from tools import material_icons; material_icons.update_icons()" - poetry run python -m tools.build_styles - - uses: peter-evans/create-pull-request@v4 - with: - token: ${{ secrets.PAT }} - commit-message: Update material design icons - title: Automated update material design icons - body: This is an auto-generated PR with icon updates. - branch: auto-update-svg +--- +name: Automated icon update + +on: # yamllint disable-line rule:truthy + schedule: + - cron: '23 1 * * *' # Runs at 01:23 UTC every day + workflow_dispatch: + +jobs: + update-material-design-icons: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: 5yutan5/setup-poetry-env@v1.1.0 + with: + python-version: '3.11' + poetry-install--only: main + - name: Download latest svg and update style + run: | + poetry run python -c \ + "from tools import material_icons; material_icons.update_icons()" + poetry run python -m tools.build_styles + - uses: peter-evans/create-pull-request@v4 + with: + token: ${{ secrets.PAT }} + commit-message: Update material design icons + title: Automated update material design icons + body: This is an auto-generated PR with icon updates. + branch: auto-update-svg diff --git a/.gitignore b/.gitignore index 84d23fef..1dba9f44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,155 +1,155 @@ -# This is a fork of Github/ignore repo. -# link: https://github.com/github/gitignore/blob/master/Python.gitignore -# License: CC0-1.0 License | https://github.com/github/gitignore/blob/master/LICENSE - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg -MANIFEST - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py -db.sqlite3 -db.sqlite3-journal - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ -docs/source/_build/ - -# PyBuilder -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ -.dmypy.json -dmypy.json - -# Pyre type checker -.pyre/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# VSCode -*.code-workspace - -# Pycharm -.idea - -# System information folder for mac -*.DS_Store - -# PyQtDarkTheme -docs/source/reference/theme_color.rst +# This is a fork of Github/ignore repo. +# link: https://github.com/github/gitignore/blob/master/Python.gitignore +# License: CC0-1.0 License | https://github.com/github/gitignore/blob/master/LICENSE + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/source/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# VSCode +*.code-workspace + +# Pycharm +.idea + +# System information folder for mac +*.DS_Store + +# PyQtDarkTheme +docs/source/reference/theme_color.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ce0c12d..5142fa69 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,107 +1,107 @@ ---- -ci: - autofix_prs: false -repos: - - repo: local - hooks: - - id: build-styles - name: Build styles - entry: python -m tools.build_styles - language: python - pass_filenames: false - - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 - hooks: - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] - - id: end-of-file-fixer - exclude: \.svg$ - - id: pretty-format-json - args: ["--autofix", "--no-sort-keys"] - - id: check-json - - id: check-toml - - id: check-yaml - - id: name-tests-test - args: [--pytest-test-first] - - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.32.2 - hooks: - - id: markdownlint - args: [--disable, MD013] - - - repo: https://github.com/adrienverge/yamllint - rev: v1.28.0 - hooks: - - id: yamllint - - - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.19.2 - hooks: - - id: check-jsonschema - name: Validate color map json files with json schema - files: ^style/colors/themes - exclude: ^style/colors/themes/validate.json$ - args: ["--schemafile", "style/colors/themes/validate.json"] - - - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 - hooks: - - id: pyupgrade - args: [--py37-plus] - - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - - - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 - hooks: - - id: blacken-docs - additional_dependencies: [black] - - - repo: https://github.com/MarcoGorelli/absolufy-imports - rev: v0.3.1 - hooks: - - id: absolufy-imports - - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 - hooks: - - id: flake8 - additional_dependencies: &flake8_dependencies - - flake8-pyproject - - flake8-pie - - flake8-print - - flake8-return - - flake8-simplify - - flake8-bugbear - - flake8-docstrings - - flake8-comprehensions - - flake8-eradicate - - flake8-rst-docstrings - - flake8-pytest-style - - pep8-naming - - - repo: https://github.com/asottile/yesqa - rev: v1.4.0 - hooks: - - id: yesqa - additional_dependencies: *flake8_dependencies - - - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 - hooks: - - id: rst-backticks - - id: rst-directive-colons - - id: rst-inline-touching-normal - - id: python-no-eval - - id: python-no-log-warn - - id: python-use-type-annotations +--- +ci: + autofix_prs: false +repos: + - repo: local + hooks: + - id: build-styles + name: Build styles + entry: python -m tools.build_styles + language: python + pass_filenames: false + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: end-of-file-fixer + exclude: \.svg$ + - id: pretty-format-json + args: ["--autofix", "--no-sort-keys"] + - id: check-json + - id: check-toml + - id: check-yaml + - id: name-tests-test + args: [--pytest-test-first] + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.32.2 + hooks: + - id: markdownlint + args: [--disable, MD013] + + - repo: https://github.com/adrienverge/yamllint + rev: v1.28.0 + hooks: + - id: yamllint + + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.19.2 + hooks: + - id: check-jsonschema + name: Validate color map json files with json schema + files: ^style/colors/themes + exclude: ^style/colors/themes/validate.json$ + args: ["--schemafile", "style/colors/themes/validate.json"] + + - repo: https://github.com/asottile/pyupgrade + rev: v3.2.2 + hooks: + - id: pyupgrade + args: [--py37-plus] + + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + + - repo: https://github.com/asottile/blacken-docs + rev: v1.12.1 + hooks: + - id: blacken-docs + additional_dependencies: [black] + + - repo: https://github.com/MarcoGorelli/absolufy-imports + rev: v0.3.1 + hooks: + - id: absolufy-imports + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + + - repo: https://github.com/PyCQA/flake8 + rev: 5.0.4 + hooks: + - id: flake8 + additional_dependencies: &flake8_dependencies + - flake8-pyproject + - flake8-pie + - flake8-print + - flake8-return + - flake8-simplify + - flake8-bugbear + - flake8-docstrings + - flake8-comprehensions + - flake8-eradicate + - flake8-rst-docstrings + - flake8-pytest-style + - pep8-naming + + - repo: https://github.com/asottile/yesqa + rev: v1.4.0 + hooks: + - id: yesqa + additional_dependencies: *flake8_dependencies + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.9.0 + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + - id: python-no-eval + - id: python-no-log-warn + - id: python-use-type-annotations diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 21fb22c0..c200340e 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,20 +1,20 @@ ---- -version: 2 - -build: - os: "ubuntu-22.04" - tools: - python: "3.10" - jobs: - post_create_environment: - - asdf plugin add poetry - - asdf install poetry latest - - asdf global poetry latest - - poetry export --only docs -o requirements.txt - - poetry install --only main - - poetry run python -m tools.build_theme_color_doc - -python: - install: - - path: . - - requirements: requirements.txt +--- +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "3.10" + jobs: + post_create_environment: + - asdf plugin add poetry + - asdf install poetry latest + - asdf global poetry latest + - poetry export --only docs -o requirements.txt + - poetry install --only main + - poetry run python -m tools.build_theme_color_doc + +python: + install: + - path: . + - requirements: requirements.txt diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3e747de6..cd9e5578 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,10 @@ -{ - "recommendations": [ - "ms-python.python", - "streetsidesoftware.code-spell-checker", - "davidanson.vscode-markdownlint", - "tamasfe.even-better-toml", - "lextudio.restructuredtext", - "trond-snekvik.simple-rst" - ] -} +{ + "recommendations": [ + "ms-python.python", + "streetsidesoftware.code-spell-checker", + "davidanson.vscode-markdownlint", + "tamasfe.even-better-toml", + "lextudio.restructuredtext", + "trond-snekvik.simple-rst" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index e919348d..dc0c730b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,77 +1,77 @@ -{ - "json.schemas": [ - { - "fileMatch": [ - "/style/colors/themes/*" - ], - "url": "/style/colors/themes/validate.json" - } - ], - "python.testing.pytestEnabled": true, - "files.exclude": { - "**/.git": true, - "**/.pytest_cache": true, - "**/__pycache__": true, - "**/.coverage": true, - "**/coverage.xml": true - }, - "cSpell.words": [ - "AARRGGBB", - "absolufy", - "alloc", - "argb", - "autofix", - "Autorelease", - "backticks", - "cmpfiles", - "codecov", - "darkdetect", - "docstrings", - "Duquesnoy", - "filecmp", - "HIGHDPI", - "isort", - "jakebailey", - "jsonschema", - "kargs", - "libobjc", - "linebreak", - "markdownlint", - "maxdepth", - "ncipollo", - "objc", - "Pixmap", - "Pixmaps", - "pydata", - "pypi", - "pyproject", - "pyqt", - "pyqtdarktheme", - "pyqtgraph", - "pyright", - "pyside", - "pytest", - "pytestqt", - "pyupgrade", - "qapp", - "qdarktheme", - "QLCD", - "qproperty", - "qtbot", - "qtpy", - "RRGGBB", - "RRGGBBAA", - "schemafile", - "snok", - "stylesheet", - "testpypi", - "venv", - "virtualenvs", - "yesqa" - ], - "python.linting.flake8Enabled": true, - "python.formatting.provider": "black", - "esbonio.sphinx.confDir": "${workspaceFolder}/docs/source", - "restructuredtext.linter.rstcheck.executablePath": "rstcheck", - "svg.preview.background": "dark-transparent" -} +{ + "json.schemas": [ + { + "fileMatch": [ + "/style/colors/themes/*" + ], + "url": "/style/colors/themes/validate.json" + } + ], + "python.testing.pytestEnabled": true, + "files.exclude": { + "**/.git": true, + "**/.pytest_cache": true, + "**/__pycache__": true, + "**/.coverage": true, + "**/coverage.xml": true + }, + "cSpell.words": [ + "AARRGGBB", + "absolufy", + "alloc", + "argb", + "autofix", + "Autorelease", + "backticks", + "cmpfiles", + "codecov", + "darkdetect", + "docstrings", + "Duquesnoy", + "filecmp", + "HIGHDPI", + "isort", + "jakebailey", + "jsonschema", + "kargs", + "libobjc", + "linebreak", + "markdownlint", + "maxdepth", + "ncipollo", + "objc", + "Pixmap", + "Pixmaps", + "pydata", + "pypi", + "pyproject", + "pyqt", + "pyqtdarktheme", + "pyqtgraph", + "pyright", + "pyside", + "pytest", + "pytestqt", + "pyupgrade", + "qapp", + "qdarktheme", + "QLCD", + "qproperty", + "qtbot", + "qtpy", + "RRGGBB", + "RRGGBBAA", + "schemafile", + "snok", + "stylesheet", + "testpypi", + "venv", + "virtualenvs", + "yesqa" + ], + "python.linting.flake8Enabled": true, + "python.formatting.provider": "black", + "esbonio.sphinx.confDir": "${workspaceFolder}/docs/source", + "restructuredtext.linter.rstcheck.executablePath": "rstcheck", + "svg.preview.background": "dark-transparent" +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index aa8869fa..e6aea28e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,12 +1,12 @@ -{ - "version": "2.0.0", - "command": "bash", - "tasks": [ - { - "label": "Check style", - "type": "shell", - "command": "poetry run python -m tools.build_styles && poetry run python -m qdarktheme.widget_gallery", - "problemMatcher": [] - } - ] -} +{ + "version": "2.0.0", + "command": "bash", + "tasks": [ + { + "label": "Check style", + "type": "shell", + "command": "poetry run python -m tools.build_styles && poetry run python -m qdarktheme.widget_gallery", + "problemMatcher": [] + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e160ff70..a1157807 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,11 @@ -# Contributing to PyQtDarkTheme - -Welcome, and thank you for your interest in contributing to PyQtDarkTheme! - -All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. - -## Creating develop environment - -You can get started by reading this: - -- [Contributing Guide](https://pyqtdarktheme.readthedocs.io/en/latest/contributing.html) +# Contributing to PyQtDarkTheme + +Welcome, and thank you for your interest in contributing to PyQtDarkTheme! + +All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. + +## Creating develop environment + +You can get started by reading this: + +- [Contributing Guide](https://pyqtdarktheme.readthedocs.io/en/latest/contributing.html) diff --git a/LICENSE.txt b/LICENSE.txt index 3cdc96d2..4b59a103 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2021-2022 Yunosuke Ohsugi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2021-2022 Yunosuke Ohsugi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 2777aa8b..0051c9c9 100644 --- a/README.md +++ b/README.md @@ -1,162 +1,165 @@ -# PyQtDarkTheme - -PyQtDarkTheme applies a flat dark theme to QtWidgets application. There's a light theme too. Color balanced from the dark theme for easy viewing in daylight. - -Check out the [complete documentation](https://pyqtdarktheme.readthedocs.io). - -**Project status** -[![PyPI Latest Release](https://img.shields.io/pypi/v/pyqtdarktheme.svg?color=orange)](https://pypi.org/project/pyqtdarktheme/) -[![Python Versions](https://img.shields.io/pypi/pyversions/pyqtdarktheme.svg?color=blue)](https://www.python.org/downloads/) -[![Qt Versions](https://img.shields.io/badge/Qt-5%20|%206-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python) -[![License](https://img.shields.io/github/license/5yutan5/PyQtDarkTheme.svg?color=green)](https://github.com/5yutan5/PyQtDarkTheme/blob/main/LICENSE.txt/) - -**Tests** -[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/5yutan5/PyQtDarkTheme/main.svg)](https://results.pre-commit.ci/latest/github/5yutan5/PyQtDarkTheme/main) -[![codecov](https://codecov.io/gh/5yutan5/PyQtDarkTheme/branch/main/graph/badge.svg?token=RTS8O0V6SF)](https://codecov.io/gh/5yutan5/PyQtDarkTheme) -[![Documentation Status](https://readthedocs.org/projects/pyqtdarktheme/badge/?version=latest)](https://pyqtdarktheme.readthedocs.io/en/latest/?badge=latest) - -## Features - -- A flat dark and light theme -- Support PySide and PyQt -- Sync with OS's theme and accent (Mac, Windows, Linux) -- Resolve the style differences between Qt versions -- Provide dark/light theme QPalette -- Override Qt old standard icons - -## Themes - -### Dark Theme - -![widget_gallery_dark_theme](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/widget_gallery_dark.png) - -### Light Theme - -![widget_gallery_light_them](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/widget_gallery_light.png) - -## Requirements - -- [Python 3.7+](https://www.python.org/downloads/) -- Qt 5.15+ -- PySide6, PyQt6, PyQt5 or PySide2 - -## Installation Method - -- Last released version - - ```plaintext - pip install pyqtdarktheme - ``` - -- Latest development version - - ```plaintext - pip install git+https://github.com/5yutan5/PyQtDarkTheme.git@main - ``` - -## Usage - -```Python -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Apply the complete dark theme to your Qt App. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() -``` - -Further information can be found in our docs: - -- [Usage Guide](https://pyqtdarktheme.readthedocs.io/en/latest/how_to_use.html) - -### Enable HiDPI - -```Python -# enable_hi_dpi() must be called before the instantiation of QApplication. -qdarktheme.enable_hi_dpi() -app = QApplication(sys.argv) -qdarktheme.setup_theme() -``` - -For Qt6 bindings, HiDPI “just works” without using this function. - -### Light theme - -```Python -qdarktheme.setup_theme("light") -``` - -### Sync with OS's theme and accent - -```Python -qdarktheme.setup_theme("auto") -``` - -![sync with os theme](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/sync_with_os_theme.gif) - -On macOS, qdarktheme also syncs with accent colors. -![sync with os accent](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/sync_with_os_accent.gif) - -### Customizing colors - -You can customize the theme color. - -```python -# Customize accent color. -qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) -``` - -For a list of all customizable colors, see the Theme Color Reference: - -- [Theme Color](https://pyqtdarktheme.readthedocs.io/en/latest/reference/theme_color.html) - -### Sharp frame - -You can change the corner style. - -```python -# Default is "rounded". -stylesheet = qdarktheme.setup_theme(corner_shape="sharp") -``` - -### QPalette and stylesheet - -You can also only load QPalette and stylesheet. `qdarktheme.setup_theme` uses the following functions internally. - -```Python -palette = qdarktheme.load_palette(theme="dark") -stylesheet = qdarktheme.load_stylesheet(theme="dark") -``` - -## Example - -To check all Qt widgets, run: - -```plaintext -python -m qdarktheme.widget_gallery -``` - -## License - -The svg files for the PyQtDarkTheme are derived [Material design icons](https://fonts.google.com/icons)(Apache License Version 2.0). Qt stylesheets are originally fork of [QDarkStyleSheet](https://github.com/ColinDuquesnoy/QDarkStyleSheet)(MIT License). Other files are covered by PyQtDarkTheme's MIT license. The accent detector(qdarktheme/_os_appearance/_accent/_mac_detect) is inspired by [darkdetect](https://github.com/albertosottile/darkdetect)(3-clause BSD License). - -## Contributing - -All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. You can get started by reading this: - -- [Contributing Guide](https://pyqtdarktheme.readthedocs.io/en/latest/contributing.html) - -## Change log - -See [Releases](https://github.com/5yutan5/PyQtDarkTheme/releases). +# PyQtDarkTheme + +PyQtDarkTheme applies a flat dark theme to QtWidgets application. There's a light theme too. Color balanced from the dark theme for easy viewing in daylight. + +Check out the [complete documentation](https://pyqtdarktheme.readthedocs.io). + +**Project status** +[![PyPI Latest Release](https://img.shields.io/pypi/v/pyqtdarktheme.svg?color=orange)](https://pypi.org/project/pyqtdarktheme/) +[![Python Versions](https://img.shields.io/pypi/pyversions/pyqtdarktheme.svg?color=blue)](https://www.python.org/downloads/) +[![Qt Versions](https://img.shields.io/badge/Qt-5%20|%206-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python) +[![License](https://img.shields.io/github/license/5yutan5/PyQtDarkTheme.svg?color=green)](https://github.com/5yutan5/PyQtDarkTheme/blob/main/LICENSE.txt/) + +**Tests** +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/5yutan5/PyQtDarkTheme/main.svg)](https://results.pre-commit.ci/latest/github/5yutan5/PyQtDarkTheme/main) +[![codecov](https://codecov.io/gh/5yutan5/PyQtDarkTheme/branch/main/graph/badge.svg?token=RTS8O0V6SF)](https://codecov.io/gh/5yutan5/PyQtDarkTheme) +[![Documentation Status](https://readthedocs.org/projects/pyqtdarktheme/badge/?version=latest)](https://pyqtdarktheme.readthedocs.io/en/latest/?badge=latest) + +## Features + +- A flat dark and light theme +- Support PySide and PyQt +- Sync with OS's theme and accent (Mac, Windows, Linux) +- Resolve the style differences between Qt versions +- Provide dark/light theme QPalette +- Override Qt old standard icons + +## Themes + +### Dark Theme + +![widget_gallery_dark_theme](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/widget_gallery_dark.png) + +### Light Theme + +![widget_gallery_light_them](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/widget_gallery_light.png) + +## Requirements + +- [Python 3.7+](https://www.python.org/downloads/) +- Qt 5.15+ +- PySide6, PyQt6, PyQt5 or PySide2 + +## Installation Method + +- Last released version + + ```plaintext + pip install pyqtdarktheme + ``` + +- Latest development version + + ```plaintext + pip install git+https://github.com/5yutan5/PyQtDarkTheme.git@main + ``` + +## Usage + +```Python +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Apply the complete dark theme to your Qt App. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() +``` + +Further information can be found in our docs: + +- [Usage Guide](https://pyqtdarktheme.readthedocs.io/en/latest/how_to_use.html) + +### Enable HiDPI + +```Python +# enable_hi_dpi() must be called before the instantiation of QApplication. +qdarktheme.enable_hi_dpi() +app = QApplication(sys.argv) +qdarktheme.setup_theme() +``` + +For Qt6 bindings, HiDPI “just works” without using this function. + +### Light theme + +```Python +qdarktheme.setup_theme("light") +``` + +### Sync with OS's theme and accent + +```Python +qdarktheme.setup_theme("auto") +``` + +![sync with os theme](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/sync_with_os_theme.gif) + +On macOS, qdarktheme also syncs with accent colors. +![sync with os accent](https://raw.githubusercontent.com/5yutan5/PyQtDarkTheme/main/images/sync_with_os_accent.gif) + +it now supports also Windows 11 +![sync with os accent](https://github.com/0ssamaak0/PyQtDarkTheme/blob/main/images/sync_with_os_accent_windows.gif?raw=true) + +### Customizing colors + +You can customize the theme color. + +```python +# Customize accent color. +qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) +``` + +For a list of all customizable colors, see the Theme Color Reference: + +- [Theme Color](https://pyqtdarktheme.readthedocs.io/en/latest/reference/theme_color.html) + +### Sharp frame + +You can change the corner style. + +```python +# Default is "rounded". +stylesheet = qdarktheme.setup_theme(corner_shape="sharp") +``` + +### QPalette and stylesheet + +You can also only load QPalette and stylesheet. `qdarktheme.setup_theme` uses the following functions internally. + +```Python +palette = qdarktheme.load_palette(theme="dark") +stylesheet = qdarktheme.load_stylesheet(theme="dark") +``` + +## Example + +To check all Qt widgets, run: + +```plaintext +python -m qdarktheme.widget_gallery +``` + +## License + +The svg files for the PyQtDarkTheme are derived [Material design icons](https://fonts.google.com/icons)(Apache License Version 2.0). Qt stylesheets are originally fork of [QDarkStyleSheet](https://github.com/ColinDuquesnoy/QDarkStyleSheet)(MIT License). Other files are covered by PyQtDarkTheme's MIT license. The accent detector(qdarktheme/_os_appearance/_accent/_mac_detect) is inspired by [darkdetect](https://github.com/albertosottile/darkdetect)(3-clause BSD License). + +## Contributing + +All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. You can get started by reading this: + +- [Contributing Guide](https://pyqtdarktheme.readthedocs.io/en/latest/contributing.html) + +## Change log + +See [Releases](https://github.com/5yutan5/PyQtDarkTheme/releases). diff --git a/docs/Makefile b/docs/Makefile index d0c3cbf1..26b94228 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -1,20 +1,20 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0758145f..c82b1a57 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,60 +1,60 @@ -"""Document configuration.""" -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -import os -import sys -import time -from datetime import datetime -from importlib.metadata import metadata as package_metadata - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -sys.path.insert(0, os.path.abspath("../..")) - - -# -- Project information ----------------------------------------------------- -project = "PyQtDarkTheme" -copyright = f"2021-2022, {package_metadata(project)['author']}" -author = package_metadata(project)["author"] - -now = datetime.utcfromtimestamp(int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))) - -version = package_metadata(project)["version"] -release = version - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autosectionlabel", - "sphinx.ext.viewcode", - "sphinx.ext.napoleon", - "sphinx.ext.todo", - "sphinx_design", - "sphinx_copybutton", -] - -autosectionlabel_prefix_document = True - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_build", "Thumbs.db", ".DS_Store"] -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "pydata_sphinx_theme" - -html_theme_options = { - "github_url": package_metadata(project)["home-page"], -} +"""Document configuration.""" +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +import os +import sys +import time +from datetime import datetime +from importlib.metadata import metadata as package_metadata + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +sys.path.insert(0, os.path.abspath("../..")) + + +# -- Project information ----------------------------------------------------- +project = "PyQtDarkTheme" +copyright = f"2021-2022, {package_metadata(project)['author']}" +author = package_metadata(project)["author"] + +now = datetime.utcfromtimestamp(int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))) + +version = package_metadata(project)["version"] +release = version + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autosectionlabel", + "sphinx.ext.viewcode", + "sphinx.ext.napoleon", + "sphinx.ext.todo", + "sphinx_design", + "sphinx_copybutton", +] + +autosectionlabel_prefix_document = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_build", "Thumbs.db", ".DS_Store"] +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "pydata_sphinx_theme" + +html_theme_options = { + "github_url": package_metadata(project)["home-page"], +} diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index dc83f7f8..baa84bdd 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -1,45 +1,45 @@ -Contributing Guide -================== - -All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. - -Local development ------------------ - -These are the basic steps needed to start developing on PyQtDarkTheme. - -#. Clone PyQtDarkTheme - You will first need to clone the repository using git and place yourself in its directory: - - .. code-block:: bash - - $ git@github.com:5yutan5/PyQtDarkTheme.git - $ cd PyQtDarkTheme -#. Install Poetry - You will need Poetry to start contributing on the PyQtDarkTheme codebase. Refer to the `Poetry documentation `__ to start using Poetry. -#. Create a virtual environment - Now, you will need to install the required dependency for PyQtDarkTheme with Poetry and install Qt bindings(PySide or PyQt) with pip. - - .. code-block:: bash - - $ poetry install - $ poetry run pip install PySide6 - -#. Run Pytest - You need to be sure that the current tests are passing on your machine: - - .. code-block:: bash - - $ poetry run pytest tests -#. Setup pre-commit - To make sure that you don't accidentally commit code that does not follow the coding style, you can install a pre-commit hook that will check that everything is in order: - - .. code-block:: bash - - $ poetry run pre-commit install -#. Check Qt theme - You can check dark/light theme with built-in app. - - .. code-block:: bash - - $ poetry run python -m qdarktheme.widget_gallery +Contributing Guide +================== + +All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome. + +Local development +----------------- + +These are the basic steps needed to start developing on PyQtDarkTheme. + +#. Clone PyQtDarkTheme + You will first need to clone the repository using git and place yourself in its directory: + + .. code-block:: bash + + $ git@github.com:5yutan5/PyQtDarkTheme.git + $ cd PyQtDarkTheme +#. Install Poetry + You will need Poetry to start contributing on the PyQtDarkTheme codebase. Refer to the `Poetry documentation `__ to start using Poetry. +#. Create a virtual environment + Now, you will need to install the required dependency for PyQtDarkTheme with Poetry and install Qt bindings(PySide or PyQt) with pip. + + .. code-block:: bash + + $ poetry install + $ poetry run pip install PySide6 + +#. Run Pytest + You need to be sure that the current tests are passing on your machine: + + .. code-block:: bash + + $ poetry run pytest tests +#. Setup pre-commit + To make sure that you don't accidentally commit code that does not follow the coding style, you can install a pre-commit hook that will check that everything is in order: + + .. code-block:: bash + + $ poetry run pre-commit install +#. Check Qt theme + You can check dark/light theme with built-in app. + + .. code-block:: bash + + $ poetry run python -m qdarktheme.widget_gallery diff --git a/docs/source/how_to_use.rst b/docs/source/how_to_use.rst index 628e2e5f..08e3ece1 100644 --- a/docs/source/how_to_use.rst +++ b/docs/source/how_to_use.rst @@ -1,242 +1,242 @@ -How to use PyQtDarkTheme -======================== - - -Apply dark theme to your Qt Application ---------------------------------------- -PyQtDarkTheme applies a flat theme to your Qt applications. - -.. tab-set:: - - .. tab-item:: PySide6 - - .. literalinclude:: ../../examples/apply_theme/pyside6.py - - .. tab-item:: PyQt6 - - .. literalinclude:: ../../examples/apply_theme/pyqt6.py - - .. tab-item:: PySide2 - - .. literalinclude:: ../../examples/apply_theme/pyside2.py - - .. tab-item:: PyQt5 - - .. literalinclude:: ../../examples/apply_theme/pyqt5.py - - .. tab-item:: pyqtgraph - - .. literalinclude:: ../../examples/apply_theme/pyqtgraph.py - -Enable HiDPI ------------- - -If you want to enable HiDPI, you can use ``qdarktheme.enable_hi_dpi()``. For Qt6 bindings, HiDPI “just works” without using this function. - -.. code-block:: python - - # enable_hi_dpi() must be called before instantiation of QApplication. - qdarktheme.enable_hi_dpi() - app = QApplication(sys.argv) - -Toggle dark/light Theme ------------------------ - -If you add ``theme`` argument as "auto", your Qt Application sync with OS's theme. On macOS, qdarktheme also syncs with accent colors. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: python - - qdarktheme.setup_theme("auto") - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/toggle_theme/sync_with_os_theme.py - -You can also switch between light and dark theme manually. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: python - - combo_box = QComboBox() - combo_box.addItems(qdarktheme.get_themes()) - combo_box.currentTextChanged.connect(qdarktheme.setup_theme) - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/toggle_theme/toggle_dark_light.py - -Toggle dark/light Theme with pyqtgraph -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can also switch between light and dark theme with pyqtgraph. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: python - - def toggle_theme(theme) -> None: - qdarktheme.setup_theme(theme) - plot_widget.setBackground("k" if theme == "dark" else "w") - - - signal.connect(toggle_theme) - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/toggle_theme/toggle_with_pyqtgraph.py - -Theme customization -------------------- - -You can customize theme color. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: python - - qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/customize_color/customize_accent_color.py - - .. tab-item:: Result - - .. image:: ../../examples/customize_color/customize_accent_color.png - :class: dark-light - - -You can also change border corner shape. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: Python - - qdarktheme.setup_theme(corner_shape="sharp") - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/customize_style/change_corner_to_sharp.py - - .. tab-item:: Result - - .. image:: ../../examples/customize_style/change_corner_to_sharp.png - :class: dark-light - -Append your own stylesheets ---------------------------- - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: Python - - qss = """ - QPushButton { - border-width: 2px; - border-style: dashed; - } - """ - qdarktheme.setup_theme(additional_qss=qss) - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/customize_style/append_stylesheet.py - - .. tab-item:: Result - - .. image:: ../../examples/customize_style/append_stylesheet.png - :class: dark-light - -Use overridden Qt default icons -------------------------------- - -If you setup theme with ``qdarktheme.setup_theme``, qdarktheme override ``QStyle.standardIcon()``. So you can easily use some `Google Material Design Icons `_. And these icons change color that adjust to theme when theme is changed. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: Python - - save_pixmap = QStyle.StandardPixmap.SP_DialogSaveButton - save_icon = win.style().standardIcon(save_pixmap) - - push_button = QPushButton("Save") - push_button.setIcon(save_icon) - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/icons/use_standard_icons.py - - .. tab-item:: Result - - .. image:: ../../examples/icons/use_standard_icons.png - - .. tab-item:: Gallery - - .. image:: ../../images/standard_icons.png - - -Use QPalette to your Qt Application ------------------------------------ - -You can apply dark and light color to your Qt Application using QPalette of PyQtDarkTheme. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: Python - - qdarktheme.load_palette() - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/qpalette/apply_dark_palette.py - - .. tab-item:: Gallery - - .. image:: ../../images/widget_gallery_dark_qpalette.png - :class: dark-light - -And you can get theme color from QPalette of PyQtDarkTheme. - -.. code-block:: Python - - import qdarktheme - - dark_palette = qdarktheme.load_palette() - link_color = dark_palette.link().color() - link_rgb = link_color.getRgb() - -Use stylesheet --------------- - -If you want to use Qt stylesheet of PyQtDarkTheme, use following function. - -.. tab-set:: - - .. tab-item:: Source - - .. code-block:: Python - - qdarktheme.load_stylesheet() - - .. tab-item:: Full source - - .. literalinclude:: ../../examples/use_stylesheet/apply_stylesheet.py +How to use PyQtDarkTheme +======================== + + +Apply dark theme to your Qt Application +--------------------------------------- +PyQtDarkTheme applies a flat theme to your Qt applications. + +.. tab-set:: + + .. tab-item:: PySide6 + + .. literalinclude:: ../../examples/apply_theme/pyside6.py + + .. tab-item:: PyQt6 + + .. literalinclude:: ../../examples/apply_theme/pyqt6.py + + .. tab-item:: PySide2 + + .. literalinclude:: ../../examples/apply_theme/pyside2.py + + .. tab-item:: PyQt5 + + .. literalinclude:: ../../examples/apply_theme/pyqt5.py + + .. tab-item:: pyqtgraph + + .. literalinclude:: ../../examples/apply_theme/pyqtgraph.py + +Enable HiDPI +------------ + +If you want to enable HiDPI, you can use ``qdarktheme.enable_hi_dpi()``. For Qt6 bindings, HiDPI “just works” without using this function. + +.. code-block:: python + + # enable_hi_dpi() must be called before instantiation of QApplication. + qdarktheme.enable_hi_dpi() + app = QApplication(sys.argv) + +Toggle dark/light Theme +----------------------- + +If you add ``theme`` argument as "auto", your Qt Application sync with OS's theme. On macOS, qdarktheme also syncs with accent colors. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: python + + qdarktheme.setup_theme("auto") + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/toggle_theme/sync_with_os_theme.py + +You can also switch between light and dark theme manually. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: python + + combo_box = QComboBox() + combo_box.addItems(qdarktheme.get_themes()) + combo_box.currentTextChanged.connect(qdarktheme.setup_theme) + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/toggle_theme/toggle_dark_light.py + +Toggle dark/light Theme with pyqtgraph +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can also switch between light and dark theme with pyqtgraph. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: python + + def toggle_theme(theme) -> None: + qdarktheme.setup_theme(theme) + plot_widget.setBackground("k" if theme == "dark" else "w") + + + signal.connect(toggle_theme) + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/toggle_theme/toggle_with_pyqtgraph.py + +Theme customization +------------------- + +You can customize theme color. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: python + + qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/customize_color/customize_accent_color.py + + .. tab-item:: Result + + .. image:: ../../examples/customize_color/customize_accent_color.png + :class: dark-light + + +You can also change border corner shape. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: Python + + qdarktheme.setup_theme(corner_shape="sharp") + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/customize_style/change_corner_to_sharp.py + + .. tab-item:: Result + + .. image:: ../../examples/customize_style/change_corner_to_sharp.png + :class: dark-light + +Append your own stylesheets +--------------------------- + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: Python + + qss = """ + QPushButton { + border-width: 2px; + border-style: dashed; + } + """ + qdarktheme.setup_theme(additional_qss=qss) + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/customize_style/append_stylesheet.py + + .. tab-item:: Result + + .. image:: ../../examples/customize_style/append_stylesheet.png + :class: dark-light + +Use overridden Qt default icons +------------------------------- + +If you setup theme with ``qdarktheme.setup_theme``, qdarktheme override ``QStyle.standardIcon()``. So you can easily use some `Google Material Design Icons `_. And these icons change color that adjust to theme when theme is changed. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: Python + + save_pixmap = QStyle.StandardPixmap.SP_DialogSaveButton + save_icon = win.style().standardIcon(save_pixmap) + + push_button = QPushButton("Save") + push_button.setIcon(save_icon) + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/icons/use_standard_icons.py + + .. tab-item:: Result + + .. image:: ../../examples/icons/use_standard_icons.png + + .. tab-item:: Gallery + + .. image:: ../../images/standard_icons.png + + +Use QPalette to your Qt Application +----------------------------------- + +You can apply dark and light color to your Qt Application using QPalette of PyQtDarkTheme. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: Python + + qdarktheme.load_palette() + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/qpalette/apply_dark_palette.py + + .. tab-item:: Gallery + + .. image:: ../../images/widget_gallery_dark_qpalette.png + :class: dark-light + +And you can get theme color from QPalette of PyQtDarkTheme. + +.. code-block:: Python + + import qdarktheme + + dark_palette = qdarktheme.load_palette() + link_color = dark_palette.link().color() + link_rgb = link_color.getRgb() + +Use stylesheet +-------------- + +If you want to use Qt stylesheet of PyQtDarkTheme, use following function. + +.. tab-set:: + + .. tab-item:: Source + + .. code-block:: Python + + qdarktheme.load_stylesheet() + + .. tab-item:: Full source + + .. literalinclude:: ../../examples/use_stylesheet/apply_stylesheet.py diff --git a/docs/source/index.rst b/docs/source/index.rst index a9a67a1d..ea37c36f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,81 +1,81 @@ -PyQtDarkTheme documentation -=========================== - -PyQtDarkTheme applies a flat dark theme to QtWidgets application(PySide and PyQt). There's a light theme too. Color and style balanced from a dark theme for easy viewing in daylight. - -.. tab-set:: - - .. tab-item:: Dark - - .. image:: ../../images/widget_gallery_dark.png - :class: dark-light - - .. tab-item:: Light - - .. image:: ../../images/widget_gallery_light.png - :class: dark-light - - .. tab-item:: Sync with OS's theme - - .. image:: ../../images/sync_with_os_theme.gif - :class: dark-light - - -**Features:** - -* A flat dark/light theme -* Support PySide, PyQt and PyInstaller -* Sync with OS's theme and accent (Mac, Windows, Linux) -* Resolve the style differences between Qt versions -* Provide dark/light theme QPalette -* :ref:`Override Qt old standard icons `. - -++++ - -.. grid:: - :gutter: 2 - - .. grid-item-card:: Installation guide - :text-align: center - :link: installation_guide - :link-type: doc - :class-item: sd-text-nowrap - - :material-regular:`install_desktop;5em;sd-text-primary` - ^^^ - - .. grid-item-card:: How to use - :text-align: center - :link: how_to_use - :link-type: doc - :class-item: sd-text-nowrap - - :material-regular:`menu_book;5em;sd-text-primary` - ^^^ - - .. grid-item-card:: API reference - :text-align: center - :link: reference/index - :link-type: doc - :class-item: sd-text-nowrap - - :material-regular:`library_books;5em;sd-text-primary` - ^^^ - - .. grid-item-card:: Contributing - :text-align: center - :link: contributing - :link-type: doc - :class-item: sd-text-nowrap - - :material-regular:`person_add;5em;sd-text-primary` - ^^^ - -.. toctree:: - :maxdepth: 2 - :hidden: - - Installation guide - How to use - API reference - Contributing +PyQtDarkTheme documentation +=========================== + +PyQtDarkTheme applies a flat dark theme to QtWidgets application(PySide and PyQt). There's a light theme too. Color and style balanced from a dark theme for easy viewing in daylight. + +.. tab-set:: + + .. tab-item:: Dark + + .. image:: ../../images/widget_gallery_dark.png + :class: dark-light + + .. tab-item:: Light + + .. image:: ../../images/widget_gallery_light.png + :class: dark-light + + .. tab-item:: Sync with OS's theme + + .. image:: ../../images/sync_with_os_theme.gif + :class: dark-light + + +**Features:** + +* A flat dark/light theme +* Support PySide, PyQt and PyInstaller +* Sync with OS's theme and accent (Mac, Windows, Linux) +* Resolve the style differences between Qt versions +* Provide dark/light theme QPalette +* :ref:`Override Qt old standard icons `. + +++++ + +.. grid:: + :gutter: 2 + + .. grid-item-card:: Installation guide + :text-align: center + :link: installation_guide + :link-type: doc + :class-item: sd-text-nowrap + + :material-regular:`install_desktop;5em;sd-text-primary` + ^^^ + + .. grid-item-card:: How to use + :text-align: center + :link: how_to_use + :link-type: doc + :class-item: sd-text-nowrap + + :material-regular:`menu_book;5em;sd-text-primary` + ^^^ + + .. grid-item-card:: API reference + :text-align: center + :link: reference/index + :link-type: doc + :class-item: sd-text-nowrap + + :material-regular:`library_books;5em;sd-text-primary` + ^^^ + + .. grid-item-card:: Contributing + :text-align: center + :link: contributing + :link-type: doc + :class-item: sd-text-nowrap + + :material-regular:`person_add;5em;sd-text-primary` + ^^^ + +.. toctree:: + :maxdepth: 2 + :hidden: + + Installation guide + How to use + API reference + Contributing diff --git a/docs/source/installation_guide.rst b/docs/source/installation_guide.rst index d65b9041..29ed6d96 100644 --- a/docs/source/installation_guide.rst +++ b/docs/source/installation_guide.rst @@ -1,33 +1,33 @@ -Installation Guide -================== - -Dependencies ------------- - -* Python 3.7+ -* Qt 5.15+ -* PySide6, PyQt6, PyQt5 or PySide2 - -Installation ------------- - -.. tab-set:: - - .. tab-item:: PyPi - - PyQtDarkTheme can be installed via pip from `PyPI `__. :: - - pip install pyqtdarktheme - - .. tab-item:: GitHub - - To get access to the latest features and bugfixes you can install PyQtDarkTheme from GitHub. :: - - pip install git+https://github.com/5yutan5/PyQtDarkTheme.git@main - -Example App ------------ - -PyQtDarkTheme includes example app that can be accessed by running: :: - - python -m qdarktheme.widget_gallery +Installation Guide +================== + +Dependencies +------------ + +* Python 3.7+ +* Qt 5.15+ +* PySide6, PyQt6, PyQt5 or PySide2 + +Installation +------------ + +.. tab-set:: + + .. tab-item:: PyPi + + PyQtDarkTheme can be installed via pip from `PyPI `__. :: + + pip install pyqtdarktheme + + .. tab-item:: GitHub + + To get access to the latest features and bugfixes you can install PyQtDarkTheme from GitHub. :: + + pip install git+https://github.com/5yutan5/PyQtDarkTheme.git@main + +Example App +----------- + +PyQtDarkTheme includes example app that can be accessed by running: :: + + python -m qdarktheme.widget_gallery diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index ccca3d3a..fe0467c6 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -1,10 +1,10 @@ -API Reference -============= - -This page gives an overview of all public qdarktheme objects, functions and theme color. All functions exposed in ``qdarktheme.*`` namespace are public. - -.. toctree:: - :maxdepth: 2 - - qdarktheme - Theme Color +API Reference +============= + +This page gives an overview of all public qdarktheme objects, functions and theme color. All functions exposed in ``qdarktheme.*`` namespace are public. + +.. toctree:: + :maxdepth: 2 + + qdarktheme + Theme Color diff --git a/docs/source/reference/qdarktheme.rst b/docs/source/reference/qdarktheme.rst index ba761a67..1cc580b7 100644 --- a/docs/source/reference/qdarktheme.rst +++ b/docs/source/reference/qdarktheme.rst @@ -1,20 +1,20 @@ -qdarktheme -========== - -``qdarktheme`` is a python module to apply original theme to PySide and PyQt. - -.. currentmodule:: qdarktheme - -.. autofunction:: qdarktheme.setup_theme - -.. autofunction:: qdarktheme.enable_hi_dpi - -.. autofunction:: qdarktheme.stop_sync - -.. autofunction:: qdarktheme.load_stylesheet - -.. autofunction:: qdarktheme.load_palette - -.. autofunction:: qdarktheme.get_themes - -.. autofunction:: qdarktheme.clear_cache +qdarktheme +========== + +``qdarktheme`` is a python module to apply original theme to PySide and PyQt. + +.. currentmodule:: qdarktheme + +.. autofunction:: qdarktheme.setup_theme + +.. autofunction:: qdarktheme.enable_hi_dpi + +.. autofunction:: qdarktheme.stop_sync + +.. autofunction:: qdarktheme.load_stylesheet + +.. autofunction:: qdarktheme.load_palette + +.. autofunction:: qdarktheme.get_themes + +.. autofunction:: qdarktheme.clear_cache diff --git a/examples/apply_theme/pyqt5.py b/examples/apply_theme/pyqt5.py index 6618a71f..bc0e0517 100644 --- a/examples/apply_theme/pyqt5.py +++ b/examples/apply_theme/pyqt5.py @@ -1,20 +1,20 @@ -import sys - -from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -# Enable HiDPI. -qdarktheme.enable_hi_dpi() - -app = QApplication(sys.argv) -# Apply dark theme. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +# Enable HiDPI. +qdarktheme.enable_hi_dpi() + +app = QApplication(sys.argv) +# Apply dark theme. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/apply_theme/pyqt6.py b/examples/apply_theme/pyqt6.py index 870de4d4..4f9204a4 100644 --- a/examples/apply_theme/pyqt6.py +++ b/examples/apply_theme/pyqt6.py @@ -1,17 +1,17 @@ -import sys - -from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Apply dark theme. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Apply dark theme. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/apply_theme/pyqtgraph.py b/examples/apply_theme/pyqtgraph.py index 7e7d45a1..abd8f22d 100644 --- a/examples/apply_theme/pyqtgraph.py +++ b/examples/apply_theme/pyqtgraph.py @@ -1,16 +1,16 @@ -import pyqtgraph as pg -from pyqtgraph.Qt.QtGui import QMainWindow, QPushButton - -import qdarktheme - -app = pg.mkQApp() -# Apply dark theme. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -pg.exec() +import pyqtgraph as pg +from pyqtgraph.Qt.QtGui import QMainWindow, QPushButton + +import qdarktheme + +app = pg.mkQApp() +# Apply dark theme. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +pg.exec() diff --git a/examples/apply_theme/pyside2.py b/examples/apply_theme/pyside2.py index 114c8950..8dd8889d 100644 --- a/examples/apply_theme/pyside2.py +++ b/examples/apply_theme/pyside2.py @@ -1,20 +1,20 @@ -import sys - -from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -# Enable HiDPI. -qdarktheme.enable_hi_dpi() - -app = QApplication(sys.argv) -# Apply dark theme. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec_() +import sys + +from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +# Enable HiDPI. +qdarktheme.enable_hi_dpi() + +app = QApplication(sys.argv) +# Apply dark theme. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec_() diff --git a/examples/apply_theme/pyside6.py b/examples/apply_theme/pyside6.py index 5c3c905d..c56f6b2b 100644 --- a/examples/apply_theme/pyside6.py +++ b/examples/apply_theme/pyside6.py @@ -1,17 +1,17 @@ -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Apply dark theme. -qdarktheme.setup_theme() - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Apply dark theme. +qdarktheme.setup_theme() + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/customize_color/customize_accent_color.py b/examples/customize_color/customize_accent_color.py index a4667d75..29f01dd5 100644 --- a/examples/customize_color/customize_accent_color.py +++ b/examples/customize_color/customize_accent_color.py @@ -1,18 +1,18 @@ -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Customize accent color. -qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) - -main_win = QMainWindow() -main_win.setContentsMargins(10, 10, 10, 10) -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Customize accent color. +qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) + +main_win = QMainWindow() +main_win.setContentsMargins(10, 10, 10, 10) +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/customize_style/append_stylesheet.py b/examples/customize_style/append_stylesheet.py index d98c4073..3e3a70b0 100644 --- a/examples/customize_style/append_stylesheet.py +++ b/examples/customize_style/append_stylesheet.py @@ -1,24 +1,24 @@ -import sys - -from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Additional stylesheet -qss = """ -QPushButton { - border-width: 2px; - border-style: dashed; -} -""" -qdarktheme.setup_theme(additional_qss=qss) - -main_win = QMainWindow() -main_win.setContentsMargins(10, 10, 10, 10) -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Additional stylesheet +qss = """ +QPushButton { + border-width: 2px; + border-style: dashed; +} +""" +qdarktheme.setup_theme(additional_qss=qss) + +main_win = QMainWindow() +main_win.setContentsMargins(10, 10, 10, 10) +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/customize_style/change_corner_to_sharp.py b/examples/customize_style/change_corner_to_sharp.py index 3501c232..3ac7ae27 100644 --- a/examples/customize_style/change_corner_to_sharp.py +++ b/examples/customize_style/change_corner_to_sharp.py @@ -1,18 +1,18 @@ -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Change border corner shape to sharp. -qdarktheme.setup_theme(corner_shape="sharp") - -main_win = QMainWindow() -main_win.setContentsMargins(10, 10, 10, 10) -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Change border corner shape to sharp. +qdarktheme.setup_theme(corner_shape="sharp") + +main_win = QMainWindow() +main_win.setContentsMargins(10, 10, 10, 10) +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/icons/use_standard_icons.py b/examples/icons/use_standard_icons.py index c06aa0df..15656577 100644 --- a/examples/icons/use_standard_icons.py +++ b/examples/icons/use_standard_icons.py @@ -1,20 +1,20 @@ -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QStyle - -import qdarktheme - -app = QApplication(sys.argv) -qdarktheme.setup_theme() - -main_win = QMainWindow() -save_pixmap = QStyle.StandardPixmap.SP_DialogSaveButton -save_icon = main_win.style().standardIcon(save_pixmap) - -push_button = QPushButton("Save") -push_button.setIcon(save_icon) -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton, QStyle + +import qdarktheme + +app = QApplication(sys.argv) +qdarktheme.setup_theme() + +main_win = QMainWindow() +save_pixmap = QStyle.StandardPixmap.SP_DialogSaveButton +save_icon = main_win.style().standardIcon(save_pixmap) + +push_button = QPushButton("Save") +push_button.setIcon(save_icon) +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/qpalette/apply_dark_palette.py b/examples/qpalette/apply_dark_palette.py index 1c2e5809..aada9195 100644 --- a/examples/qpalette/apply_dark_palette.py +++ b/examples/qpalette/apply_dark_palette.py @@ -1,17 +1,17 @@ -import sys - -from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -# Apply dark theme -app.setPalette(qdarktheme.load_palette()) - -main_win.show() - -app.exec() +import sys + +from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +# Apply dark theme +app.setPalette(qdarktheme.load_palette()) + +main_win.show() + +app.exec() diff --git a/examples/toggle_theme/sync_with_os_theme.py b/examples/toggle_theme/sync_with_os_theme.py index 188ea49f..deef66e9 100644 --- a/examples/toggle_theme/sync_with_os_theme.py +++ b/examples/toggle_theme/sync_with_os_theme.py @@ -1,16 +1,16 @@ -import sys - -from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -qdarktheme.setup_theme("auto") - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +qdarktheme.setup_theme("auto") + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/examples/toggle_theme/toggle_dark_light.py b/examples/toggle_theme/toggle_dark_light.py index 71eb5577..4bcff525 100644 --- a/examples/toggle_theme/toggle_dark_light.py +++ b/examples/toggle_theme/toggle_dark_light.py @@ -1,25 +1,25 @@ -import sys - -from PyQt6.QtWidgets import QApplication, QComboBox, QHBoxLayout, QMainWindow, QWidget - -import qdarktheme - -app = QApplication(sys.argv) -qdarktheme.setup_theme("dark") - -main_win = QMainWindow() - -combo_box = QComboBox() -combo_box.addItems(qdarktheme.get_themes()) -combo_box.currentTextChanged.connect(qdarktheme.setup_theme) - -layout = QHBoxLayout() -layout.addWidget(combo_box) - -central_widget = QWidget() -central_widget.setLayout(layout) -main_win.setCentralWidget(central_widget) - -main_win.show() - -app.exec() +import sys + +from PyQt6.QtWidgets import QApplication, QComboBox, QHBoxLayout, QMainWindow, QWidget + +import qdarktheme + +app = QApplication(sys.argv) +qdarktheme.setup_theme("dark") + +main_win = QMainWindow() + +combo_box = QComboBox() +combo_box.addItems(qdarktheme.get_themes()) +combo_box.currentTextChanged.connect(qdarktheme.setup_theme) + +layout = QHBoxLayout() +layout.addWidget(combo_box) + +central_widget = QWidget() +central_widget.setLayout(layout) +main_win.setCentralWidget(central_widget) + +main_win.show() + +app.exec() diff --git a/examples/toggle_theme/toggle_with_pyqtgraph.py b/examples/toggle_theme/toggle_with_pyqtgraph.py index aeacc4f5..439cbced 100644 --- a/examples/toggle_theme/toggle_with_pyqtgraph.py +++ b/examples/toggle_theme/toggle_with_pyqtgraph.py @@ -1,36 +1,36 @@ -import sys - -import pyqtgraph as pg -from PySide6.QtCore import Slot -from PySide6.QtWidgets import QApplication, QComboBox, QMainWindow, QVBoxLayout, QWidget - -import qdarktheme - -app = QApplication(sys.argv) -qdarktheme.setup_theme - -main_win = QMainWindow() -combo_box = QComboBox() -plot_widget = pg.PlotWidget() - - -@Slot(str) -def toggle_theme(theme) -> None: - qdarktheme.setup_theme(theme) - plot_widget.setBackground("k" if theme == "dark" else "w") - - -combo_box.addItems(qdarktheme.get_themes()) -combo_box.currentTextChanged.connect(toggle_theme) - -layout = QVBoxLayout() -layout.addWidget(combo_box) -layout.addWidget(plot_widget) - -central_widget = QWidget() -central_widget.setLayout(layout) -main_win.setCentralWidget(central_widget) - -main_win.show() - -app.exec() +import sys + +import pyqtgraph as pg +from PySide6.QtCore import Slot +from PySide6.QtWidgets import QApplication, QComboBox, QMainWindow, QVBoxLayout, QWidget + +import qdarktheme + +app = QApplication(sys.argv) +qdarktheme.setup_theme + +main_win = QMainWindow() +combo_box = QComboBox() +plot_widget = pg.PlotWidget() + + +@Slot(str) +def toggle_theme(theme) -> None: + qdarktheme.setup_theme(theme) + plot_widget.setBackground("k" if theme == "dark" else "w") + + +combo_box.addItems(qdarktheme.get_themes()) +combo_box.currentTextChanged.connect(toggle_theme) + +layout = QVBoxLayout() +layout.addWidget(combo_box) +layout.addWidget(plot_widget) + +central_widget = QWidget() +central_widget.setLayout(layout) +main_win.setCentralWidget(central_widget) + +main_win.show() + +app.exec() diff --git a/examples/use_stylesheet/apply_stylesheet.py b/examples/use_stylesheet/apply_stylesheet.py index 83f9d7ba..14071a6b 100644 --- a/examples/use_stylesheet/apply_stylesheet.py +++ b/examples/use_stylesheet/apply_stylesheet.py @@ -1,17 +1,17 @@ -import sys - -from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton - -import qdarktheme - -app = QApplication(sys.argv) -# Apply stylesheet as "dark" theme -app.setStyleSheet(qdarktheme.load_stylesheet()) - -main_win = QMainWindow() -push_button = QPushButton("PyQtDarkTheme!!") -main_win.setCentralWidget(push_button) - -main_win.show() - -app.exec() +import sys + +from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton + +import qdarktheme + +app = QApplication(sys.argv) +# Apply stylesheet as "dark" theme +app.setStyleSheet(qdarktheme.load_stylesheet()) + +main_win = QMainWindow() +push_button = QPushButton("PyQtDarkTheme!!") +main_win.setCentralWidget(push_button) + +main_win.show() + +app.exec() diff --git a/images/sync_with_os_accent_windows.gif b/images/sync_with_os_accent_windows.gif new file mode 100644 index 00000000..686c56d3 Binary files /dev/null and b/images/sync_with_os_accent_windows.gif differ diff --git a/poetry.lock b/poetry.lock index 67ba7b81..323a29ad 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,1707 +1,1707 @@ -[[package]] -name = "alabaster" -version = "0.7.12" -description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "astor" -version = "0.8.1" -description = "Read/rewrite/write Python ASTs" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[[package]] -name = "attrs" -version = "22.1.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] -docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] -tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] -tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] - -[[package]] -name = "babel" -version = "2.11.0" -description = "Internationalization utilities" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pytz = ">=2015.7" - -[[package]] -name = "beautifulsoup4" -version = "4.11.1" -description = "Screen-scraping library" -category = "dev" -optional = false -python-versions = ">=3.6.0" - -[package.dependencies] -soupsieve = ">1.2" - -[package.extras] -html5lib = ["html5lib"] -lxml = ["lxml"] - -[[package]] -name = "black" -version = "22.10.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2022.12.7" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "cfgv" -version = "3.3.1" -description = "Validate configuration and produce human readable error messages." -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[[package]] -name = "charset-normalizer" -version = "2.1.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" -optional = false -python-versions = ">=3.6.0" - -[package.extras] -unicode-backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.1.3" -description = "Composable command line interface toolkit" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" - -[[package]] -name = "commonmark" -version = "0.9.1" -description = "Python parser for the CommonMark Markdown spec" -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] - -[[package]] -name = "coverage" -version = "6.5.0" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "darkdetect" -version = "0.7.1" -description = "Detect OS Dark Mode from Python" -category = "main" -optional = false -python-versions = "*" - -[[package]] -name = "distlib" -version = "0.3.6" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "docutils" -version = "0.17.1" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "eradicate" -version = "2.1.0" -description = "Removes commented-out code." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "exceptiongroup" -version = "1.0.4" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "filelock" -version = "3.8.0" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] -testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] - -[[package]] -name = "flake8" -version = "5.0.4" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} -mccabe = ">=0.7.0,<0.8.0" -pycodestyle = ">=2.9.0,<2.10.0" -pyflakes = ">=2.5.0,<2.6.0" - -[[package]] -name = "flake8-bugbear" -version = "22.10.27" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -flake8 = ">=3.0.0" - -[package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "tox"] - -[[package]] -name = "flake8-comprehensions" -version = "3.10.1" -description = "A flake8 plugin to help you write better list/set/dict comprehensions." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = ">=3.0,<3.2.0 || >3.2.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "flake8-docstrings" -version = "1.6.0" -description = "Extension for flake8 which uses pydocstyle to check docstrings" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = ">=3" -pydocstyle = ">=2.1" - -[[package]] -name = "flake8-eradicate" -version = "1.4.0" -description = "Flake8 plugin to find commented out code" -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -attrs = "*" -eradicate = ">=2.0,<3.0" -flake8 = ">=3.5,<6" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "flake8-pie" -version = "0.16.0" -description = "A flake8 extension that implements misc. lints" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing_extensions = "*" - -[[package]] -name = "flake8-plugin-utils" -version = "1.3.2" -description = "The package provides base classes and utils for flake8 plugin writing" -category = "dev" -optional = false -python-versions = ">=3.6,<4.0" - -[[package]] -name = "flake8-print" -version = "5.0.0" -description = "print statement checker plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = ">=3.0" -pycodestyle = "*" - -[[package]] -name = "flake8-pyproject" -version = "1.2.1" -description = "Flake8 plug-in loading the configuration from pyproject.toml" -category = "dev" -optional = false -python-versions = ">= 3.6" - -[package.dependencies] -Flake8 = ">=5,<7" -TOMLi = {version = "*", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["pyTest", "pyTest-cov"] - -[[package]] -name = "flake8-pytest-style" -version = "1.6.0" -description = "A flake8 plugin checking common style issues or inconsistencies with pytest-based tests." -category = "dev" -optional = false -python-versions = ">=3.6.2,<4.0.0" - -[package.dependencies] -flake8-plugin-utils = ">=1.3.2,<2.0.0" - -[[package]] -name = "flake8-return" -version = "1.2.0" -description = "Flake8 plugin that checks return values" -category = "dev" -optional = false -python-versions = ">=3.6,<4.0" - -[package.dependencies] -flake8-plugin-utils = ">=1.0,<2.0" - -[[package]] -name = "flake8-rst-docstrings" -version = "0.2.7" -description = "Python docstring reStructuredText (RST) validator" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = ">=3.0.0" -pygments = "*" -restructuredtext-lint = "*" - -[[package]] -name = "flake8-simplify" -version = "0.19.3" -description = "flake8 plugin which checks for code that can be simplified" -category = "dev" -optional = false -python-versions = ">=3.6.1" - -[package.dependencies] -astor = ">=0.1" -flake8 = ">=3.7" -importlib-metadata = {version = ">=0.9", markers = "python_version < \"3.8\""} - -[[package]] -name = "identify" -version = "2.5.9" -description = "File identification library for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.4" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "importlib-metadata" -version = "4.2.0" -description = "Read metadata from Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "isort" -version = "5.10.1" -description = "A Python utility / library to sort Python imports." -category = "dev" -optional = false -python-versions = ">=3.6.1,<4.0" - -[package.extras] -colors = ["colorama (>=0.4.3,<0.5.0)"] -pipfile-deprecated-finder = ["pipreqs", "requirementslib"] -plugins = ["setuptools"] -requirements-deprecated-finder = ["pip-api", "pipreqs"] - -[[package]] -name = "jinja2" -version = "3.1.2" -description = "A very fast and expressive template engine." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.1" -description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "nodeenv" -version = "1.7.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathspec" -version = "0.10.2" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "pep8-naming" -version = "0.13.2" -description = "Check PEP-8 naming conventions, plugin for flake8" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = ">=3.9.1" - -[[package]] -name = "platformdirs" -version = "2.5.4" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] -test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "2.20.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -toml = "*" -virtualenv = ">=20.0.8" - -[[package]] -name = "pycodestyle" -version = "2.9.1" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pydantic" -version = "1.10.2" -description = "Data validation and settings management using python type hints" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -typing-extensions = ">=4.1.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pydata-sphinx-theme" -version = "0.11.0" -description = "Bootstrap-based Sphinx theme from the PyData community" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -beautifulsoup4 = "*" -docutils = "!=0.17.0" -packaging = "*" -pygments = ">=2.7" -sphinx = ">=4.2" - -[package.extras] -coverage = ["codecov", "pydata-sphinx-theme[test]", "pytest-cov"] -dev = ["nox", "pre-commit", "pydata-sphinx-theme[coverage]", "pyyaml"] -doc = ["ablog", "jupyter_sphinx", "matplotlib", "myst-nb", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "pytest", "pytest-regressions", "rich", "sphinx-copybutton", "sphinx-design", "sphinx-sitemap", "sphinx-togglebutton", "sphinxext-rediraffe", "xarray"] -test = ["pydata-sphinx-theme[doc]", "pytest"] - -[[package]] -name = "pydocstyle" -version = "6.1.1" -description = "Python docstring style checker" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -snowballstemmer = "*" - -[package.extras] -toml = ["toml"] - -[[package]] -name = "pyflakes" -version = "2.5.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "pygments" -version = "2.13.0" -description = "Pygments is a syntax highlighting package written in Python." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -plugins = ["importlib-metadata"] - -[[package]] -name = "pyparsing" -version = "3.0.9" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -category = "dev" -optional = false -python-versions = ">=3.6.8" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "7.2.0" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "4.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pytest-github-actions-annotate-failures" -version = "0.1.7" -description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pytest = ">=4.0.0" - -[[package]] -name = "pytest-mock" -version = "3.10.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=5.0" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "pytest-qt" -version = "4.2.0" -description = "pytest support for PyQt and PySide applications" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -pytest = ">=3.0.0" - -[package.extras] -dev = ["pre-commit", "tox"] -doc = ["sphinx", "sphinx-rtd-theme"] - -[[package]] -name = "pytest-randomly" -version = "3.12.0" -description = "Pytest plugin to randomly order tests and control random.seed." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} -pytest = "*" - -[[package]] -name = "pytest-xvfb" -version = "2.0.0" -description = "A pytest plugin to run Xvfb for tests." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.dependencies] -pytest = ">=2.8.1" -pyvirtualdisplay = ">=1.3" - -[[package]] -name = "pytz" -version = "2022.6" -description = "World timezone definitions, modern and historical" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pyvirtualdisplay" -version = "3.0" -description = "python wrapper for Xvfb, Xephyr and Xvnc" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "pyyaml" -version = "6.0" -description = "YAML parser and emitter for Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "requests" -version = "2.28.1" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=3.7, <4" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<3" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "restructuredtext-lint" -version = "1.4.0" -description = "reStructuredText linter" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -docutils = ">=0.11,<1.0" - -[[package]] -name = "rich" -version = "12.6.0" -description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" -category = "dev" -optional = false -python-versions = ">=3.6.3,<4.0.0" - -[package.dependencies] -commonmark = ">=0.9.0,<0.10.0" -pygments = ">=2.6.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] - -[[package]] -name = "rstcheck" -version = "6.1.1" -description = "Checks syntax of reStructuredText and code blocks nested within it" -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -importlib-metadata = {version = ">=1.6,<5.0", markers = "python_version < \"3.8\""} -rstcheck-core = ">=1.0.2,<2.0.0" -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} -typer = {version = ">=0.4.1,<0.8", extras = ["all"]} -typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["m2r2 (>=0.3.2)", "sphinx", "sphinx-autobuild (==2021.3.14)", "sphinx-click (>=4.0.3,<5.0.0)", "sphinx-rtd-dark-mode (>=1.2.4,<2.0.0)", "sphinx-rtd-theme (<1)", "sphinxcontrib-spelling (>=7.3)"] -sphinx = ["sphinx"] -testing = ["coverage-conditional-plugin (>=0.5)", "coverage[toml] (>=6.0)", "pytest (>=6.0)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.0)", "pytest-sugar (>=0.9.5)"] -toml = ["tomli"] - -[[package]] -name = "rstcheck-core" -version = "1.0.3" -description = "Checks syntax of reStructuredText and code blocks nested within it" -category = "dev" -optional = false -python-versions = ">=3.7,<4.0" - -[package.dependencies] -docutils = ">=0.7,<0.20" -importlib-metadata = {version = ">=1.6,<5.0", markers = "python_version < \"3.8\""} -pydantic = ">=1.2,<2.0" -types-docutils = ">=0.18,<0.20" -typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.8\""} - -[package.extras] -docs = ["m2r2 (>=0.3.2)", "sphinx (>=4.0,<6.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-autodoc-typehints (>=1.15)", "sphinx-rtd-dark-mode (>=1.2.4,<2.0.0)", "sphinx-rtd-theme (<1)", "sphinxcontrib-apidoc (>=0.3)", "sphinxcontrib-spelling (>=7.3)"] -sphinx = ["sphinx (>=4.0,<6.0)"] -testing = ["coverage-conditional-plugin (>=0.5)", "coverage[toml] (>=6.0)", "pytest (>=6.0)", "pytest-cov (>=3.0)", "pytest-mock (>=3.7)", "pytest-randomly (>=3.0)", "pytest-sugar (>=0.9.5)"] -toml = ["tomli (>=2.0,<3.0)"] - -[[package]] -name = "setuptools" -version = "65.6.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "shellingham" -version = "1.5.0" -description = "Tool to Detect Surrounding Shell" -category = "dev" -optional = false -python-versions = ">=3.4" - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "soupsieve" -version = "2.3.2.post1" -description = "A modern CSS selector implementation for Beautiful Soup." -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "sphinx" -version = "4.3.2" -description = "Python documentation generator" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=1.3" -colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.18" -imagesize = "*" -Jinja2 = ">=2.3" -packaging = "*" -Pygments = ">=2.0" -requests = ">=2.5.0" -setuptools = "*" -snowballstemmer = ">=1.1" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "types-pkg-resources", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] - -[[package]] -name = "sphinx-copybutton" -version = "0.5.1" -description = "Add a copy button to each of your code cells." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -sphinx = ">=1.8" - -[package.extras] -code-style = ["pre-commit (==2.12.1)"] -rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] - -[[package]] -name = "sphinx-design" -version = "0.3.0" -description = "A sphinx extension for designing beautiful, view size responsive web components." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -sphinx = ">=4,<6" - -[package.extras] -code-style = ["pre-commit (>=2.12,<3.0)"] -rtd = ["myst-parser (>=0.18.0,<0.19.0)"] -testing = ["myst-parser (>=0.18.0,<0.19.0)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] -theme-furo = ["furo (>=2022.06.04,<2022.07)"] -theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"] -theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] -theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.2" -description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" -optional = false -python-versions = ">=3.5" - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "typed-ast" -version = "1.5.4" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typer" -version = "0.7.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -click = ">=7.1.1,<9.0.0" -colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} -rich = {version = ">=10.11.0,<13.0.0", optional = true, markers = "extra == \"all\""} -shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} - -[package.extras] -all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] -dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] -doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] -test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] - -[[package]] -name = "types-docutils" -version = "0.19.1.1" -description = "Typing stubs for docutils" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "typing-extensions" -version = "4.4.0" -description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "urllib3" -version = "1.26.13" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.16.2" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -platformdirs = ">=2,<3" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] - -[[package]] -name = "zipp" -version = "3.11.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] -testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] - -[metadata] -lock-version = "1.1" -python-versions = ">=3.7,<3.12" -content-hash = "e7a0e0d8a985d0308494d742332e248f11f1c762595ba505d4957bca704354d9" - -[metadata.files] -alabaster = [ - {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, - {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, -] -astor = [ - {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, - {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, -] -attrs = [ - {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, - {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, -] -babel = [ - {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, - {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, -] -beautifulsoup4 = [ - {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, - {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, -] -black = [ - {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, - {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, - {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, - {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, - {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, - {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, - {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, - {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, - {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, - {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, - {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, - {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, - {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, - {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, - {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, - {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, - {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, - {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, - {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, - {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, - {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, -] -certifi = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, -] -cfgv = [ - {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, - {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, - {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, -] -click = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, -] -colorama = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -commonmark = [ - {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, - {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, -] -coverage = [ - {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, - {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, - {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, - {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, - {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, - {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, - {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, - {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, - {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, - {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, - {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, - {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, - {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, - {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, - {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, - {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, - {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, - {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, - {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, - {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, - {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, - {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, - {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, - {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, - {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, - {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, - {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, - {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, -] -darkdetect = [ - {file = "darkdetect-0.7.1-py2.py3-none-any.whl", hash = "sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866"}, - {file = "darkdetect-0.7.1.tar.gz", hash = "sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013"}, -] -distlib = [ - {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, - {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, -] -docutils = [ - {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, - {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, -] -eradicate = [ - {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, - {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, -] -exceptiongroup = [ - {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, - {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, -] -filelock = [ - {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, - {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, -] -flake8 = [ - {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, - {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, -] -flake8-bugbear = [ - {file = "flake8-bugbear-22.10.27.tar.gz", hash = "sha256:a6708608965c9e0de5fff13904fed82e0ba21ac929fe4896459226a797e11cd5"}, - {file = "flake8_bugbear-22.10.27-py3-none-any.whl", hash = "sha256:6ad0ab754507319060695e2f2be80e6d8977cfcea082293089a9226276bd825d"}, -] -flake8-comprehensions = [ - {file = "flake8-comprehensions-3.10.1.tar.gz", hash = "sha256:412052ac4a947f36b891143430fef4859705af11b2572fbb689f90d372cf26ab"}, - {file = "flake8_comprehensions-3.10.1-py3-none-any.whl", hash = "sha256:d763de3c74bc18a79c039a7ec732e0a1985b0c79309ceb51e56401ad0a2cd44e"}, -] -flake8-docstrings = [ - {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, - {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, -] -flake8-eradicate = [ - {file = "flake8-eradicate-1.4.0.tar.gz", hash = "sha256:3088cfd6717d1c9c6c3ac45ef2e5f5b6c7267f7504d5a74b781500e95cb9c7e1"}, - {file = "flake8_eradicate-1.4.0-py3-none-any.whl", hash = "sha256:e3bbd0871be358e908053c1ab728903c114f062ba596b4d40c852fd18f473d56"}, -] -flake8-pie = [ - {file = "flake8-pie-0.16.0.tar.gz", hash = "sha256:b8dcb7b92706fa33d05d92a4b3e49b7a9fd3f0041849166275b646ba50e515ba"}, - {file = "flake8_pie-0.16.0-py3-none-any.whl", hash = "sha256:24cd7849b0eee22e2328b9e9d2a1dea40013b0a3106864bbadd06a4b05dbb71f"}, -] -flake8-plugin-utils = [ - {file = "flake8-plugin-utils-1.3.2.tar.gz", hash = "sha256:20fa2a8ca2decac50116edb42e6af0a1253ef639ad79941249b840531889c65a"}, - {file = "flake8_plugin_utils-1.3.2-py3-none-any.whl", hash = "sha256:1fe43e3e9acf3a7c0f6b88f5338cad37044d2f156c43cb6b080b5f9da8a76f06"}, -] -flake8-print = [ - {file = "flake8-print-5.0.0.tar.gz", hash = "sha256:76915a2a389cc1c0879636c219eb909c38501d3a43cc8dae542081c9ba48bdf9"}, - {file = "flake8_print-5.0.0-py3-none-any.whl", hash = "sha256:84a1a6ea10d7056b804221ac5e62b1cee1aefc897ce16f2e5c42d3046068f5d8"}, -] -flake8-pyproject = [ - {file = "flake8_pyproject-1.2.1-py3-none-any.whl", hash = "sha256:d2470803b38883881c250c27f361ca4c49e7b8f9e3a17f7d44aa43b6965b2363"}, -] -flake8-pytest-style = [ - {file = "flake8-pytest-style-1.6.0.tar.gz", hash = "sha256:c1175713e9e11b78cd1a035ed0bca0d1e41d09c4af329a952750b61d4194ddac"}, - {file = "flake8_pytest_style-1.6.0-py3-none-any.whl", hash = "sha256:5fedb371a950e9fe0e0e6bfc854be7d99151271208f34cd2cc517681ece27780"}, -] -flake8-return = [ - {file = "flake8-return-1.2.0.tar.gz", hash = "sha256:68dfa56582cd704febd02ad86dcf5df67e38e0836d62f1ceae7930d76d3dd955"}, - {file = "flake8_return-1.2.0-py3-none-any.whl", hash = "sha256:1f07af12954ed03ebe2c2aac2418f78b55374e9929d4956109664588f31582a1"}, -] -flake8-rst-docstrings = [ - {file = "flake8-rst-docstrings-0.2.7.tar.gz", hash = "sha256:2740067ab9237559dd45a3434d8c987792c7b259ca563621a3b95efe201f5382"}, - {file = "flake8_rst_docstrings-0.2.7-py3-none-any.whl", hash = "sha256:5d56075dce360bcc9c6775bfe7cb431aa395de600ca7e8d40580a28d50b2a803"}, -] -flake8-simplify = [ - {file = "flake8_simplify-0.19.3-py3-none-any.whl", hash = "sha256:1057320e9312d75849541fee822900d27bcad05b2405edc84713affee635629e"}, - {file = "flake8_simplify-0.19.3.tar.gz", hash = "sha256:2fb083bf5142a98d9c9554755cf2f56f8926eb4a33eae30c0809041b1546879e"}, -] -identify = [ - {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, - {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, -] -idna = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, -] -imagesize = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -isort = [ - {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, - {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, -] -jinja2 = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, -] -markupsafe = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, -] -mccabe = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, - {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, - {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, -] -pep8-naming = [ - {file = "pep8-naming-0.13.2.tar.gz", hash = "sha256:93eef62f525fd12a6f8c98f4dcc17fa70baae2f37fa1f73bec00e3e44392fa48"}, - {file = "pep8_naming-0.13.2-py3-none-any.whl", hash = "sha256:59e29e55c478db69cffbe14ab24b5bd2cd615c0413edf790d47d3fb7ba9a4e23"}, -] -platformdirs = [ - {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, - {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -pre-commit = [ - {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, - {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, -] -pycodestyle = [ - {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, - {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, -] -pydantic = [ - {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, - {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, - {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, - {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, - {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, - {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, - {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, - {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, - {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, - {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, - {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, - {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, - {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, - {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, - {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, - {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, - {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, - {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, - {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, - {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, - {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, - {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, - {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, -] -pydata-sphinx-theme = [ - {file = "pydata_sphinx_theme-0.11.0-py3-none-any.whl", hash = "sha256:64f1fc4c79a1f066b913541116cd5a7e83e140ba0e983509eedd9e66e1f948c8"}, - {file = "pydata_sphinx_theme-0.11.0.tar.gz", hash = "sha256:5fb1751d68a4b8574f817296e925132835281332616eb2839ddac4ef4503cd99"}, -] -pydocstyle = [ - {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, - {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, -] -pyflakes = [ - {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, - {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, -] -pygments = [ - {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, - {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, -] -pyparsing = [ - {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, - {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, -] -pytest = [ - {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, - {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, -] -pytest-cov = [ - {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, - {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, -] -pytest-github-actions-annotate-failures = [ - {file = "pytest-github-actions-annotate-failures-0.1.7.tar.gz", hash = "sha256:c6af8f9d13f1f09ef4c104a30875a4975db131ddbba979c8e48fdc456c8dde1f"}, - {file = "pytest_github_actions_annotate_failures-0.1.7-py2.py3-none-any.whl", hash = "sha256:c4a7346d1d95f731a6b53e9a45f10ca56593978149266dd7526876cce403ea38"}, -] -pytest-mock = [ - {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, - {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, -] -pytest-qt = [ - {file = "pytest-qt-4.2.0.tar.gz", hash = "sha256:00a17b586dd530b6d7a9399923a40489ca4a9a309719011175f55dc6b5dc8f41"}, - {file = "pytest_qt-4.2.0-py2.py3-none-any.whl", hash = "sha256:a7659960a1ab2af8fc944655a157ff45d714b80ed7a6af96a4b5bb99ecf40a22"}, -] -pytest-randomly = [ - {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, - {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, -] -pytest-xvfb = [ - {file = "pytest-xvfb-2.0.0.tar.gz", hash = "sha256:c4ba642de05499940db7f65ee111621939be513e3e75c3da9156b7235e2ed8cf"}, - {file = "pytest_xvfb-2.0.0-py3-none-any.whl", hash = "sha256:6d21b46f099c06d6b8b200e73341da3adb73d67e9139c55d617930881779360b"}, -] -pytz = [ - {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, - {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, -] -pyvirtualdisplay = [ - {file = "PyVirtualDisplay-3.0-py3-none-any.whl", hash = "sha256:40d4b8dfe4b8de8552e28eb367647f311f88a130bf837fe910e7f180d5477f0e"}, - {file = "PyVirtualDisplay-3.0.tar.gz", hash = "sha256:09755bc3ceb6eb725fb07eca5425f43f2358d3bf08e00d2a9b792a1aedd16159"}, -] -pyyaml = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, -] -requests = [ - {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, - {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, -] -restructuredtext-lint = [ - {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, -] -rich = [ - {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, - {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, -] -rstcheck = [ - {file = "rstcheck-6.1.1-py3-none-any.whl", hash = "sha256:edeff9ad0644d12bd250100b677887424193789254c90d95c13375062ee2cbac"}, - {file = "rstcheck-6.1.1.tar.gz", hash = "sha256:8e43485a644e794b8127f8c4868ef62c14ec7919bdda6cb16642157055d32e47"}, -] -rstcheck-core = [ - {file = "rstcheck_core-1.0.3-py3-none-any.whl", hash = "sha256:d75d7df8f15b58e8aafe322d6fb6ef1ac8d12bb563089b0696948a00ee7f601a"}, - {file = "rstcheck_core-1.0.3.tar.gz", hash = "sha256:add19c9a1b97d9087f4b463b49c12cd8a9c03689a255e99089c70a2692f16369"}, -] -setuptools = [ - {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, - {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, -] -shellingham = [ - {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, - {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, -] -snowballstemmer = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] -soupsieve = [ - {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, - {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, -] -sphinx = [ - {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"}, - {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"}, -] -sphinx-copybutton = [ - {file = "sphinx-copybutton-0.5.1.tar.gz", hash = "sha256:366251e28a6f6041514bfb5439425210418d6c750e98d3a695b73e56866a677a"}, - {file = "sphinx_copybutton-0.5.1-py3-none-any.whl", hash = "sha256:0842851b5955087a7ec7fc870b622cb168618ad408dee42692e9a5c97d071da8"}, -] -sphinx-design = [ - {file = "sphinx_design-0.3.0-py3-none-any.whl", hash = "sha256:823c1dd74f31efb3285ec2f1254caefed29d762a40cd676f58413a1e4ed5cc96"}, - {file = "sphinx_design-0.3.0.tar.gz", hash = "sha256:7183fa1fae55b37ef01bda5125a21ee841f5bbcbf59a35382be598180c4cefba"}, -] -sphinxcontrib-applehelp = [ - {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, - {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, -] -sphinxcontrib-devhelp = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] -sphinxcontrib-htmlhelp = [ - {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, - {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, -] -sphinxcontrib-jsmath = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] -sphinxcontrib-qthelp = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] -sphinxcontrib-serializinghtml = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] -typed-ast = [ - {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, - {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, - {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, - {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, - {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, - {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, - {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, - {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, - {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, - {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, - {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, - {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, - {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, - {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, - {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, - {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, - {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, -] -typer = [ - {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, - {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, -] -types-docutils = [ - {file = "types-docutils-0.19.1.1.tar.gz", hash = "sha256:be0a51ba1c7dd215d9d2df66d6845e63c1009b4bbf4c5beb87a0d9745cdba962"}, - {file = "types_docutils-0.19.1.1-py3-none-any.whl", hash = "sha256:a024cada35f0c13cc45eb0b68a102719018a634013690b7fef723bcbfadbd1f1"}, -] -typing-extensions = [ - {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, - {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, -] -urllib3 = [ - {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, - {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, -] -virtualenv = [ - {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, - {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, -] -zipp = [ - {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, - {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, -] +[[package]] +name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "babel" +version = "2.11.0" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "22.10.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "darkdetect" +version = "0.7.1" +description = "Detect OS Dark Mode from Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "eradicate" +version = "2.1.0" +description = "Removes commented-out code." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "exceptiongroup" +version = "1.0.4" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.8.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "5.0.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +importlib-metadata = {version = ">=1.1.0,<4.3", markers = "python_version < \"3.8\""} +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.9.0,<2.10.0" +pyflakes = ">=2.5.0,<2.6.0" + +[[package]] +name = "flake8-bugbear" +version = "22.10.27" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +flake8 = ">=3.0.0" + +[package.extras] +dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit", "tox"] + +[[package]] +name = "flake8-comprehensions" +version = "3.10.1" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +flake8 = ">=3.0,<3.2.0 || >3.2.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "flake8-docstrings" +version = "1.6.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +flake8 = ">=3" +pydocstyle = ">=2.1" + +[[package]] +name = "flake8-eradicate" +version = "1.4.0" +description = "Flake8 plugin to find commented out code" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +attrs = "*" +eradicate = ">=2.0,<3.0" +flake8 = ">=3.5,<6" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "flake8-pie" +version = "0.16.0" +description = "A flake8 extension that implements misc. lints" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing_extensions = "*" + +[[package]] +name = "flake8-plugin-utils" +version = "1.3.2" +description = "The package provides base classes and utils for flake8 plugin writing" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "flake8-print" +version = "5.0.0" +description = "print statement checker plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +flake8 = ">=3.0" +pycodestyle = "*" + +[[package]] +name = "flake8-pyproject" +version = "1.2.1" +description = "Flake8 plug-in loading the configuration from pyproject.toml" +category = "dev" +optional = false +python-versions = ">= 3.6" + +[package.dependencies] +Flake8 = ">=5,<7" +TOMLi = {version = "*", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["pyTest", "pyTest-cov"] + +[[package]] +name = "flake8-pytest-style" +version = "1.6.0" +description = "A flake8 plugin checking common style issues or inconsistencies with pytest-based tests." +category = "dev" +optional = false +python-versions = ">=3.6.2,<4.0.0" + +[package.dependencies] +flake8-plugin-utils = ">=1.3.2,<2.0.0" + +[[package]] +name = "flake8-return" +version = "1.2.0" +description = "Flake8 plugin that checks return values" +category = "dev" +optional = false +python-versions = ">=3.6,<4.0" + +[package.dependencies] +flake8-plugin-utils = ">=1.0,<2.0" + +[[package]] +name = "flake8-rst-docstrings" +version = "0.2.7" +description = "Python docstring reStructuredText (RST) validator" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +flake8 = ">=3.0.0" +pygments = "*" +restructuredtext-lint = "*" + +[[package]] +name = "flake8-simplify" +version = "0.19.3" +description = "flake8 plugin which checks for code that can be simplified" +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[package.dependencies] +astor = ">=0.1" +flake8 = ">=3.7" +importlib-metadata = {version = ">=0.9", markers = "python_version < \"3.8\""} + +[[package]] +name = "identify" +version = "2.5.9" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "4.2.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pathspec" +version = "0.10.2" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pep8-naming" +version = "0.13.2" +description = "Check PEP-8 naming conventions, plugin for flake8" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +flake8 = ">=3.9.1" + +[[package]] +name = "platformdirs" +version = "2.5.4" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.4)"] +test = ["appdirs (==1.4.4)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "pycodestyle" +version = "2.9.1" +description = "Python style guide checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pydantic" +version = "1.10.2" +description = "Data validation and settings management using python type hints" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = ">=4.1.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.11.0" +description = "Bootstrap-based Sphinx theme from the PyData community" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +beautifulsoup4 = "*" +docutils = "!=0.17.0" +packaging = "*" +pygments = ">=2.7" +sphinx = ">=4.2" + +[package.extras] +coverage = ["codecov", "pydata-sphinx-theme[test]", "pytest-cov"] +dev = ["nox", "pre-commit", "pydata-sphinx-theme[coverage]", "pyyaml"] +doc = ["ablog", "jupyter_sphinx", "matplotlib", "myst-nb", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "pytest", "pytest-regressions", "rich", "sphinx-copybutton", "sphinx-design", "sphinx-sitemap", "sphinx-togglebutton", "sphinxext-rediraffe", "xarray"] +test = ["pydata-sphinx-theme[doc]", "pytest"] + +[[package]] +name = "pydocstyle" +version = "6.1.1" +description = "Python docstring style checker" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +snowballstemmer = "*" + +[package.extras] +toml = ["toml"] + +[[package]] +name = "pyflakes" +version = "2.5.0" +description = "passive checker of Python programs" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.2.0" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-github-actions-annotate-failures" +version = "0.1.7" +description = "pytest plugin to annotate failed tests with a workflow command for GitHub Actions" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +pytest = ">=4.0.0" + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-qt" +version = "4.2.0" +description = "pytest support for PyQt and PySide applications" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pytest = ">=3.0.0" + +[package.extras] +dev = ["pre-commit", "tox"] +doc = ["sphinx", "sphinx-rtd-theme"] + +[[package]] +name = "pytest-randomly" +version = "3.12.0" +description = "Pytest plugin to randomly order tests and control random.seed." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +pytest = "*" + +[[package]] +name = "pytest-xvfb" +version = "2.0.0" +description = "A pytest plugin to run Xvfb for tests." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pytest = ">=2.8.1" +pyvirtualdisplay = ">=1.3" + +[[package]] +name = "pytz" +version = "2022.6" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyvirtualdisplay" +version = "3.0" +description = "python wrapper for Xvfb, Xephyr and Xvnc" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "restructuredtext-lint" +version = "1.4.0" +description = "reStructuredText linter" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +docutils = ">=0.11,<1.0" + +[[package]] +name = "rich" +version = "12.6.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "dev" +optional = false +python-versions = ">=3.6.3,<4.0.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "rstcheck" +version = "6.1.1" +description = "Checks syntax of reStructuredText and code blocks nested within it" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +importlib-metadata = {version = ">=1.6,<5.0", markers = "python_version < \"3.8\""} +rstcheck-core = ">=1.0.2,<2.0.0" +tomli = {version = "*", optional = true, markers = "extra == \"toml\""} +typer = {version = ">=0.4.1,<0.8", extras = ["all"]} +typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["m2r2 (>=0.3.2)", "sphinx", "sphinx-autobuild (==2021.3.14)", "sphinx-click (>=4.0.3,<5.0.0)", "sphinx-rtd-dark-mode (>=1.2.4,<2.0.0)", "sphinx-rtd-theme (<1)", "sphinxcontrib-spelling (>=7.3)"] +sphinx = ["sphinx"] +testing = ["coverage-conditional-plugin (>=0.5)", "coverage[toml] (>=6.0)", "pytest (>=6.0)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.0)", "pytest-sugar (>=0.9.5)"] +toml = ["tomli"] + +[[package]] +name = "rstcheck-core" +version = "1.0.3" +description = "Checks syntax of reStructuredText and code blocks nested within it" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +docutils = ">=0.7,<0.20" +importlib-metadata = {version = ">=1.6,<5.0", markers = "python_version < \"3.8\""} +pydantic = ">=1.2,<2.0" +types-docutils = ">=0.18,<0.20" +typing-extensions = {version = ">=3.7.4,<5.0", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["m2r2 (>=0.3.2)", "sphinx (>=4.0,<6.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-autodoc-typehints (>=1.15)", "sphinx-rtd-dark-mode (>=1.2.4,<2.0.0)", "sphinx-rtd-theme (<1)", "sphinxcontrib-apidoc (>=0.3)", "sphinxcontrib-spelling (>=7.3)"] +sphinx = ["sphinx (>=4.0,<6.0)"] +testing = ["coverage-conditional-plugin (>=0.5)", "coverage[toml] (>=6.0)", "pytest (>=6.0)", "pytest-cov (>=3.0)", "pytest-mock (>=3.7)", "pytest-randomly (>=3.0)", "pytest-sugar (>=0.9.5)"] +toml = ["tomli (>=2.0,<3.0)"] + +[[package]] +name = "setuptools" +version = "65.6.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.0" +description = "Tool to Detect Surrounding Shell" +category = "dev" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sphinx" +version = "4.3.2" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=1.3" +colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.18" +imagesize = "*" +Jinja2 = ">=2.3" +packaging = "*" +Pygments = ">=2.0" +requests = ">=2.5.0" +setuptools = "*" +snowballstemmer = ">=1.1" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.920)", "types-pkg-resources", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.1" +description = "Add a copy button to each of your code cells." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +sphinx = ">=1.8" + +[package.extras] +code-style = ["pre-commit (==2.12.1)"] +rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] + +[[package]] +name = "sphinx-design" +version = "0.3.0" +description = "A sphinx extension for designing beautiful, view size responsive web components." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +sphinx = ">=4,<6" + +[package.extras] +code-style = ["pre-commit (>=2.12,<3.0)"] +rtd = ["myst-parser (>=0.18.0,<0.19.0)"] +testing = ["myst-parser (>=0.18.0,<0.19.0)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"] +theme-furo = ["furo (>=2022.06.04,<2022.07)"] +theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"] +theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"] +theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typer" +version = "0.7.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} +rich = {version = ">=10.11.0,<13.0.0", optional = true, markers = "extra == \"all\""} +shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + +[[package]] +name = "types-docutils" +version = "0.19.1.1" +description = "Typing stubs for docutils" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.13" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.16.2" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.1,<1" +filelock = ">=3.2,<4" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} +platformdirs = ">=2,<3" + +[package.extras] +docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] +testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "packaging (>=20.0)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)"] + +[[package]] +name = "zipp" +version = "3.11.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = ">=3.7,<3.12" +content-hash = "e7a0e0d8a985d0308494d742332e248f11f1c762595ba505d4957bca704354d9" + +[metadata.files] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +astor = [ + {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, + {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +babel = [ + {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, + {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] +black = [ + {file = "black-22.10.0-1fixedarch-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa"}, + {file = "black-22.10.0-1fixedarch-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef"}, + {file = "black-22.10.0-1fixedarch-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6"}, + {file = "black-22.10.0-1fixedarch-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d"}, + {file = "black-22.10.0-1fixedarch-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4"}, + {file = "black-22.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb"}, + {file = "black-22.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7"}, + {file = "black-22.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66"}, + {file = "black-22.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae"}, + {file = "black-22.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b"}, + {file = "black-22.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d"}, + {file = "black-22.10.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650"}, + {file = "black-22.10.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d"}, + {file = "black-22.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff"}, + {file = "black-22.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87"}, + {file = "black-22.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395"}, + {file = "black-22.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0"}, + {file = "black-22.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383"}, + {file = "black-22.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de"}, + {file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"}, + {file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"}, +] +certifi = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] +coverage = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] +darkdetect = [ + {file = "darkdetect-0.7.1-py2.py3-none-any.whl", hash = "sha256:3efe69f8ecd5f1b7f4fbb0d1d93f656b0e493c45cc49222380ffe2a529cbc866"}, + {file = "darkdetect-0.7.1.tar.gz", hash = "sha256:47be3cf5134432ddb616bbffc927237718407914993c82809983e7ccebf49013"}, +] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] +docutils = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] +eradicate = [ + {file = "eradicate-2.1.0-py3-none-any.whl", hash = "sha256:8bfaca181db9227dc88bdbce4d051a9627604c2243e7d85324f6d6ce0fd08bb2"}, + {file = "eradicate-2.1.0.tar.gz", hash = "sha256:aac7384ab25b1bf21c4c012de9b4bf8398945a14c98c911545b2ea50ab558014"}, +] +exceptiongroup = [ + {file = "exceptiongroup-1.0.4-py3-none-any.whl", hash = "sha256:542adf9dea4055530d6e1279602fa5cb11dab2395fa650b8674eaec35fc4a828"}, + {file = "exceptiongroup-1.0.4.tar.gz", hash = "sha256:bd14967b79cd9bdb54d97323216f8fdf533e278df937aa2a90089e7d6e06e5ec"}, +] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +flake8 = [ + {file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"}, + {file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"}, +] +flake8-bugbear = [ + {file = "flake8-bugbear-22.10.27.tar.gz", hash = "sha256:a6708608965c9e0de5fff13904fed82e0ba21ac929fe4896459226a797e11cd5"}, + {file = "flake8_bugbear-22.10.27-py3-none-any.whl", hash = "sha256:6ad0ab754507319060695e2f2be80e6d8977cfcea082293089a9226276bd825d"}, +] +flake8-comprehensions = [ + {file = "flake8-comprehensions-3.10.1.tar.gz", hash = "sha256:412052ac4a947f36b891143430fef4859705af11b2572fbb689f90d372cf26ab"}, + {file = "flake8_comprehensions-3.10.1-py3-none-any.whl", hash = "sha256:d763de3c74bc18a79c039a7ec732e0a1985b0c79309ceb51e56401ad0a2cd44e"}, +] +flake8-docstrings = [ + {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, + {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, +] +flake8-eradicate = [ + {file = "flake8-eradicate-1.4.0.tar.gz", hash = "sha256:3088cfd6717d1c9c6c3ac45ef2e5f5b6c7267f7504d5a74b781500e95cb9c7e1"}, + {file = "flake8_eradicate-1.4.0-py3-none-any.whl", hash = "sha256:e3bbd0871be358e908053c1ab728903c114f062ba596b4d40c852fd18f473d56"}, +] +flake8-pie = [ + {file = "flake8-pie-0.16.0.tar.gz", hash = "sha256:b8dcb7b92706fa33d05d92a4b3e49b7a9fd3f0041849166275b646ba50e515ba"}, + {file = "flake8_pie-0.16.0-py3-none-any.whl", hash = "sha256:24cd7849b0eee22e2328b9e9d2a1dea40013b0a3106864bbadd06a4b05dbb71f"}, +] +flake8-plugin-utils = [ + {file = "flake8-plugin-utils-1.3.2.tar.gz", hash = "sha256:20fa2a8ca2decac50116edb42e6af0a1253ef639ad79941249b840531889c65a"}, + {file = "flake8_plugin_utils-1.3.2-py3-none-any.whl", hash = "sha256:1fe43e3e9acf3a7c0f6b88f5338cad37044d2f156c43cb6b080b5f9da8a76f06"}, +] +flake8-print = [ + {file = "flake8-print-5.0.0.tar.gz", hash = "sha256:76915a2a389cc1c0879636c219eb909c38501d3a43cc8dae542081c9ba48bdf9"}, + {file = "flake8_print-5.0.0-py3-none-any.whl", hash = "sha256:84a1a6ea10d7056b804221ac5e62b1cee1aefc897ce16f2e5c42d3046068f5d8"}, +] +flake8-pyproject = [ + {file = "flake8_pyproject-1.2.1-py3-none-any.whl", hash = "sha256:d2470803b38883881c250c27f361ca4c49e7b8f9e3a17f7d44aa43b6965b2363"}, +] +flake8-pytest-style = [ + {file = "flake8-pytest-style-1.6.0.tar.gz", hash = "sha256:c1175713e9e11b78cd1a035ed0bca0d1e41d09c4af329a952750b61d4194ddac"}, + {file = "flake8_pytest_style-1.6.0-py3-none-any.whl", hash = "sha256:5fedb371a950e9fe0e0e6bfc854be7d99151271208f34cd2cc517681ece27780"}, +] +flake8-return = [ + {file = "flake8-return-1.2.0.tar.gz", hash = "sha256:68dfa56582cd704febd02ad86dcf5df67e38e0836d62f1ceae7930d76d3dd955"}, + {file = "flake8_return-1.2.0-py3-none-any.whl", hash = "sha256:1f07af12954ed03ebe2c2aac2418f78b55374e9929d4956109664588f31582a1"}, +] +flake8-rst-docstrings = [ + {file = "flake8-rst-docstrings-0.2.7.tar.gz", hash = "sha256:2740067ab9237559dd45a3434d8c987792c7b259ca563621a3b95efe201f5382"}, + {file = "flake8_rst_docstrings-0.2.7-py3-none-any.whl", hash = "sha256:5d56075dce360bcc9c6775bfe7cb431aa395de600ca7e8d40580a28d50b2a803"}, +] +flake8-simplify = [ + {file = "flake8_simplify-0.19.3-py3-none-any.whl", hash = "sha256:1057320e9312d75849541fee822900d27bcad05b2405edc84713affee635629e"}, + {file = "flake8_simplify-0.19.3.tar.gz", hash = "sha256:2fb083bf5142a98d9c9554755cf2f56f8926eb4a33eae30c0809041b1546879e"}, +] +identify = [ + {file = "identify-2.5.9-py2.py3-none-any.whl", hash = "sha256:a390fb696e164dbddb047a0db26e57972ae52fbd037ae68797e5ae2f4492485d"}, + {file = "identify-2.5.9.tar.gz", hash = "sha256:906036344ca769539610436e40a684e170c3648b552194980bb7b617a8daeb9f"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, + {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +mccabe = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pathspec = [ + {file = "pathspec-0.10.2-py3-none-any.whl", hash = "sha256:88c2606f2c1e818b978540f73ecc908e13999c6c3a383daf3705652ae79807a5"}, + {file = "pathspec-0.10.2.tar.gz", hash = "sha256:8f6bf73e5758fd365ef5d58ce09ac7c27d2833a8d7da51712eac6e27e35141b0"}, +] +pep8-naming = [ + {file = "pep8-naming-0.13.2.tar.gz", hash = "sha256:93eef62f525fd12a6f8c98f4dcc17fa70baae2f37fa1f73bec00e3e44392fa48"}, + {file = "pep8_naming-0.13.2-py3-none-any.whl", hash = "sha256:59e29e55c478db69cffbe14ab24b5bd2cd615c0413edf790d47d3fb7ba9a4e23"}, +] +platformdirs = [ + {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"}, + {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +pre-commit = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] +pycodestyle = [ + {file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"}, + {file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"}, +] +pydantic = [ + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, +] +pydata-sphinx-theme = [ + {file = "pydata_sphinx_theme-0.11.0-py3-none-any.whl", hash = "sha256:64f1fc4c79a1f066b913541116cd5a7e83e140ba0e983509eedd9e66e1f948c8"}, + {file = "pydata_sphinx_theme-0.11.0.tar.gz", hash = "sha256:5fb1751d68a4b8574f817296e925132835281332616eb2839ddac4ef4503cd99"}, +] +pydocstyle = [ + {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, + {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, +] +pyflakes = [ + {file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"}, + {file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pytest = [ + {file = "pytest-7.2.0-py3-none-any.whl", hash = "sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71"}, + {file = "pytest-7.2.0.tar.gz", hash = "sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"}, +] +pytest-cov = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] +pytest-github-actions-annotate-failures = [ + {file = "pytest-github-actions-annotate-failures-0.1.7.tar.gz", hash = "sha256:c6af8f9d13f1f09ef4c104a30875a4975db131ddbba979c8e48fdc456c8dde1f"}, + {file = "pytest_github_actions_annotate_failures-0.1.7-py2.py3-none-any.whl", hash = "sha256:c4a7346d1d95f731a6b53e9a45f10ca56593978149266dd7526876cce403ea38"}, +] +pytest-mock = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] +pytest-qt = [ + {file = "pytest-qt-4.2.0.tar.gz", hash = "sha256:00a17b586dd530b6d7a9399923a40489ca4a9a309719011175f55dc6b5dc8f41"}, + {file = "pytest_qt-4.2.0-py2.py3-none-any.whl", hash = "sha256:a7659960a1ab2af8fc944655a157ff45d714b80ed7a6af96a4b5bb99ecf40a22"}, +] +pytest-randomly = [ + {file = "pytest-randomly-3.12.0.tar.gz", hash = "sha256:d60c2db71ac319aee0fc6c4110a7597d611a8b94a5590918bfa8583f00caccb2"}, + {file = "pytest_randomly-3.12.0-py3-none-any.whl", hash = "sha256:f4f2e803daf5d1ba036cc22bf4fe9dbbf99389ec56b00e5cba732fb5c1d07fdd"}, +] +pytest-xvfb = [ + {file = "pytest-xvfb-2.0.0.tar.gz", hash = "sha256:c4ba642de05499940db7f65ee111621939be513e3e75c3da9156b7235e2ed8cf"}, + {file = "pytest_xvfb-2.0.0-py3-none-any.whl", hash = "sha256:6d21b46f099c06d6b8b200e73341da3adb73d67e9139c55d617930881779360b"}, +] +pytz = [ + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, +] +pyvirtualdisplay = [ + {file = "PyVirtualDisplay-3.0-py3-none-any.whl", hash = "sha256:40d4b8dfe4b8de8552e28eb367647f311f88a130bf837fe910e7f180d5477f0e"}, + {file = "PyVirtualDisplay-3.0.tar.gz", hash = "sha256:09755bc3ceb6eb725fb07eca5425f43f2358d3bf08e00d2a9b792a1aedd16159"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +restructuredtext-lint = [ + {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, +] +rich = [ + {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, + {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, +] +rstcheck = [ + {file = "rstcheck-6.1.1-py3-none-any.whl", hash = "sha256:edeff9ad0644d12bd250100b677887424193789254c90d95c13375062ee2cbac"}, + {file = "rstcheck-6.1.1.tar.gz", hash = "sha256:8e43485a644e794b8127f8c4868ef62c14ec7919bdda6cb16642157055d32e47"}, +] +rstcheck-core = [ + {file = "rstcheck_core-1.0.3-py3-none-any.whl", hash = "sha256:d75d7df8f15b58e8aafe322d6fb6ef1ac8d12bb563089b0696948a00ee7f601a"}, + {file = "rstcheck_core-1.0.3.tar.gz", hash = "sha256:add19c9a1b97d9087f4b463b49c12cd8a9c03689a255e99089c70a2692f16369"}, +] +setuptools = [ + {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"}, + {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"}, +] +shellingham = [ + {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, + {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +sphinx = [ + {file = "Sphinx-4.3.2-py3-none-any.whl", hash = "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851"}, + {file = "Sphinx-4.3.2.tar.gz", hash = "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c"}, +] +sphinx-copybutton = [ + {file = "sphinx-copybutton-0.5.1.tar.gz", hash = "sha256:366251e28a6f6041514bfb5439425210418d6c750e98d3a695b73e56866a677a"}, + {file = "sphinx_copybutton-0.5.1-py3-none-any.whl", hash = "sha256:0842851b5955087a7ec7fc870b622cb168618ad408dee42692e9a5c97d071da8"}, +] +sphinx-design = [ + {file = "sphinx_design-0.3.0-py3-none-any.whl", hash = "sha256:823c1dd74f31efb3285ec2f1254caefed29d762a40cd676f58413a1e4ed5cc96"}, + {file = "sphinx_design-0.3.0.tar.gz", hash = "sha256:7183fa1fae55b37ef01bda5125a21ee841f5bbcbf59a35382be598180c4cefba"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typer = [ + {file = "typer-0.7.0-py3-none-any.whl", hash = "sha256:b5e704f4e48ec263de1c0b3a2387cd405a13767d2f907f44c1a08cbad96f606d"}, + {file = "typer-0.7.0.tar.gz", hash = "sha256:ff797846578a9f2a201b53442aedeb543319466870fbe1c701eab66dd7681165"}, +] +types-docutils = [ + {file = "types-docutils-0.19.1.1.tar.gz", hash = "sha256:be0a51ba1c7dd215d9d2df66d6845e63c1009b4bbf4c5beb87a0d9745cdba962"}, + {file = "types_docutils-0.19.1.1-py3-none-any.whl", hash = "sha256:a024cada35f0c13cc45eb0b68a102719018a634013690b7fef723bcbfadbd1f1"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +urllib3 = [ + {file = "urllib3-1.26.13-py2.py3-none-any.whl", hash = "sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc"}, + {file = "urllib3-1.26.13.tar.gz", hash = "sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8"}, +] +virtualenv = [ + {file = "virtualenv-20.16.2-py2.py3-none-any.whl", hash = "sha256:635b272a8e2f77cb051946f46c60a54ace3cb5e25568228bd6b57fc70eca9ff3"}, + {file = "virtualenv-20.16.2.tar.gz", hash = "sha256:0ef5be6d07181946891f5abc8047fda8bc2f0b4b9bf222c64e6e8963baee76db"}, +] +zipp = [ + {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, + {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, +] diff --git a/pyproject.toml b/pyproject.toml index a1770a32..fb93d64d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,101 +1,101 @@ -[tool.poetry] -name = "PyQtDarkTheme" -version = "2.1.0" -description = "Flat dark theme for PySide and PyQt." -authors = ["Yunosuke Ohsugi <63651161+5yutan5@users.noreply.github.com>"] -license = "MIT" -readme = "README.md" -repository = "https://github.com/5yutan5/PyQtDarkTheme" -homepage = "https://github.com/5yutan5/PyQtDarkTheme" -documentation = "https://pyqtdarktheme.readthedocs.io" -packages = [{ include = "qdarktheme" }] - -keywords = ["qt", "stylesheets", "dark-theme"] - -classifiers = [ - "Topic :: Software Development :: Libraries :: Python Modules", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Environment :: X11 Applications :: Qt", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", -] - -[tool.poetry.dependencies] -python = ">=3.7,<3.12" -darkdetect = "^0.7.1" - -[tool.poetry.group.dev.dependencies] -black = "^22.10.0" -isort = "^5.10.1" -pre-commit = "^2.20.0" - -[tool.poetry.group.flake8.dependencies] -flake8 = "^5.0.4" -flake8-return = "^1.1.3" -flake8-pie = "^0.16.0" -flake8-print = "^5.0.0" -flake8-docstrings = "^1.6.0" -flake8-bugbear = "^22.10.25" -flake8-simplify = "^0.19.3" -flake8-comprehensions = "^3.10.0" -flake8-eradicate = "^1.4.0" -flake8-rst-docstrings = "^0.2.7" -flake8-pyproject = "^1.2.1" -flake8-pytest-style = "^1.6.0" -pep8-naming = "^0.13.2" - -[tool.poetry.group.test.dependencies] -pytest = "^7.2.0" -pytest-cov = "^4.0.0" -pytest-randomly = "^3.12.0" -pytest-qt = "^4.2.0" -pytest-xvfb = "^2.0.0" -pytest-mock = "^3.10.0" - -[tool.poetry.group.docs.dependencies] -# Latest of Sphinx and flake8 cause dependency comflict. -# Temporarily keep the Sphinx at v4.x. -# Latest Sphinx version is v5.x. -Sphinx = "^4.3.1" -rstcheck = {extras = ["toml"], version = "^6.1.0"} -pydata-sphinx-theme = "^0.11.0" -sphinx-design = "^0.3.0" -sphinx-copybutton = "^0.5.0" - -[tool.poetry.group.github-actions] -# only used in github actions -optional = true - -[tool.poetry.group.github-actions.dependencies] -pytest-github-actions-annotate-failures = "^0.1.7" - -[build-system] -requires = ["poetry-core>=1.0.0"] -build-backend = "poetry.core.masonry.api" - -[tool.flake8] -max-line-length = 104 -max-complexity = 7 -docstring-convention = "google" -per-file-ignores = ["**/__init__.py:F401", "examples/**:D103,D100"] - -# See https://github.com/peterjc/flake8-rst-docstrings#configuration -extend-ignore = ["RST201", "RST203", "RST301"] - -[tool.black] -line-length = 104 - -[tool.isort] -profile = "black" -line_length = 104 - -[tool.pyright] -typeCheckingMode = "basic" -ignore = ["examples"] +[tool.poetry] +name = "PyQtDarkTheme" +version = "2.1.0" +description = "Flat dark theme for PySide and PyQt." +authors = ["Yunosuke Ohsugi <63651161+5yutan5@users.noreply.github.com>"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/5yutan5/PyQtDarkTheme" +homepage = "https://github.com/5yutan5/PyQtDarkTheme" +documentation = "https://pyqtdarktheme.readthedocs.io" +packages = [{ include = "qdarktheme" }] + +keywords = ["qt", "stylesheets", "dark-theme"] + +classifiers = [ + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Environment :: X11 Applications :: Qt", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + +[tool.poetry.dependencies] +python = ">=3.7,<3.12" +darkdetect = "^0.7.1" + +[tool.poetry.group.dev.dependencies] +black = "^22.10.0" +isort = "^5.10.1" +pre-commit = "^2.20.0" + +[tool.poetry.group.flake8.dependencies] +flake8 = "^5.0.4" +flake8-return = "^1.1.3" +flake8-pie = "^0.16.0" +flake8-print = "^5.0.0" +flake8-docstrings = "^1.6.0" +flake8-bugbear = "^22.10.25" +flake8-simplify = "^0.19.3" +flake8-comprehensions = "^3.10.0" +flake8-eradicate = "^1.4.0" +flake8-rst-docstrings = "^0.2.7" +flake8-pyproject = "^1.2.1" +flake8-pytest-style = "^1.6.0" +pep8-naming = "^0.13.2" + +[tool.poetry.group.test.dependencies] +pytest = "^7.2.0" +pytest-cov = "^4.0.0" +pytest-randomly = "^3.12.0" +pytest-qt = "^4.2.0" +pytest-xvfb = "^2.0.0" +pytest-mock = "^3.10.0" + +[tool.poetry.group.docs.dependencies] +# Latest of Sphinx and flake8 cause dependency comflict. +# Temporarily keep the Sphinx at v4.x. +# Latest Sphinx version is v5.x. +Sphinx = "^4.3.1" +rstcheck = {extras = ["toml"], version = "^6.1.0"} +pydata-sphinx-theme = "^0.11.0" +sphinx-design = "^0.3.0" +sphinx-copybutton = "^0.5.0" + +[tool.poetry.group.github-actions] +# only used in github actions +optional = true + +[tool.poetry.group.github-actions.dependencies] +pytest-github-actions-annotate-failures = "^0.1.7" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.flake8] +max-line-length = 104 +max-complexity = 7 +docstring-convention = "google" +per-file-ignores = ["**/__init__.py:F401", "examples/**:D103,D100"] + +# See https://github.com/peterjc/flake8-rst-docstrings#configuration +extend-ignore = ["RST201", "RST203", "RST301"] + +[tool.black] +line-length = 104 + +[tool.isort] +profile = "black" +line_length = 104 + +[tool.pyright] +typeCheckingMode = "basic" +ignore = ["examples"] diff --git a/qdarktheme/__init__.py b/qdarktheme/__init__.py index db948a64..d7f1ecc7 100644 --- a/qdarktheme/__init__.py +++ b/qdarktheme/__init__.py @@ -1,46 +1,46 @@ -"""PyQtDarkTheme - A flat dark theme for PySide and PyQt. - -- Repository: https://github.com/5yutan5/PyQtDarkTheme -- Documentation: https://pyqtdarktheme.readthedocs.io - - -License Information -=================== - -Material design icons ---------------------- - -All svg files in PyQtDarkTheme is from Material design icons(which uses an Apache 2.0 license). - -- Author: Google -- Site: https://fonts.google.com/icons -- Source: https://github.com/google/material-design-icons -- License: Apache License Version 2.0 | https://www.apache.org/licenses/LICENSE-2.0.txt - -Modifications made to each files to change the icon color and angle and remove svg namespace. - -The current Material design icons license summary can be viewed at: -https://github.com/google/material-design-icons/blob/master/LICENSE - - -QDarkStyleSheet(Source code) ----------------------------- - -Qt stylesheets are originally fork of QDarkStyleSheet(MIT License). - -- Author: Colin Duquesnoy -- Site: https://github.com/ColinDuquesnoy/QDarkStyleSheet -- Source: https://github.com/ColinDuquesnoy/QDarkStyleSheet -- License: MIT License | https://opensource.org/licenses/MIT - -Modifications made to a file to change the style. - -The current QDarkStyleSheet license summary can be viewed at: -https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/LICENSE.rst - -""" -# Version of PyQtDarkTheme -__version__ = "2.1.0" - -from qdarktheme._main import enable_hi_dpi, setup_theme, stop_sync -from qdarktheme._style_loader import clear_cache, get_themes, load_palette, load_stylesheet +"""PyQtDarkTheme - A flat dark theme for PySide and PyQt. + +- Repository: https://github.com/5yutan5/PyQtDarkTheme +- Documentation: https://pyqtdarktheme.readthedocs.io + + +License Information +=================== + +Material design icons +--------------------- + +All svg files in PyQtDarkTheme is from Material design icons(which uses an Apache 2.0 license). + +- Author: Google +- Site: https://fonts.google.com/icons +- Source: https://github.com/google/material-design-icons +- License: Apache License Version 2.0 | https://www.apache.org/licenses/LICENSE-2.0.txt + +Modifications made to each files to change the icon color and angle and remove svg namespace. + +The current Material design icons license summary can be viewed at: +https://github.com/google/material-design-icons/blob/master/LICENSE + + +QDarkStyleSheet(Source code) +---------------------------- + +Qt stylesheets are originally fork of QDarkStyleSheet(MIT License). + +- Author: Colin Duquesnoy +- Site: https://github.com/ColinDuquesnoy/QDarkStyleSheet +- Source: https://github.com/ColinDuquesnoy/QDarkStyleSheet +- License: MIT License | https://opensource.org/licenses/MIT + +Modifications made to a file to change the style. + +The current QDarkStyleSheet license summary can be viewed at: +https://github.com/ColinDuquesnoy/QDarkStyleSheet/blob/master/LICENSE.rst + +""" +# Version of PyQtDarkTheme +__version__ = "2.1.0" + +from qdarktheme._main import enable_hi_dpi, setup_theme, stop_sync +from qdarktheme._style_loader import clear_cache, get_themes, load_palette, load_stylesheet diff --git a/qdarktheme/_color.py b/qdarktheme/_color.py index 5a296026..dc256e28 100644 --- a/qdarktheme/_color.py +++ b/qdarktheme/_color.py @@ -1,231 +1,231 @@ -"""Module for color code.""" -from __future__ import annotations - -import colorsys -import math - - -def _round_float(number: float, decimal_points: int = 3) -> float: - decimal = 10**decimal_points - return round(number * decimal) / decimal - - -class _RGBA: - """Class handling RGBA color code.""" - - def __init__(self, r: int, g: int, b: int, a: float = 1) -> None: - """Initialize rgba value. - - Args: - r: Red(0~255). - g: Green(0~255). - b: Blue(0~255). - a: Alpha(0~1). Defaults to 1. - """ - self._r = min(255, max(0, r)) | 0 - self._g = min(255, max(0, g)) | 0 - self._b = min(255, max(0, b)) | 0 - self._a = _round_float(max(min(1, a), 0)) - - def __str__(self) -> str: - """Format RGBA class. - - e.g. rgba(100, 100, 100, 0.5). - """ - return f"rgba({self.r}, {self.g}, {self.b}, {self.a:.3f})" - - def __getitem__(self, item: int) -> int | float: - """Unpack to (r, g, b, a).""" - return [self.r, self.g, self.b, self.a][item] - - def __eq__(self, other: _RGBA) -> bool: - """Returns true if `r`, `g`, `b` and `a` are all the same.""" - return [self.r, self.g, self.b, self.a] == [other.r, other.g, other.b, other.a] - - @property - def r(self) -> int: - return self._r - - @property - def g(self) -> int: - return self._g - - @property - def b(self) -> int: - return self._b - - @property - def a(self) -> float: - return self._a - - -class _HSLA: - def __init__(self, h: int, s: float, l: float, a: float = 1) -> None: # noqa: E741 - self._h = max(min(360, h), 0) | 0 - self._s = _round_float(max(min(1, s), 0)) - self._l = _round_float(max(min(1, l), 0)) - self._a = _round_float(max(min(1, a), 0)) - - def __eq__(self, other: _HSLA) -> bool: - """Returns true if `h`, `s`, `l` and `a` are all the same.""" - return [self.h, self.s, self.l, self.a] == [other.h, other.s, other.l, other.a] - - @property - def h(self) -> int: - return self._h - - @property - def s(self) -> float: - return self._s - - @property - def l(self) -> float: # noqa: E741, E743 - return self._l - - @property - def a(self) -> float: - return self._a - - @staticmethod - def from_rgba(rgba: _RGBA) -> _HSLA: - hls = colorsys.rgb_to_hls(rgba.r / 255, rgba.g / 255, rgba.b / 255) - return _HSLA(int(hls[0] * 360), hls[2], hls[1], rgba.a) - - def to_rgba(self) -> _RGBA: - rgb = colorsys.hls_to_rgb(self.h / 360, self.l, self.s) - return _RGBA(round(rgb[0] * 255), round(rgb[1] * 255), round(rgb[2] * 255), self.a) - - -class Color: - """Class handling color code(RGBA and HSLA).""" - - def __init__(self, color_code: _RGBA | _HSLA) -> None: - """Initialize color code.""" - self._hsla, self._hsva = None, None - if isinstance(color_code, _RGBA): - self._rgba = color_code - elif isinstance(color_code, _HSLA): - self._hsla = color_code - self._rgba = self._hsla.to_rgba() - - @property - def rgba(self) -> _RGBA: - """Return rgba.""" - return self._rgba - - @property - def hsla(self) -> _HSLA: - """Return hsla.""" - return self._hsla if self._hsla else _HSLA.from_rgba(self.rgba) - - def __str__(self) -> str: - """Format Color class. - - e.g. rgba(100, 100, 100, 0.5). - """ - return str(self.rgba) - - @staticmethod - def _check_hex_format(hex_format: str) -> None: - """Check if string is hex format.""" - try: - hex = hex_format.lstrip("#") - if not len(hex) in (3, 4, 6, 8): - raise ValueError - int(hex, 16) - except ValueError: - raise ValueError( - f'invalid hex color format: "{hex_format}". ' - "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " - "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " - "(0-9, a-f or A-F)." - ) from None - - @staticmethod - def from_rgba(r: int, g: int, b: int, a: int) -> Color: - """Convert rgba to Color object.""" - rgba = _RGBA(r, g, b, a / 255) - return Color(rgba) - - @staticmethod - def from_hex(hex: str) -> Color: - """Convert hex string to Color object. - - Args: - color_hex: Color hex string. - - Returns: - Color: Color object converted from hex. - """ - Color._check_hex_format(hex) - hex = hex.lstrip("#") - r, g, b, a = 255, 0, 0, 1 - if len(hex) == 3: # #RGB format - r, g, b = (int(char, 16) for char in hex) - r, g, b = 16 * r + r, 16 * g + g, 16 * b + b - if len(hex) == 4: # #RGBA format - r, g, b, a = (int(char, 16) for char in hex) - r, g, b = 16 * r + r, 16 * g + g, 16 * b + b - a = (16 * a + a) / 255 - if len(hex) == 6: # #RRGGBB format - r, g, b = bytes.fromhex(hex) - a = 1 - elif len(hex) == 8: # #RRGGBBAA format - r, g, b, a = bytes.fromhex(hex) - a = a / 255 - return Color(_RGBA(r, g, b, a)) - - def _to_hex(self) -> str: - """Convert Color object to hex(#RRGGBBAA). - - Args: - color: Color object. - - Returns: - str: Hex converted from Color object. - """ - r, g, b, a = self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a - hex_color = f"{math.floor(r):02x}{math.floor(g):02x}{math.floor(b):02x}" - if a != 1: - hex_color += f"{math.floor(a*255):02x}" - return hex_color - - def to_hex_argb(self) -> str: - """Convert Color object to hex(#AARRGGBB). - - Args: - color: Color object. - - Returns: - str: Hex converted from Color object. - """ - r, g, b, a = self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a - hex_color = "" if a == 1 else f"{math.floor(a*255):02x}" - hex_color += f"{math.floor(r):02x}{math.floor(g):02x}{math.floor(b):02x}" - return hex_color - - def to_svg_tiny_color_format(self) -> str: - """Convert Color object to string for svg. - - QtSvg does not support #RRGGBBAA format. - Therefore, we need to set the alpha value to `fill-opacity` instead. - - Returns: - str: RGBA format. - """ - r, g, b, a = self.rgba - if a == 1: - return f'fill="#{self._to_hex()}"' - return f'fill="rgb({r},{g},{b})" fill-opacity="{a}"' - - def lighten(self, factor: float) -> Color: - """Lighten color.""" - return Color(_HSLA(self.hsla.h, self.hsla.s, self.hsla.l + self.hsla.l * factor, self.hsla.a)) - - def darken(self, factor: float) -> Color: - """Darken color.""" - return Color(_HSLA(self.hsla.h, self.hsla.s, self.hsla.l - self.hsla.l * factor, self.hsla.a)) - - def transparent(self, factor: float) -> Color: - """Make color transparent.""" - return Color(_RGBA(self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a * factor)) +"""Module for color code.""" +from __future__ import annotations + +import colorsys +import math + + +def _round_float(number: float, decimal_points: int = 3) -> float: + decimal = 10**decimal_points + return round(number * decimal) / decimal + + +class _RGBA: + """Class handling RGBA color code.""" + + def __init__(self, r: int, g: int, b: int, a: float = 1) -> None: + """Initialize rgba value. + + Args: + r: Red(0~255). + g: Green(0~255). + b: Blue(0~255). + a: Alpha(0~1). Defaults to 1. + """ + self._r = min(255, max(0, r)) | 0 + self._g = min(255, max(0, g)) | 0 + self._b = min(255, max(0, b)) | 0 + self._a = _round_float(max(min(1, a), 0)) + + def __str__(self) -> str: + """Format RGBA class. + + e.g. rgba(100, 100, 100, 0.5). + """ + return f"rgba({self.r}, {self.g}, {self.b}, {self.a:.3f})" + + def __getitem__(self, item: int) -> int | float: + """Unpack to (r, g, b, a).""" + return [self.r, self.g, self.b, self.a][item] + + def __eq__(self, other: _RGBA) -> bool: + """Returns true if `r`, `g`, `b` and `a` are all the same.""" + return [self.r, self.g, self.b, self.a] == [other.r, other.g, other.b, other.a] + + @property + def r(self) -> int: + return self._r + + @property + def g(self) -> int: + return self._g + + @property + def b(self) -> int: + return self._b + + @property + def a(self) -> float: + return self._a + + +class _HSLA: + def __init__(self, h: int, s: float, l: float, a: float = 1) -> None: # noqa: E741 + self._h = max(min(360, h), 0) | 0 + self._s = _round_float(max(min(1, s), 0)) + self._l = _round_float(max(min(1, l), 0)) + self._a = _round_float(max(min(1, a), 0)) + + def __eq__(self, other: _HSLA) -> bool: + """Returns true if `h`, `s`, `l` and `a` are all the same.""" + return [self.h, self.s, self.l, self.a] == [other.h, other.s, other.l, other.a] + + @property + def h(self) -> int: + return self._h + + @property + def s(self) -> float: + return self._s + + @property + def l(self) -> float: # noqa: E741, E743 + return self._l + + @property + def a(self) -> float: + return self._a + + @staticmethod + def from_rgba(rgba: _RGBA) -> _HSLA: + hls = colorsys.rgb_to_hls(rgba.r / 255, rgba.g / 255, rgba.b / 255) + return _HSLA(int(hls[0] * 360), hls[2], hls[1], rgba.a) + + def to_rgba(self) -> _RGBA: + rgb = colorsys.hls_to_rgb(self.h / 360, self.l, self.s) + return _RGBA(round(rgb[0] * 255), round(rgb[1] * 255), round(rgb[2] * 255), self.a) + + +class Color: + """Class handling color code(RGBA and HSLA).""" + + def __init__(self, color_code: _RGBA | _HSLA) -> None: + """Initialize color code.""" + self._hsla, self._hsva = None, None + if isinstance(color_code, _RGBA): + self._rgba = color_code + elif isinstance(color_code, _HSLA): + self._hsla = color_code + self._rgba = self._hsla.to_rgba() + + @property + def rgba(self) -> _RGBA: + """Return rgba.""" + return self._rgba + + @property + def hsla(self) -> _HSLA: + """Return hsla.""" + return self._hsla if self._hsla else _HSLA.from_rgba(self.rgba) + + def __str__(self) -> str: + """Format Color class. + + e.g. rgba(100, 100, 100, 0.5). + """ + return str(self.rgba) + + @staticmethod + def _check_hex_format(hex_format: str) -> None: + """Check if string is hex format.""" + try: + hex = hex_format.lstrip("#") + if not len(hex) in (3, 4, 6, 8): + raise ValueError + int(hex, 16) + except ValueError: + raise ValueError( + f'invalid hex color format: "{hex_format}". ' + "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " + "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " + "(0-9, a-f or A-F)." + ) from None + + @staticmethod + def from_rgba(r: int, g: int, b: int, a: int) -> Color: + """Convert rgba to Color object.""" + rgba = _RGBA(r, g, b, a / 255) + return Color(rgba) + + @staticmethod + def from_hex(hex: str) -> Color: + """Convert hex string to Color object. + + Args: + color_hex: Color hex string. + + Returns: + Color: Color object converted from hex. + """ + Color._check_hex_format(hex) + hex = hex.lstrip("#") + r, g, b, a = 255, 0, 0, 1 + if len(hex) == 3: # #RGB format + r, g, b = (int(char, 16) for char in hex) + r, g, b = 16 * r + r, 16 * g + g, 16 * b + b + if len(hex) == 4: # #RGBA format + r, g, b, a = (int(char, 16) for char in hex) + r, g, b = 16 * r + r, 16 * g + g, 16 * b + b + a = (16 * a + a) / 255 + if len(hex) == 6: # #RRGGBB format + r, g, b = bytes.fromhex(hex) + a = 1 + elif len(hex) == 8: # #RRGGBBAA format + r, g, b, a = bytes.fromhex(hex) + a = a / 255 + return Color(_RGBA(r, g, b, a)) + + def _to_hex(self) -> str: + """Convert Color object to hex(#RRGGBBAA). + + Args: + color: Color object. + + Returns: + str: Hex converted from Color object. + """ + r, g, b, a = self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a + hex_color = f"{math.floor(r):02x}{math.floor(g):02x}{math.floor(b):02x}" + if a != 1: + hex_color += f"{math.floor(a*255):02x}" + return hex_color + + def to_hex_argb(self) -> str: + """Convert Color object to hex(#AARRGGBB). + + Args: + color: Color object. + + Returns: + str: Hex converted from Color object. + """ + r, g, b, a = self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a + hex_color = "" if a == 1 else f"{math.floor(a*255):02x}" + hex_color += f"{math.floor(r):02x}{math.floor(g):02x}{math.floor(b):02x}" + return hex_color + + def to_svg_tiny_color_format(self) -> str: + """Convert Color object to string for svg. + + QtSvg does not support #RRGGBBAA format. + Therefore, we need to set the alpha value to `fill-opacity` instead. + + Returns: + str: RGBA format. + """ + r, g, b, a = self.rgba + if a == 1: + return f'fill="#{self._to_hex()}"' + return f'fill="rgb({r},{g},{b})" fill-opacity="{a}"' + + def lighten(self, factor: float) -> Color: + """Lighten color.""" + return Color(_HSLA(self.hsla.h, self.hsla.s, self.hsla.l + self.hsla.l * factor, self.hsla.a)) + + def darken(self, factor: float) -> Color: + """Darken color.""" + return Color(_HSLA(self.hsla.h, self.hsla.s, self.hsla.l - self.hsla.l * factor, self.hsla.a)) + + def transparent(self, factor: float) -> Color: + """Make color transparent.""" + return Color(_RGBA(self.rgba.r, self.rgba.g, self.rgba.b, self.rgba.a * factor)) diff --git a/qdarktheme/_icon/icon_engine.py b/qdarktheme/_icon/icon_engine.py index 720960f4..d1918eda 100644 --- a/qdarktheme/_icon/icon_engine.py +++ b/qdarktheme/_icon/icon_engine.py @@ -1,56 +1,56 @@ -from qdarktheme._color import Color -from qdarktheme._icon.svg import Svg -from qdarktheme.qtpy.QtCore import QPoint, QRect, QRectF, QSize, Qt -from qdarktheme.qtpy.QtGui import ( - QGuiApplication, - QIcon, - QIconEngine, - QImage, - QPainter, - QPalette, - QPixmap, -) -from qdarktheme.qtpy.QtSvg import QSvgRenderer - - -class SvgIconEngine(QIconEngine): - """A custom QIconEngine that can render an SVG buffer.""" - - def __init__(self, svg: Svg) -> None: - """Initialize icon engine.""" - super().__init__() - self._svg = svg - - def paint(self, painter: QPainter, rect: QRect, mode: QIcon.Mode, state): - """Paint the icon int ``rect`` using ``painter``.""" - palette = QGuiApplication.palette() - - if mode == QIcon.Mode.Disabled: - rgba = palette.color(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text).getRgb() - color = Color.from_rgba(*rgba) - else: - rgba = palette.text().color().getRgb() - color = Color.from_rgba(*rgba) - self._svg.colored(color) - - svg_byte = str(self._svg).encode("utf-8") - renderer = QSvgRenderer(svg_byte) # type: ignore - renderer.render(painter, QRectF(rect)) - - def clone(self): - """Required to subclass abstract QIconEngine.""" - return SvgIconEngine(self._svg) - - def pixmap(self, size: QSize, mode: QIcon.Mode, state: QIcon.State): - """Return the icon as a pixmap with requested size, mode, and state.""" - # Make size to square. - min_size = min(size.width(), size.height()) - size.setHeight(min_size) - size.setWidth(min_size) - - img = QImage(size, QImage.Format.Format_ARGB32) - img.fill(Qt.GlobalColor.transparent) - pixmap = QPixmap.fromImage(img, Qt.ImageConversionFlag.NoFormatConversion) - size.width() - self.paint(QPainter(pixmap), QRect(QPoint(0, 0), size), mode, state) - return pixmap +from qdarktheme._color import Color +from qdarktheme._icon.svg import Svg +from qdarktheme.qtpy.QtCore import QPoint, QRect, QRectF, QSize, Qt +from qdarktheme.qtpy.QtGui import ( + QGuiApplication, + QIcon, + QIconEngine, + QImage, + QPainter, + QPalette, + QPixmap, +) +from qdarktheme.qtpy.QtSvg import QSvgRenderer + + +class SvgIconEngine(QIconEngine): + """A custom QIconEngine that can render an SVG buffer.""" + + def __init__(self, svg: Svg) -> None: + """Initialize icon engine.""" + super().__init__() + self._svg = svg + + def paint(self, painter: QPainter, rect: QRect, mode: QIcon.Mode, state): + """Paint the icon int ``rect`` using ``painter``.""" + palette = QGuiApplication.palette() + + if mode == QIcon.Mode.Disabled: + rgba = palette.color(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text).getRgb() + color = Color.from_rgba(*rgba) + else: + rgba = palette.text().color().getRgb() + color = Color.from_rgba(*rgba) + self._svg.colored(color) + + svg_byte = str(self._svg).encode("utf-8") + renderer = QSvgRenderer(svg_byte) # type: ignore + renderer.render(painter, QRectF(rect)) + + def clone(self): + """Required to subclass abstract QIconEngine.""" + return SvgIconEngine(self._svg) + + def pixmap(self, size: QSize, mode: QIcon.Mode, state: QIcon.State): + """Return the icon as a pixmap with requested size, mode, and state.""" + # Make size to square. + min_size = min(size.width(), size.height()) + size.setHeight(min_size) + size.setWidth(min_size) + + img = QImage(size, QImage.Format.Format_ARGB32) + img.fill(Qt.GlobalColor.transparent) + pixmap = QPixmap.fromImage(img, Qt.ImageConversionFlag.NoFormatConversion) + size.width() + self.paint(QPainter(pixmap), QRect(QPoint(0, 0), size), mode, state) + return pixmap diff --git a/qdarktheme/_icon/svg.py b/qdarktheme/_icon/svg.py index a9241c07..10fb4aff 100644 --- a/qdarktheme/_icon/svg.py +++ b/qdarktheme/_icon/svg.py @@ -1,75 +1,75 @@ -from __future__ import annotations - -import json -import re -from functools import lru_cache - -from qdarktheme import _resources -from qdarktheme._color import Color - - -@lru_cache() -def _svg_resources() -> dict[str, str]: - return json.loads(_resources.svg.SVG_RESOURCES) - - -class Svg: - """Class to manage SVG.""" - - _SVG_FILL_RE = re.compile(r'fill=".*?"') - _SVG_FILL_OPACITY_RE = re.compile(r'fill-opacity=".*?"') - _SVG_TRANSFORM_RE = re.compile(r'transform=".*?"') - - def __init__(self, id: str) -> None: - """Initialize svg manager.""" - self._id = id - self._color = None - self._rotate = None - self._source = _svg_resources()[self._id] - - def __str__(self) -> str: - """Return the svg source code.""" - return self._source - - def colored(self, color: Color) -> Svg: - """Add or change svg color.""" - svg_tiny_color_formats = color.to_svg_tiny_color_format().split(" ") - if len(svg_tiny_color_formats) == 2: - new_svg_color, new_svg_opacity = svg_tiny_color_formats - else: - new_svg_color = svg_tiny_color_formats[0] - new_svg_opacity = None - - current_svg_color = Svg._SVG_FILL_RE.search(self._source) - current_svg_opacity = Svg._SVG_FILL_OPACITY_RE.search(self._source) - - # Add or change SVG color. - if current_svg_color is None: - self._source = self._source.replace(" Svg: - """Rotate svg.""" - if rotate == 0: - return self - - current_svg_transform = Svg._SVG_TRANSFORM_RE.search(self._source) - new_svg_transform = f'transform="rotate({rotate}, 12, 12)"' - if current_svg_transform is None: - self._source = self._source.replace(" dict[str, str]: + return json.loads(_resources.svg.SVG_RESOURCES) + + +class Svg: + """Class to manage SVG.""" + + _SVG_FILL_RE = re.compile(r'fill=".*?"') + _SVG_FILL_OPACITY_RE = re.compile(r'fill-opacity=".*?"') + _SVG_TRANSFORM_RE = re.compile(r'transform=".*?"') + + def __init__(self, id: str) -> None: + """Initialize svg manager.""" + self._id = id + self._color = None + self._rotate = None + self._source = _svg_resources()[self._id] + + def __str__(self) -> str: + """Return the svg source code.""" + return self._source + + def colored(self, color: Color) -> Svg: + """Add or change svg color.""" + svg_tiny_color_formats = color.to_svg_tiny_color_format().split(" ") + if len(svg_tiny_color_formats) == 2: + new_svg_color, new_svg_opacity = svg_tiny_color_formats + else: + new_svg_color = svg_tiny_color_formats[0] + new_svg_opacity = None + + current_svg_color = Svg._SVG_FILL_RE.search(self._source) + current_svg_opacity = Svg._SVG_FILL_OPACITY_RE.search(self._source) + + # Add or change SVG color. + if current_svg_color is None: + self._source = self._source.replace(" Svg: + """Rotate svg.""" + if rotate == 0: + return self + + current_svg_transform = Svg._SVG_TRANSFORM_RE.search(self._source) + new_svg_transform = f'transform="rotate({rotate}, 12, 12)"' + if current_svg_transform is None: + self._source = self._source.replace(" None: - from qdarktheme._proxy_style import QDarkThemeStyle - - stylesheet = load_stylesheet(**kargs) - if additional_qss is not None: - stylesheet += additional_qss - app.setStyleSheet(stylesheet) - - app.setPalette( - load_palette( - kargs["theme"], - kargs["custom_colors"], - for_stylesheet=True, - default_theme=kargs["default_theme"], - ) - ) - - global _proxy_style - if _proxy_style is None: - _proxy_style = QDarkThemeStyle() - app.setStyle(_proxy_style) - - -def _sync_theme_with_system(app, callback) -> None: - from qdarktheme._os_appearance import listener - - global _listener - if _listener is not None: - _listener.sig_run.emit(True) - return - - _listener = listener.OSThemeSwitchListener(callback) - - if platform.system() == "Darwin": - app.installEventFilter(_listener) - else: - atexit.register(_listener.kill) - _listener.start() - - -def enable_hi_dpi() -> None: - """Allow to HiDPI. - - This function must be set before instantiation of QApplication.. - For Qt6 bindings, HiDPI “just works” without using this function. - """ - from qdarktheme.qtpy.QtCore import Qt - from qdarktheme.qtpy.QtGui import QGuiApplication - - if hasattr(Qt.ApplicationAttribute, "AA_UseHighDpiPixmaps"): - QGuiApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore - if hasattr(Qt.ApplicationAttribute, "AA_EnableHighDpiScaling"): - QGuiApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) # type: ignore - if hasattr(Qt, "HighDpiScaleFactorRoundingPolicy"): - os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" - QGuiApplication.setHighDpiScaleFactorRoundingPolicy( - Qt.HighDpiScaleFactorRoundingPolicy.PassThrough - ) - - -def stop_sync() -> None: - """Stop sync with system theme.""" - from qdarktheme.qtpy.QtCore import QCoreApplication - - app = QCoreApplication.instance() - global _listener - if not app or not _listener: - return - _listener.sig_run.emit(False) - - -def setup_theme( - theme: str = "dark", - corner_shape: str = "rounded", - custom_colors: dict[str, str | dict[str, str]] | None = None, - additional_qss: str | None = None, - *, - default_theme: str = "dark", -) -> None: - """Apply the theme which looks like flat design to the Qt App completely. - - This function applies the complete style to your Qt application. If the argument theme is ``auto``, - try to listen to changes to the OS's theme and switch the application theme accordingly. - - Args: - theme: The theme name. There are `dark`, `light` and `auto`. - If ``auto``, try to sync with your OS's theme and accent (accent is only on Mac). - If failed to detect OS's theme, use the default theme set in argument ``default_theme``. - When primary color(``primary``) or primary child colors - (such as ``primary>selection.background``) are set to custom_colors, - disable to sync with the accent. - corner_shape: The corner shape. There are `rounded` and `sharp` shape. - custom_colors: The custom color map. Overrides the default color for color id you set. - Also you can customize a specific theme only. See example 5. - additional_qss: Additional stylesheet text. You can add your original stylesheet text. - default_theme: The default theme name. - The theme set by this argument will be used when system theme detection fails. - - Raises: - ValueError: If the argument is wrong. - KeyError: If the color id of custom_colors is wrong. - - Returns: - The stylesheet string for the given arguments. - - Examples: - Set stylesheet to your Qt application. - - 1. Setup style and sync to system theme :: - - app = QApplication([]) - qdarktheme.setup_theme() - - 2. Use Dark Theme :: - - app = QApplication([]) - qdarktheme.setup_theme("dark") - - 3. Sharp corner :: - - # Change corner shape to sharp. - app = QApplication([]) - qdarktheme.setup_theme(corner_shape="sharp") - - 4. Customize color :: - - app = QApplication([]) - qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) - - 5. Customize a specific theme only :: - - app = QApplication([]) - qdarktheme.setup_theme( - theme="auto", - custom_colors={ - "[dark]": { - "primary": "#D0BCFF", - } - }, - ) - """ - from qdarktheme.qtpy.QtCore import QCoreApplication - - app = QCoreApplication.instance() - if not app: - raise Exception("setup_theme() must be called after instantiation of QApplication.") - if theme != "auto": - stop_sync() - app.setProperty("_qdarktheme_use_setup_style", True) - - def callback(): - _apply_style( - app, - additional_qss, - theme=theme, - corner_shape=corner_shape, - custom_colors=custom_colors, - default_theme=default_theme, - ) - - callback() - - if theme == "auto" and darkdetect.theme() is not None: - _sync_theme_with_system(app, callback) +from __future__ import annotations + +import atexit +import os +import platform + +import darkdetect + +from qdarktheme._style_loader import load_palette, load_stylesheet + +_listener = None +_proxy_style = None + + +def _apply_style(app, additional_qss: str | None, **kargs) -> None: + from qdarktheme._proxy_style import QDarkThemeStyle + + stylesheet = load_stylesheet(**kargs) + if additional_qss is not None: + stylesheet += additional_qss + app.setStyleSheet(stylesheet) + + app.setPalette( + load_palette( + kargs["theme"], + kargs["custom_colors"], + for_stylesheet=True, + default_theme=kargs["default_theme"], + ) + ) + + global _proxy_style + if _proxy_style is None: + _proxy_style = QDarkThemeStyle() + app.setStyle(_proxy_style) + + +def _sync_theme_with_system(app, callback) -> None: + from qdarktheme._os_appearance import listener + + global _listener + if _listener is not None: + _listener.sig_run.emit(True) + return + + _listener = listener.OSThemeSwitchListener(callback) + + if platform.system() == "Darwin": + app.installEventFilter(_listener) + else: + atexit.register(_listener.kill) + _listener.start() + + +def enable_hi_dpi() -> None: + """Allow to HiDPI. + + This function must be set before instantiation of QApplication.. + For Qt6 bindings, HiDPI “just works” without using this function. + """ + from qdarktheme.qtpy.QtCore import Qt + from qdarktheme.qtpy.QtGui import QGuiApplication + + if hasattr(Qt.ApplicationAttribute, "AA_UseHighDpiPixmaps"): + QGuiApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore + if hasattr(Qt.ApplicationAttribute, "AA_EnableHighDpiScaling"): + QGuiApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling) # type: ignore + if hasattr(Qt, "HighDpiScaleFactorRoundingPolicy"): + os.environ["QT_ENABLE_HIGHDPI_SCALING"] = "1" + QGuiApplication.setHighDpiScaleFactorRoundingPolicy( + Qt.HighDpiScaleFactorRoundingPolicy.PassThrough + ) + + +def stop_sync() -> None: + """Stop sync with system theme.""" + from qdarktheme.qtpy.QtCore import QCoreApplication + + app = QCoreApplication.instance() + global _listener + if not app or not _listener: + return + _listener.sig_run.emit(False) + + +def setup_theme( + theme: str = "dark", + corner_shape: str = "rounded", + custom_colors: dict[str, str | dict[str, str]] | None = None, + additional_qss: str | None = None, + *, + default_theme: str = "dark", +) -> None: + """Apply the theme which looks like flat design to the Qt App completely. + + This function applies the complete style to your Qt application. If the argument theme is ``auto``, + try to listen to changes to the OS's theme and switch the application theme accordingly. + + Args: + theme: The theme name. There are `dark`, `light` and `auto`. + If ``auto``, try to sync with your OS's theme and accent (accent is only on Mac). + If failed to detect OS's theme, use the default theme set in argument ``default_theme``. + When primary color(``primary``) or primary child colors + (such as ``primary>selection.background``) are set to custom_colors, + disable to sync with the accent. + corner_shape: The corner shape. There are `rounded` and `sharp` shape. + custom_colors: The custom color map. Overrides the default color for color id you set. + Also you can customize a specific theme only. See example 5. + additional_qss: Additional stylesheet text. You can add your original stylesheet text. + default_theme: The default theme name. + The theme set by this argument will be used when system theme detection fails. + + Raises: + ValueError: If the argument is wrong. + KeyError: If the color id of custom_colors is wrong. + + Returns: + The stylesheet string for the given arguments. + + Examples: + Set stylesheet to your Qt application. + + 1. Setup style and sync to system theme :: + + app = QApplication([]) + qdarktheme.setup_theme() + + 2. Use Dark Theme :: + + app = QApplication([]) + qdarktheme.setup_theme("dark") + + 3. Sharp corner :: + + # Change corner shape to sharp. + app = QApplication([]) + qdarktheme.setup_theme(corner_shape="sharp") + + 4. Customize color :: + + app = QApplication([]) + qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) + + 5. Customize a specific theme only :: + + app = QApplication([]) + qdarktheme.setup_theme( + theme="auto", + custom_colors={ + "[dark]": { + "primary": "#D0BCFF", + } + }, + ) + """ + from qdarktheme.qtpy.QtCore import QCoreApplication + + app = QCoreApplication.instance() + if not app: + raise Exception("setup_theme() must be called after instantiation of QApplication.") + if theme != "auto": + stop_sync() + app.setProperty("_qdarktheme_use_setup_style", True) + + def callback(): + _apply_style( + app, + additional_qss, + theme=theme, + corner_shape=corner_shape, + custom_colors=custom_colors, + default_theme=default_theme, + ) + + callback() + + if theme == "auto" and darkdetect.theme() is not None: + _sync_theme_with_system(app, callback) diff --git a/qdarktheme/_os_appearance/__init__.py b/qdarktheme/_os_appearance/__init__.py index 584f4b8a..35588bdf 100644 --- a/qdarktheme/_os_appearance/__init__.py +++ b/qdarktheme/_os_appearance/__init__.py @@ -1 +1 @@ -from qdarktheme._os_appearance._accent import accent +from qdarktheme._os_appearance._accent import accent diff --git a/qdarktheme/_os_appearance/_accent/__init__.py b/qdarktheme/_os_appearance/_accent/__init__.py index 753790b7..35e0a3f1 100644 --- a/qdarktheme/_os_appearance/_accent/__init__.py +++ b/qdarktheme/_os_appearance/_accent/__init__.py @@ -1,29 +1,39 @@ -import platform - - -def _check_macos_supported_version(): - sys_ver = platform.mac_ver()[0] # typically 10.14.2 or 12.3 - major = int(sys_ver.split(".")[0]) - if major < 10: - return False - if major >= 11: - return True - minor = int(sys_ver.split(".")[1]) - return minor >= 14 - - -def _dummy_accent_detector() -> None: - return None - - -def _select_accent_detector(): - if platform.system() == "Darwin": - if _check_macos_supported_version(): - from qdarktheme._os_appearance._accent._mac_detect import get_mac_accent - - return get_mac_accent - return _dummy_accent_detector - return _dummy_accent_detector - - -accent = _select_accent_detector() +import platform + + +def _check_macos_supported_version(): + sys_ver = platform.mac_ver()[0] # typically 10.14.2 or 12.3 + major = int(sys_ver.split(".")[0]) + if major < 10: + return False + if major >= 11: + return True + minor = int(sys_ver.split(".")[1]) + return minor >= 14 + +def _check_windows_supported_version(): + try: + sys_ver = platform.win32_ver()[1].split(".")[-1] + return(int(sys_ver) >= 22000) # windows 11 only + except: + return False + +def _dummy_accent_detector() -> None: + return None + + +def _select_accent_detector(): + if platform.system() == "Darwin": + if _check_macos_supported_version(): + from qdarktheme._os_appearance._accent._mac_detect import get_mac_accent + return get_mac_accent + return _dummy_accent_detector + if platform.system() == "Windows": + if _check_windows_supported_version(): + from qdarktheme._os_appearance._accent._win_detect import get_windows_accent + return get_windows_accent + return _dummy_accent_detector + return _dummy_accent_detector + + +accent = _select_accent_detector() diff --git a/qdarktheme/_os_appearance/_accent/_mac_detect.py b/qdarktheme/_os_appearance/_accent/_mac_detect.py index f85368ee..78945113 100644 --- a/qdarktheme/_os_appearance/_accent/_mac_detect.py +++ b/qdarktheme/_os_appearance/_accent/_mac_detect.py @@ -1,61 +1,61 @@ -"""This script is inspired by darkdetect(https://github.com/albertosottile/darkdetect).""" -from __future__ import annotations - -import ctypes -from ctypes import c_void_p - -try: - # macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries" - _objc = ctypes.cdll.LoadLibrary("libobjc.dylib") -except OSError: - import ctypes.util - - # revert to full path for older OS versions and hardened programs - _objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) # type: ignore - -_objc.objc_getClass.restype = c_void_p -_objc.sel_registerName.restype = c_void_p - -msg_prototype = ctypes.CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_void_p) -msg = msg_prototype(("objc_msgSend", _objc), ((1, "", None), (1, "", None), (1, "", None))) - -_MAC_ACCENT_COLORS = { - None: None, - "-1": "graphite", - "0": "red", - "1": "orange", - "2": "yellow", - "3": "green", - "4": "blue", - "5": "purple", - "6": "pink", -} - - -def _utf8(s: str | bytes): - return s.encode("utf8") if not isinstance(s, bytes) else s - - -def _n(name: str): - return _objc.sel_registerName(_utf8(name)) - - -def _c(classname: str): - return _objc.objc_getClass(_utf8(classname)) - - -def get_mac_accent() -> str | None: - """Get system accent color on Mac.""" - pool = msg(_c("NSAutoreleasePool"), _n("alloc")) - pool = msg(pool, _n("init")) - - std_user_def = msg(_c("NSUserDefaults"), _n("standardUserDefaults")) - - key = msg(_c("NSString"), _n("stringWithUTF8String:"), _utf8("AppleAccentColor")) - accent_id = msg(std_user_def, _n("stringForKey:"), c_void_p(key)) - accent_id = msg(accent_id, _n("UTF8String")) - - accent_id = None if accent_id is None else ctypes.string_at(accent_id).decode() - - msg(pool, _n("release")) - return _MAC_ACCENT_COLORS.get(accent_id) +"""This script is inspired by darkdetect(https://github.com/albertosottile/darkdetect).""" +from __future__ import annotations + +import ctypes +from ctypes import c_void_p + +try: + # macOS Big Sur+ use "a built-in dynamic linker cache of all system-provided libraries" + _objc = ctypes.cdll.LoadLibrary("libobjc.dylib") +except OSError: + import ctypes.util + + # revert to full path for older OS versions and hardened programs + _objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) # type: ignore + +_objc.objc_getClass.restype = c_void_p +_objc.sel_registerName.restype = c_void_p + +msg_prototype = ctypes.CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_void_p) +msg = msg_prototype(("objc_msgSend", _objc), ((1, "", None), (1, "", None), (1, "", None))) + +_MAC_ACCENT_COLORS = { + None: None, + "-1": "graphite", + "0": "red", + "1": "orange", + "2": "yellow", + "3": "green", + "4": "blue", + "5": "purple", + "6": "pink", +} + + +def _utf8(s: str | bytes): + return s.encode("utf8") if not isinstance(s, bytes) else s + + +def _n(name: str): + return _objc.sel_registerName(_utf8(name)) + + +def _c(classname: str): + return _objc.objc_getClass(_utf8(classname)) + + +def get_mac_accent() -> str | None: + """Get system accent color on Mac.""" + pool = msg(_c("NSAutoreleasePool"), _n("alloc")) + pool = msg(pool, _n("init")) + + std_user_def = msg(_c("NSUserDefaults"), _n("standardUserDefaults")) + + key = msg(_c("NSString"), _n("stringWithUTF8String:"), _utf8("AppleAccentColor")) + accent_id = msg(std_user_def, _n("stringForKey:"), c_void_p(key)) + accent_id = msg(accent_id, _n("UTF8String")) + + accent_id = None if accent_id is None else ctypes.string_at(accent_id).decode() + + msg(pool, _n("release")) + return _MAC_ACCENT_COLORS.get(accent_id) diff --git a/qdarktheme/_os_appearance/_accent/_win_detect.py b/qdarktheme/_os_appearance/_accent/_win_detect.py new file mode 100644 index 00000000..43daa217 --- /dev/null +++ b/qdarktheme/_os_appearance/_accent/_win_detect.py @@ -0,0 +1,20 @@ +def get_windows_accent(): + import winreg + # Open the registry key for the current user's personalization settings + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Explorer\Accent") + # Read the value of AccentColorMenu + value = winreg.QueryValueEx(key, "AccentColorMenu")[0] + # Convert the value to hexadecimal string + hex_value = hex(value)[2:].zfill(8) + # Extract the RGB components from the string + r = int(hex_value[6:8], 16) + g = int(hex_value[4:6], 16) + b = int(hex_value[2:4], 16) + # Close the registry key + winreg.CloseKey(key) + # Return the HEX tuple + return '#%02x%02x%02x' % (r, g, b) + + + + diff --git a/qdarktheme/_os_appearance/listener.py b/qdarktheme/_os_appearance/listener.py index 8223dd54..137f62cc 100644 --- a/qdarktheme/_os_appearance/listener.py +++ b/qdarktheme/_os_appearance/listener.py @@ -1,59 +1,59 @@ -from __future__ import annotations - -import darkdetect - -from qdarktheme import _os_appearance -from qdarktheme.qtpy.QtCore import QCoreApplication, QEvent, QObject, QThread, Signal - - -class OSThemeSwitchListener(QThread): - """Listener to detect to change OS's theme.""" - - sig_run = Signal(bool) - _sig_listen_os_theme = Signal() - - def __init__(self, callable) -> None: - """Initialize listener.""" - super().__init__() - self.setProperty("is_running", True) - self._theme = darkdetect.theme() - self._accent = _os_appearance.accent() - self.sig_run.connect(lambda state: self.setProperty("is_running", state)) - self._sig_listen_os_theme.connect(callable) - - def eventFilter(self, q_object: QObject, event: QEvent) -> bool: # noqa: N802 - """Override QObject.eventFilter. - - This override is for Mac. - Qt can listen to change OS's theme on only mac and changed QPalette automatically. - So enable to listen to change OS's theme via palette change event. - """ - if ( - self.property("is_running") - and q_object == QCoreApplication.instance() - and event.type() == QEvent.Type.ApplicationPaletteChange - ): - accent = _os_appearance.accent() - theme = darkdetect.theme() - if self._theme != theme or self._accent != accent: - self._theme = theme - self._accent = accent - self._sig_listen_os_theme.emit() - return True - return super().eventFilter(q_object, event) - - def run(self) -> None: - """Override QThread.run. - - This override is for except Mac. - Qt cannot listen to change OS's theme on except Mac. - Use ``darkdetect.listener`` to detect to change OS's theme. - """ - darkdetect.listener( - lambda theme: self.property("is_running") and self._sig_listen_os_theme.emit() - ) - - def kill(self) -> None: - """Kill thread.""" - self.terminate() - self.deleteLater() +from __future__ import annotations + +import darkdetect + +from qdarktheme import _os_appearance +from qdarktheme.qtpy.QtCore import QCoreApplication, QEvent, QObject, QThread, Signal + + +class OSThemeSwitchListener(QThread): + """Listener to detect to change OS's theme.""" + + sig_run = Signal(bool) + _sig_listen_os_theme = Signal() + + def __init__(self, callable) -> None: + """Initialize listener.""" + super().__init__() + self.setProperty("is_running", True) + self._theme = darkdetect.theme() + self._accent = _os_appearance.accent() + self.sig_run.connect(lambda state: self.setProperty("is_running", state)) + self._sig_listen_os_theme.connect(callable) + + def eventFilter(self, q_object: QObject, event: QEvent) -> bool: # noqa: N802 + """Override QObject.eventFilter. + + This override is for Mac. + Qt can listen to change OS's theme on only mac and changed QPalette automatically. + So enable to listen to change OS's theme via palette change event. + """ + if ( + self.property("is_running") + and q_object == QCoreApplication.instance() + and event.type() == QEvent.Type.ApplicationPaletteChange + ): + accent = _os_appearance.accent() + theme = darkdetect.theme() + if self._theme != theme or self._accent != accent: + self._theme = theme + self._accent = accent + self._sig_listen_os_theme.emit() + return True + return super().eventFilter(q_object, event) + + def run(self) -> None: + """Override QThread.run. + + This override is for except Mac. + Qt cannot listen to change OS's theme on except Mac. + Use ``darkdetect.listener`` to detect to change OS's theme. + """ + darkdetect.listener( + lambda theme: self.property("is_running") and self._sig_listen_os_theme.emit() + ) + + def kill(self) -> None: + """Kill thread.""" + self.terminate() + self.deleteLater() diff --git a/qdarktheme/_proxy_style.py b/qdarktheme/_proxy_style.py index b1fb3585..5f300ea5 100644 --- a/qdarktheme/_proxy_style.py +++ b/qdarktheme/_proxy_style.py @@ -1,34 +1,34 @@ -from __future__ import annotations - -import platform - -from qdarktheme._icon.icon_engine import SvgIconEngine -from qdarktheme._icon.svg import Svg -from qdarktheme._resources.standard_icons import NEW_STANDARD_ICON_MAP -from qdarktheme.qtpy.QtGui import QIcon -from qdarktheme.qtpy.QtWidgets import QProxyStyle, QStyle, QStyleOption - - -class QDarkThemeStyle(QProxyStyle): - """Style proxy to improve theme.""" - - def __init__(self): - """Initialize style proxy.""" - super().__init__() - - def standardIcon( # noqa: N802 - self, standard_icon: QStyle.StandardPixmap, option: QStyleOption | None, widget - ) -> QIcon: - """Implement QProxyStyle.standardIcon.""" - icon_info = NEW_STANDARD_ICON_MAP.get(standard_icon) - if icon_info is None: - return super().standardIcon(standard_icon, option, widget) - - os_list = icon_info.get("os") - if os_list is not None and platform.system() not in os_list: - return super().standardIcon(standard_icon, option, widget) - - rotate = icon_info.get("rotate", 0) - svg = Svg(icon_info["id"]).rotate(rotate) - icon_engine = SvgIconEngine(svg) - return QIcon(icon_engine) +from __future__ import annotations + +import platform + +from qdarktheme._icon.icon_engine import SvgIconEngine +from qdarktheme._icon.svg import Svg +from qdarktheme._resources.standard_icons import NEW_STANDARD_ICON_MAP +from qdarktheme.qtpy.QtGui import QIcon +from qdarktheme.qtpy.QtWidgets import QProxyStyle, QStyle, QStyleOption + + +class QDarkThemeStyle(QProxyStyle): + """Style proxy to improve theme.""" + + def __init__(self): + """Initialize style proxy.""" + super().__init__() + + def standardIcon( # noqa: N802 + self, standard_icon: QStyle.StandardPixmap, option: QStyleOption | None, widget + ) -> QIcon: + """Implement QProxyStyle.standardIcon.""" + icon_info = NEW_STANDARD_ICON_MAP.get(standard_icon) + if icon_info is None: + return super().standardIcon(standard_icon, option, widget) + + os_list = icon_info.get("os") + if os_list is not None and platform.system() not in os_list: + return super().standardIcon(standard_icon, option, widget) + + rotate = icon_info.get("rotate", 0) + svg = Svg(icon_info["id"]).rotate(rotate) + icon_engine = SvgIconEngine(svg) + return QIcon(icon_engine) diff --git a/qdarktheme/_resources/__init__.py b/qdarktheme/_resources/__init__.py index 761b40e7..138c0c6d 100644 --- a/qdarktheme/_resources/__init__.py +++ b/qdarktheme/_resources/__init__.py @@ -1,12 +1,12 @@ -"""Package including resources. - -**Warning** - -This package created programmatically. All changes made in this file will be lost! -Created by the `PyQtDarkTheme/tools/build_styles`. - -""" - -from qdarktheme._resources import colors, palette, stylesheets, svg - -THEMES = ("dark", "light", "auto") +"""Package including resources. + +**Warning** + +This package created programmatically. All changes made in this file will be lost! +Created by the `PyQtDarkTheme/tools/build_styles`. + +""" + +from qdarktheme._resources import colors, palette, stylesheets, svg + +THEMES = ("dark", "light", "auto") diff --git a/qdarktheme/_resources/colors.py b/qdarktheme/_resources/colors.py index f2abe92e..722f28c6 100644 --- a/qdarktheme/_resources/colors.py +++ b/qdarktheme/_resources/colors.py @@ -1,28 +1,28 @@ -"""Default color values.""" - -THEME_COLOR_VALUES = { - "dark": '{"background": {"base": "#202124", "list": {}, "panel": {"darken": 0.3}, "popup": {"lighten": 0.3}, "table": {"darken": 0.5}, "textarea": {"darken": 0.13}, "title": {"darken": 0.3}}, "border": {"base": "#3f4042", "input": {"transparent": 0}}, "foreground": {"base": "#e4e7eb", "defaultButton.disabledBackground": {"transparent": 0.2}, "disabled": {"transparent": 0.4}, "disabledSelectionBackground": {"transparent": 0.2}, "icon": {"darken": 0.01}, "icon.unfocused": {"transparent": 0.6}, "input.placeholder": {"transparent": 0.6}, "progressBar.disabledBackground": {"transparent": 0.2}, "slider.disabledBackground": {"transparent": 0.2}, "sliderTrack.inactiveBackground": {"transparent": 0.1}}, "input.background": "#3f4042", "inputButton.hoverBackground": "#ffffff25", "linkVisited": "#c58af8", "list.alternateBackground": "#ffffff0c", "list.hoverBackground": "#ffffff13", "menubar.selectionBackground": "#ffffff25", "popupItem.checkbox.background": "#ffffff19", "popupItem.selectionBackground": "#ffffff22", "primary": {"base": "#8ab4f7", "button.activeBackground": {"darken": 0.14, "transparent": 0.23}, "button.hoverBackground": {"darken": 0.1, "transparent": 0.11}, "defaultButton.activeBackground": {"darken": 0.12}, "defaultButton.hoverBackground": {"darken": 0.06}, "list.inactiveSelectionBackground": {"lighten": 0.2, "transparent": 0.15}, "list.selectionBackground": {"darken": 0.2, "transparent": 0.4}, "progressBar.background": {"darken": 0.1}, "selection.background": {"darken": 0.2, "lighten": 0.1, "transparent": 0.4}, "sliderHandle.activeBackground": {"darken": 0.09}, "table.inactiveSelectionBackground": {"lighten": 0.2, "transparent": 0.18}, "table.selectionBackground": {"darken": 0.2, "transparent": 0.55}, "textarea.selectionBackground": {"darken": 0.2, "lighten": 0.1, "transparent": 0.4}}, "scrollbar.background": "#ffffff10", "scrollbarSlider.activeBackground": "#ffffff60", "scrollbarSlider.background": "#ffffff30", "scrollbarSlider.disabledBackground": "#ffffff15", "scrollbarSlider.hoverBackground": "#ffffff45", "statusBar.background": "#2a2b2e", "statusBarItem.activeBackground": "#ffffff34", "statusBarItem.hoverBackground": "#ffffff22", "tab.activeBackground": "#ffffff00", "tab.hoverBackground": "#ffffff18", "tabCloseButton.hoverBackground": "#ffffff25", "table.alternateBackground": "#ffffff15", "tableSectionHeader.background": "#3f4042", "textarea.inactiveSelectionBackground": "#ffffff20", "toolbar.activeBackground": "#ffffff34", "toolbar.background": "#333333", "toolbar.hoverBackground": "#ffffff22", "tree.inactiveIndentGuidesStroke": "#ffffff35", "tree.indentGuidesStroke": "#ffffff60", "treeSectionHeader.background": "#3f4042"}', # noqa: E501 - "light": '{"background": {"base": "#f8f9fa", "list": {}, "panel": {"lighten": 0.5}, "popup": {"lighten": 0.2}, "table": {"lighten": 0.5}, "textarea": {"lighten": 0.25}, "title": {"darken": 0.04}}, "border": {"base": "#dadce0", "input": {}}, "foreground": {"base": "#4d5157", "defaultButton.disabledBackground": {"transparent": 0.25}, "disabled": {"transparent": 0.4}, "disabledSelectionBackground": {"transparent": 0.25}, "icon": {"darken": 0.05}, "icon.unfocused": {"transparent": 0.6}, "input.placeholder": {"transparent": 0.6}, "progressBar.disabledBackground": {"transparent": 0.25}, "slider.disabledBackground": {"transparent": 0.25}, "sliderTrack.inactiveBackground": {"transparent": 0.2}}, "input.background": "#f8f9fa", "inputButton.hoverBackground": "#00000018", "linkVisited": "#660098", "list.alternateBackground": "#00000009", "list.hoverBackground": "#00000013", "menubar.selectionBackground": "#00000020", "popupItem.checkbox.background": "#00000019", "popupItem.selectionBackground": "#00000022", "primary": {"base": "#1a73e8", "button.activeBackground": {"darken": 0.03, "transparent": 0.24}, "button.hoverBackground": {"darken": 0.03, "transparent": 0.1}, "defaultButton.activeBackground": {"lighten": 0.3}, "defaultButton.hoverBackground": {"lighten": 0.1}, "list.inactiveSelectionBackground": {"darken": 0.43, "transparent": 0.09}, "list.selectionBackground": {"lighten": 0.2, "transparent": 0.35}, "progressBar.background": {"lighten": 0.2}, "selection.background": {"lighten": 0.3, "transparent": 0.5}, "sliderHandle.activeBackground": {"lighten": 0.2}, "table.inactiveSelectionBackground": {"darken": 0.43, "transparent": 0.09}, "table.selectionBackground": {"lighten": 0.1, "transparent": 0.5}, "textarea.selectionBackground": {"lighten": 0.3, "transparent": 0.5}}, "scrollbar.background": "#00000010", "scrollbarSlider.activeBackground": "#00000060", "scrollbarSlider.background": "#00000040", "scrollbarSlider.disabledBackground": "#00000015", "scrollbarSlider.hoverBackground": "#00000050", "statusBar.background": "#dfe1e5", "statusBarItem.activeBackground": "#00000024", "statusBarItem.hoverBackground": "#00000015", "tab.activeBackground": "#00000000", "tab.hoverBackground": "#00000015", "tabCloseButton.hoverBackground": "#00000020", "table.alternateBackground": "#00000012", "tableSectionHeader.background": "#dadce0", "textarea.inactiveSelectionBackground": "#00000015", "toolbar.activeBackground": "#00000024", "toolbar.background": "#ebebeb", "toolbar.hoverBackground": "#00000015", "tree.inactiveIndentGuidesStroke": "#00000030", "tree.indentGuidesStroke": "#00000050", "treeSectionHeader.background": "#dadce0"}', # noqa: E501 -} -ACCENT_COLORS = { - "dark": { - "blue": "#8ab4f7", - "graphite": "#898a8f", - "green": "#4caf50", - "orange": "#ff9800", - "pink": "#c7457f", - "purple": "#af52bf", - "red": "#f6685e", - "yellow": "#ffeb3b", - }, - "light": { - "blue": "#1a73e8", - "graphite": "#898a8f", - "green": "#4caf50", - "orange": "#ff9800", - "pink": "#c7457f", - "purple": "#9c27b0", - "red": "#f44336", - "yellow": "#f4c65f", - }, -} +"""Default color values.""" + +THEME_COLOR_VALUES = { + "dark": '{"background": {"base": "#202124", "list": {}, "panel": {"darken": 0.3}, "popup": {"lighten": 0.3}, "table": {"darken": 0.5}, "textarea": {"darken": 0.13}, "title": {"darken": 0.3}}, "border": {"base": "#3f4042", "input": {"transparent": 0}}, "foreground": {"base": "#e4e7eb", "defaultButton.disabledBackground": {"transparent": 0.2}, "disabled": {"transparent": 0.4}, "disabledSelectionBackground": {"transparent": 0.2}, "icon": {"darken": 0.01}, "icon.unfocused": {"transparent": 0.6}, "input.placeholder": {"transparent": 0.6}, "progressBar.disabledBackground": {"transparent": 0.2}, "slider.disabledBackground": {"transparent": 0.2}, "sliderTrack.inactiveBackground": {"transparent": 0.1}}, "input.background": "#3f4042", "inputButton.hoverBackground": "#ffffff25", "linkVisited": "#c58af8", "list.alternateBackground": "#ffffff0c", "list.hoverBackground": "#ffffff13", "menubar.selectionBackground": "#ffffff25", "popupItem.checkbox.background": "#ffffff19", "popupItem.selectionBackground": "#ffffff22", "primary": {"base": "#8ab4f7", "button.activeBackground": {"darken": 0.14, "transparent": 0.23}, "button.hoverBackground": {"darken": 0.1, "transparent": 0.11}, "defaultButton.activeBackground": {"darken": 0.12}, "defaultButton.hoverBackground": {"darken": 0.06}, "list.inactiveSelectionBackground": {"lighten": 0.2, "transparent": 0.15}, "list.selectionBackground": {"darken": 0.2, "transparent": 0.4}, "progressBar.background": {"darken": 0.1}, "selection.background": {"darken": 0.2, "lighten": 0.1, "transparent": 0.4}, "sliderHandle.activeBackground": {"darken": 0.09}, "table.inactiveSelectionBackground": {"lighten": 0.2, "transparent": 0.18}, "table.selectionBackground": {"darken": 0.2, "transparent": 0.55}, "textarea.selectionBackground": {"darken": 0.2, "lighten": 0.1, "transparent": 0.4}}, "scrollbar.background": "#ffffff10", "scrollbarSlider.activeBackground": "#ffffff60", "scrollbarSlider.background": "#ffffff30", "scrollbarSlider.disabledBackground": "#ffffff15", "scrollbarSlider.hoverBackground": "#ffffff45", "statusBar.background": "#2a2b2e", "statusBarItem.activeBackground": "#ffffff34", "statusBarItem.hoverBackground": "#ffffff22", "tab.activeBackground": "#ffffff00", "tab.hoverBackground": "#ffffff18", "tabCloseButton.hoverBackground": "#ffffff25", "table.alternateBackground": "#ffffff15", "tableSectionHeader.background": "#3f4042", "textarea.inactiveSelectionBackground": "#ffffff20", "toolbar.activeBackground": "#ffffff34", "toolbar.background": "#333333", "toolbar.hoverBackground": "#ffffff22", "tree.inactiveIndentGuidesStroke": "#ffffff35", "tree.indentGuidesStroke": "#ffffff60", "treeSectionHeader.background": "#3f4042"}', # noqa: E501 + "light": '{"background": {"base": "#f8f9fa", "list": {}, "panel": {"lighten": 0.5}, "popup": {"lighten": 0.2}, "table": {"lighten": 0.5}, "textarea": {"lighten": 0.25}, "title": {"darken": 0.04}}, "border": {"base": "#dadce0", "input": {}}, "foreground": {"base": "#4d5157", "defaultButton.disabledBackground": {"transparent": 0.25}, "disabled": {"transparent": 0.4}, "disabledSelectionBackground": {"transparent": 0.25}, "icon": {"darken": 0.05}, "icon.unfocused": {"transparent": 0.6}, "input.placeholder": {"transparent": 0.6}, "progressBar.disabledBackground": {"transparent": 0.25}, "slider.disabledBackground": {"transparent": 0.25}, "sliderTrack.inactiveBackground": {"transparent": 0.2}}, "input.background": "#f8f9fa", "inputButton.hoverBackground": "#00000018", "linkVisited": "#660098", "list.alternateBackground": "#00000009", "list.hoverBackground": "#00000013", "menubar.selectionBackground": "#00000020", "popupItem.checkbox.background": "#00000019", "popupItem.selectionBackground": "#00000022", "primary": {"base": "#1a73e8", "button.activeBackground": {"darken": 0.03, "transparent": 0.24}, "button.hoverBackground": {"darken": 0.03, "transparent": 0.1}, "defaultButton.activeBackground": {"lighten": 0.3}, "defaultButton.hoverBackground": {"lighten": 0.1}, "list.inactiveSelectionBackground": {"darken": 0.43, "transparent": 0.09}, "list.selectionBackground": {"lighten": 0.2, "transparent": 0.35}, "progressBar.background": {"lighten": 0.2}, "selection.background": {"lighten": 0.3, "transparent": 0.5}, "sliderHandle.activeBackground": {"lighten": 0.2}, "table.inactiveSelectionBackground": {"darken": 0.43, "transparent": 0.09}, "table.selectionBackground": {"lighten": 0.1, "transparent": 0.5}, "textarea.selectionBackground": {"lighten": 0.3, "transparent": 0.5}}, "scrollbar.background": "#00000010", "scrollbarSlider.activeBackground": "#00000060", "scrollbarSlider.background": "#00000040", "scrollbarSlider.disabledBackground": "#00000015", "scrollbarSlider.hoverBackground": "#00000050", "statusBar.background": "#dfe1e5", "statusBarItem.activeBackground": "#00000024", "statusBarItem.hoverBackground": "#00000015", "tab.activeBackground": "#00000000", "tab.hoverBackground": "#00000015", "tabCloseButton.hoverBackground": "#00000020", "table.alternateBackground": "#00000012", "tableSectionHeader.background": "#dadce0", "textarea.inactiveSelectionBackground": "#00000015", "toolbar.activeBackground": "#00000024", "toolbar.background": "#ebebeb", "toolbar.hoverBackground": "#00000015", "tree.inactiveIndentGuidesStroke": "#00000030", "tree.indentGuidesStroke": "#00000050", "treeSectionHeader.background": "#dadce0"}', # noqa: E501 +} +ACCENT_COLORS = { + "dark": { + "blue": "#8ab4f7", + "graphite": "#898a8f", + "green": "#4caf50", + "orange": "#ff9800", + "pink": "#c7457f", + "purple": "#af52bf", + "red": "#f6685e", + "yellow": "#ffeb3b", + }, + "light": { + "blue": "#1a73e8", + "graphite": "#898a8f", + "green": "#4caf50", + "orange": "#ff9800", + "pink": "#c7457f", + "purple": "#9c27b0", + "red": "#f44336", + "yellow": "#f4c65f", + }, +} diff --git a/qdarktheme/_resources/palette.py b/qdarktheme/_resources/palette.py index 9477c1be..84b23b31 100644 --- a/qdarktheme/_resources/palette.py +++ b/qdarktheme/_resources/palette.py @@ -1,114 +1,114 @@ -"""Module loading QPalette.""" -from __future__ import annotations - -from functools import partial - -from qdarktheme._template.engine import Template - - -def q_palette(mk_template: partial[Template], color_map: dict[str, str | dict], for_stylesheet: bool): - """Generate QPalette.""" - from qdarktheme.qtpy.QtGui import QColor, QPalette - - def _mk_q_color(text: str): - template = mk_template(text) - color_format = template.render(color_map) - return QColor(color_format) - - palette = QPalette() - - if not for_stylesheet: - # base - palette.setColor(QPalette.ColorRole.WindowText, _mk_q_color("{{ foreground|color|palette }}")) - palette.setColor( - QPalette.ColorRole.Button, _mk_q_color("{{ treeSectionHeader.background|color|palette }}") - ) - palette.setColor(QPalette.ColorRole.ButtonText, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor(QPalette.ColorRole.Base, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Window, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Highlight, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor( - QPalette.ColorRole.HighlightedText, _mk_q_color("{{ background|color|palette }}") - ) - palette.setColor( - QPalette.ColorRole.AlternateBase, - _mk_q_color("{{ list.alternateBackground|color|palette }}"), - ) - palette.setColor( - QPalette.ColorRole.ToolTipBase, _mk_q_color('{{ background|color(state="popup")|palette }}') - ) - palette.setColor(QPalette.ColorRole.ToolTipText, _mk_q_color("{{ foreground|color|palette }}")) - if hasattr(QPalette.ColorRole, "Foreground"): - palette.setColor( - QPalette.ColorRole.Foreground, # type: ignore - _mk_q_color("{{ foreground|color|palette }}"), - ) - - palette.setColor(QPalette.ColorRole.Light, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Midlight, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Dark, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Mid, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Shadow, _mk_q_color("{{ border|color|palette }}")) - - # disabled - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.WindowText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.ButtonText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Highlight, - _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.HighlightedText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - - # inactive - palette.setColor( - QPalette.ColorGroup.Inactive, - QPalette.ColorRole.Highlight, - _mk_q_color('{{ primary|color(state="list.inactiveSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Inactive, - QPalette.ColorRole.HighlightedText, - _mk_q_color("{{ foreground|color|palette }}"), - ) - - palette.setColor( - QPalette.ColorRole.Text, _mk_q_color('{{ foreground|color(state="icon")|palette }}') - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Text, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor(QPalette.ColorRole.Link, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor(QPalette.ColorRole.LinkVisited, _mk_q_color("{{ linkVisited|color|palette }}")) - if hasattr(QPalette.ColorRole, "PlaceholderText"): - palette.setColor( - QPalette.ColorRole.PlaceholderText, - _mk_q_color('{{ foreground|color(state="input.placeholder")|palette }}'), - ) - - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Link, - _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.LinkVisited, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - - return palette +"""Module loading QPalette.""" +from __future__ import annotations + +from functools import partial + +from qdarktheme._template.engine import Template + + +def q_palette(mk_template: partial[Template], color_map: dict[str, str | dict], for_stylesheet: bool): + """Generate QPalette.""" + from qdarktheme.qtpy.QtGui import QColor, QPalette + + def _mk_q_color(text: str): + template = mk_template(text) + color_format = template.render(color_map) + return QColor(color_format) + + palette = QPalette() + + if not for_stylesheet: + # base + palette.setColor(QPalette.ColorRole.WindowText, _mk_q_color("{{ foreground|color|palette }}")) + palette.setColor( + QPalette.ColorRole.Button, _mk_q_color("{{ treeSectionHeader.background|color|palette }}") + ) + palette.setColor(QPalette.ColorRole.ButtonText, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor(QPalette.ColorRole.Base, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Window, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Highlight, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor( + QPalette.ColorRole.HighlightedText, _mk_q_color("{{ background|color|palette }}") + ) + palette.setColor( + QPalette.ColorRole.AlternateBase, + _mk_q_color("{{ list.alternateBackground|color|palette }}"), + ) + palette.setColor( + QPalette.ColorRole.ToolTipBase, _mk_q_color('{{ background|color(state="popup")|palette }}') + ) + palette.setColor(QPalette.ColorRole.ToolTipText, _mk_q_color("{{ foreground|color|palette }}")) + if hasattr(QPalette.ColorRole, "Foreground"): + palette.setColor( + QPalette.ColorRole.Foreground, # type: ignore + _mk_q_color("{{ foreground|color|palette }}"), + ) + + palette.setColor(QPalette.ColorRole.Light, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Midlight, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Dark, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Mid, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Shadow, _mk_q_color("{{ border|color|palette }}")) + + # disabled + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.WindowText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.ButtonText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Highlight, + _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.HighlightedText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + + # inactive + palette.setColor( + QPalette.ColorGroup.Inactive, + QPalette.ColorRole.Highlight, + _mk_q_color('{{ primary|color(state="list.inactiveSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Inactive, + QPalette.ColorRole.HighlightedText, + _mk_q_color("{{ foreground|color|palette }}"), + ) + + palette.setColor( + QPalette.ColorRole.Text, _mk_q_color('{{ foreground|color(state="icon")|palette }}') + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Text, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor(QPalette.ColorRole.Link, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor(QPalette.ColorRole.LinkVisited, _mk_q_color("{{ linkVisited|color|palette }}")) + if hasattr(QPalette.ColorRole, "PlaceholderText"): + palette.setColor( + QPalette.ColorRole.PlaceholderText, + _mk_q_color('{{ foreground|color(state="input.placeholder")|palette }}'), + ) + + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Link, + _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.LinkVisited, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + + return palette diff --git a/qdarktheme/_resources/standard_icons.py b/qdarktheme/_resources/standard_icons.py index 07f0f678..620336c5 100644 --- a/qdarktheme/_resources/standard_icons.py +++ b/qdarktheme/_resources/standard_icons.py @@ -1,81 +1,81 @@ -"""Icon map that overrides standard icons.""" -from qdarktheme.qtpy.QtWidgets import QStyle - -NEW_STANDARD_ICON_MAP = { - QStyle.StandardPixmap.SP_ArrowBack: {"id": "arrow_upward", "rotate": 270}, - QStyle.StandardPixmap.SP_ArrowDown: {"id": "arrow_upward", "rotate": 180}, - QStyle.StandardPixmap.SP_ArrowForward: {"id": "arrow_upward", "rotate": 90}, - QStyle.StandardPixmap.SP_ArrowLeft: {"id": "arrow_upward", "rotate": 270}, - QStyle.StandardPixmap.SP_ArrowRight: {"id": "arrow_upward", "rotate": 90}, - QStyle.StandardPixmap.SP_ArrowUp: {"id": "arrow_upward"}, - QStyle.StandardPixmap.SP_BrowserReload: {"id": "refresh"}, - QStyle.StandardPixmap.SP_BrowserStop: {"id": "close"}, - QStyle.StandardPixmap.SP_CommandLink: {"id": "east"}, - QStyle.StandardPixmap.SP_DialogApplyButton: {"id": "check_circle"}, - QStyle.StandardPixmap.SP_DialogCancelButton: {"id": "cancel"}, - QStyle.StandardPixmap.SP_DialogCloseButton: {"id": "close"}, - QStyle.StandardPixmap.SP_DialogDiscardButton: {"id": "delete"}, - QStyle.StandardPixmap.SP_DialogHelpButton: {"id": "help"}, - QStyle.StandardPixmap.SP_DialogNoButton: {"id": "not_interested"}, - QStyle.StandardPixmap.SP_DialogOkButton: {"id": "check"}, - QStyle.StandardPixmap.SP_DialogOpenButton: {"id": "launch"}, - QStyle.StandardPixmap.SP_DialogResetButton: {"id": "cleaning_services"}, - QStyle.StandardPixmap.SP_DialogSaveButton: {"id": "save"}, - QStyle.StandardPixmap.SP_DialogYesButton: {"id": "circle"}, - QStyle.StandardPixmap.SP_DirHomeIcon: {"id": "home"}, - QStyle.StandardPixmap.SP_DockWidgetCloseButton: {"id": "close"}, - QStyle.StandardPixmap.SP_FileDialogBack: {"id": "arrow_upward", "rotate": 270}, - QStyle.StandardPixmap.SP_FileDialogContentsView: {"id": "search"}, - QStyle.StandardPixmap.SP_FileDialogDetailedView: {"id": "list"}, - QStyle.StandardPixmap.SP_FileDialogEnd: {"id": "drive_file_move_rtl"}, - QStyle.StandardPixmap.SP_FileDialogInfoView: {"id": "info"}, - QStyle.StandardPixmap.SP_FileDialogListView: {"id": "grid_view"}, - QStyle.StandardPixmap.SP_FileDialogNewFolder: {"id": "create_new_folder"}, - QStyle.StandardPixmap.SP_FileDialogStart: {"id": "drive_file_move"}, - QStyle.StandardPixmap.SP_FileDialogToParent: {"id": "arrow_upward"}, - QStyle.StandardPixmap.SP_MediaPause: {"id": "pause"}, - QStyle.StandardPixmap.SP_MediaPlay: {"id": "play_arrow"}, - QStyle.StandardPixmap.SP_MediaSeekBackward: {"id": "fast_rewind"}, - QStyle.StandardPixmap.SP_MediaSeekForward: {"id": "fast_forward"}, - QStyle.StandardPixmap.SP_MediaSkipBackward: {"id": "skip_previous"}, - QStyle.StandardPixmap.SP_MediaSkipForward: {"id": "skip_next"}, - QStyle.StandardPixmap.SP_MediaStop: {"id": "stop"}, - QStyle.StandardPixmap.SP_MediaVolume: {"id": "volume_up"}, - QStyle.StandardPixmap.SP_MediaVolumeMuted: {"id": "volume_mute"}, - QStyle.StandardPixmap.SP_MessageBoxQuestion: {"id": "help", "os": ["Darwin", "Linux"]}, - QStyle.StandardPixmap.SP_TitleBarCloseButton: {"id": "close"}, - QStyle.StandardPixmap.SP_TitleBarContextHelpButton: {"id": "question_mark"}, - QStyle.StandardPixmap.SP_TitleBarMaxButton: {"id": "fullscreen"}, - QStyle.StandardPixmap.SP_TitleBarMinButton: {"id": "minimize"}, - QStyle.StandardPixmap.SP_TitleBarNormalButton: {"id": "flip_to_front"}, - QStyle.StandardPixmap.SP_TitleBarShadeButton: {"id": "chevron_right", "rotate": "270"}, - QStyle.StandardPixmap.SP_TitleBarUnshadeButton: {"id": "chevron_right", "rotate": "90"}, - QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton: {"id": "double_arrow"}, - QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton: {"id": "double_arrow", "rotate": 90}, - QStyle.StandardPixmap.SP_TrashIcon: {"id": "delete", "os": ["Windows"]}, - QStyle.StandardPixmap.SP_VistaShield: {"id": "security", "os": ["Darwin", "Linux"]}, -} - -if hasattr(QStyle.StandardPixmap, "SP_DialogAbortButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogAbortButton] = {"id": "not_interested"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_DialogIgnoreButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogIgnoreButton] = {"id": "visibility_off"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_DialogNoToAllButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogNoToAllButton] = {"id": "close"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_DialogRetryButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogRetryButton] = {"id": "refresh"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_DialogSaveAllButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogSaveAllButton] = {"id": "save"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_DialogYesToAllButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogYesToAllButton] = {"id": "done_all"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_LineEditClearButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_LineEditClearButton] = {"id": "close"} # type: ignore # noqa: E501 - -if hasattr(QStyle.StandardPixmap, "SP_TabCloseButton"): - NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_TabCloseButton] = {"id": "close"} # type: ignore # noqa: E501 +"""Icon map that overrides standard icons.""" +from qdarktheme.qtpy.QtWidgets import QStyle + +NEW_STANDARD_ICON_MAP = { + QStyle.StandardPixmap.SP_ArrowBack: {"id": "arrow_upward", "rotate": 270}, + QStyle.StandardPixmap.SP_ArrowDown: {"id": "arrow_upward", "rotate": 180}, + QStyle.StandardPixmap.SP_ArrowForward: {"id": "arrow_upward", "rotate": 90}, + QStyle.StandardPixmap.SP_ArrowLeft: {"id": "arrow_upward", "rotate": 270}, + QStyle.StandardPixmap.SP_ArrowRight: {"id": "arrow_upward", "rotate": 90}, + QStyle.StandardPixmap.SP_ArrowUp: {"id": "arrow_upward"}, + QStyle.StandardPixmap.SP_BrowserReload: {"id": "refresh"}, + QStyle.StandardPixmap.SP_BrowserStop: {"id": "close"}, + QStyle.StandardPixmap.SP_CommandLink: {"id": "east"}, + QStyle.StandardPixmap.SP_DialogApplyButton: {"id": "check_circle"}, + QStyle.StandardPixmap.SP_DialogCancelButton: {"id": "cancel"}, + QStyle.StandardPixmap.SP_DialogCloseButton: {"id": "close"}, + QStyle.StandardPixmap.SP_DialogDiscardButton: {"id": "delete"}, + QStyle.StandardPixmap.SP_DialogHelpButton: {"id": "help"}, + QStyle.StandardPixmap.SP_DialogNoButton: {"id": "not_interested"}, + QStyle.StandardPixmap.SP_DialogOkButton: {"id": "check"}, + QStyle.StandardPixmap.SP_DialogOpenButton: {"id": "launch"}, + QStyle.StandardPixmap.SP_DialogResetButton: {"id": "cleaning_services"}, + QStyle.StandardPixmap.SP_DialogSaveButton: {"id": "save"}, + QStyle.StandardPixmap.SP_DialogYesButton: {"id": "circle"}, + QStyle.StandardPixmap.SP_DirHomeIcon: {"id": "home"}, + QStyle.StandardPixmap.SP_DockWidgetCloseButton: {"id": "close"}, + QStyle.StandardPixmap.SP_FileDialogBack: {"id": "arrow_upward", "rotate": 270}, + QStyle.StandardPixmap.SP_FileDialogContentsView: {"id": "search"}, + QStyle.StandardPixmap.SP_FileDialogDetailedView: {"id": "list"}, + QStyle.StandardPixmap.SP_FileDialogEnd: {"id": "drive_file_move_rtl"}, + QStyle.StandardPixmap.SP_FileDialogInfoView: {"id": "info"}, + QStyle.StandardPixmap.SP_FileDialogListView: {"id": "grid_view"}, + QStyle.StandardPixmap.SP_FileDialogNewFolder: {"id": "create_new_folder"}, + QStyle.StandardPixmap.SP_FileDialogStart: {"id": "drive_file_move"}, + QStyle.StandardPixmap.SP_FileDialogToParent: {"id": "arrow_upward"}, + QStyle.StandardPixmap.SP_MediaPause: {"id": "pause"}, + QStyle.StandardPixmap.SP_MediaPlay: {"id": "play_arrow"}, + QStyle.StandardPixmap.SP_MediaSeekBackward: {"id": "fast_rewind"}, + QStyle.StandardPixmap.SP_MediaSeekForward: {"id": "fast_forward"}, + QStyle.StandardPixmap.SP_MediaSkipBackward: {"id": "skip_previous"}, + QStyle.StandardPixmap.SP_MediaSkipForward: {"id": "skip_next"}, + QStyle.StandardPixmap.SP_MediaStop: {"id": "stop"}, + QStyle.StandardPixmap.SP_MediaVolume: {"id": "volume_up"}, + QStyle.StandardPixmap.SP_MediaVolumeMuted: {"id": "volume_mute"}, + QStyle.StandardPixmap.SP_MessageBoxQuestion: {"id": "help", "os": ["Darwin", "Linux"]}, + QStyle.StandardPixmap.SP_TitleBarCloseButton: {"id": "close"}, + QStyle.StandardPixmap.SP_TitleBarContextHelpButton: {"id": "question_mark"}, + QStyle.StandardPixmap.SP_TitleBarMaxButton: {"id": "fullscreen"}, + QStyle.StandardPixmap.SP_TitleBarMinButton: {"id": "minimize"}, + QStyle.StandardPixmap.SP_TitleBarNormalButton: {"id": "flip_to_front"}, + QStyle.StandardPixmap.SP_TitleBarShadeButton: {"id": "chevron_right", "rotate": "270"}, + QStyle.StandardPixmap.SP_TitleBarUnshadeButton: {"id": "chevron_right", "rotate": "90"}, + QStyle.StandardPixmap.SP_ToolBarHorizontalExtensionButton: {"id": "double_arrow"}, + QStyle.StandardPixmap.SP_ToolBarVerticalExtensionButton: {"id": "double_arrow", "rotate": 90}, + QStyle.StandardPixmap.SP_TrashIcon: {"id": "delete", "os": ["Windows"]}, + QStyle.StandardPixmap.SP_VistaShield: {"id": "security", "os": ["Darwin", "Linux"]}, +} + +if hasattr(QStyle.StandardPixmap, "SP_DialogAbortButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogAbortButton] = {"id": "not_interested"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_DialogIgnoreButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogIgnoreButton] = {"id": "visibility_off"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_DialogNoToAllButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogNoToAllButton] = {"id": "close"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_DialogRetryButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogRetryButton] = {"id": "refresh"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_DialogSaveAllButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogSaveAllButton] = {"id": "save"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_DialogYesToAllButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_DialogYesToAllButton] = {"id": "done_all"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_LineEditClearButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_LineEditClearButton] = {"id": "close"} # type: ignore # noqa: E501 + +if hasattr(QStyle.StandardPixmap, "SP_TabCloseButton"): + NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.SP_TabCloseButton] = {"id": "close"} # type: ignore # noqa: E501 diff --git a/qdarktheme/_resources/stylesheets.py b/qdarktheme/_resources/stylesheets.py index bfddb63f..25187656 100644 --- a/qdarktheme/_resources/stylesheets.py +++ b/qdarktheme/_resources/stylesheets.py @@ -1,4 +1,4 @@ -"""Template stylesheet.""" - -TEMPLATE_STYLESHEET = 'QWidget {background:{{background|color}};color:{{foreground|color}};selection-color:{{foreground|color}};selection-background-color:{{primary|color(state="selection.background")}}}QWidget:disabled {color:{{foreground|color(state="disabled")}};selection-background-color:{{foreground|color(state="disabledSelectionBackground")}};selection-color:{{foreground|color(state="disabled")}}}QWidget:focus {outline:none}QCheckBox:!window,QRadioButton:!window,QPushButton:!window,QLabel:!window,QLCDNumber:!window {background:transparent}QMdiSubWindow > QCheckBox:!window,QMdiSubWindow > QRadioButton:!window,QMdiSubWindow > QPushButton:!window,QMdiSubWindow > QLabel:!window,QMdiSubWindow > QLCDNumber:!window {background:{{background|color}}}QMainWindow::separator {width:4px;height:4px;background:{{border|color}}}QMainWindow::separator:hover,QMainWindow::separator:pressed {background:{{primary|color}}}QToolTip {background:{{background|color(state="popup")}};color:{{foreground|color}}}QSizeGrip {width:0;height:0;image:none}QStatusBar {background:{{statusBar.background|color}}}QStatusBar::item {border:none}QStatusBar QWidget {background:transparent;padding:3px;border-radius:{{corner-shape|corner(size=4)}}px}QStatusBar > .QSizeGrip {padding:0}QStatusBar QWidget:hover {background:{{statusBarItem.hoverBackground|color}}}QStatusBar QWidget:pressed,QStatusBar QWidget:checked {background:{{statusBarItem.activeBackground|color}}}QCheckBox,QRadioButton {border-top:2px solid transparent;border-bottom:2px solid transparent}QCheckBox:hover,QRadioButton:hover {border-bottom:2px solid {{primary|color}}}QGroupBox {font-weight:bold;margin-top:8px;padding:2px 1px 1px 1px;border-radius:{{corner-shape|corner(size=4)}}px;border:1px solid {{border|color}}}QGroupBox::title {subcontrol-origin:margin;subcontrol-position:top left;left:7px;margin:0 2px 0 3px}QGroupBox:flat {border-color:transparent}QMenuBar {padding:2px;border-bottom:1px solid {{border|color}};background:{{background|color}}}QMenuBar::item {background:transparent;padding:4px}QMenuBar::item:selected {padding:4px;border-radius:{{corner-shape|corner(size=4)}}px;background:{{menubar.selectionBackground|color}}}QMenuBar::item:pressed {padding:4px;margin-bottom:0;padding-bottom:0}QToolBar {padding:1px;font-weight:bold;spacing:2px;margin:1px;background:{{toolbar.background|color}};border-style:none}QToolBar::handle:horizontal {width:20px;image:{{foreground|color(state="icon")|url(id="drag_indicator")}}}QToolBar::handle:vertical {height:20px;image:{{foreground|color(state="icon")|url(id="drag_indicator",rotate=90)}}}QToolBar::handle:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="drag_indicator")}}}QToolBar::handle:vertical:disabled {image:{{foreground|color(state="disabled")|url(id="drag_indicator",rotate=90)}}}QToolBar::separator {background:{{border|color}}}QToolBar::separator:horizontal {width:2px;margin:0 6px}QToolBar::separator:vertical {height:2px;margin:6px 0}QToolBar > QToolButton {background:transparent;padding:3px;border-radius:{{corner-shape|corner(size=4)}}px}QToolBar > QToolButton:hover,QToolBar > QToolButton::menu-button:hover {background:{{toolbar.hoverBackground|color}}}QToolBar > QToolButton::menu-button {border-top-right-radius:{{corner-shape|corner(size=4)}}px;border-bottom-right-radius:{{corner-shape|corner(size=4)}}px}QToolBar > QToolButton:pressed,QToolBar > QToolButton::menu-button:pressed:enabled,QToolBar > QToolButton:checked:enabled {background:{{toolbar.activeBackground|color}}}QToolBar > QWidget {background:transparent}QMenu {background:{{background|color(state="popup")}};padding:8px 0;{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version=">=6.4.1",os="Darwin")}}}QMenu::separator {margin:4px 0;height:1px;background:{{border|color}}}QMenu::item {padding:4px 19px}QMenu::item:selected {background:{{popupItem.selectionBackground|color}}}QMenu::icon {padding-left:10px;width:14px;height:14px}QMenu::right-arrow {margin:2px;padding-left:12px;height:20px;width:20px;image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QMenu::right-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QScrollBar {background:{{scrollbar.background|color}};border-radius:{{corner-shape|corner(size=4)}}px;{{|env(value="background:transparent",os="Darwin")}}}QScrollBar:horizontal {height:14px;{{|env(value="height:7px;",os="Darwin")}}}QScrollBar:vertical {width:14px;{{|env(value="width:7px;",os="Darwin")}}}QScrollBar::handle {background:{{scrollbarSlider.background|color}};border-radius:{{corner-shape|corner(size=3)}}px}QScrollBar::handle:hover {background:{{scrollbarSlider.hoverBackground|color}}}QScrollBar::handle:pressed {background:{{scrollbarSlider.activeBackground|color}}}QScrollBar::handle:disabled {background:{{scrollbarSlider.disabledBackground|color}}}QScrollBar::handle:horizontal {min-width:8px;margin:4px 14px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:horizontal:hover {margin:2px 14px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:vertical {min-height:8px;margin:14px 4px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:vertical:hover {margin:14px 2px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::sub-page,QScrollBar::add-page {background:transparent}QScrollBar::sub-line,QScrollBar::add-line {background:transparent;{{|env(value="width:0;height:0",os="Darwin")}}}QScrollBar::up-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up")}}}QScrollBar::right-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=90)}}}QScrollBar::down-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=180)}}}QScrollBar::left-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=270)}}}QScrollBar::up-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up")}}}QScrollBar::right-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=90)}}}QScrollBar::down-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=180)}}}QScrollBar::left-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=270)}}}QProgressBar {text-align:center;border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QProgressBar::chunk {background:{{primary|color(state="progressBar.background")}};border-radius:{{corner-shape|corner(size=3)}}px}QProgressBar::chunk:disabled {background:{{foreground|color(state="progressBar.disabledBackground")}}}QPushButton {color:{{primary|color}};border:1px solid {{border|color}};padding:4px 8px;border-radius:{{corner-shape|corner(size=4)}}px}QPushButton:flat,QPushButton:default {border:none;padding:5px 9px}QPushButton:default {color:{{background|color}};background:{{primary|color}}}QPushButton:hover {background:{{primary|color(state="button.hoverBackground")}}}QPushButton:pressed {background:{{primary|color(state="button.activeBackground")}}}QPushButton:checked:enabled {background:{{primary|color(state="button.activeBackground")}}}QPushButton:default:hover {background:{{primary|color(state="defaultButton.hoverBackground")}}}QPushButton:default:pressed,QPushButton:default:checked {background:{{primary|color(state="defaultButton.activeBackground")}}}QPushButton:default:disabled,QPushButton:default:checked:disabled {background:{{foreground|color(state="defaultButton.disabledBackground")}}}QDialogButtonBox {dialogbuttonbox-buttons-have-icons:0}QDialogButtonBox QPushButton {min-width:65px}QToolButton {background:transparent;padding:5px;spacing:2px;border-radius:{{corner-shape|corner(size=2)}}px}QToolButton:hover,QToolButton::menu-button:hover {background:{{primary|color(state="button.hoverBackground")}}}QToolButton:pressed,QToolButton:checked:pressed,QToolButton::menu-button:pressed:enabled {background:{{primary|color(state="button.activeBackground")}}}QToolButton:selected:enabled,QToolButton:checked:enabled {background:{{primary|color(state="button.activeBackground")}}}QToolButton::menu-indicator {height:18px;width:18px;top:6px;left:3px;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QToolButton::menu-indicator:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QToolButton::menu-arrow {image:unset}QToolButton::menu-button {subcontrol-origin:margin;width:17px;border-top-right-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QToolButton::menu-button:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QToolButton[{{|env(value="popupMode=MenuButtonPopup",version="<6.0.0",qt="PySide2")}}{{|env(value="popupMode=\\"1\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="popupMode=MenuButtonPopup",version=">=6.0.0")}}] {padding-right:1px;margin-right:18px;border-top-right-radius:0;border-bottom-right-radius:0}QComboBox {min-height:1.5em;padding:0 8px 0 4px;background:{{input.background|color}};border:1px solid {{border|color(state="input")}};border-radius:{{corner-shape|corner(size=4)}}px}QComboBox:focus,QComboBox:open {border-color:{{primary|color}}}QComboBox::drop-down {margin:2px 2px 2px -6px;border-radius:{{corner-shape|corner(size=4)}}}QComboBox::drop-down:editable:hover {background:{{inputButton.hoverBackground|color}}}QComboBox::down-arrow {image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QComboBox::down-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QComboBox::down-arrow:editable:open {image:{{foreground|color(state="icon")|url(id="expand_less")}}}QComboBox::down-arrow:editable:open:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less")}}}QComboBox::item:selected {border:none;background:{{primary|color(state="list.selectionBackground")}};border-radius:{{corner-shape|corner(size=4)}}px}QComboBox QListView[{{|env(value="frameShape=\\"0\\"",version="<6.0.0")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {margin:0;padding:4px;background:{{background|color(state="popup")}};{{primary|color(state="list.selectionBackground")|env(value="selection-background-color:${};",version="<6.0.0")}} border-radius:0;{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version=">=6.4.1",os="Darwin")}}}QComboBox QListView::item {border-radius:{{corner-shape|corner(size=4)}}px}QSlider {padding:2px 0}QSlider::groove {border-radius:{{corner-shape|corner(size=2)}}px}QSlider::groove:horizontal {height:4px}QSlider::groove:vertical {width:4px}QSlider::sub-page:horizontal,QSlider::add-page:vertical,QSlider::handle {background:{{primary|color}}}QSlider::sub-page:horizontal:disabled,QSlider::add-page:vertical:disabled,QSlider::handle:disabled {background:{{foreground|color(state="slider.disabledBackground")}}}QSlider::add-page:horizontal,QSlider::sub-page:vertical {background:{{foreground|color(state="sliderTrack.inactiveBackground")}}}QSlider::handle:hover,QSlider::handle:pressed {background:{{primary|color(state="sliderHandle.activeBackground")}}}QSlider::handle:horizontal {width:16px;height:8px;margin:-6px 0;border-radius:8px}QSlider::handle:vertical {width:8px;height:16px;margin:0 -6px;border-radius:8px}QTabWidget::pane {border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QTabBar {qproperty-drawBase:0}QTabBar::close-button {image:{{foreground|color(state="icon")|url(id="close")}}}QTabBar::close-button:hover {background:{{tabCloseButton.hoverBackground|color}};border-radius:{{corner-shape|corner(size=4)}}px}QTabBar::close-button:!selected {image:{{foreground|color(state="icon.unfocused")|url(id="close")}}}QTabBar::close-button:disabled {image:{{foreground|color(state="disabled")|url(id="close")}}}QTabBar::tab {padding:3px;border-style:solid}QTabBar::tab:hover,QTabBar::tab:selected:hover:enabled {background:{{tab.hoverBackground|color}}}QTabBar::tab:selected:enabled {color:{{primary|color}};background:{{tab.activeBackground|color}};border-color:{{primary|color}}}QTabBar::tab:selected:disabled,QTabBar::tab:only-one:selected:enabled {border-color:{{border|color}}}QTabBar::tab:top {border-bottom-width:2px;margin:3px 6px 0 0;border-top-left-radius:{{corner-shape|corner(size=2)}}px;border-top-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:bottom {border-top-width:2px;margin:0 6px 3px 0;border-bottom-left-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:left {border-right-width:2px;margin:0 0 6px 3px;border-top-left-radius:{{corner-shape|corner(size=2)}}px;border-bottom-left-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:right {border-left-width:2px;margin-bottom:6px;margin:0 3px 6px 0;border-top-right-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:top:first,QTabBar::tab:top:only-one,QTabBar::tab:bottom:first,QTabBar::tab:bottom:only-one {margin-left:2px}QTabBar::tab:top:last,QTabBar::tab:top:only-one,QTabBar::tab:bottom:last,QTabBar::tab:bottom:only-one {margin-right:2px}QTabBar::tab:left:first,QTabBar::tab:left:only-one,QTabBar::tab:right:first,QTabBar::tab:right:only-one {margin-top:2px}QTabBar::tab:left:last,QTabBar::tab:left:only-one,QTabBar::tab:right:last,QTabBar::tab:right:only-one {margin-bottom:2px}QDockWidget {border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QDockWidget::title {padding:3px;spacing:4px;background:{{background|color(state="title")}}}QDockWidget::close-button,QDockWidget::float-button {border-radius:{{corner-shape|corner(size=2)}}px}QDockWidget::close-button:hover,QDockWidget::float-button:hover {background:{{primary|color(state="button.hoverBackground")}}}QDockWidget::close-button:pressed,QDockWidget::float-button:pressed {background:{{primary|color(state="button.activeBackground")}}}QFrame {border:1px solid {{border|color}};padding:1px;border-radius:{{corner-shape|corner(size=4)}}px}.QFrame {padding:0}QFrame[{{|env(value="frameShape=NoFrame",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"0\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {border-color:transparent;padding:0}.QFrame[{{|env(value="frameShape=NoFrame",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"0\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {border:none}QFrame[{{|env(value="frameShape=Panel",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"2\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=Panel",version=">=6.0.0")}}] {border-color:{{background|color(state="panel")}};background:{{background|color(state="panel")}}}QFrame[{{|env(value="frameShape=HLine",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"4\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=HLine",version=">=6.0.0")}}] {max-height:2px;border:none;background:{{border|color}}}QFrame[{{|env(value="frameShape=VLine",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"5\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=VLine",version=">=6.0.0")}}] {max-width:2px;border:none;background:{{border|color}}}QLCDNumber {min-width:2em;margin:2px}QToolBox::tab {background:{{background|color(state="title")}};border-bottom:2px solid {{border|color}};border-top-left-radius:{{corner-shape|corner(size=4)}}px;border-top-right-radius:{{corner-shape|corner(size=4)}}px}QToolBox::tab:selected:enabled {border-bottom-color:{{primary|color}}}QSplitter::handle {background:{{border|color}};margin:1px 3px}QSplitter::handle:hover {background:{{primary|color}}}QSplitter::handle:horizontal {width:5px;image:{{foreground|color(state="icon")|url(id="horizontal_rule",rotate=90)}}}QSplitter::handle:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="horizontal_rule",rotate=90)}}}QSplitter::handle:vertical {height:5px;image:{{foreground|color(state="icon")|url(id="horizontal_rule")}}}QSplitter::handle:vertical:disabled {image:{{foreground|color(state="disabled")|url(id="horizontal_rule")}}}QSplitterHandle::item:hover {}QAbstractScrollArea {margin:1px}QAbstractScrollArea::corner {background:transparent}QAbstractScrollArea > .QWidget {background:transparent}QAbstractScrollArea > .QWidget > .QWidget {background:transparent}QMdiArea {qproperty-background:{{background|color(state="panel")}};border-radius:0}QMdiSubWindow {background:{{background|color}};border:1px solid;padding:0 3px}QMdiSubWindow > QWidget {border:1px solid {{border|color}}}QTextEdit, QPlainTextEdit {background:{{background|color(state="textarea")}}}QTextEdit:focus,QTextEdit:selected,QPlainTextEdit:focus,QPlainTextEdit:selected {border:1px solid {{primary|color}};selection-background-color:{{primary|color(state="textarea.selectionBackground")}}}QTextEdit:!focus,QPlainTextEdit:!focus { {{textarea.inactiveSelectionBackground|color|env(value="selection-background-color:${}",version=">=5.15.0")}}}QTextEdit:!active,QPlainTextEdit:!active { {{textarea.inactiveSelectionBackground|color|env(value="selection-background-color:${}",version="<5.15.0")}}}QAbstractItemView {padding:0;alternate-background-color:transparent;selection-background-color:transparent}QAbstractItemView:disabled {selection-background-color:transparent}QAbstractItemView::item:alternate,QAbstractItemView::branch:alternate {background:{{list.alternateBackground|color}}}QAbstractItemView::item:selected,QAbstractItemView::branch:selected {background:{{primary|color(state="list.selectionBackground")}}}QAbstractItemView::item:selected:!active,QAbstractItemView::branch:selected:!active {background:{{primary|color(state="list.inactiveSelectionBackground")}}}QAbstractItemView QLineEdit,QAbstractItemView QAbstractSpinBox,QAbstractItemView QAbstractButton {padding:0;margin:1px}QListView {padding:1px}QListView,QTreeView {background:{{background|color(state="list")}}}QListView::item:!selected:hover,QTreeView::item:!selected:hover,QTreeView::branch:!selected:hover {background:{{list.hoverBackground|color}}}QTreeView::branch:!selected:hover,QTreeView::branch:alternate,QTreeView::branch:selected,QTreeView::branch:selected:!active { {{|env(value="background:transparent;",version=">=6.4.1")}}}QTreeView::branch {border-image:{{tree.inactiveIndentGuidesStroke|color|url(id="vertical_line")}} 0}QTreeView::branch:active {border-image:{{tree.indentGuidesStroke|color(state="icon")|url(id="vertical_line")}} 0}QTreeView::branch:has-siblings:adjoins-item,QTreeView::branch:!has-children:!has-siblings:adjoins-item {border-image:unset}QTreeView::branch:has-children:!has-siblings:closed,QTreeView::branch:closed:has-children:has-siblings {border-image:unset;image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QTreeView::branch:has-children:!has-siblings:closed:disabled,QTreeView::branch:closed:has-children:has-siblings:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QTreeView::branch:open:has-children:!has-siblings,QTreeView::branch:open:has-children:has-siblings {border-image:unset;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QTreeView::branch:open:has-children:!has-siblings:disabled,QTreeView::branch:open:has-children:has-siblings:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QTreeView > QHeaderView {background:{{background|color(state="list")}}}QTreeView > QHeaderView::section {background:{{treeSectionHeader.background|color}}}QListView::left-arrow {margin:-2px;image:{{foreground|color(state="icon.unfocused")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow {margin:-2px;image:{{foreground|color(state="icon.unfocused")|url(id="chevron_right")}}}QListView::left-arrow:selected:enabled {image:{{foreground|color(state="icon")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow:selected:enabled {image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QListView::left-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QColumnView {background:{{background|color(state="list")}}}QColumnViewGrip {margin:-4px;background:{{background|color(state="list")}};image:{{foreground|color(state="icon")|url(id="drag_handle",rotate=90)}}}QColumnViewGrip:disabled {image:{{foreground|color(state="disabled")|url(id="drag_handle",rotate=90)}}}QTableView {gridline-color:{{tableSectionHeader.background|color}};background:{{background|color(state="table")}};{{primary|color(state="table.selectionBackground")|env(value="selection-background-color:${};",version=">=6.4.1")}} {{table.alternateBackground|color|env(value="alternate-background-color:${};",version=">=6.4.1")}}}QTableView:!active { {{primary|color(state="table.inactiveSelectionBackground")|env(value="selection-background-color:${};",version="<6.4.1")}}}QTableView::item:alternate { {{table.alternateBackground|color|env(value="background:${};",version="<6.4.1")}}}QTableView::item:selected { {{primary|color(state="table.selectionBackground")|env(value="background:${};",version="<6.4.1")}}}QTableView QTableCornerButton::section {margin:0 1px 1px 0;background:{{tableSectionHeader.background|color}};border-top-left-radius:{{corner-shape|corner(size=2)}}px}QTableView QTableCornerButton::section:pressed {background:{{primary|color(state="table.selectionBackground")}}}QTableView > QHeaderView {background:{{background|color(state="table")}};border-radius:{{corner-shape|corner(size=3)}}}QTableView > QHeaderView::section {background:{{tableSectionHeader.background|color}}}QHeaderView {margin:0;border:none}QHeaderView::section {border:none;background:{{treeSectionHeader.background|color}};padding-left:4px}QHeaderView::section:horizontal {margin-right:1px}QHeaderView::section:vertical {margin-bottom:1px}QHeaderView::section:on:enabled,QHeaderView::section:on:pressed {color:{{primary|color}}}QHeaderView::section:last,QHeaderView::section:only-one {margin:0}QHeaderView::down-arrow:horizontal {margin-left:-19px;subcontrol-position:center right;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QHeaderView::down-arrow:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QHeaderView::up-arrow:horizontal {margin-left:-19px;subcontrol-position:center right;image:{{foreground|color(state="icon")|url(id="expand_less")}}}QHeaderView::up-arrow:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less")}}}QHeaderView::down-arrow:vertical,QHeaderView::up-arrow:vertical {width:0;height:0}QCalendarWidget > .QWidget {background:{{background|color(state="table")}};border-bottom:1px solid {{border|color}};border-top-left-radius:{{corner-shape|corner(size=4)}}px;border-top-right-radius:{{corner-shape|corner(size=4)}}px}QCalendarWidget > .QWidget > QWidget {padding:1px}QCalendarWidget .QWidget > QToolButton {border-radius:{{corner-shape|corner(size=4)}}px}QCalendarWidget > QTableView {margin:0;border:none;border-radius:{{corner-shape|corner(size=4)}}px;border-top-left-radius:0;border-top-right-radius:0;alternate-background-color:{{table.alternateBackground|color}};{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{primary|color(state="table.selectionBackground")|env(value="selection-background-color:${};",version="<6.0.0")}}}QLineEdit,QAbstractSpinBox {padding:3px 4px;min-height:1em;border:1px solid {{border|color(state="input")}};background:{{input.background|color}};border-radius:{{corner-shape|corner(size=4)}}px}QLineEdit:focus,QAbstractSpinBox:focus {border-color:{{primary|color}}}QAbstractSpinBox::up-button,QAbstractSpinBox::down-button {subcontrol-position:center right;border-radius:{{corner-shape|corner(size=4)}}px}QAbstractSpinBox::up-button:hover:on,QAbstractSpinBox::down-button:hover:on {background:{{inputButton.hoverBackground|color}}}QAbstractSpinBox::up-button {bottom:5px;right:4px}QAbstractSpinBox::up-arrow:on {image:{{foreground|color(state="icon")|url(id="arrow_drop_up")}}}QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off {image:{{foreground|color(state="disabled")|url(id="arrow_drop_up")}}}QAbstractSpinBox::down-button {top:5px;right:4px}QAbstractSpinBox::down-arrow:on {image:{{foreground|color(state="icon")|url(id="arrow_drop_up",rotate=180)}}}QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off {image:{{foreground|color(state="disabled")|url(id="arrow_drop_up",rotate=180)}}}QDateTimeEdit::drop-down {padding-right:4px;width:16px;image:{{foreground|color(state="icon")|url(id="calendar_today")}}}QDateTimeEdit::drop-down:disabled {image:{{foreground|color(state="disabled")|url(id="calendar_today")}}}QDateTimeEdit::down-arrow[calendarPopup=true] {image:none}QFileDialog QFrame {border:none}QFontDialog QListView {min-height:60px}QComboBox::indicator,QMenu::indicator {width:18px;height:18px}QMenu::indicator {background:{{popupItem.checkbox.background|color}};margin-left:3px;border-radius:{{corner-shape|corner(size=4)}}px}QComboBox::indicator:checked,QMenu::indicator:checked {image:{{foreground|color(state="icon")|url(id="check")}}}QCheckBox,QRadioButton {spacing:8px}QGroupBox::title,QAbstractItemView::item {spacing:6px}QCheckBox::indicator,QGroupBox::indicator,QAbstractItemView::indicator,QRadioButton::indicator {height:18px;width:18px}QCheckBox::indicator,QGroupBox::indicator,QAbstractItemView::indicator {image:{{foreground|color(state="icon")|url(id="check_box_outline_blank")}}}QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QAbstractItemView::indicator:unchecked:disabled {image:{{foreground|color(state="disabled")|url(id="check_box_outline_blank")}}}QCheckBox::indicator:checked,QGroupBox::indicator:checked,QAbstractItemView::indicator:checked {image:{{primary|color|url(id="check_box")}}}QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QAbstractItemView::indicator:checked:disabled {image:{{foreground|color(state="disabled")|url(id="check_box")}}}QCheckBox::indicator:indeterminate,QAbstractItemView::indicator:indeterminate {image:{{primary|color|url(id="indeterminate_check_box")}}}QCheckBox::indicator:indeterminate:disabled,QAbstractItemView::indicator:indeterminate:disabled {image:{{foreground|color(state="disabled")|url(id="indeterminate_check_box")}}}QRadioButton::indicator:unchecked {image:{{foreground|color(state="icon")|url(id="radio_button_unchecked")}}}QRadioButton::indicator:unchecked:disabled {image:{{foreground|color(state="disabled")|url(id="radio_button_unchecked")}}}QRadioButton::indicator:checked {image:{{primary|color|url(id="radio_button_checked")}}}QRadioButton::indicator:checked:disabled {image:{{foreground|color(state="disabled")|url(id="radio_button_checked")}}}PlotWidget {padding:0}ParameterTree > .QWidget > .QWidget > .QWidget > QComboBox{min-height:1.2em}ParameterTree::item,ParameterTree > .QWidget {background:{{background|color(state="list")}}}' # noqa: E501 -TEMPLATE_STANDARD_ICONS_STYLESHEET = 'QCalendarWidget{leftarrow-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=270)}};rightarrow-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=90)}}}QCommandLinkButton{qproperty-icon:{{foreground|color(state="icon")|url(id="east")}}}QDockWidget,QMdiSubWindow{titlebar-close-icon:{{foreground|color(state="icon")|url(id="close")}};titlebar-normal-icon:{{foreground|color(state="icon")|url(id="flip_to_front")}}}QFileDialog{backward-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=270)}};filedialog-detailedview-icon:{{foreground|color(state="icon")|url(id="list")}};filedialog-listview-icon:{{foreground|color(state="icon")|url(id="grid_view")}};filedialog-new-directory-icon:{{foreground|color(state="icon")|url(id="create_new_folder")}};filedialog-parent-directory-icon:{{foreground|color(state="icon")|url(id="arrow_upward")}};forward-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=90)}}}QLineEdit{ {{foreground|color(state="icon")|url(id="close")|env(value="lineedit-clear-button-icon:${};",version=">=6.0.0")}}}QMdiSubWindow{titlebar-maximize-icon:{{foreground|color(state="icon")|url(id="fullscreen")}};titlebar-minimize-icon:{{foreground|color(state="icon")|url(id="minimize")}}}QToolBarExtension{qproperty-icon:{{foreground|color(state="icon")|url(id="double_arrow")}}}' # noqa: E501 +"""Template stylesheet.""" + +TEMPLATE_STYLESHEET = 'QWidget {background:{{background|color}};color:{{foreground|color}};selection-color:{{foreground|color}};selection-background-color:{{primary|color(state="selection.background")}}}QWidget:disabled {color:{{foreground|color(state="disabled")}};selection-background-color:{{foreground|color(state="disabledSelectionBackground")}};selection-color:{{foreground|color(state="disabled")}}}QWidget:focus {outline:none}QCheckBox:!window,QRadioButton:!window,QPushButton:!window,QLabel:!window,QLCDNumber:!window {background:transparent}QMdiSubWindow > QCheckBox:!window,QMdiSubWindow > QRadioButton:!window,QMdiSubWindow > QPushButton:!window,QMdiSubWindow > QLabel:!window,QMdiSubWindow > QLCDNumber:!window {background:{{background|color}}}QMainWindow::separator {width:4px;height:4px;background:{{border|color}}}QMainWindow::separator:hover,QMainWindow::separator:pressed {background:{{primary|color}}}QToolTip {background:{{background|color(state="popup")}};color:{{foreground|color}}}QSizeGrip {width:0;height:0;image:none}QStatusBar {background:{{statusBar.background|color}}}QStatusBar::item {border:none}QStatusBar QWidget {background:transparent;padding:3px;border-radius:{{corner-shape|corner(size=4)}}px}QStatusBar > .QSizeGrip {padding:0}QStatusBar QWidget:hover {background:{{statusBarItem.hoverBackground|color}}}QStatusBar QWidget:pressed,QStatusBar QWidget:checked {background:{{statusBarItem.activeBackground|color}}}QCheckBox,QRadioButton {border-top:2px solid transparent;border-bottom:2px solid transparent}QCheckBox:hover,QRadioButton:hover {border-bottom:2px solid {{primary|color}}}QGroupBox {font-weight:bold;margin-top:8px;padding:2px 1px 1px 1px;border-radius:{{corner-shape|corner(size=4)}}px;border:1px solid {{border|color}}}QGroupBox::title {subcontrol-origin:margin;subcontrol-position:top left;left:7px;margin:0 2px 0 3px}QGroupBox:flat {border-color:transparent}QMenuBar {padding:2px;border-bottom:1px solid {{border|color}};background:{{background|color}}}QMenuBar::item {background:transparent;padding:4px}QMenuBar::item:selected {padding:4px;border-radius:{{corner-shape|corner(size=4)}}px;background:{{menubar.selectionBackground|color}}}QMenuBar::item:pressed {padding:4px;margin-bottom:0;padding-bottom:0}QToolBar {padding:1px;font-weight:bold;spacing:2px;margin:1px;background:{{toolbar.background|color}};border-style:none}QToolBar::handle:horizontal {width:20px;image:{{foreground|color(state="icon")|url(id="drag_indicator")}}}QToolBar::handle:vertical {height:20px;image:{{foreground|color(state="icon")|url(id="drag_indicator",rotate=90)}}}QToolBar::handle:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="drag_indicator")}}}QToolBar::handle:vertical:disabled {image:{{foreground|color(state="disabled")|url(id="drag_indicator",rotate=90)}}}QToolBar::separator {background:{{border|color}}}QToolBar::separator:horizontal {width:2px;margin:0 6px}QToolBar::separator:vertical {height:2px;margin:6px 0}QToolBar > QToolButton {background:transparent;padding:3px;border-radius:{{corner-shape|corner(size=4)}}px}QToolBar > QToolButton:hover,QToolBar > QToolButton::menu-button:hover {background:{{toolbar.hoverBackground|color}}}QToolBar > QToolButton::menu-button {border-top-right-radius:{{corner-shape|corner(size=4)}}px;border-bottom-right-radius:{{corner-shape|corner(size=4)}}px}QToolBar > QToolButton:pressed,QToolBar > QToolButton::menu-button:pressed:enabled,QToolBar > QToolButton:checked:enabled {background:{{toolbar.activeBackground|color}}}QToolBar > QWidget {background:transparent}QMenu {background:{{background|color(state="popup")}};padding:8px 0;{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version=">=6.4.1",os="Darwin")}}}QMenu::separator {margin:4px 0;height:1px;background:{{border|color}}}QMenu::item {padding:4px 19px}QMenu::item:selected {background:{{popupItem.selectionBackground|color}}}QMenu::icon {padding-left:10px;width:14px;height:14px}QMenu::right-arrow {margin:2px;padding-left:12px;height:20px;width:20px;image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QMenu::right-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QScrollBar {background:{{scrollbar.background|color}};border-radius:{{corner-shape|corner(size=4)}}px;{{|env(value="background:transparent",os="Darwin")}}}QScrollBar:horizontal {height:14px;{{|env(value="height:7px;",os="Darwin")}}}QScrollBar:vertical {width:14px;{{|env(value="width:7px;",os="Darwin")}}}QScrollBar::handle {background:{{scrollbarSlider.background|color}};border-radius:{{corner-shape|corner(size=3)}}px}QScrollBar::handle:hover {background:{{scrollbarSlider.hoverBackground|color}}}QScrollBar::handle:pressed {background:{{scrollbarSlider.activeBackground|color}}}QScrollBar::handle:disabled {background:{{scrollbarSlider.disabledBackground|color}}}QScrollBar::handle:horizontal {min-width:8px;margin:4px 14px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:horizontal:hover {margin:2px 14px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:vertical {min-height:8px;margin:14px 4px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::handle:vertical:hover {margin:14px 2px;{{|env(value="margin:0;",os="Darwin")}}}QScrollBar::sub-page,QScrollBar::add-page {background:transparent}QScrollBar::sub-line,QScrollBar::add-line {background:transparent;{{|env(value="width:0;height:0",os="Darwin")}}}QScrollBar::up-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up")}}}QScrollBar::right-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=90)}}}QScrollBar::down-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=180)}}}QScrollBar::left-arrow:enabled {image:{{scrollbarSlider.background|color|url(id="arrow_drop_up",rotate=270)}}}QScrollBar::up-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up")}}}QScrollBar::right-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=90)}}}QScrollBar::down-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=180)}}}QScrollBar::left-arrow:hover {image:{{scrollbarSlider.activeBackground|color|url(id="arrow_drop_up",rotate=270)}}}QProgressBar {text-align:center;border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QProgressBar::chunk {background:{{primary|color(state="progressBar.background")}};border-radius:{{corner-shape|corner(size=3)}}px}QProgressBar::chunk:disabled {background:{{foreground|color(state="progressBar.disabledBackground")}}}QPushButton {color:{{primary|color}};border:1px solid {{border|color}};padding:4px 8px;border-radius:{{corner-shape|corner(size=4)}}px}QPushButton:flat,QPushButton:default {border:none;padding:5px 9px}QPushButton:default {color:{{background|color}};background:{{primary|color}}}QPushButton:hover {background:{{primary|color(state="button.hoverBackground")}}}QPushButton:pressed {background:{{primary|color(state="button.activeBackground")}}}QPushButton:checked:enabled {background:{{primary|color(state="button.activeBackground")}}}QPushButton:default:hover {background:{{primary|color(state="defaultButton.hoverBackground")}}}QPushButton:default:pressed,QPushButton:default:checked {background:{{primary|color(state="defaultButton.activeBackground")}}}QPushButton:default:disabled,QPushButton:default:checked:disabled {background:{{foreground|color(state="defaultButton.disabledBackground")}}}QDialogButtonBox {dialogbuttonbox-buttons-have-icons:0}QDialogButtonBox QPushButton {min-width:65px}QToolButton {background:transparent;padding:5px;spacing:2px;border-radius:{{corner-shape|corner(size=2)}}px}QToolButton:hover,QToolButton::menu-button:hover {background:{{primary|color(state="button.hoverBackground")}}}QToolButton:pressed,QToolButton:checked:pressed,QToolButton::menu-button:pressed:enabled {background:{{primary|color(state="button.activeBackground")}}}QToolButton:selected:enabled,QToolButton:checked:enabled {background:{{primary|color(state="button.activeBackground")}}}QToolButton::menu-indicator {height:18px;width:18px;top:6px;left:3px;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QToolButton::menu-indicator:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QToolButton::menu-arrow {image:unset}QToolButton::menu-button {subcontrol-origin:margin;width:17px;border-top-right-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QToolButton::menu-button:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QToolButton[{{|env(value="popupMode=MenuButtonPopup",version="<6.0.0",qt="PySide2")}}{{|env(value="popupMode=\\"1\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="popupMode=MenuButtonPopup",version=">=6.0.0")}}] {padding-right:1px;margin-right:18px;border-top-right-radius:0;border-bottom-right-radius:0}QComboBox {min-height:1.5em;padding:0 8px 0 4px;background:{{input.background|color}};border:1px solid {{border|color(state="input")}};border-radius:{{corner-shape|corner(size=4)}}px}QComboBox:focus,QComboBox:open {border-color:{{primary|color}}}QComboBox::drop-down {margin:2px 2px 2px -6px;border-radius:{{corner-shape|corner(size=4)}}}QComboBox::drop-down:editable:hover {background:{{inputButton.hoverBackground|color}}}QComboBox::down-arrow {image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QComboBox::down-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QComboBox::down-arrow:editable:open {image:{{foreground|color(state="icon")|url(id="expand_less")}}}QComboBox::down-arrow:editable:open:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less")}}}QComboBox::item:selected {border:none;background:{{primary|color(state="list.selectionBackground")}};border-radius:{{corner-shape|corner(size=4)}}px}QComboBox QListView[{{|env(value="frameShape=\\"0\\"",version="<6.0.0")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {margin:0;padding:4px;background:{{background|color(state="popup")}};{{primary|color(state="list.selectionBackground")|env(value="selection-background-color:${};",version="<6.0.0")}} border-radius:0;{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version=">=6.4.1",os="Darwin")}}}QComboBox QListView::item {border-radius:{{corner-shape|corner(size=4)}}px}QSlider {padding:2px 0}QSlider::groove {border-radius:{{corner-shape|corner(size=2)}}px}QSlider::groove:horizontal {height:4px}QSlider::groove:vertical {width:4px}QSlider::sub-page:horizontal,QSlider::add-page:vertical,QSlider::handle {background:{{primary|color}}}QSlider::sub-page:horizontal:disabled,QSlider::add-page:vertical:disabled,QSlider::handle:disabled {background:{{foreground|color(state="slider.disabledBackground")}}}QSlider::add-page:horizontal,QSlider::sub-page:vertical {background:{{foreground|color(state="sliderTrack.inactiveBackground")}}}QSlider::handle:hover,QSlider::handle:pressed {background:{{primary|color(state="sliderHandle.activeBackground")}}}QSlider::handle:horizontal {width:16px;height:8px;margin:-6px 0;border-radius:8px}QSlider::handle:vertical {width:8px;height:16px;margin:0 -6px;border-radius:8px}QTabWidget::pane {border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QTabBar {qproperty-drawBase:0}QTabBar::close-button {image:{{foreground|color(state="icon")|url(id="close")}}}QTabBar::close-button:hover {background:{{tabCloseButton.hoverBackground|color}};border-radius:{{corner-shape|corner(size=4)}}px}QTabBar::close-button:!selected {image:{{foreground|color(state="icon.unfocused")|url(id="close")}}}QTabBar::close-button:disabled {image:{{foreground|color(state="disabled")|url(id="close")}}}QTabBar::tab {padding:3px;border-style:solid}QTabBar::tab:hover,QTabBar::tab:selected:hover:enabled {background:{{tab.hoverBackground|color}}}QTabBar::tab:selected:enabled {color:{{primary|color}};background:{{tab.activeBackground|color}};border-color:{{primary|color}}}QTabBar::tab:selected:disabled,QTabBar::tab:only-one:selected:enabled {border-color:{{border|color}}}QTabBar::tab:top {border-bottom-width:2px;margin:3px 6px 0 0;border-top-left-radius:{{corner-shape|corner(size=2)}}px;border-top-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:bottom {border-top-width:2px;margin:0 6px 3px 0;border-bottom-left-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:left {border-right-width:2px;margin:0 0 6px 3px;border-top-left-radius:{{corner-shape|corner(size=2)}}px;border-bottom-left-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:right {border-left-width:2px;margin-bottom:6px;margin:0 3px 6px 0;border-top-right-radius:{{corner-shape|corner(size=2)}}px;border-bottom-right-radius:{{corner-shape|corner(size=2)}}px}QTabBar::tab:top:first,QTabBar::tab:top:only-one,QTabBar::tab:bottom:first,QTabBar::tab:bottom:only-one {margin-left:2px}QTabBar::tab:top:last,QTabBar::tab:top:only-one,QTabBar::tab:bottom:last,QTabBar::tab:bottom:only-one {margin-right:2px}QTabBar::tab:left:first,QTabBar::tab:left:only-one,QTabBar::tab:right:first,QTabBar::tab:right:only-one {margin-top:2px}QTabBar::tab:left:last,QTabBar::tab:left:only-one,QTabBar::tab:right:last,QTabBar::tab:right:only-one {margin-bottom:2px}QDockWidget {border:1px solid {{border|color}};border-radius:{{corner-shape|corner(size=4)}}px}QDockWidget::title {padding:3px;spacing:4px;background:{{background|color(state="title")}}}QDockWidget::close-button,QDockWidget::float-button {border-radius:{{corner-shape|corner(size=2)}}px}QDockWidget::close-button:hover,QDockWidget::float-button:hover {background:{{primary|color(state="button.hoverBackground")}}}QDockWidget::close-button:pressed,QDockWidget::float-button:pressed {background:{{primary|color(state="button.activeBackground")}}}QFrame {border:1px solid {{border|color}};padding:1px;border-radius:{{corner-shape|corner(size=4)}}px}.QFrame {padding:0}QFrame[{{|env(value="frameShape=NoFrame",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"0\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {border-color:transparent;padding:0}.QFrame[{{|env(value="frameShape=NoFrame",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"0\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=NoFrame",version=">=6.0.0")}}] {border:none}QFrame[{{|env(value="frameShape=Panel",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"2\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=Panel",version=">=6.0.0")}}] {border-color:{{background|color(state="panel")}};background:{{background|color(state="panel")}}}QFrame[{{|env(value="frameShape=HLine",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"4\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=HLine",version=">=6.0.0")}}] {max-height:2px;border:none;background:{{border|color}}}QFrame[{{|env(value="frameShape=VLine",version="<6.0.0",qt="PySide2")}}{{|env(value="frameShape=\\"5\\"",version="<6.0.0",qt="PyQt5")}}{{|env(value="frameShape=VLine",version=">=6.0.0")}}] {max-width:2px;border:none;background:{{border|color}}}QLCDNumber {min-width:2em;margin:2px}QToolBox::tab {background:{{background|color(state="title")}};border-bottom:2px solid {{border|color}};border-top-left-radius:{{corner-shape|corner(size=4)}}px;border-top-right-radius:{{corner-shape|corner(size=4)}}px}QToolBox::tab:selected:enabled {border-bottom-color:{{primary|color}}}QSplitter::handle {background:{{border|color}};margin:1px 3px}QSplitter::handle:hover {background:{{primary|color}}}QSplitter::handle:horizontal {width:5px;image:{{foreground|color(state="icon")|url(id="horizontal_rule",rotate=90)}}}QSplitter::handle:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="horizontal_rule",rotate=90)}}}QSplitter::handle:vertical {height:5px;image:{{foreground|color(state="icon")|url(id="horizontal_rule")}}}QSplitter::handle:vertical:disabled {image:{{foreground|color(state="disabled")|url(id="horizontal_rule")}}}QSplitterHandle::item:hover {}QAbstractScrollArea {margin:1px}QAbstractScrollArea::corner {background:transparent}QAbstractScrollArea > .QWidget {background:transparent}QAbstractScrollArea > .QWidget > .QWidget {background:transparent}QMdiArea {qproperty-background:{{background|color(state="panel")}};border-radius:0}QMdiSubWindow {background:{{background|color}};border:1px solid;padding:0 3px}QMdiSubWindow > QWidget {border:1px solid {{border|color}}}QTextEdit, QPlainTextEdit {background:{{background|color(state="textarea")}}}QTextEdit:focus,QTextEdit:selected,QPlainTextEdit:focus,QPlainTextEdit:selected {border:1px solid {{primary|color}};selection-background-color:{{primary|color(state="textarea.selectionBackground")}}}QTextEdit:!focus,QPlainTextEdit:!focus { {{textarea.inactiveSelectionBackground|color|env(value="selection-background-color:${}",version=">=5.15.0")}}}QTextEdit:!active,QPlainTextEdit:!active { {{textarea.inactiveSelectionBackground|color|env(value="selection-background-color:${}",version="<5.15.0")}}}QAbstractItemView {padding:0;alternate-background-color:transparent;selection-background-color:transparent}QAbstractItemView:disabled {selection-background-color:transparent}QAbstractItemView::item:alternate,QAbstractItemView::branch:alternate {background:{{list.alternateBackground|color}}}QAbstractItemView::item:selected,QAbstractItemView::branch:selected {background:{{primary|color(state="list.selectionBackground")}}}QAbstractItemView::item:selected:!active,QAbstractItemView::branch:selected:!active {background:{{primary|color(state="list.inactiveSelectionBackground")}}}QAbstractItemView QLineEdit,QAbstractItemView QAbstractSpinBox,QAbstractItemView QAbstractButton {padding:0;margin:1px}QListView {padding:1px}QListView,QTreeView {background:{{background|color(state="list")}}}QListView::item:!selected:hover,QTreeView::item:!selected:hover,QTreeView::branch:!selected:hover {background:{{list.hoverBackground|color}}}QTreeView::branch:!selected:hover,QTreeView::branch:alternate,QTreeView::branch:selected,QTreeView::branch:selected:!active { {{|env(value="background:transparent;",version=">=6.4.1")}}}QTreeView::branch {border-image:{{tree.inactiveIndentGuidesStroke|color|url(id="vertical_line")}} 0}QTreeView::branch:active {border-image:{{tree.indentGuidesStroke|color(state="icon")|url(id="vertical_line")}} 0}QTreeView::branch:has-siblings:adjoins-item,QTreeView::branch:!has-children:!has-siblings:adjoins-item {border-image:unset}QTreeView::branch:has-children:!has-siblings:closed,QTreeView::branch:closed:has-children:has-siblings {border-image:unset;image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QTreeView::branch:has-children:!has-siblings:closed:disabled,QTreeView::branch:closed:has-children:has-siblings:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QTreeView::branch:open:has-children:!has-siblings,QTreeView::branch:open:has-children:has-siblings {border-image:unset;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QTreeView::branch:open:has-children:!has-siblings:disabled,QTreeView::branch:open:has-children:has-siblings:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QTreeView > QHeaderView {background:{{background|color(state="list")}}}QTreeView > QHeaderView::section {background:{{treeSectionHeader.background|color}}}QListView::left-arrow {margin:-2px;image:{{foreground|color(state="icon.unfocused")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow {margin:-2px;image:{{foreground|color(state="icon.unfocused")|url(id="chevron_right")}}}QListView::left-arrow:selected:enabled {image:{{foreground|color(state="icon")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow:selected:enabled {image:{{foreground|color(state="icon")|url(id="chevron_right")}}}QListView::left-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right",rotate=180)}}}QListView::right-arrow:disabled {image:{{foreground|color(state="disabled")|url(id="chevron_right")}}}QColumnView {background:{{background|color(state="list")}}}QColumnViewGrip {margin:-4px;background:{{background|color(state="list")}};image:{{foreground|color(state="icon")|url(id="drag_handle",rotate=90)}}}QColumnViewGrip:disabled {image:{{foreground|color(state="disabled")|url(id="drag_handle",rotate=90)}}}QTableView {gridline-color:{{tableSectionHeader.background|color}};background:{{background|color(state="table")}};{{primary|color(state="table.selectionBackground")|env(value="selection-background-color:${};",version=">=6.4.1")}} {{table.alternateBackground|color|env(value="alternate-background-color:${};",version=">=6.4.1")}}}QTableView:!active { {{primary|color(state="table.inactiveSelectionBackground")|env(value="selection-background-color:${};",version="<6.4.1")}}}QTableView::item:alternate { {{table.alternateBackground|color|env(value="background:${};",version="<6.4.1")}}}QTableView::item:selected { {{primary|color(state="table.selectionBackground")|env(value="background:${};",version="<6.4.1")}}}QTableView QTableCornerButton::section {margin:0 1px 1px 0;background:{{tableSectionHeader.background|color}};border-top-left-radius:{{corner-shape|corner(size=2)}}px}QTableView QTableCornerButton::section:pressed {background:{{primary|color(state="table.selectionBackground")}}}QTableView > QHeaderView {background:{{background|color(state="table")}};border-radius:{{corner-shape|corner(size=3)}}}QTableView > QHeaderView::section {background:{{tableSectionHeader.background|color}}}QHeaderView {margin:0;border:none}QHeaderView::section {border:none;background:{{treeSectionHeader.background|color}};padding-left:4px}QHeaderView::section:horizontal {margin-right:1px}QHeaderView::section:vertical {margin-bottom:1px}QHeaderView::section:on:enabled,QHeaderView::section:on:pressed {color:{{primary|color}}}QHeaderView::section:last,QHeaderView::section:only-one {margin:0}QHeaderView::down-arrow:horizontal {margin-left:-19px;subcontrol-position:center right;image:{{foreground|color(state="icon")|url(id="expand_less",rotate=180)}}}QHeaderView::down-arrow:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less",rotate=180)}}}QHeaderView::up-arrow:horizontal {margin-left:-19px;subcontrol-position:center right;image:{{foreground|color(state="icon")|url(id="expand_less")}}}QHeaderView::up-arrow:horizontal:disabled {image:{{foreground|color(state="disabled")|url(id="expand_less")}}}QHeaderView::down-arrow:vertical,QHeaderView::up-arrow:vertical {width:0;height:0}QCalendarWidget > .QWidget {background:{{background|color(state="table")}};border-bottom:1px solid {{border|color}};border-top-left-radius:{{corner-shape|corner(size=4)}}px;border-top-right-radius:{{corner-shape|corner(size=4)}}px}QCalendarWidget > .QWidget > QWidget {padding:1px}QCalendarWidget .QWidget > QToolButton {border-radius:{{corner-shape|corner(size=4)}}px}QCalendarWidget > QTableView {margin:0;border:none;border-radius:{{corner-shape|corner(size=4)}}px;border-top-left-radius:0;border-top-right-radius:0;alternate-background-color:{{table.alternateBackground|color}};{{corner-shape|corner(size=4)|env(value="border-radius:${}px;",version="<6.0.0",os="Darwin")}} {{primary|color(state="table.selectionBackground")|env(value="selection-background-color:${};",version="<6.0.0")}}}QLineEdit,QAbstractSpinBox {padding:3px 4px;min-height:1em;border:1px solid {{border|color(state="input")}};background:{{input.background|color}};border-radius:{{corner-shape|corner(size=4)}}px}QLineEdit:focus,QAbstractSpinBox:focus {border-color:{{primary|color}}}QAbstractSpinBox::up-button,QAbstractSpinBox::down-button {subcontrol-position:center right;border-radius:{{corner-shape|corner(size=4)}}px}QAbstractSpinBox::up-button:hover:on,QAbstractSpinBox::down-button:hover:on {background:{{inputButton.hoverBackground|color}}}QAbstractSpinBox::up-button {bottom:5px;right:4px}QAbstractSpinBox::up-arrow:on {image:{{foreground|color(state="icon")|url(id="arrow_drop_up")}}}QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off {image:{{foreground|color(state="disabled")|url(id="arrow_drop_up")}}}QAbstractSpinBox::down-button {top:5px;right:4px}QAbstractSpinBox::down-arrow:on {image:{{foreground|color(state="icon")|url(id="arrow_drop_up",rotate=180)}}}QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off {image:{{foreground|color(state="disabled")|url(id="arrow_drop_up",rotate=180)}}}QDateTimeEdit::drop-down {padding-right:4px;width:16px;image:{{foreground|color(state="icon")|url(id="calendar_today")}}}QDateTimeEdit::drop-down:disabled {image:{{foreground|color(state="disabled")|url(id="calendar_today")}}}QDateTimeEdit::down-arrow[calendarPopup=true] {image:none}QFileDialog QFrame {border:none}QFontDialog QListView {min-height:60px}QComboBox::indicator,QMenu::indicator {width:18px;height:18px}QMenu::indicator {background:{{popupItem.checkbox.background|color}};margin-left:3px;border-radius:{{corner-shape|corner(size=4)}}px}QComboBox::indicator:checked,QMenu::indicator:checked {image:{{foreground|color(state="icon")|url(id="check")}}}QCheckBox,QRadioButton {spacing:8px}QGroupBox::title,QAbstractItemView::item {spacing:6px}QCheckBox::indicator,QGroupBox::indicator,QAbstractItemView::indicator,QRadioButton::indicator {height:18px;width:18px}QCheckBox::indicator,QGroupBox::indicator,QAbstractItemView::indicator {image:{{foreground|color(state="icon")|url(id="check_box_outline_blank")}}}QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QAbstractItemView::indicator:unchecked:disabled {image:{{foreground|color(state="disabled")|url(id="check_box_outline_blank")}}}QCheckBox::indicator:checked,QGroupBox::indicator:checked,QAbstractItemView::indicator:checked {image:{{primary|color|url(id="check_box")}}}QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QAbstractItemView::indicator:checked:disabled {image:{{foreground|color(state="disabled")|url(id="check_box")}}}QCheckBox::indicator:indeterminate,QAbstractItemView::indicator:indeterminate {image:{{primary|color|url(id="indeterminate_check_box")}}}QCheckBox::indicator:indeterminate:disabled,QAbstractItemView::indicator:indeterminate:disabled {image:{{foreground|color(state="disabled")|url(id="indeterminate_check_box")}}}QRadioButton::indicator:unchecked {image:{{foreground|color(state="icon")|url(id="radio_button_unchecked")}}}QRadioButton::indicator:unchecked:disabled {image:{{foreground|color(state="disabled")|url(id="radio_button_unchecked")}}}QRadioButton::indicator:checked {image:{{primary|color|url(id="radio_button_checked")}}}QRadioButton::indicator:checked:disabled {image:{{foreground|color(state="disabled")|url(id="radio_button_checked")}}}PlotWidget {padding:0}ParameterTree > .QWidget > .QWidget > .QWidget > QComboBox{min-height:1.2em}ParameterTree::item,ParameterTree > .QWidget {background:{{background|color(state="list")}}}' # noqa: E501 +TEMPLATE_STANDARD_ICONS_STYLESHEET = 'QCalendarWidget{leftarrow-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=270)}};rightarrow-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=90)}}}QCommandLinkButton{qproperty-icon:{{foreground|color(state="icon")|url(id="east")}}}QDockWidget,QMdiSubWindow{titlebar-close-icon:{{foreground|color(state="icon")|url(id="close")}};titlebar-normal-icon:{{foreground|color(state="icon")|url(id="flip_to_front")}}}QFileDialog{backward-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=270)}};filedialog-detailedview-icon:{{foreground|color(state="icon")|url(id="list")}};filedialog-listview-icon:{{foreground|color(state="icon")|url(id="grid_view")}};filedialog-new-directory-icon:{{foreground|color(state="icon")|url(id="create_new_folder")}};filedialog-parent-directory-icon:{{foreground|color(state="icon")|url(id="arrow_upward")}};forward-icon:{{foreground|color(state="icon")|url(id="arrow_upward",rotate=90)}}}QLineEdit{ {{foreground|color(state="icon")|url(id="close")|env(value="lineedit-clear-button-icon:${};",version=">=6.0.0")}}}QMdiSubWindow{titlebar-maximize-icon:{{foreground|color(state="icon")|url(id="fullscreen")}};titlebar-minimize-icon:{{foreground|color(state="icon")|url(id="minimize")}}}QToolBarExtension{qproperty-icon:{{foreground|color(state="icon")|url(id="double_arrow")}}}' # noqa: E501 diff --git a/qdarktheme/_resources/svg.py b/qdarktheme/_resources/svg.py index 04aa0af2..9d6cee61 100644 --- a/qdarktheme/_resources/svg.py +++ b/qdarktheme/_resources/svg.py @@ -1,5 +1,5 @@ -"""SVG resource.""" - -SVG_RESOURCES = """ -{"arrow_drop_up": "", "arrow_upward": "", "calendar_today": "", "cancel": "", "check": "", "check_box": "", "check_box_outline_blank": "", "check_circle": "", "chevron_right": "", "circle": "", "cleaning_services": "", "close": "", "create_new_folder": "", "delete": "", "done_all": "", "double_arrow": "", "drag_handle": "", "drag_indicator": "", "drive_file_move": "", "drive_file_move_rtl": "", "east": "", "expand_less": "", "fast_forward": "", "fast_rewind": "", "flip_to_front": "", "fullscreen": "", "grid_view": "", "help": "", "home": "", "horizontal_rule": "", "indeterminate_check_box": "", "info": "", "launch": "", "list": "", "minimize": "", "not_interested": "", "pause": "", "play_arrow": "", "question_mark": "", "radio_button_checked": "", "radio_button_unchecked": "", "refresh": "", "restart_alt": "", "save": "", "search": "", "security": "", "skip_next": "", "skip_previous": "", "stop": "", "vertical_line": "", "visibility_off": "", "volume_mute": "", "volume_up": ""} -""" # noqa: E501 +"""SVG resource.""" + +SVG_RESOURCES = """ +{"arrow_drop_up": "", "arrow_upward": "", "calendar_today": "", "cancel": "", "check": "", "check_box": "", "check_box_outline_blank": "", "check_circle": "", "chevron_right": "", "circle": "", "cleaning_services": "", "close": "", "create_new_folder": "", "delete": "", "done_all": "", "double_arrow": "", "drag_handle": "", "drag_indicator": "", "drive_file_move": "", "drive_file_move_rtl": "", "east": "", "expand_less": "", "fast_forward": "", "fast_rewind": "", "flip_to_front": "", "fullscreen": "", "grid_view": "", "help": "", "home": "", "horizontal_rule": "", "indeterminate_check_box": "", "info": "", "launch": "", "list": "", "minimize": "", "not_interested": "", "pause": "", "play_arrow": "", "question_mark": "", "radio_button_checked": "", "radio_button_unchecked": "", "refresh": "", "restart_alt": "", "save": "", "search": "", "security": "", "skip_next": "", "skip_previous": "", "stop": "", "vertical_line": "", "visibility_off": "", "volume_mute": "", "volume_up": ""} +""" # noqa: E501 diff --git a/qdarktheme/_style_loader.py b/qdarktheme/_style_loader.py index e74533fb..c9be8713 100644 --- a/qdarktheme/_style_loader.py +++ b/qdarktheme/_style_loader.py @@ -1,302 +1,304 @@ -"""Module for loading style data for Qt.""" -from __future__ import annotations - -import json -import shutil -from functools import partial - -from qdarktheme import __version__, _os_appearance, _resources -from qdarktheme._template import filter -from qdarktheme._template.engine import Template -from qdarktheme._util import get_cash_root_path, get_logger - -_logger = get_logger(__name__) - - -def _detect_system_theme(default_theme: str) -> str: - import darkdetect - - system_theme = darkdetect.theme() - if system_theme is None: - _logger.info(f'failed to detect system theme, qdarktheme use "{default_theme}" theme.') - return default_theme - return system_theme.lower() - - -def _color_values(theme: str) -> dict[str, str | dict]: - try: - return json.loads(_resources.colors.THEME_COLOR_VALUES[theme]) - except KeyError: - raise ValueError(f'invalid argument, not a dark, light or auto: "{theme}"') from None - - -def _has_primary_color(custom_colors: dict[str, str | dict[str, str]], theme: str) -> bool: - if any("primary" in color for color in custom_colors): - return True - custom_colors_with_theme = custom_colors.get(f"[{theme}]") - if custom_colors_with_theme is None: - return False - if not isinstance(custom_colors_with_theme, dict): - raise ValueError( - "invalid value for argument custom_colors, not a dict type: " - f'"{custom_colors_with_theme}" of "[{theme}]" key.' - ) - return any("primary" in color for color in custom_colors_with_theme) - - -def _apply_os_accent_color( - custom_colors: dict[str, str | dict[str, str]] | None, theme: str -) -> dict[str, str | dict[str, str]] | None: - accent = _os_appearance.accent() - if accent is None: - return custom_colors - try: - accent_color = _resources.colors.ACCENT_COLORS[theme].get(accent) - except KeyError: - raise ValueError(f'invalid argument, not a dark, light or auto: "{theme}"') from None - if accent_color is None: - return custom_colors - - if custom_colors is None: - return {"primary": accent_color} - custom_colors = custom_colors.copy() - if not _has_primary_color(custom_colors, theme): - custom_colors["primary"] = accent_color - return custom_colors - - -def _mix_theme_colors(custom_colors: dict[str, str | dict[str, str]], theme: str) -> dict[str, str]: - colors = {id: color for id, color in custom_colors.items() if isinstance(color, str)} - custom_colors_with_theme = custom_colors.get(f"[{theme}]") - if isinstance(custom_colors_with_theme, dict): - colors.update(custom_colors_with_theme) - elif isinstance(custom_colors_with_theme, str): - raise ValueError( - "invalid value for argument custom_colors, not a dict type: " - f'"{custom_colors_with_theme}" of "[{theme}]" key.' - ) - return colors - - -def _marge_colors( - color_values: dict[str, str | dict], custom_colors: dict[str, str | dict[str, str]], theme: str -): - for color_id, color in _mix_theme_colors(custom_colors, theme).items(): - try: - parent_key, *child_keys = color_id.split(">") - color_value = color_values[parent_key] - if len(child_keys) > 1 or (isinstance(color_value, str) and len(child_keys) != 0): - raise KeyError - - if isinstance(color_value, str): - color_values[parent_key] = color - else: - child_key = "base" if len(child_keys) == 0 else child_keys[0] - color_value[child_key] # Check if child_key exists. - color_value[child_key] = color - except KeyError: - raise KeyError(f'invalid color id for argument custom_colors: "{color_id}".') from None - - -def load_stylesheet( - theme: str = "dark", - corner_shape: str = "rounded", - custom_colors: dict[str, str | dict[str, str]] | None = None, - *, - default_theme: str = "dark", -) -> str: - """Load the style sheet which looks like flat design. There are `dark` and `light` theme. - - Args: - theme: The theme name. There are `dark`, `light` and `auto`. - If ``auto``, try to detect your OS's theme and accent (accent is only on Mac). - If failed to detect OS's theme, use the default theme set in argument ``default_theme``. - When primary color(``primary``) or primary child colors - (such as ``primary>selection.background``) are set to custom_colors, - disable to detect the accent. - corner_shape: The corner shape. There are `rounded` and `sharp` shape. - custom_colors: The custom color map. Overrides the default color for color id you set. - Also you can customize a specific theme only. See example 6. - default_theme: The default theme name. - The theme set by this argument will be used when system theme detection fails. - - Raises: - ValueError: If the arguments of this method is wrong. - KeyError: If the color id of custom_colors is wrong. - - Returns: - The stylesheet string for the given arguments. - - Examples: - Set stylesheet to your Qt application. - - 1. Dark Theme :: - - app = QApplication([]) - app.setStyleSheet(qdarktheme.load_stylesheet()) - # or - app.setStyleSheet(qdarktheme.load_stylesheet("dark")) - - 2. Light Theme :: - - app = QApplication([]) - app.setStyleSheet(qdarktheme.load_stylesheet("light")) - - 3. Automatic detection of system theme :: - - app = QApplication([]) - app.setStyleSheet(qdarktheme.load_stylesheet("auto")) - - 4. Sharp corner :: - - # Change corner shape to sharp. - app = QApplication([]) - app.setStyleSheet(qdarktheme.load_stylesheet(corner_shape="sharp")) - - 5. Customize color :: - - app = QApplication([]) - app.setStyleSheet(qdarktheme.load_stylesheet(custom_colors={"primary": "#D0BCFF"})) - - 6. Customize a specific theme only :: - - app = QApplication([]) - app.setStyleSheet( - qdarktheme.load_stylesheet( - theme="auto", - custom_colors={ - "[dark]": { - "primary": "#D0BCFF", - } - }, - ) - ) - """ - if theme == "auto": - theme = _detect_system_theme(default_theme) - custom_colors = _apply_os_accent_color(custom_colors, theme) - color_values = _color_values(theme) - if corner_shape not in ("rounded", "sharp"): - raise ValueError(f'invalid argument, not a rounded or sharp: "{corner_shape}"') - - if custom_colors is not None: - _marge_colors(color_values, custom_colors, theme) - - get_cash_root_path(__version__).mkdir(parents=True, exist_ok=True) - - stylesheet = _resources.stylesheets.TEMPLATE_STYLESHEET - try: - from qdarktheme.qtpy.QtCore import QCoreApplication - - app = QCoreApplication.instance() - if app is not None and not app.property("_qdarktheme_use_setup_style"): - stylesheet += _resources.stylesheets.TEMPLATE_STANDARD_ICONS_STYLESHEET - except Exception: # noqa: PIE786 - pass - - # Build stylesheet - template = Template( - stylesheet, - {"color": filter.color, "corner": filter.corner, "env": filter.env, "url": filter.url}, - ) - replacements = dict(color_values, **{"corner-shape": corner_shape}) - return template.render(replacements) - - -def clear_cache() -> None: - """Clear the caches in system home path. - - PyQtDarkTheme build the caches of resources in the system home path. - You can clear the caches by running this method. - """ - try: - cache_path = get_cash_root_path(__version__) - shutil.rmtree(cache_path) - _logger.info(f"The caches({cache_path}) has been deleted") - except FileNotFoundError: - _logger.info("There is no caches") - - -def load_palette( - theme: str = "dark", - custom_colors: dict[str, str | dict[str, str]] | None = None, - *, - default_theme: str = "dark", - for_stylesheet: bool = False, -): - """Load the QPalette for the dark or light theme. - - Args: - theme: The theme name. There are `dark`, `light` and `auto`. - If ``auto``, try to detect system theme. - If failed to detect system theme, use the theme set in argument ``default_theme``. - custom_colors: The custom color map. Overrides the default color for color id you set. - Also you can customize a specific theme only. See example 5. - default_theme: The default theme name. - The theme set by this argument will be used when system theme detection fails. - for_stylesheet: If True, only includes colors that cannot be set in stylesheets, such as - ``link`` and ``placeholder``. - - Raises: - TypeError: If the arg name of theme is wrong. - KeyError: If the color id of custom_colors is wrong. - - Returns: - QPalette: The QPalette for the given theme. - - Examples: - Set QPalette to your Qt application. - - 1. Dark Theme :: - - app = QApplication([]) - app.setPalette(qdarktheme.load_palette()) - # or - app.setPalette(qdarktheme.load_palette("dark")) - - 2. Light Theme :: - - app = QApplication([]) - app.setPalette(qdarktheme.load_palette("light")) - - 3. Automatic detection of system theme :: - - app = QApplication([]) - app.setPalette(qdarktheme.load_palette("auto")) - - 4. Customize color :: - - app = QApplication([]) - app.setPalette(custom_colors={"primary": "#D0BCFF"}) - - 5. Customize a specific theme only :: - - app = QApplication([]) - app.setStyleSheet( - qdarktheme.load_stylesheet( - theme="auto", - custom_colors={ - "[dark]": { - "primary": "#D0BCFF", - } - }, - ) - ) - """ - if theme == "auto": - theme = _detect_system_theme(default_theme) - color_values = _color_values(theme) - if custom_colors is not None: - _marge_colors(color_values, custom_colors, theme) - - mk_template = partial(Template, filters={"color": filter.color, "palette": filter.palette_format}) - return _resources.palette.q_palette(mk_template, color_values, for_stylesheet) - - -def get_themes() -> tuple[str, ...]: - """Return available theme names. - - Returns: - Tuple of available theme names. - """ - return _resources.THEMES +"""Module for loading style data for Qt.""" +from __future__ import annotations + +import json +import shutil +from functools import partial + +from qdarktheme import __version__, _os_appearance, _resources +from qdarktheme._template import filter +from qdarktheme._template.engine import Template +from qdarktheme._util import get_cash_root_path, get_logger + +_logger = get_logger(__name__) + + +def _detect_system_theme(default_theme: str) -> str: + import darkdetect + + system_theme = darkdetect.theme() + if system_theme is None: + _logger.info(f'failed to detect system theme, qdarktheme use "{default_theme}" theme.') + return default_theme + return system_theme.lower() + + +def _color_values(theme: str) -> dict[str, str | dict]: + try: + return json.loads(_resources.colors.THEME_COLOR_VALUES[theme]) + except KeyError: + raise ValueError(f'invalid argument, not a dark, light or auto: "{theme}"') from None + + +def _has_primary_color(custom_colors: dict[str, str | dict[str, str]], theme: str) -> bool: + if any("primary" in color for color in custom_colors): + return True + custom_colors_with_theme = custom_colors.get(f"[{theme}]") + if custom_colors_with_theme is None: + return False + if not isinstance(custom_colors_with_theme, dict): + raise ValueError( + "invalid value for argument custom_colors, not a dict type: " + f'"{custom_colors_with_theme}" of "[{theme}]" key.' + ) + return any("primary" in color for color in custom_colors_with_theme) + + +def _apply_os_accent_color( + custom_colors: dict[str, str | dict[str, str]] | None, theme: str +) -> dict[str, str | dict[str, str]] | None: + accent = _os_appearance.accent() + if accent is None: + return custom_colors + try: + if accent[0] != "#": + accent_color = _resources.colors.ACCENT_COLORS[theme].get(accent) + else: + accent_color = accent + except KeyError: + raise ValueError(f'invalid argument, not a dark, light or auto: "{theme}"') from None + if accent_color is None: + return custom_colors + if custom_colors is None: + return {"primary": accent_color} + custom_colors = custom_colors.copy() + if not _has_primary_color(custom_colors, theme): + custom_colors["primary"] = accent_color + return custom_colors + + +def _mix_theme_colors(custom_colors: dict[str, str | dict[str, str]], theme: str) -> dict[str, str]: + colors = {id: color for id, color in custom_colors.items() if isinstance(color, str)} + custom_colors_with_theme = custom_colors.get(f"[{theme}]") + if isinstance(custom_colors_with_theme, dict): + colors.update(custom_colors_with_theme) + elif isinstance(custom_colors_with_theme, str): + raise ValueError( + "invalid value for argument custom_colors, not a dict type: " + f'"{custom_colors_with_theme}" of "[{theme}]" key.' + ) + return colors + + +def _marge_colors( + color_values: dict[str, str | dict], custom_colors: dict[str, str | dict[str, str]], theme: str +): + for color_id, color in _mix_theme_colors(custom_colors, theme).items(): + try: + parent_key, *child_keys = color_id.split(">") + color_value = color_values[parent_key] + if len(child_keys) > 1 or (isinstance(color_value, str) and len(child_keys) != 0): + raise KeyError + + if isinstance(color_value, str): + color_values[parent_key] = color + else: + child_key = "base" if len(child_keys) == 0 else child_keys[0] + color_value[child_key] # Check if child_key exists. + color_value[child_key] = color + except KeyError: + raise KeyError(f'invalid color id for argument custom_colors: "{color_id}".') from None + + +def load_stylesheet( + theme: str = "dark", + corner_shape: str = "rounded", + custom_colors: dict[str, str | dict[str, str]] | None = None, + *, + default_theme: str = "dark", +) -> str: + """Load the style sheet which looks like flat design. There are `dark` and `light` theme. + + Args: + theme: The theme name. There are `dark`, `light` and `auto`. + If ``auto``, try to detect your OS's theme and accent (accent is only on Mac). + If failed to detect OS's theme, use the default theme set in argument ``default_theme``. + When primary color(``primary``) or primary child colors + (such as ``primary>selection.background``) are set to custom_colors, + disable to detect the accent. + corner_shape: The corner shape. There are `rounded` and `sharp` shape. + custom_colors: The custom color map. Overrides the default color for color id you set. + Also you can customize a specific theme only. See example 6. + default_theme: The default theme name. + The theme set by this argument will be used when system theme detection fails. + + Raises: + ValueError: If the arguments of this method is wrong. + KeyError: If the color id of custom_colors is wrong. + + Returns: + The stylesheet string for the given arguments. + + Examples: + Set stylesheet to your Qt application. + + 1. Dark Theme :: + + app = QApplication([]) + app.setStyleSheet(qdarktheme.load_stylesheet()) + # or + app.setStyleSheet(qdarktheme.load_stylesheet("dark")) + + 2. Light Theme :: + + app = QApplication([]) + app.setStyleSheet(qdarktheme.load_stylesheet("light")) + + 3. Automatic detection of system theme :: + + app = QApplication([]) + app.setStyleSheet(qdarktheme.load_stylesheet("auto")) + + 4. Sharp corner :: + + # Change corner shape to sharp. + app = QApplication([]) + app.setStyleSheet(qdarktheme.load_stylesheet(corner_shape="sharp")) + + 5. Customize color :: + + app = QApplication([]) + app.setStyleSheet(qdarktheme.load_stylesheet(custom_colors={"primary": "#D0BCFF"})) + + 6. Customize a specific theme only :: + + app = QApplication([]) + app.setStyleSheet( + qdarktheme.load_stylesheet( + theme="auto", + custom_colors={ + "[dark]": { + "primary": "#D0BCFF", + } + }, + ) + ) + """ + if theme == "auto": + theme = _detect_system_theme(default_theme) + custom_colors = _apply_os_accent_color(custom_colors, theme) + color_values = _color_values(theme) + if corner_shape not in ("rounded", "sharp"): + raise ValueError(f'invalid argument, not a rounded or sharp: "{corner_shape}"') + + if custom_colors is not None: + _marge_colors(color_values, custom_colors, theme) + + get_cash_root_path(__version__).mkdir(parents=True, exist_ok=True) + + stylesheet = _resources.stylesheets.TEMPLATE_STYLESHEET + try: + from qdarktheme.qtpy.QtCore import QCoreApplication + + app = QCoreApplication.instance() + if app is not None and not app.property("_qdarktheme_use_setup_style"): + stylesheet += _resources.stylesheets.TEMPLATE_STANDARD_ICONS_STYLESHEET + except Exception: # noqa: PIE786 + pass + + # Build stylesheet + template = Template( + stylesheet, + {"color": filter.color, "corner": filter.corner, "env": filter.env, "url": filter.url}, + ) + replacements = dict(color_values, **{"corner-shape": corner_shape}) + return template.render(replacements) + + +def clear_cache() -> None: + """Clear the caches in system home path. + + PyQtDarkTheme build the caches of resources in the system home path. + You can clear the caches by running this method. + """ + try: + cache_path = get_cash_root_path(__version__) + shutil.rmtree(cache_path) + _logger.info(f"The caches({cache_path}) has been deleted") + except FileNotFoundError: + _logger.info("There is no caches") + + +def load_palette( + theme: str = "dark", + custom_colors: dict[str, str | dict[str, str]] | None = None, + *, + default_theme: str = "dark", + for_stylesheet: bool = False, +): + """Load the QPalette for the dark or light theme. + + Args: + theme: The theme name. There are `dark`, `light` and `auto`. + If ``auto``, try to detect system theme. + If failed to detect system theme, use the theme set in argument ``default_theme``. + custom_colors: The custom color map. Overrides the default color for color id you set. + Also you can customize a specific theme only. See example 5. + default_theme: The default theme name. + The theme set by this argument will be used when system theme detection fails. + for_stylesheet: If True, only includes colors that cannot be set in stylesheets, such as + ``link`` and ``placeholder``. + + Raises: + TypeError: If the arg name of theme is wrong. + KeyError: If the color id of custom_colors is wrong. + + Returns: + QPalette: The QPalette for the given theme. + + Examples: + Set QPalette to your Qt application. + + 1. Dark Theme :: + + app = QApplication([]) + app.setPalette(qdarktheme.load_palette()) + # or + app.setPalette(qdarktheme.load_palette("dark")) + + 2. Light Theme :: + + app = QApplication([]) + app.setPalette(qdarktheme.load_palette("light")) + + 3. Automatic detection of system theme :: + + app = QApplication([]) + app.setPalette(qdarktheme.load_palette("auto")) + + 4. Customize color :: + + app = QApplication([]) + app.setPalette(custom_colors={"primary": "#D0BCFF"}) + + 5. Customize a specific theme only :: + + app = QApplication([]) + app.setStyleSheet( + qdarktheme.load_stylesheet( + theme="auto", + custom_colors={ + "[dark]": { + "primary": "#D0BCFF", + } + }, + ) + ) + """ + if theme == "auto": + theme = _detect_system_theme(default_theme) + color_values = _color_values(theme) + if custom_colors is not None: + _marge_colors(color_values, custom_colors, theme) + + mk_template = partial(Template, filters={"color": filter.color, "palette": filter.palette_format}) + return _resources.palette.q_palette(mk_template, color_values, for_stylesheet) + + +def get_themes() -> tuple[str, ...]: + """Return available theme names. + + Returns: + Tuple of available theme names. + """ + return _resources.THEMES diff --git a/qdarktheme/_template/engine.py b/qdarktheme/_template/engine.py index 233409fe..35c4f1ef 100644 --- a/qdarktheme/_template/engine.py +++ b/qdarktheme/_template/engine.py @@ -1,84 +1,84 @@ -"""Module for handling template text.""" -from __future__ import annotations - -import json -import re -from dataclasses import dataclass -from itertools import chain, zip_longest - -from qdarktheme._util import multi_replace - - -@dataclass(unsafe_hash=True, frozen=True) -class _Placeholder: - match_text: str - value: str | int | float - filters: tuple[str] - - -class Template: - """Class that handles template text like jinja2.""" - - _PLACEHOLDER_RE = re.compile(r"{{.*?}}") - _STRING_RE = re.compile(r"""('([^'\\]*(?:\\.[^'\\]*)*)'|"([^"\\]*(?:\\.[^"\\]*)*)")""", re.S) - - def __init__(self, text: str, filters: dict): - """Initialize Template class.""" - self._target_text = text - self._filters = filters - - @staticmethod - def _to_py_value(text: str): - try: - return int(text) - except ValueError: - try: - return float(text) - except ValueError: - return text - - @staticmethod - def _parse_placeholders(text: str): - placeholders: set[_Placeholder] = set() - for match in re.finditer(Template._PLACEHOLDER_RE, text): - match_text = match.group() - contents, *filters = match_text.strip("{}").replace(" ", "").split("|") - value = Template._to_py_value(contents) - placeholders.add(_Placeholder(match_text, value, tuple(filters))) - return placeholders - - def _run_filter(self, value: str | int | float, filter_text: str): - contents = filter_text.split("(") - if len(contents) == 1: - return self._filters[contents[0]](value) - - filter_name, arg_text = contents - py_strings = [match.group() for match in Template._STRING_RE.finditer(arg_text)] - if len(py_strings) == 0: - json_text = '{"' + arg_text.replace("=", '":').replace(",", ',"').replace(")", "}") - else: - py_strings_escaped = [re.escape(py_string) for py_string in py_strings] - words = re.split("|".join(py_strings_escaped), arg_text) - words = [word.replace("=", '":').replace(",", ',"').replace(")", "}") for word in words] - json_text = '{"' + "".join( - chain.from_iterable(zip_longest(words, py_strings, fillvalue="")) - ) - arguments: dict = json.loads(json_text) - return self._filters[filter_name](value, **arguments) - - def render(self, replacements: dict) -> str: - """Render replacements.""" - placeholders = Template._parse_placeholders(self._target_text) - new_replacements: dict[str, str] = {} - for placeholder in placeholders: - value = placeholder.value - if type(value) is str and len(value) != 0: - value = replacements.get(value) - if value is None: - raise AssertionError( - f"There is no replacements for: {placeholder.value} in {placeholder.match_text}" - ) - for filter in placeholder.filters: - value = self._run_filter(value, filter) - new_replacements[placeholder.match_text] = str(value) - return multi_replace(self._target_text, new_replacements) +"""Module for handling template text.""" +from __future__ import annotations + +import json +import re +from dataclasses import dataclass +from itertools import chain, zip_longest + +from qdarktheme._util import multi_replace + + +@dataclass(unsafe_hash=True, frozen=True) +class _Placeholder: + match_text: str + value: str | int | float + filters: tuple[str] + + +class Template: + """Class that handles template text like jinja2.""" + + _PLACEHOLDER_RE = re.compile(r"{{.*?}}") + _STRING_RE = re.compile(r"""('([^'\\]*(?:\\.[^'\\]*)*)'|"([^"\\]*(?:\\.[^"\\]*)*)")""", re.S) + + def __init__(self, text: str, filters: dict): + """Initialize Template class.""" + self._target_text = text + self._filters = filters + + @staticmethod + def _to_py_value(text: str): + try: + return int(text) + except ValueError: + try: + return float(text) + except ValueError: + return text + + @staticmethod + def _parse_placeholders(text: str): + placeholders: set[_Placeholder] = set() + for match in re.finditer(Template._PLACEHOLDER_RE, text): + match_text = match.group() + contents, *filters = match_text.strip("{}").replace(" ", "").split("|") + value = Template._to_py_value(contents) + placeholders.add(_Placeholder(match_text, value, tuple(filters))) + return placeholders + + def _run_filter(self, value: str | int | float, filter_text: str): + contents = filter_text.split("(") + if len(contents) == 1: + return self._filters[contents[0]](value) + + filter_name, arg_text = contents + py_strings = [match.group() for match in Template._STRING_RE.finditer(arg_text)] + if len(py_strings) == 0: + json_text = '{"' + arg_text.replace("=", '":').replace(",", ',"').replace(")", "}") + else: + py_strings_escaped = [re.escape(py_string) for py_string in py_strings] + words = re.split("|".join(py_strings_escaped), arg_text) + words = [word.replace("=", '":').replace(",", ',"').replace(")", "}") for word in words] + json_text = '{"' + "".join( + chain.from_iterable(zip_longest(words, py_strings, fillvalue="")) + ) + arguments: dict = json.loads(json_text) + return self._filters[filter_name](value, **arguments) + + def render(self, replacements: dict) -> str: + """Render replacements.""" + placeholders = Template._parse_placeholders(self._target_text) + new_replacements: dict[str, str] = {} + for placeholder in placeholders: + value = placeholder.value + if type(value) is str and len(value) != 0: + value = replacements.get(value) + if value is None: + raise AssertionError( + f"There is no replacements for: {placeholder.value} in {placeholder.match_text}" + ) + for filter in placeholder.filters: + value = self._run_filter(value, filter) + new_replacements[placeholder.match_text] = str(value) + return multi_replace(self._target_text, new_replacements) diff --git a/qdarktheme/_template/filter.py b/qdarktheme/_template/filter.py index c4b2cd7b..53bb7b1e 100644 --- a/qdarktheme/_template/filter.py +++ b/qdarktheme/_template/filter.py @@ -1,94 +1,94 @@ -"""A module containing multiple filters used by template engine.""" -from __future__ import annotations - -import platform - -from qdarktheme import __version__ -from qdarktheme._color import Color -from qdarktheme._icon.svg import Svg -from qdarktheme._util import analyze_version_str, get_cash_root_path, get_logger -from qdarktheme.qtpy import __version__ as qt_version -from qdarktheme.qtpy.qt_compat import QT_API - -_logger = get_logger(__name__) - -if qt_version is None: - _logger.warning("Failed to detect Qt version. Load Qt version as the latest version.") - _QT_VERSION = "10.0.0" # Fairly future version for always setting latest version. -else: - _QT_VERSION = qt_version - -if QT_API is None: - _QT_API = "PySide6" - _logger.warning(f"Failed to detect Qt binding. Load Qt API as '{_QT_API}'.") -else: - _QT_API = QT_API - -if None in (qt_version, QT_API): - _logger.warning( - "Maybe you need to install qt-binding. " - "Available Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2." - ) - - -def _transform(color: Color, color_state: dict[str, float]) -> Color: - if color_state.get("transparent"): - color = color.transparent(color_state["transparent"]) - if color_state.get("darken"): - color = color.darken(color_state["darken"]) - if color_state.get("lighten"): - return color.lighten(color_state["lighten"]) - return color - - -def color(color_info: str | dict[str, str | dict], state: str | None = None) -> Color: - """Filter for template engine. This filter convert color info data to color object.""" - if isinstance(color_info, str): - return Color.from_hex(color_info) - - base_color_format: str = color_info["base"] # type: ignore - color = Color.from_hex(base_color_format) - - if state is None: - return color - - transforms = color_info[state] - return Color.from_hex(transforms) if isinstance(transforms, str) else _transform(color, transforms) - - -def palette_format(color: Color) -> str: - """Filter for template engine. This filter convert color object to ARGB hex format. - - QPalette parser for hex only support ARGB hex format. color.Color class use RGB hex format. - So we need to convert Color object to ARGB hex format. - """ - return f"#{color.to_hex_argb()}" - - -def url(color: Color, id: str, rotate: int = 0) -> str: - """Filter for template engine. This filter create url for svg and output svg file.""" - svg_path = get_cash_root_path(__version__) / f"{id}_{color._to_hex()}_{rotate}.svg" - url = f"url({svg_path.as_posix()})" - if svg_path.exists(): - return url - svg = Svg(id).colored(color).rotate(rotate) - svg_path.write_text(str(svg)) - return url - - -def env( - text, value: str, version: str | None = None, qt: str | None = None, os: str | None = None -) -> str: - """Filter for template engine. This filter output empty string when unexpected environment.""" - if version and not analyze_version_str(_QT_VERSION, version): - return "" - if qt and qt.lower() != _QT_API.lower(): - return "" - if os and platform.system().lower() not in os.lower(): - return "" - return value.replace("${}", str(text)) - - -def corner(corner_shape: str, size: str) -> str: - """Filter for template engine. This filter manage corner shape.""" - return size if corner_shape == "rounded" else "0" +"""A module containing multiple filters used by template engine.""" +from __future__ import annotations + +import platform + +from qdarktheme import __version__ +from qdarktheme._color import Color +from qdarktheme._icon.svg import Svg +from qdarktheme._util import analyze_version_str, get_cash_root_path, get_logger +from qdarktheme.qtpy import __version__ as qt_version +from qdarktheme.qtpy.qt_compat import QT_API + +_logger = get_logger(__name__) + +if qt_version is None: + _logger.warning("Failed to detect Qt version. Load Qt version as the latest version.") + _QT_VERSION = "10.0.0" # Fairly future version for always setting latest version. +else: + _QT_VERSION = qt_version + +if QT_API is None: + _QT_API = "PySide6" + _logger.warning(f"Failed to detect Qt binding. Load Qt API as '{_QT_API}'.") +else: + _QT_API = QT_API + +if None in (qt_version, QT_API): + _logger.warning( + "Maybe you need to install qt-binding. " + "Available Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2." + ) + + +def _transform(color: Color, color_state: dict[str, float]) -> Color: + if color_state.get("transparent"): + color = color.transparent(color_state["transparent"]) + if color_state.get("darken"): + color = color.darken(color_state["darken"]) + if color_state.get("lighten"): + return color.lighten(color_state["lighten"]) + return color + + +def color(color_info: str | dict[str, str | dict], state: str | None = None) -> Color: + """Filter for template engine. This filter convert color info data to color object.""" + if isinstance(color_info, str): + return Color.from_hex(color_info) + + base_color_format: str = color_info["base"] # type: ignore + color = Color.from_hex(base_color_format) + + if state is None: + return color + + transforms = color_info[state] + return Color.from_hex(transforms) if isinstance(transforms, str) else _transform(color, transforms) + + +def palette_format(color: Color) -> str: + """Filter for template engine. This filter convert color object to ARGB hex format. + + QPalette parser for hex only support ARGB hex format. color.Color class use RGB hex format. + So we need to convert Color object to ARGB hex format. + """ + return f"#{color.to_hex_argb()}" + + +def url(color: Color, id: str, rotate: int = 0) -> str: + """Filter for template engine. This filter create url for svg and output svg file.""" + svg_path = get_cash_root_path(__version__) / f"{id}_{color._to_hex()}_{rotate}.svg" + url = f"url({svg_path.as_posix()})" + if svg_path.exists(): + return url + svg = Svg(id).colored(color).rotate(rotate) + svg_path.write_text(str(svg)) + return url + + +def env( + text, value: str, version: str | None = None, qt: str | None = None, os: str | None = None +) -> str: + """Filter for template engine. This filter output empty string when unexpected environment.""" + if version and not analyze_version_str(_QT_VERSION, version): + return "" + if qt and qt.lower() != _QT_API.lower(): + return "" + if os and platform.system().lower() not in os.lower(): + return "" + return value.replace("${}", str(text)) + + +def corner(corner_shape: str, size: str) -> str: + """Filter for template engine. This filter manage corner shape.""" + return size if corner_shape == "rounded" else "0" diff --git a/qdarktheme/_util.py b/qdarktheme/_util.py index 3e13d1b1..fc2f6a40 100644 --- a/qdarktheme/_util.py +++ b/qdarktheme/_util.py @@ -1,82 +1,82 @@ -"""Utility methods for qdarktheme.""" -from __future__ import annotations - -import inspect -import logging -import operator as ope -import re -from pathlib import Path - -import qdarktheme - -# greater_equal and less_equal must be evaluated before greater and less. -_OPERATORS = {"==": ope.eq, "!=": ope.ne, ">=": ope.ge, "<=": ope.le, ">": ope.gt, "<": ope.lt} - - -def multi_replace(target: str, replacements: dict[str, str]) -> str: - """Given a string and a replacement map, it returns the replaced string. - - See https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729. - - Args: - target: String to execute replacements on. - replacements: Replacement dictionary {value to find: value to replace}. - - Returns: - str: Target string that replaced with `replacements`. - """ - if len(replacements) == 0: - return target - - replacements_sorted = sorted(replacements, key=len, reverse=True) - replacements_escaped = [re.escape(i) for i in replacements_sorted] - pattern = re.compile("|".join(replacements_escaped)) - return pattern.sub(lambda match: replacements[match.group()], target) - - -def get_logger(logger_name: str) -> logging.Logger: - """Return the logger with the name specified by logger_name arg. - - Args: - logger_name: The name of logger. - - Returns: - Logger reformatted for this package. - """ - logger = logging.getLogger(logger_name) - logger.propagate = False - logger.setLevel(logging.INFO) - ch = logging.StreamHandler() - ch.setFormatter(logging.Formatter("[%(name)s] [%(levelname)s] %(message)s")) - logger.addHandler(ch) - return logger - - -def get_cash_root_path(version: str) -> Path: - """Return the cash root dir path.""" - return Path.home() / ".cache" / "qdarktheme" / f"v{version}" - - -def get_qdarktheme_root_path() -> Path: - """Return the qdarktheme package root path. - - Returns: - qdarktheme package root path. - """ - return Path(inspect.getfile(qdarktheme)).parent - - -def _compare_v(v1: str, operator: str, v2: str) -> bool: - """Comparing two versions.""" - v1_list, v2_list = (tuple(map(int, (v.split(".")))) for v in (v1, v2)) - return _OPERATORS[operator](v1_list, v2_list) - - -def analyze_version_str(target_version: str, version_text: str) -> bool: - """Analyze text comparing versions.""" - for operator in _OPERATORS: - if operator not in version_text: - continue - version = version_text.replace(operator, "") - return _compare_v(target_version, operator, version) - raise AssertionError("Text comparing versions is wrong.") +"""Utility methods for qdarktheme.""" +from __future__ import annotations + +import inspect +import logging +import operator as ope +import re +from pathlib import Path + +import qdarktheme + +# greater_equal and less_equal must be evaluated before greater and less. +_OPERATORS = {"==": ope.eq, "!=": ope.ne, ">=": ope.ge, "<=": ope.le, ">": ope.gt, "<": ope.lt} + + +def multi_replace(target: str, replacements: dict[str, str]) -> str: + """Given a string and a replacement map, it returns the replaced string. + + See https://gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729. + + Args: + target: String to execute replacements on. + replacements: Replacement dictionary {value to find: value to replace}. + + Returns: + str: Target string that replaced with `replacements`. + """ + if len(replacements) == 0: + return target + + replacements_sorted = sorted(replacements, key=len, reverse=True) + replacements_escaped = [re.escape(i) for i in replacements_sorted] + pattern = re.compile("|".join(replacements_escaped)) + return pattern.sub(lambda match: replacements[match.group()], target) + + +def get_logger(logger_name: str) -> logging.Logger: + """Return the logger with the name specified by logger_name arg. + + Args: + logger_name: The name of logger. + + Returns: + Logger reformatted for this package. + """ + logger = logging.getLogger(logger_name) + logger.propagate = False + logger.setLevel(logging.INFO) + ch = logging.StreamHandler() + ch.setFormatter(logging.Formatter("[%(name)s] [%(levelname)s] %(message)s")) + logger.addHandler(ch) + return logger + + +def get_cash_root_path(version: str) -> Path: + """Return the cash root dir path.""" + return Path.home() / ".cache" / "qdarktheme" / f"v{version}" + + +def get_qdarktheme_root_path() -> Path: + """Return the qdarktheme package root path. + + Returns: + qdarktheme package root path. + """ + return Path(inspect.getfile(qdarktheme)).parent + + +def _compare_v(v1: str, operator: str, v2: str) -> bool: + """Comparing two versions.""" + v1_list, v2_list = (tuple(map(int, (v.split(".")))) for v in (v1, v2)) + return _OPERATORS[operator](v1_list, v2_list) + + +def analyze_version_str(target_version: str, version_text: str) -> bool: + """Analyze text comparing versions.""" + for operator in _OPERATORS: + if operator not in version_text: + continue + version = version_text.replace(operator, "") + return _compare_v(target_version, operator, version) + raise AssertionError("Text comparing versions is wrong.") diff --git a/qdarktheme/qtpy/QtCore/__init__.py b/qdarktheme/qtpy/QtCore/__init__.py index 6a3d01ac..35741b6e 100644 --- a/qdarktheme/qtpy/QtCore/__init__.py +++ b/qdarktheme/qtpy/QtCore/__init__.py @@ -1,19 +1,19 @@ -"""Module for QtCore.""" -from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error - -if QT_API is None: - raise qt_import_error -if QT_API == "PySide6": - from PySide6.QtCore import * # type: ignore # noqa: F403 -elif QT_API == "PyQt6": - from PyQt6.QtCore import * # type: ignore # noqa: F403 - - Slot = pyqtSlot # noqa: F405 - Signal = pyqtSignal # noqa: F405 -elif QT_API == "PyQt5": - from PyQt5.QtCore import * # type: ignore # noqa: F403 - - Slot = pyqtSlot # type: ignore # noqa: F405 - Signal = pyqtSignal # type: ignore # noqa: F405 -elif QT_API == "PySide2": - from PySide2.QtCore import * # type: ignore # noqa: F403 +"""Module for QtCore.""" +from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error + +if QT_API is None: + raise qt_import_error +if QT_API == "PySide6": + from PySide6.QtCore import * # type: ignore # noqa: F403 +elif QT_API == "PyQt6": + from PyQt6.QtCore import * # type: ignore # noqa: F403 + + Slot = pyqtSlot # noqa: F405 + Signal = pyqtSignal # noqa: F405 +elif QT_API == "PyQt5": + from PyQt5.QtCore import * # type: ignore # noqa: F403 + + Slot = pyqtSlot # type: ignore # noqa: F405 + Signal = pyqtSignal # type: ignore # noqa: F405 +elif QT_API == "PySide2": + from PySide2.QtCore import * # type: ignore # noqa: F403 diff --git a/qdarktheme/qtpy/QtGui/__init__.py b/qdarktheme/qtpy/QtGui/__init__.py index aa536567..2c3aafaa 100644 --- a/qdarktheme/qtpy/QtGui/__init__.py +++ b/qdarktheme/qtpy/QtGui/__init__.py @@ -1,20 +1,20 @@ -"""Module for QtGui.""" -from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error - -if QT_API is None: - raise qt_import_error -if QT_API == "PySide6": - from PySide6.QtGui import * # type: ignore # noqa: F403 -elif QT_API == "PyQt6": - from PyQt6.QtGui import * # type: ignore # noqa: F403 -elif QT_API == "PyQt5": - from PyQt5.QtGui import * # type: ignore # noqa: F403 - from PyQt5.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore -elif QT_API == "PySide2": - from PySide2.QtGui import * # type: ignore # noqa: F403 - from PySide2.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore - -if QT_API in ["PyQt5", "PySide2"]: - QAction = QAction # type: ignore # noqa: SIM909 - QActionGroup = QActionGroup # type: ignore # noqa: SIM909 - QShortcut = QShortcut # type: ignore # noqa: SIM909 +"""Module for QtGui.""" +from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error + +if QT_API is None: + raise qt_import_error +if QT_API == "PySide6": + from PySide6.QtGui import * # type: ignore # noqa: F403 +elif QT_API == "PyQt6": + from PyQt6.QtGui import * # type: ignore # noqa: F403 +elif QT_API == "PyQt5": + from PyQt5.QtGui import * # type: ignore # noqa: F403 + from PyQt5.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore +elif QT_API == "PySide2": + from PySide2.QtGui import * # type: ignore # noqa: F403 + from PySide2.QtWidgets import QAction, QActionGroup, QShortcut # type: ignore + +if QT_API in ["PyQt5", "PySide2"]: + QAction = QAction # type: ignore # noqa: SIM909 + QActionGroup = QActionGroup # type: ignore # noqa: SIM909 + QShortcut = QShortcut # type: ignore # noqa: SIM909 diff --git a/qdarktheme/qtpy/QtSvg/__init__.py b/qdarktheme/qtpy/QtSvg/__init__.py index 043b9573..66bc4a24 100644 --- a/qdarktheme/qtpy/QtSvg/__init__.py +++ b/qdarktheme/qtpy/QtSvg/__init__.py @@ -1,13 +1,13 @@ -"""Module for QtSvg.""" -from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error - -if QT_API is None: - raise qt_import_error -if QT_API == "PySide6": - from PySide6.QtSvg import * # type: ignore # noqa: F403 -elif QT_API == "PyQt6": - from PyQt6.QtSvg import * # type: ignore # noqa: F403 -elif QT_API == "PyQt5": - from PyQt5.QtSvg import * # type: ignore # noqa: F403 -elif QT_API == "PySide2": - from PySide2.QtSvg import * # type: ignore # noqa: F403 +"""Module for QtSvg.""" +from qdarktheme.qtpy.qt_compat import QT_API, qt_import_error + +if QT_API is None: + raise qt_import_error +if QT_API == "PySide6": + from PySide6.QtSvg import * # type: ignore # noqa: F403 +elif QT_API == "PyQt6": + from PyQt6.QtSvg import * # type: ignore # noqa: F403 +elif QT_API == "PyQt5": + from PyQt5.QtSvg import * # type: ignore # noqa: F403 +elif QT_API == "PySide2": + from PySide2.QtSvg import * # type: ignore # noqa: F403 diff --git a/qdarktheme/qtpy/QtWidgets/__init__.py b/qdarktheme/qtpy/QtWidgets/__init__.py index 70ae931b..58878aa7 100644 --- a/qdarktheme/qtpy/QtWidgets/__init__.py +++ b/qdarktheme/qtpy/QtWidgets/__init__.py @@ -1,36 +1,36 @@ -"""Module for QtWidgets.""" -from __future__ import annotations - -from collections.abc import Sequence - -from qdarktheme.qtpy.qt_compat import QT_API - -if QT_API == "PySide6": - from PySide6.QtWidgets import * # type: ignore # noqa: F403 -elif QT_API == "PyQt6": - from PyQt6.QtWidgets import * # type: ignore # noqa: F403 -elif QT_API == "PyQt5": - from PyQt5.QtWidgets import * # type: ignore # noqa: F403 -elif QT_API == "PySide2": - from PySide2.QtWidgets import * # type: ignore # noqa: F403 - - -class Application(QApplication): # type: ignore # noqa: F405 - """Override QApplication.""" - - def __init__(self, args: Sequence[str] | None = None) -> None: - """Override QApplication method.""" - super().__init__(args) - - def exec(self) -> int: - """Override QApplication method.""" - if hasattr(super(), "exec"): - return super().exec() - return super().exec_() - - def exit(self, returnCode: int = 0) -> None: # noqa: N803 - """Override QApplication method.""" - return super().exit(returnCode) - - -QApplication = Application +"""Module for QtWidgets.""" +from __future__ import annotations + +from collections.abc import Sequence + +from qdarktheme.qtpy.qt_compat import QT_API + +if QT_API == "PySide6": + from PySide6.QtWidgets import * # type: ignore # noqa: F403 +elif QT_API == "PyQt6": + from PyQt6.QtWidgets import * # type: ignore # noqa: F403 +elif QT_API == "PyQt5": + from PyQt5.QtWidgets import * # type: ignore # noqa: F403 +elif QT_API == "PySide2": + from PySide2.QtWidgets import * # type: ignore # noqa: F403 + + +class Application(QApplication): # type: ignore # noqa: F405 + """Override QApplication.""" + + def __init__(self, args: Sequence[str] | None = None) -> None: + """Override QApplication method.""" + super().__init__(args) + + def exec(self) -> int: + """Override QApplication method.""" + if hasattr(super(), "exec"): + return super().exec() + return super().exec_() + + def exit(self, returnCode: int = 0) -> None: # noqa: N803 + """Override QApplication method.""" + return super().exit(returnCode) + + +QApplication = Application diff --git a/qdarktheme/qtpy/__init__.py b/qdarktheme/qtpy/__init__.py index 164af8d6..80183914 100644 --- a/qdarktheme/qtpy/__init__.py +++ b/qdarktheme/qtpy/__init__.py @@ -1,11 +1,11 @@ -"""Package applying Qt compat of PyQt6, PySide6, PyQt5 and PySide2.""" -from qdarktheme.qtpy.qt_compat import QtImportError -from qdarktheme.qtpy.qt_version import __version__ - -try: - from qdarktheme.qtpy import QtCore, QtGui, QtSvg, QtWidgets -except ImportError: - from qdarktheme._util import get_logger as __get_logger - - __logger = __get_logger(__name__) - __logger.warning("Failed to import QtCore, QtGui, QtSvg and QtWidgets.") +"""Package applying Qt compat of PyQt6, PySide6, PyQt5 and PySide2.""" +from qdarktheme.qtpy.qt_compat import QtImportError +from qdarktheme.qtpy.qt_version import __version__ + +try: + from qdarktheme.qtpy import QtCore, QtGui, QtSvg, QtWidgets +except ImportError: + from qdarktheme._util import get_logger as __get_logger + + __logger = __get_logger(__name__) + __logger.warning("Failed to import QtCore, QtGui, QtSvg and QtWidgets.") diff --git a/qdarktheme/qtpy/qt_compat.py b/qdarktheme/qtpy/qt_compat.py index 015f2333..bb54387a 100644 --- a/qdarktheme/qtpy/qt_compat.py +++ b/qdarktheme/qtpy/qt_compat.py @@ -1,80 +1,80 @@ -"""Module for Qt compat.""" -from __future__ import annotations - -import os -import sys - - -class QtImportError(ImportError): - """Error raise if no bindings could be selected.""" - - -qt_import_error = QtImportError( - "Failed to import qt-binding. Check packages(pip list)." - "\n\tAvailable Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2." -) - - -# Qt6 -_QT_API_PYSIDE6 = "PySide6" -_QT_API_PYQT6 = "PyQt6" -# Qt5 -_QT_API_PYQT5 = "PyQt5" -_QT_API_PYSIDE2 = "PySide2" - - -_API_LIST = [_QT_API_PYSIDE6, _QT_API_PYQT6, _QT_API_PYQT5, _QT_API_PYSIDE2] - - -def _get_loaded_api() -> str | None: - """Return which API is loaded. - - If this returns anything besides None, - importing any other Qt-binding is unsafe. - """ - for api in _API_LIST: - if sys.modules.get(f"{api}.QtCore"): - return api - return None - - -def _get_environ_api() -> str | None: - """Return which API is specified in environ.""" - _qt_api_env = os.environ.get("QT_API") - if _qt_api_env is not None: - _qt_api_env = _qt_api_env.lower() - - _env_to_module = { - "pyside6": _QT_API_PYSIDE6, - "pyqt6": _QT_API_PYQT6, - "pyqt5": _QT_API_PYQT5, - "pyside2": _QT_API_PYSIDE2, - None: None, - } - try: - return _env_to_module[_qt_api_env] - except KeyError: - raise KeyError( - "The environment variable QT_API has the unrecognized value " - f"{_qt_api_env!r}. " - f"Valid values are {[k for k in _env_to_module if k is not None]}" - ) from None - - -def _get_installed_api() -> str | None: - """Return which API is installed.""" - # Fix [AttributeError: module 'importlib' has no attribute 'util'] - # See https://stackoverflow.com/a/39661116/13452582 - from importlib import util - - for api in _API_LIST: - if util.find_spec(api) is not None: - return api - return None - - -QT_API = _get_loaded_api() -if QT_API is None: - QT_API = _get_environ_api() -if QT_API is None: - QT_API = _get_installed_api() +"""Module for Qt compat.""" +from __future__ import annotations + +import os +import sys + + +class QtImportError(ImportError): + """Error raise if no bindings could be selected.""" + + +qt_import_error = QtImportError( + "Failed to import qt-binding. Check packages(pip list)." + "\n\tAvailable Qt-binding packages: PySide6, PyQt6, PyQt5, PySide2." +) + + +# Qt6 +_QT_API_PYSIDE6 = "PySide6" +_QT_API_PYQT6 = "PyQt6" +# Qt5 +_QT_API_PYQT5 = "PyQt5" +_QT_API_PYSIDE2 = "PySide2" + + +_API_LIST = [_QT_API_PYSIDE6, _QT_API_PYQT6, _QT_API_PYQT5, _QT_API_PYSIDE2] + + +def _get_loaded_api() -> str | None: + """Return which API is loaded. + + If this returns anything besides None, + importing any other Qt-binding is unsafe. + """ + for api in _API_LIST: + if sys.modules.get(f"{api}.QtCore"): + return api + return None + + +def _get_environ_api() -> str | None: + """Return which API is specified in environ.""" + _qt_api_env = os.environ.get("QT_API") + if _qt_api_env is not None: + _qt_api_env = _qt_api_env.lower() + + _env_to_module = { + "pyside6": _QT_API_PYSIDE6, + "pyqt6": _QT_API_PYQT6, + "pyqt5": _QT_API_PYQT5, + "pyside2": _QT_API_PYSIDE2, + None: None, + } + try: + return _env_to_module[_qt_api_env] + except KeyError: + raise KeyError( + "The environment variable QT_API has the unrecognized value " + f"{_qt_api_env!r}. " + f"Valid values are {[k for k in _env_to_module if k is not None]}" + ) from None + + +def _get_installed_api() -> str | None: + """Return which API is installed.""" + # Fix [AttributeError: module 'importlib' has no attribute 'util'] + # See https://stackoverflow.com/a/39661116/13452582 + from importlib import util + + for api in _API_LIST: + if util.find_spec(api) is not None: + return api + return None + + +QT_API = _get_loaded_api() +if QT_API is None: + QT_API = _get_environ_api() +if QT_API is None: + QT_API = _get_installed_api() diff --git a/qdarktheme/qtpy/qt_version.py b/qdarktheme/qtpy/qt_version.py index b0b01bc6..37f91b09 100644 --- a/qdarktheme/qtpy/qt_version.py +++ b/qdarktheme/qtpy/qt_version.py @@ -1,22 +1,22 @@ -"""Module for detecting Qt version.""" -from __future__ import annotations - -from qdarktheme.qtpy.qt_compat import QT_API - -__version__: str | None = None -if QT_API == "PySide6": - from PySide6.QtCore import qVersion # type: ignore - - __version__ = qVersion() -elif QT_API == "PyQt6": - from PyQt6.QtCore import qVersion # type: ignore - - __version__ = qVersion() -elif QT_API == "PyQt5": - from PyQt5.QtCore import qVersion # type: ignore - - __version__ = qVersion() -elif QT_API == "PySide2": - from PySide2.QtCore import qVersion # type: ignore - - __version__ = qVersion() +"""Module for detecting Qt version.""" +from __future__ import annotations + +from qdarktheme.qtpy.qt_compat import QT_API + +__version__: str | None = None +if QT_API == "PySide6": + from PySide6.QtCore import qVersion # type: ignore + + __version__ = qVersion() +elif QT_API == "PyQt6": + from PyQt6.QtCore import qVersion # type: ignore + + __version__ = qVersion() +elif QT_API == "PyQt5": + from PyQt5.QtCore import qVersion # type: ignore + + __version__ = qVersion() +elif QT_API == "PySide2": + from PySide2.QtCore import qVersion # type: ignore + + __version__ = qVersion() diff --git a/qdarktheme/widget_gallery/__init__.py b/qdarktheme/widget_gallery/__init__.py index cffed1ef..29d1679e 100644 --- a/qdarktheme/widget_gallery/__init__.py +++ b/qdarktheme/widget_gallery/__init__.py @@ -1,8 +1,8 @@ -"""Example of PyQtDarkTheme use for Qt applications. - -To check example app, run: - -```shell -python -m qdarktheme.widget_gallery -``` -""" +"""Example of PyQtDarkTheme use for Qt applications. + +To check example app, run: + +```shell +python -m qdarktheme.widget_gallery +``` +""" diff --git a/qdarktheme/widget_gallery/__main__.py b/qdarktheme/widget_gallery/__main__.py index 3f63627a..2687298f 100644 --- a/qdarktheme/widget_gallery/__main__.py +++ b/qdarktheme/widget_gallery/__main__.py @@ -1,14 +1,14 @@ -"""Module allowing for `python -m qdarktheme.widget_gallery`.""" -import sys - -import qdarktheme -from qdarktheme.qtpy.QtWidgets import QApplication -from qdarktheme.widget_gallery.main_window import WidgetGallery - -if __name__ == "__main__": - qdarktheme.enable_hi_dpi() - app = QApplication(sys.argv) - qdarktheme.setup_theme("auto") - win = WidgetGallery() - win.show() - app.exec() +"""Module allowing for `python -m qdarktheme.widget_gallery`.""" +import sys + +import qdarktheme +from qdarktheme.qtpy.QtWidgets import QApplication +from qdarktheme.widget_gallery.main_window import WidgetGallery + +if __name__ == "__main__": + qdarktheme.enable_hi_dpi() + app = QApplication(sys.argv) + qdarktheme.setup_theme("auto") + win = WidgetGallery() + win.show() + app.exec() diff --git a/qdarktheme/widget_gallery/_ui/__init__.py b/qdarktheme/widget_gallery/_ui/__init__.py index be4973ab..deade6c3 100644 --- a/qdarktheme/widget_gallery/_ui/__init__.py +++ b/qdarktheme/widget_gallery/_ui/__init__.py @@ -1 +1 @@ -"""Package including ui for WidgetGallery.""" +"""Package including ui for WidgetGallery.""" diff --git a/qdarktheme/widget_gallery/_ui/dock_ui.py b/qdarktheme/widget_gallery/_ui/dock_ui.py index 152abfc8..02eee148 100644 --- a/qdarktheme/widget_gallery/_ui/dock_ui.py +++ b/qdarktheme/widget_gallery/_ui/dock_ui.py @@ -1,42 +1,42 @@ -"""Module setting up ui of dock window.""" -from __future__ import annotations - -from qdarktheme.qtpy.QtCore import Qt -from qdarktheme.qtpy.QtWidgets import QDockWidget, QMainWindow, QTextEdit, QVBoxLayout, QWidget - - -class DockUI: - """The ui class of dock window.""" - - def setup_ui(self, win: QWidget) -> None: - """Set up ui.""" - # Widgets - left_dock = QDockWidget("Left dock") - right_dock = QDockWidget("Right dock") - top_dock = QDockWidget("Top dock") - bottom_dock = QDockWidget("Bottom dock") - - # Setup widgets - left_dock.setWidget(QTextEdit("This is the left widget.")) - right_dock.setWidget(QTextEdit("This is the right widget.")) - top_dock.setWidget(QTextEdit("This is the top widget.")) - bottom_dock.setWidget(QTextEdit("This is the bottom widget.")) - for dock in (left_dock, right_dock, top_dock, bottom_dock): - dock.setAllowedAreas( - Qt.DockWidgetArea.LeftDockWidgetArea - | Qt.DockWidgetArea.RightDockWidgetArea - | Qt.DockWidgetArea.BottomDockWidgetArea - | Qt.DockWidgetArea.TopDockWidgetArea - ) - - # Layout - main_win = QMainWindow() - main_win.setCentralWidget(QTextEdit("This is the central widget.")) - main_win.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock) - main_win.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock) - main_win.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, top_dock) - main_win.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, bottom_dock) - - layout = QVBoxLayout(win) - layout.addWidget(main_win) - layout.setContentsMargins(0, 0, 0, 0) +"""Module setting up ui of dock window.""" +from __future__ import annotations + +from qdarktheme.qtpy.QtCore import Qt +from qdarktheme.qtpy.QtWidgets import QDockWidget, QMainWindow, QTextEdit, QVBoxLayout, QWidget + + +class DockUI: + """The ui class of dock window.""" + + def setup_ui(self, win: QWidget) -> None: + """Set up ui.""" + # Widgets + left_dock = QDockWidget("Left dock") + right_dock = QDockWidget("Right dock") + top_dock = QDockWidget("Top dock") + bottom_dock = QDockWidget("Bottom dock") + + # Setup widgets + left_dock.setWidget(QTextEdit("This is the left widget.")) + right_dock.setWidget(QTextEdit("This is the right widget.")) + top_dock.setWidget(QTextEdit("This is the top widget.")) + bottom_dock.setWidget(QTextEdit("This is the bottom widget.")) + for dock in (left_dock, right_dock, top_dock, bottom_dock): + dock.setAllowedAreas( + Qt.DockWidgetArea.LeftDockWidgetArea + | Qt.DockWidgetArea.RightDockWidgetArea + | Qt.DockWidgetArea.BottomDockWidgetArea + | Qt.DockWidgetArea.TopDockWidgetArea + ) + + # Layout + main_win = QMainWindow() + main_win.setCentralWidget(QTextEdit("This is the central widget.")) + main_win.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, left_dock) + main_win.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, right_dock) + main_win.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea, top_dock) + main_win.addDockWidget(Qt.DockWidgetArea.BottomDockWidgetArea, bottom_dock) + + layout = QVBoxLayout(win) + layout.addWidget(main_win) + layout.setContentsMargins(0, 0, 0, 0) diff --git a/qdarktheme/widget_gallery/_ui/frame_ui.py b/qdarktheme/widget_gallery/_ui/frame_ui.py index 0a14ce7d..0e51989d 100644 --- a/qdarktheme/widget_gallery/_ui/frame_ui.py +++ b/qdarktheme/widget_gallery/_ui/frame_ui.py @@ -1,79 +1,79 @@ -"""Module setting up ui of frame window.""" -from qdarktheme.qtpy.QtGui import QIcon -from qdarktheme.qtpy.QtWidgets import ( - QCalendarWidget, - QCheckBox, - QFrame, - QGridLayout, - QGroupBox, - QHBoxLayout, - QPushButton, - QRadioButton, - QScrollArea, - QSpinBox, - QToolButton, - QVBoxLayout, - QWidget, -) - - -class FrameUI: - """The ui class of frame window.""" - - def setup_ui(self, win: QWidget) -> None: - """Set up ui.""" - # Widgets - group_box = QGroupBox("frameShape = Box") - group_panel = QGroupBox("frameShape = Panel") - group_none = QGroupBox("frameShape = NoFrame") - group_line = QGroupBox("frameShape = VLine HLine") - frame_box, frame_panel, frame_none, frame_v_line, frame_h_line = (QFrame() for _ in range(5)) - - # Setup widgets - frame_box.setFrameShape(QFrame.Shape.Box) - frame_panel.setFrameShape(QFrame.Shape.Panel) - frame_none.setFrameShape(QFrame.Shape.NoFrame) - frame_v_line.setFrameShape(QFrame.Shape.VLine) - frame_h_line.setFrameShape(QFrame.Shape.HLine) - - # Layout - for frame in (frame_box, frame_panel, frame_none): - push_btn_flat = QPushButton("Push button(flat)") - push_btn_flat.setFlat(True) - tool_btn = QToolButton() - tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg")) - calender = QCalendarWidget() - - g_layout = QGridLayout(frame) - g_layout.addWidget(QPushButton("Push button"), 0, 0) - g_layout.addWidget(push_btn_flat, 0, 1) - g_layout.addWidget(QSpinBox(), 1, 0) - g_layout.addWidget(tool_btn, 1, 1) - g_layout.addWidget(QRadioButton("Radio button"), 2, 0) - g_layout.addWidget(QCheckBox("Check box"), 2, 1) - g_layout.addWidget(calender, 3, 0, 1, 2) - - for group, frame in ( - (group_box, frame_box), - (group_panel, frame_panel), - (group_none, frame_none), - ): - v_layout = QVBoxLayout(group) - v_layout.addWidget(frame) - - h_layout = QHBoxLayout(group_line) - h_layout.addWidget(frame_v_line) - h_layout.addWidget(frame_h_line) - - widget_container = QWidget() - g_layout = QGridLayout(widget_container) - g_layout.addWidget(group_box, 0, 0) - g_layout.addWidget(group_panel, 0, 1) - g_layout.addWidget(group_none, 1, 0) - g_layout.addWidget(group_line, 1, 1) - - scroll_area = QScrollArea() - scroll_area.setWidget(widget_container) - - v_main_layout = QVBoxLayout(win) - v_main_layout.addWidget(scroll_area) +"""Module setting up ui of frame window.""" +from qdarktheme.qtpy.QtGui import QIcon +from qdarktheme.qtpy.QtWidgets import ( + QCalendarWidget, + QCheckBox, + QFrame, + QGridLayout, + QGroupBox, + QHBoxLayout, + QPushButton, + QRadioButton, + QScrollArea, + QSpinBox, + QToolButton, + QVBoxLayout, + QWidget, +) + + +class FrameUI: + """The ui class of frame window.""" + + def setup_ui(self, win: QWidget) -> None: + """Set up ui.""" + # Widgets + group_box = QGroupBox("frameShape = Box") + group_panel = QGroupBox("frameShape = Panel") + group_none = QGroupBox("frameShape = NoFrame") + group_line = QGroupBox("frameShape = VLine HLine") + frame_box, frame_panel, frame_none, frame_v_line, frame_h_line = (QFrame() for _ in range(5)) + + # Setup widgets + frame_box.setFrameShape(QFrame.Shape.Box) + frame_panel.setFrameShape(QFrame.Shape.Panel) + frame_none.setFrameShape(QFrame.Shape.NoFrame) + frame_v_line.setFrameShape(QFrame.Shape.VLine) + frame_h_line.setFrameShape(QFrame.Shape.HLine) + + # Layout + for frame in (frame_box, frame_panel, frame_none): + push_btn_flat = QPushButton("Push button(flat)") + push_btn_flat.setFlat(True) + tool_btn = QToolButton() + tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg")) + calender = QCalendarWidget() + + g_layout = QGridLayout(frame) + g_layout.addWidget(QPushButton("Push button"), 0, 0) + g_layout.addWidget(push_btn_flat, 0, 1) + g_layout.addWidget(QSpinBox(), 1, 0) + g_layout.addWidget(tool_btn, 1, 1) + g_layout.addWidget(QRadioButton("Radio button"), 2, 0) + g_layout.addWidget(QCheckBox("Check box"), 2, 1) + g_layout.addWidget(calender, 3, 0, 1, 2) + + for group, frame in ( + (group_box, frame_box), + (group_panel, frame_panel), + (group_none, frame_none), + ): + v_layout = QVBoxLayout(group) + v_layout.addWidget(frame) + + h_layout = QHBoxLayout(group_line) + h_layout.addWidget(frame_v_line) + h_layout.addWidget(frame_h_line) + + widget_container = QWidget() + g_layout = QGridLayout(widget_container) + g_layout.addWidget(group_box, 0, 0) + g_layout.addWidget(group_panel, 0, 1) + g_layout.addWidget(group_none, 1, 0) + g_layout.addWidget(group_line, 1, 1) + + scroll_area = QScrollArea() + scroll_area.setWidget(widget_container) + + v_main_layout = QVBoxLayout(win) + v_main_layout.addWidget(scroll_area) diff --git a/qdarktheme/widget_gallery/_ui/icons_ui.py b/qdarktheme/widget_gallery/_ui/icons_ui.py index 175a8229..8ec189b9 100644 --- a/qdarktheme/widget_gallery/_ui/icons_ui.py +++ b/qdarktheme/widget_gallery/_ui/icons_ui.py @@ -1,42 +1,42 @@ -"""The ui to show Qt standard icons.""" -from __future__ import annotations - -from qdarktheme.qtpy.QtCore import Qt -from qdarktheme.qtpy.QtWidgets import ( - QGridLayout, - QScrollArea, - QStyle, - QToolButton, - QVBoxLayout, - QWidget, -) - - -class IconsUi: - """The ui class to show Qt standard icons.""" - - def setup_ui(self, win: QWidget) -> None: - """Set up ui.""" - widget_container = QWidget() - layout = QGridLayout(widget_container) - standard_pixmap_names = sorted( - attr for attr in dir(QStyle.StandardPixmap) if attr.startswith("SP_") - ) - if len(standard_pixmap_names) == 0: - standard_pixmap_names = sorted(attr for attr in dir(QStyle) if attr.startswith("SP_")) - - for i, name in enumerate(standard_pixmap_names): - button = QToolButton() - button.setText(f":{name}:") - button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) - - pixmap = getattr(QStyle.StandardPixmap, name) - icon = win.style().standardIcon(pixmap) - button.setIcon(icon) - layout.addWidget(button, int(i / 4), i % 4) - - scroll_area = QScrollArea() - scroll_area.setWidget(widget_container) - - v_main_layout = QVBoxLayout(win) - v_main_layout.addWidget(scroll_area) +"""The ui to show Qt standard icons.""" +from __future__ import annotations + +from qdarktheme.qtpy.QtCore import Qt +from qdarktheme.qtpy.QtWidgets import ( + QGridLayout, + QScrollArea, + QStyle, + QToolButton, + QVBoxLayout, + QWidget, +) + + +class IconsUi: + """The ui class to show Qt standard icons.""" + + def setup_ui(self, win: QWidget) -> None: + """Set up ui.""" + widget_container = QWidget() + layout = QGridLayout(widget_container) + standard_pixmap_names = sorted( + attr for attr in dir(QStyle.StandardPixmap) if attr.startswith("SP_") + ) + if len(standard_pixmap_names) == 0: + standard_pixmap_names = sorted(attr for attr in dir(QStyle) if attr.startswith("SP_")) + + for i, name in enumerate(standard_pixmap_names): + button = QToolButton() + button.setText(f":{name}:") + button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) + + pixmap = getattr(QStyle.StandardPixmap, name) + icon = win.style().standardIcon(pixmap) + button.setIcon(icon) + layout.addWidget(button, int(i / 4), i % 4) + + scroll_area = QScrollArea() + scroll_area.setWidget(widget_container) + + v_main_layout = QVBoxLayout(win) + v_main_layout.addWidget(scroll_area) diff --git a/qdarktheme/widget_gallery/_ui/mdi_ui.py b/qdarktheme/widget_gallery/_ui/mdi_ui.py index eb49ac92..575701d3 100644 --- a/qdarktheme/widget_gallery/_ui/mdi_ui.py +++ b/qdarktheme/widget_gallery/_ui/mdi_ui.py @@ -1,76 +1,76 @@ -"""Module setting up ui of mdi window.""" -from qdarktheme.qtpy.QtWidgets import ( - QHBoxLayout, - QLabel, - QMdiArea, - QMdiSubWindow, - QPushButton, - QSplitter, - QTextEdit, - QVBoxLayout, - QWidget, -) - - -class MdiUI: - """The ui class of mdi window.""" - - def _make_mdi_area_test_widget(self, enable_tab_mode=False): - # Widgets - container = QWidget() - mdi_area = QMdiArea() - label_test_name = QLabel() - cascade_button = QPushButton("Cascade") - new_button = QPushButton("Add new") - tiled_button = QPushButton("Tiled") - - # Setup widgets - if enable_tab_mode: - mdi_area.setViewMode(QMdiArea.ViewMode.TabbedView) - label_test_name.setText("QMdiArea(QMdiArea.viewMode = TabbedView)") - else: - label_test_name.setText("QMdiArea(QMdiArea.viewMode = SubWindowView)") - - def add_new_sub_window(): - sub_win = QMdiSubWindow(container) - sub_win_main_widget = QWidget(sub_win) - v_layout = QVBoxLayout(sub_win_main_widget) - v_layout.addWidget(QTextEdit("Sub window")) - - sub_win.setWidget(sub_win_main_widget) - mdi_area.addSubWindow(sub_win) - sub_win.show() - - add_new_sub_window() - new_button.pressed.connect(add_new_sub_window) - cascade_button.pressed.connect(mdi_area.cascadeSubWindows) - tiled_button.pressed.connect(mdi_area.tileSubWindows) - new_button.setDefault(True) - - # Layout - h_layout = QHBoxLayout() - h_layout.addWidget(new_button) - h_layout.addWidget(cascade_button) - h_layout.addWidget(tiled_button) - - v_main_layout = QVBoxLayout(container) - v_main_layout.addWidget(label_test_name) - v_main_layout.addLayout(h_layout) - v_main_layout.addWidget(mdi_area) - return container - - def setup_ui(self, win: QWidget) -> None: - """Set up ui.""" - # Widgets - splitter = QSplitter() - - # Setup widgets - mdi_area = self._make_mdi_area_test_widget() - mdi_area_with_tab = self._make_mdi_area_test_widget(enable_tab_mode=True) - - # Layout - splitter.addWidget(mdi_area) - splitter.addWidget(mdi_area_with_tab) - - main_layout = QVBoxLayout(win) - main_layout.addWidget(splitter) +"""Module setting up ui of mdi window.""" +from qdarktheme.qtpy.QtWidgets import ( + QHBoxLayout, + QLabel, + QMdiArea, + QMdiSubWindow, + QPushButton, + QSplitter, + QTextEdit, + QVBoxLayout, + QWidget, +) + + +class MdiUI: + """The ui class of mdi window.""" + + def _make_mdi_area_test_widget(self, enable_tab_mode=False): + # Widgets + container = QWidget() + mdi_area = QMdiArea() + label_test_name = QLabel() + cascade_button = QPushButton("Cascade") + new_button = QPushButton("Add new") + tiled_button = QPushButton("Tiled") + + # Setup widgets + if enable_tab_mode: + mdi_area.setViewMode(QMdiArea.ViewMode.TabbedView) + label_test_name.setText("QMdiArea(QMdiArea.viewMode = TabbedView)") + else: + label_test_name.setText("QMdiArea(QMdiArea.viewMode = SubWindowView)") + + def add_new_sub_window(): + sub_win = QMdiSubWindow(container) + sub_win_main_widget = QWidget(sub_win) + v_layout = QVBoxLayout(sub_win_main_widget) + v_layout.addWidget(QTextEdit("Sub window")) + + sub_win.setWidget(sub_win_main_widget) + mdi_area.addSubWindow(sub_win) + sub_win.show() + + add_new_sub_window() + new_button.pressed.connect(add_new_sub_window) + cascade_button.pressed.connect(mdi_area.cascadeSubWindows) + tiled_button.pressed.connect(mdi_area.tileSubWindows) + new_button.setDefault(True) + + # Layout + h_layout = QHBoxLayout() + h_layout.addWidget(new_button) + h_layout.addWidget(cascade_button) + h_layout.addWidget(tiled_button) + + v_main_layout = QVBoxLayout(container) + v_main_layout.addWidget(label_test_name) + v_main_layout.addLayout(h_layout) + v_main_layout.addWidget(mdi_area) + return container + + def setup_ui(self, win: QWidget) -> None: + """Set up ui.""" + # Widgets + splitter = QSplitter() + + # Setup widgets + mdi_area = self._make_mdi_area_test_widget() + mdi_area_with_tab = self._make_mdi_area_test_widget(enable_tab_mode=True) + + # Layout + splitter.addWidget(mdi_area) + splitter.addWidget(mdi_area_with_tab) + + main_layout = QVBoxLayout(win) + main_layout.addWidget(splitter) diff --git a/qdarktheme/widget_gallery/_ui/widgets_ui.py b/qdarktheme/widget_gallery/_ui/widgets_ui.py index 939f28b5..bfab9517 100644 --- a/qdarktheme/widget_gallery/_ui/widgets_ui.py +++ b/qdarktheme/widget_gallery/_ui/widgets_ui.py @@ -1,329 +1,329 @@ -"""Module setting up ui of widgets window.""" -from __future__ import annotations - -from typing import Any - -from qdarktheme.qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt -from qdarktheme.qtpy.QtGui import QIcon, QStandardItem, QStandardItemModel, QTextOption -from qdarktheme.qtpy.QtWidgets import ( - QCheckBox, - QColumnView, - QComboBox, - QDateTimeEdit, - QDial, - QGridLayout, - QGroupBox, - QLabel, - QLCDNumber, - QLineEdit, - QListWidget, - QProgressBar, - QPushButton, - QRadioButton, - QScrollArea, - QSlider, - QSpinBox, - QSplitter, - QTableView, - QTabWidget, - QTextEdit, - QToolBox, - QToolButton, - QTreeWidget, - QTreeWidgetItem, - QVBoxLayout, - QWidget, -) - - -class _Group1(QGroupBox): - def __init__(self) -> None: - super().__init__("Buttons") - - # Widgets - group_push = QGroupBox("Push Button") - group_tool = QGroupBox("Tool Button") - group_radio = QGroupBox("Radio Button") - group_checkbox = QGroupBox("Check Box") - - push_btn, push_btn_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED") - push_btn_flat, push_btn_flat_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED") - tool_btn, tool_btn_toggled, tool_btn_text, tool_btn_menu = (QToolButton() for _ in range(4)) - radio_btn_1, radio_btn_2 = QRadioButton("Normal 1"), QRadioButton("Normal 2") - checkbox, checkbox_tristate = QCheckBox("Normal"), QCheckBox("Tristate") - - # Setup widgets - self.setCheckable(True) - push_btn_flat.setFlat(True) - push_btn_flat_toggled.setFlat(True) - for btn in (push_btn_toggled, push_btn_flat_toggled): - btn.setCheckable(True) - btn.setChecked(True) - - tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg")) - tool_btn_toggled.setIcon(QIcon("icons:favorite_border_24dp.svg")) - tool_btn_text.setIcon(QIcon("icons:favorite_border_24dp.svg")) - tool_btn_menu.setIcon(QIcon("icons:favorite_border_24dp.svg")) - tool_btn_text.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) - tool_btn_text.setText("Text") - tool_btn_menu.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) - tool_btn_toggled.setCheckable(True) - tool_btn_toggled.setChecked(True) - - radio_btn_1.setChecked(True) - checkbox.setChecked(True) - checkbox_tristate.setTristate(True) - checkbox_tristate.setCheckState(Qt.CheckState.PartiallyChecked) - - # Layout - g_layout_push = QGridLayout() - g_layout_push.addWidget(QLabel("Normal"), 0, 0) - g_layout_push.addWidget(push_btn, 1, 0) - g_layout_push.addWidget(push_btn_toggled, 2, 0) - g_layout_push.addWidget(QLabel("Flat"), 0, 1) - g_layout_push.addWidget(push_btn_flat, 1, 1) - g_layout_push.addWidget(push_btn_flat_toggled, 2, 1) - group_push.setLayout(g_layout_push) - - v_layout_tool = QVBoxLayout() - v_layout_tool.addWidget(tool_btn) - v_layout_tool.addWidget(tool_btn_toggled) - v_layout_tool.addWidget(tool_btn_text) - v_layout_tool.addWidget(tool_btn_menu) - group_tool.setLayout(v_layout_tool) - - v_layout_radio = QVBoxLayout() - v_layout_radio.addWidget(radio_btn_1) - v_layout_radio.addWidget(radio_btn_2) - group_radio.setLayout(v_layout_radio) - - v_layout_checkbox = QVBoxLayout() - v_layout_checkbox.addWidget(checkbox) - v_layout_checkbox.addWidget(checkbox_tristate) - group_checkbox.setLayout(v_layout_checkbox) - - g_layout_main = QGridLayout(self) - g_layout_main.addWidget(group_push, 0, 0) - g_layout_main.addWidget(group_tool, 0, 1) - g_layout_main.addWidget(group_radio, 1, 0) - g_layout_main.addWidget(group_checkbox, 1, 1) - - -class _Group2(QGroupBox): - def __init__(self) -> None: - super().__init__("Line boxes") - # Widgets - group_spinbox = QGroupBox("Spinbox") - group_combobox = QGroupBox("Combobox") - group_editable = QGroupBox("Line edit") - group_date = QGroupBox("Date time edit") - - spinbox, spinbox_suffix = QSpinBox(), QSpinBox() - combobox, combobox_line_edit = QComboBox(), QComboBox() - line_edit = QLineEdit() - date_time_edit, date_time_edit_calendar = QDateTimeEdit(), QDateTimeEdit() - - # Setup widgets - self.setCheckable(True) - spinbox_suffix.setSuffix(" m") - - combobox.addItems(("Item 1", "Item 2", "Item 3")) - combobox_line_edit.addItems(("Item 1", "Item 2", "Item 3")) - combobox_line_edit.setEditable(True) - - line_edit.setPlaceholderText("Placeholder text") - date_time_edit_calendar.setCalendarPopup(True) - - # Layout - v_layout_spin = QVBoxLayout() - v_layout_spin.addWidget(spinbox) - v_layout_spin.addWidget(spinbox_suffix) - group_spinbox.setLayout(v_layout_spin) - - v_layout_combo = QVBoxLayout() - v_layout_combo.addWidget(combobox) - v_layout_combo.addWidget(combobox_line_edit) - group_combobox.setLayout(v_layout_combo) - - v_layout_line_edit = QVBoxLayout() - v_layout_line_edit.addWidget(line_edit) - group_editable.setLayout(v_layout_line_edit) - - v_layout_date = QVBoxLayout() - v_layout_date.addWidget(date_time_edit) - v_layout_date.addWidget(date_time_edit_calendar) - group_date.setLayout(v_layout_date) - - g_layout_main = QGridLayout(self) - g_layout_main.addWidget(group_spinbox, 0, 0) - g_layout_main.addWidget(group_combobox, 0, 1) - g_layout_main.addWidget(group_editable, 1, 0) - g_layout_main.addWidget(group_date, 1, 1) - - -class _TableModel(QAbstractTableModel): - def __init__(self) -> None: - super().__init__() - self._data = [[i * 10 + j for j in range(4)] for i in range(5)] - - def data(self, index: QModelIndex, role: int) -> Any: - if role == Qt.ItemDataRole.DisplayRole: - return self._data[index.row()][index.column()] - if role == Qt.ItemDataRole.CheckStateRole and index.column() == 1: - return Qt.CheckState.Checked if index.row() % 2 == 0 else Qt.CheckState.Unchecked - if role == Qt.ItemDataRole.EditRole and index.column() == 2: - return self._data[index.row()][index.column()] # pragma: no cover - return None - - def rowCount(self, index) -> int: # noqa: N802 - return len(self._data) - - def columnCount(self, index) -> int: # noqa: N802 - return len(self._data[0]) - - def flags(self, index: QModelIndex) -> Qt.ItemFlag: - flag = super().flags(index) - if index.column() == 1: - flag |= Qt.ItemFlag.ItemIsUserCheckable - elif index.column() in (2, 3): - flag |= Qt.ItemFlag.ItemIsEditable - return flag # type: ignore - - def headerData( # noqa: N802 - self, section: int, orientation: Qt.Orientation, role: int = ... - ) -> Any: - if role != Qt.ItemDataRole.DisplayRole: - return None - if orientation == Qt.Orientation.Horizontal: - return ["Normal", "Checkbox", "Spinbox", "LineEdit"][section] - return section * 100 - - -class _Group3(QGroupBox): - def __init__(self) -> None: - super().__init__("Scroll area and QTabWidget (QGroupBox.flat = True)") - - # Widgets - tab_widget = QTabWidget() - tab_text_edit = QTextEdit() - tab_table = QTableView() - tab_list = QListWidget() - tab_tree = QTreeWidget() - tab_column = QColumnView() - btn_toggle_alternating = QPushButton("Alternating") - - # Setup widgets - self.setCheckable(True) - self.setFlat(True) - tab_widget.setTabsClosable(True) - tab_widget.setMovable(True) - tab_text_edit.append("PyQtDarkTheme") - tab_text_edit.append("Dark theme for PySide and PyQt.") - tab_text_edit.append("This project is licensed under the MIT license.") - tab_text_edit.append('PyQtDarkTheme Doc') - tab_text_edit.setWordWrapMode(QTextOption.WrapMode.NoWrap) - - tab_table.setModel(_TableModel()) - tab_table.setSortingEnabled(True) - - tab_list.addItems([f"Item {i+1}" for i in range(30)]) - - tab_tree.setColumnCount(2) - for i in range(5): - item = QTreeWidgetItem([f"Item {i+1}" for _ in range(2)]) - for j in range(2): - item.addChild(QTreeWidgetItem([f"Child Item {i+1}_{j+1}" for _ in range(2)])) - tab_tree.insertTopLevelItem(i, item) - - tab_column_model = QStandardItemModel() - tab_column_model.setHorizontalHeaderLabels(("Header 1", "Header 2")) - for row in range(5): - item = QStandardItem(f"Item {row+1}") - for column in range(15): - item.setChild(column, QStandardItem(f"Child Item {row+1}_{column+1}")) - tab_column_model.setItem(row, item) - tab_column.setModel(tab_column_model) - - def toggle_alternating(checked: bool): - tab_table.setAlternatingRowColors(checked) - tab_list.setAlternatingRowColors(checked) - tab_tree.setAlternatingRowColors(checked) - tab_column.setAlternatingRowColors(checked) - - btn_toggle_alternating.setCheckable(True) - btn_toggle_alternating.toggled.connect(toggle_alternating) - btn_toggle_alternating.setChecked(True) - - # layout - tab_widget.addTab(tab_table, "Table") - tab_widget.addTab(tab_text_edit, "Text Edit") - tab_widget.addTab(tab_list, "List") - tab_widget.addTab(tab_tree, "Tree") - tab_widget.addTab(tab_column, "Column") - - v_layout_main = QVBoxLayout(self) - v_layout_main.addWidget(tab_widget) - v_layout_main.addWidget(btn_toggle_alternating) - - -class _Group4(QGroupBox): - def __init__(self) -> None: - super().__init__("QToolBox") - # Widgets - toolbox = QToolBox() - h_slider, v_slider = QSlider(Qt.Orientation.Horizontal), QSlider(Qt.Orientation.Vertical) - dial_ticks = QDial() - progressbar = QProgressBar() - lcd_number = QLCDNumber() - - # Setup widgets - self.setCheckable(True) - # If the slider value is 50, it is not clear which orientation is active. - h_slider.setValue(30) - v_slider.setValue(30) - dial_ticks.setNotchesVisible(True) - progressbar.setValue(50) - lcd_number.setSegmentStyle(QLCDNumber.SegmentStyle.Flat) - lcd_number.display(123) - - # Layout - slider_component = QWidget() - v_layout = QVBoxLayout(slider_component) - v_layout.addWidget(h_slider) - v_layout.addWidget(v_slider) - toolbox.addItem(slider_component, "Slider") - toolbox.addItem(dial_ticks, "Dial") - toolbox.addItem(progressbar, "Progress Bar") - toolbox.addItem(lcd_number, "LCD Number") - QVBoxLayout(self).addWidget(toolbox) - - -class WidgetsUI: - """The ui class of widgets window.""" - - def setup_ui(self, win: QWidget) -> None: - """Set up ui.""" - # Widgets - h_splitter_1, h_splitter_2 = QSplitter(Qt.Orientation.Horizontal), QSplitter( - Qt.Orientation.Horizontal - ) - - # Setup widgets - h_splitter_1.setMinimumHeight(350) # Fix bug layout crush - - # Layout - h_splitter_1.addWidget(_Group1()) - h_splitter_1.addWidget(_Group2()) - h_splitter_2.addWidget(_Group3()) - h_splitter_2.addWidget(_Group4()) - - widget_container = QWidget() - v_layout = QVBoxLayout(widget_container) - v_layout.addWidget(h_splitter_1) - v_layout.addWidget(h_splitter_2) - - scroll_area = QScrollArea() - scroll_area.setWidget(widget_container) - - v_main_layout = QVBoxLayout(win) - v_main_layout.addWidget(scroll_area) +"""Module setting up ui of widgets window.""" +from __future__ import annotations + +from typing import Any + +from qdarktheme.qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt +from qdarktheme.qtpy.QtGui import QIcon, QStandardItem, QStandardItemModel, QTextOption +from qdarktheme.qtpy.QtWidgets import ( + QCheckBox, + QColumnView, + QComboBox, + QDateTimeEdit, + QDial, + QGridLayout, + QGroupBox, + QLabel, + QLCDNumber, + QLineEdit, + QListWidget, + QProgressBar, + QPushButton, + QRadioButton, + QScrollArea, + QSlider, + QSpinBox, + QSplitter, + QTableView, + QTabWidget, + QTextEdit, + QToolBox, + QToolButton, + QTreeWidget, + QTreeWidgetItem, + QVBoxLayout, + QWidget, +) + + +class _Group1(QGroupBox): + def __init__(self) -> None: + super().__init__("Buttons") + + # Widgets + group_push = QGroupBox("Push Button") + group_tool = QGroupBox("Tool Button") + group_radio = QGroupBox("Radio Button") + group_checkbox = QGroupBox("Check Box") + + push_btn, push_btn_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED") + push_btn_flat, push_btn_flat_toggled = QPushButton("NORMAL"), QPushButton("TOGGLED") + tool_btn, tool_btn_toggled, tool_btn_text, tool_btn_menu = (QToolButton() for _ in range(4)) + radio_btn_1, radio_btn_2 = QRadioButton("Normal 1"), QRadioButton("Normal 2") + checkbox, checkbox_tristate = QCheckBox("Normal"), QCheckBox("Tristate") + + # Setup widgets + self.setCheckable(True) + push_btn_flat.setFlat(True) + push_btn_flat_toggled.setFlat(True) + for btn in (push_btn_toggled, push_btn_flat_toggled): + btn.setCheckable(True) + btn.setChecked(True) + + tool_btn.setIcon(QIcon("icons:favorite_border_24dp.svg")) + tool_btn_toggled.setIcon(QIcon("icons:favorite_border_24dp.svg")) + tool_btn_text.setIcon(QIcon("icons:favorite_border_24dp.svg")) + tool_btn_menu.setIcon(QIcon("icons:favorite_border_24dp.svg")) + tool_btn_text.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon) + tool_btn_text.setText("Text") + tool_btn_menu.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) + tool_btn_toggled.setCheckable(True) + tool_btn_toggled.setChecked(True) + + radio_btn_1.setChecked(True) + checkbox.setChecked(True) + checkbox_tristate.setTristate(True) + checkbox_tristate.setCheckState(Qt.CheckState.PartiallyChecked) + + # Layout + g_layout_push = QGridLayout() + g_layout_push.addWidget(QLabel("Normal"), 0, 0) + g_layout_push.addWidget(push_btn, 1, 0) + g_layout_push.addWidget(push_btn_toggled, 2, 0) + g_layout_push.addWidget(QLabel("Flat"), 0, 1) + g_layout_push.addWidget(push_btn_flat, 1, 1) + g_layout_push.addWidget(push_btn_flat_toggled, 2, 1) + group_push.setLayout(g_layout_push) + + v_layout_tool = QVBoxLayout() + v_layout_tool.addWidget(tool_btn) + v_layout_tool.addWidget(tool_btn_toggled) + v_layout_tool.addWidget(tool_btn_text) + v_layout_tool.addWidget(tool_btn_menu) + group_tool.setLayout(v_layout_tool) + + v_layout_radio = QVBoxLayout() + v_layout_radio.addWidget(radio_btn_1) + v_layout_radio.addWidget(radio_btn_2) + group_radio.setLayout(v_layout_radio) + + v_layout_checkbox = QVBoxLayout() + v_layout_checkbox.addWidget(checkbox) + v_layout_checkbox.addWidget(checkbox_tristate) + group_checkbox.setLayout(v_layout_checkbox) + + g_layout_main = QGridLayout(self) + g_layout_main.addWidget(group_push, 0, 0) + g_layout_main.addWidget(group_tool, 0, 1) + g_layout_main.addWidget(group_radio, 1, 0) + g_layout_main.addWidget(group_checkbox, 1, 1) + + +class _Group2(QGroupBox): + def __init__(self) -> None: + super().__init__("Line boxes") + # Widgets + group_spinbox = QGroupBox("Spinbox") + group_combobox = QGroupBox("Combobox") + group_editable = QGroupBox("Line edit") + group_date = QGroupBox("Date time edit") + + spinbox, spinbox_suffix = QSpinBox(), QSpinBox() + combobox, combobox_line_edit = QComboBox(), QComboBox() + line_edit = QLineEdit() + date_time_edit, date_time_edit_calendar = QDateTimeEdit(), QDateTimeEdit() + + # Setup widgets + self.setCheckable(True) + spinbox_suffix.setSuffix(" m") + + combobox.addItems(("Item 1", "Item 2", "Item 3")) + combobox_line_edit.addItems(("Item 1", "Item 2", "Item 3")) + combobox_line_edit.setEditable(True) + + line_edit.setPlaceholderText("Placeholder text") + date_time_edit_calendar.setCalendarPopup(True) + + # Layout + v_layout_spin = QVBoxLayout() + v_layout_spin.addWidget(spinbox) + v_layout_spin.addWidget(spinbox_suffix) + group_spinbox.setLayout(v_layout_spin) + + v_layout_combo = QVBoxLayout() + v_layout_combo.addWidget(combobox) + v_layout_combo.addWidget(combobox_line_edit) + group_combobox.setLayout(v_layout_combo) + + v_layout_line_edit = QVBoxLayout() + v_layout_line_edit.addWidget(line_edit) + group_editable.setLayout(v_layout_line_edit) + + v_layout_date = QVBoxLayout() + v_layout_date.addWidget(date_time_edit) + v_layout_date.addWidget(date_time_edit_calendar) + group_date.setLayout(v_layout_date) + + g_layout_main = QGridLayout(self) + g_layout_main.addWidget(group_spinbox, 0, 0) + g_layout_main.addWidget(group_combobox, 0, 1) + g_layout_main.addWidget(group_editable, 1, 0) + g_layout_main.addWidget(group_date, 1, 1) + + +class _TableModel(QAbstractTableModel): + def __init__(self) -> None: + super().__init__() + self._data = [[i * 10 + j for j in range(4)] for i in range(5)] + + def data(self, index: QModelIndex, role: int) -> Any: + if role == Qt.ItemDataRole.DisplayRole: + return self._data[index.row()][index.column()] + if role == Qt.ItemDataRole.CheckStateRole and index.column() == 1: + return Qt.CheckState.Checked if index.row() % 2 == 0 else Qt.CheckState.Unchecked + if role == Qt.ItemDataRole.EditRole and index.column() == 2: + return self._data[index.row()][index.column()] # pragma: no cover + return None + + def rowCount(self, index) -> int: # noqa: N802 + return len(self._data) + + def columnCount(self, index) -> int: # noqa: N802 + return len(self._data[0]) + + def flags(self, index: QModelIndex) -> Qt.ItemFlag: + flag = super().flags(index) + if index.column() == 1: + flag |= Qt.ItemFlag.ItemIsUserCheckable + elif index.column() in (2, 3): + flag |= Qt.ItemFlag.ItemIsEditable + return flag # type: ignore + + def headerData( # noqa: N802 + self, section: int, orientation: Qt.Orientation, role: int = ... + ) -> Any: + if role != Qt.ItemDataRole.DisplayRole: + return None + if orientation == Qt.Orientation.Horizontal: + return ["Normal", "Checkbox", "Spinbox", "LineEdit"][section] + return section * 100 + + +class _Group3(QGroupBox): + def __init__(self) -> None: + super().__init__("Scroll area and QTabWidget (QGroupBox.flat = True)") + + # Widgets + tab_widget = QTabWidget() + tab_text_edit = QTextEdit() + tab_table = QTableView() + tab_list = QListWidget() + tab_tree = QTreeWidget() + tab_column = QColumnView() + btn_toggle_alternating = QPushButton("Alternating") + + # Setup widgets + self.setCheckable(True) + self.setFlat(True) + tab_widget.setTabsClosable(True) + tab_widget.setMovable(True) + tab_text_edit.append("PyQtDarkTheme") + tab_text_edit.append("Dark theme for PySide and PyQt.") + tab_text_edit.append("This project is licensed under the MIT license.") + tab_text_edit.append('PyQtDarkTheme Doc') + tab_text_edit.setWordWrapMode(QTextOption.WrapMode.NoWrap) + + tab_table.setModel(_TableModel()) + tab_table.setSortingEnabled(True) + + tab_list.addItems([f"Item {i+1}" for i in range(30)]) + + tab_tree.setColumnCount(2) + for i in range(5): + item = QTreeWidgetItem([f"Item {i+1}" for _ in range(2)]) + for j in range(2): + item.addChild(QTreeWidgetItem([f"Child Item {i+1}_{j+1}" for _ in range(2)])) + tab_tree.insertTopLevelItem(i, item) + + tab_column_model = QStandardItemModel() + tab_column_model.setHorizontalHeaderLabels(("Header 1", "Header 2")) + for row in range(5): + item = QStandardItem(f"Item {row+1}") + for column in range(15): + item.setChild(column, QStandardItem(f"Child Item {row+1}_{column+1}")) + tab_column_model.setItem(row, item) + tab_column.setModel(tab_column_model) + + def toggle_alternating(checked: bool): + tab_table.setAlternatingRowColors(checked) + tab_list.setAlternatingRowColors(checked) + tab_tree.setAlternatingRowColors(checked) + tab_column.setAlternatingRowColors(checked) + + btn_toggle_alternating.setCheckable(True) + btn_toggle_alternating.toggled.connect(toggle_alternating) + btn_toggle_alternating.setChecked(True) + + # layout + tab_widget.addTab(tab_table, "Table") + tab_widget.addTab(tab_text_edit, "Text Edit") + tab_widget.addTab(tab_list, "List") + tab_widget.addTab(tab_tree, "Tree") + tab_widget.addTab(tab_column, "Column") + + v_layout_main = QVBoxLayout(self) + v_layout_main.addWidget(tab_widget) + v_layout_main.addWidget(btn_toggle_alternating) + + +class _Group4(QGroupBox): + def __init__(self) -> None: + super().__init__("QToolBox") + # Widgets + toolbox = QToolBox() + h_slider, v_slider = QSlider(Qt.Orientation.Horizontal), QSlider(Qt.Orientation.Vertical) + dial_ticks = QDial() + progressbar = QProgressBar() + lcd_number = QLCDNumber() + + # Setup widgets + self.setCheckable(True) + # If the slider value is 50, it is not clear which orientation is active. + h_slider.setValue(30) + v_slider.setValue(30) + dial_ticks.setNotchesVisible(True) + progressbar.setValue(50) + lcd_number.setSegmentStyle(QLCDNumber.SegmentStyle.Flat) + lcd_number.display(123) + + # Layout + slider_component = QWidget() + v_layout = QVBoxLayout(slider_component) + v_layout.addWidget(h_slider) + v_layout.addWidget(v_slider) + toolbox.addItem(slider_component, "Slider") + toolbox.addItem(dial_ticks, "Dial") + toolbox.addItem(progressbar, "Progress Bar") + toolbox.addItem(lcd_number, "LCD Number") + QVBoxLayout(self).addWidget(toolbox) + + +class WidgetsUI: + """The ui class of widgets window.""" + + def setup_ui(self, win: QWidget) -> None: + """Set up ui.""" + # Widgets + h_splitter_1, h_splitter_2 = QSplitter(Qt.Orientation.Horizontal), QSplitter( + Qt.Orientation.Horizontal + ) + + # Setup widgets + h_splitter_1.setMinimumHeight(350) # Fix bug layout crush + + # Layout + h_splitter_1.addWidget(_Group1()) + h_splitter_1.addWidget(_Group2()) + h_splitter_2.addWidget(_Group3()) + h_splitter_2.addWidget(_Group4()) + + widget_container = QWidget() + v_layout = QVBoxLayout(widget_container) + v_layout.addWidget(h_splitter_1) + v_layout.addWidget(h_splitter_2) + + scroll_area = QScrollArea() + scroll_area.setWidget(widget_container) + + v_main_layout = QVBoxLayout(win) + v_main_layout.addWidget(scroll_area) diff --git a/qdarktheme/widget_gallery/main_window.py b/qdarktheme/widget_gallery/main_window.py index d010aaab..378eedeb 100644 --- a/qdarktheme/widget_gallery/main_window.py +++ b/qdarktheme/widget_gallery/main_window.py @@ -1,223 +1,223 @@ -"""Main module of widget gallery.""" -import qdarktheme -from qdarktheme._util import get_qdarktheme_root_path -from qdarktheme.qtpy.QtCore import QDir, Qt, Slot -from qdarktheme.qtpy.QtGui import QAction, QActionGroup, QFont, QIcon -from qdarktheme.qtpy.QtWidgets import ( - QColorDialog, - QFileDialog, - QFontDialog, - QLabel, - QMainWindow, - QMenuBar, - QMessageBox, - QSizePolicy, - QStackedWidget, - QStatusBar, - QToolBar, - QToolButton, - QWidget, -) -from qdarktheme.widget_gallery._ui.dock_ui import DockUI -from qdarktheme.widget_gallery._ui.frame_ui import FrameUI -from qdarktheme.widget_gallery._ui.icons_ui import IconsUi -from qdarktheme.widget_gallery._ui.mdi_ui import MdiUI -from qdarktheme.widget_gallery._ui.widgets_ui import WidgetsUI - - -class _WidgetGalleryUI: - def setup_ui(self, main_win: QMainWindow) -> None: - # Actions - self.action_open_folder = QAction(QIcon("icons:folder_open_24dp.svg"), "Open folder dialog") - self.action_open_color_dialog = QAction(QIcon("icons:palette_24dp.svg"), "Open color dialog") - self.action_open_font_dialog = QAction( - QIcon("icons:font_download_24dp.svg"), "Open font dialog" - ) - self.action_enable = QAction(QIcon("icons:circle_24dp.svg"), "Enable") - self.action_disable = QAction(QIcon("icons:clear_24dp.svg"), "Disable") - self.actions_theme = [QAction(theme, main_win) for theme in ["auto", "dark", "light"]] - self.actions_page = ( - QAction(QIcon("icons:widgets_24dp.svg"), "Move to widgets"), - QAction(QIcon("icons:flip_to_front_24dp.svg"), "Move to dock"), - QAction(QIcon("icons:crop_din_24dp.svg"), "Move to frame"), - QAction(QIcon("icons:branding_watermark_24dp.svg"), "Move to mdi"), - QAction(QIcon("icons:image_24dp.svg"), "Move to icons"), - ) - self.actions_message_box = ( - QAction(text="Open question dialog"), - QAction(text="Open information dialog"), - QAction(text="Open warning dialog"), - QAction(text="Open critical dialog"), - ) - self.actions_corner_radius = (QAction(text="rounded"), QAction(text="sharp")) - - action_group_toolbar = QActionGroup(main_win) - - # Widgets - self.central_window = QMainWindow() - self.stack_widget = QStackedWidget() - self.toolbar = QToolBar("Toolbar") - - activitybar = QToolBar("activitybar") - statusbar = QStatusBar() - menubar = QMenuBar() - tool_btn_settings, tool_btn_theme, tool_btn_enable, tool_btn_disable, tool_btn_message_box = ( - QToolButton() for _ in range(5) - ) - - spacer = QToolButton() - - # Setup Actions - for action in self.actions_page: - action.setCheckable(True) - action_group_toolbar.addAction(action) - self.actions_page[0].setChecked(True) - - # Setup Widgets - spacer.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) - spacer.setEnabled(False) - - activitybar.setMovable(False) - activitybar.addActions(self.actions_page) - activitybar.addWidget(spacer) - activitybar.addWidget(tool_btn_settings) - - tool_btn_settings.setIcon(QIcon("icons:settings_24dp.svg")) - tool_btn_settings.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) - tool_btn_enable.setDefaultAction(self.action_enable) - tool_btn_disable.setDefaultAction(self.action_disable) - tool_btn_message_box.setIcon(QIcon("icons:announcement_24dp.svg")) - tool_btn_message_box.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) - tool_btn_theme.setIcon(QIcon("icons:contrast_24dp.svg")) - tool_btn_theme.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) - - self.toolbar.addActions( - (self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog) - ) - self.toolbar.addSeparator() - self.toolbar.addWidget(QLabel("Popup")) - self.toolbar.addWidget(tool_btn_message_box) - self.toolbar.addWidget(tool_btn_theme) - - statusbar.addPermanentWidget(tool_btn_enable) - statusbar.addPermanentWidget(tool_btn_disable) - statusbar.showMessage("Enable") - - menu_toggle = menubar.addMenu("&Toggle") - menu_toggle.addActions((self.action_enable, self.action_disable)) - menu_theme = menubar.addMenu("&Theme") - menu_theme.addActions(self.actions_theme) - menu_dialog = menubar.addMenu("&Dialog") - menu_option = menubar.addMenu("&Option") - menu_option.addActions(self.actions_corner_radius) - menu_dialog.addActions( - (self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog) - ) - menu_message_box = menu_dialog.addMenu("&Messages") - menu_message_box.addActions(self.actions_message_box) - - tool_btn_settings.setMenu(menu_toggle) - tool_btn_theme.setMenu(menu_theme) - tool_btn_message_box.setMenu(menu_message_box) - - self.action_enable.setEnabled(False) - - # Layout - for ui in (WidgetsUI, DockUI, FrameUI, MdiUI, IconsUi): - container = QWidget() - ui().setup_ui(container) - self.stack_widget.addWidget(container) - - self.central_window.setCentralWidget(self.stack_widget) - self.central_window.addToolBar(self.toolbar) - - main_win.setCentralWidget(self.central_window) - main_win.addToolBar(Qt.ToolBarArea.LeftToolBarArea, activitybar) - main_win.setMenuBar(menubar) - main_win.setStatusBar(statusbar) - - -class WidgetGallery(QMainWindow): - """The main window class of example app.""" - - def __init__(self) -> None: - """Initialize the WidgetGallery class.""" - super().__init__() - QDir.addSearchPath("icons", f"{get_qdarktheme_root_path().as_posix()}/widget_gallery/svg") - self._ui = _WidgetGalleryUI() - self._ui.setup_ui(self) - self._theme = "dark" - self._corner_shape = "rounded" - - # Signal - self._ui.action_open_folder.triggered.connect( - lambda: QFileDialog.getOpenFileName( - self, "Open File", options=QFileDialog.Option.DontUseNativeDialog - ) - ) - self._ui.action_open_color_dialog.triggered.connect( - lambda: QColorDialog.getColor( - parent=self, options=QColorDialog.ColorDialogOption.DontUseNativeDialog - ) - ) - self._ui.action_open_font_dialog.triggered.connect( - lambda: QFontDialog.getFont( - QFont(), parent=self, options=QFontDialog.FontDialogOption.DontUseNativeDialog - ) - ) - self._ui.action_enable.triggered.connect(self._toggle_state) - self._ui.action_disable.triggered.connect(self._toggle_state) - for action in self._ui.actions_theme: - action.triggered.connect(self._change_theme) - for action in self._ui.actions_page: - action.triggered.connect(self._change_page) - for action in self._ui.actions_message_box: - action.triggered.connect(self._popup_message_box) - for action in self._ui.actions_corner_radius: - action.triggered.connect(self._change_corner_radius) - - @Slot() - def _change_page(self) -> None: - action_name: str = self.sender().text() # type: ignore - if "widgets" in action_name: - index = 0 - elif "dock" in action_name: - index = 1 - elif "frame" in action_name: - index = 2 - elif "mdi" in action_name: - index = 3 - else: - index = 4 - self._ui.stack_widget.setCurrentIndex(index) - - @Slot() - def _toggle_state(self) -> None: - state: str = self.sender().text() # type: ignore - self._ui.central_window.centralWidget().setEnabled(state == "Enable") - self._ui.toolbar.setEnabled(state == "Enable") - self._ui.action_enable.setEnabled(state == "Disable") - self._ui.action_disable.setEnabled(state == "Enable") - self.statusBar().showMessage(state) - - @Slot() - def _change_theme(self) -> None: - self._theme = self.sender().text() # type: ignore - qdarktheme.setup_theme(self._theme, self._corner_shape) - - @Slot() - def _change_corner_radius(self) -> None: - self._corner_shape: str = self.sender().text() # type: ignore - qdarktheme.setup_theme(self._theme, self._corner_shape) - - @Slot() - def _popup_message_box(self) -> None: - action_name: str = self.sender().text() # type: ignore - if "question" in action_name: - QMessageBox.question(self, "Question", "Question") - elif "information" in action_name: - QMessageBox.information(self, "Information", "Information") - elif "warning" in action_name: - QMessageBox.warning(self, "Warning", "Warning") - elif "critical" in action_name: - QMessageBox.critical(self, "Critical", "Critical") +"""Main module of widget gallery.""" +import qdarktheme +from qdarktheme._util import get_qdarktheme_root_path +from qdarktheme.qtpy.QtCore import QDir, Qt, Slot +from qdarktheme.qtpy.QtGui import QAction, QActionGroup, QFont, QIcon +from qdarktheme.qtpy.QtWidgets import ( + QColorDialog, + QFileDialog, + QFontDialog, + QLabel, + QMainWindow, + QMenuBar, + QMessageBox, + QSizePolicy, + QStackedWidget, + QStatusBar, + QToolBar, + QToolButton, + QWidget, +) +from qdarktheme.widget_gallery._ui.dock_ui import DockUI +from qdarktheme.widget_gallery._ui.frame_ui import FrameUI +from qdarktheme.widget_gallery._ui.icons_ui import IconsUi +from qdarktheme.widget_gallery._ui.mdi_ui import MdiUI +from qdarktheme.widget_gallery._ui.widgets_ui import WidgetsUI + + +class _WidgetGalleryUI: + def setup_ui(self, main_win: QMainWindow) -> None: + # Actions + self.action_open_folder = QAction(QIcon("icons:folder_open_24dp.svg"), "Open folder dialog") + self.action_open_color_dialog = QAction(QIcon("icons:palette_24dp.svg"), "Open color dialog") + self.action_open_font_dialog = QAction( + QIcon("icons:font_download_24dp.svg"), "Open font dialog" + ) + self.action_enable = QAction(QIcon("icons:circle_24dp.svg"), "Enable") + self.action_disable = QAction(QIcon("icons:clear_24dp.svg"), "Disable") + self.actions_theme = [QAction(theme, main_win) for theme in ["auto", "dark", "light"]] + self.actions_page = ( + QAction(QIcon("icons:widgets_24dp.svg"), "Move to widgets"), + QAction(QIcon("icons:flip_to_front_24dp.svg"), "Move to dock"), + QAction(QIcon("icons:crop_din_24dp.svg"), "Move to frame"), + QAction(QIcon("icons:branding_watermark_24dp.svg"), "Move to mdi"), + QAction(QIcon("icons:image_24dp.svg"), "Move to icons"), + ) + self.actions_message_box = ( + QAction(text="Open question dialog"), + QAction(text="Open information dialog"), + QAction(text="Open warning dialog"), + QAction(text="Open critical dialog"), + ) + self.actions_corner_radius = (QAction(text="rounded"), QAction(text="sharp")) + + action_group_toolbar = QActionGroup(main_win) + + # Widgets + self.central_window = QMainWindow() + self.stack_widget = QStackedWidget() + self.toolbar = QToolBar("Toolbar") + + activitybar = QToolBar("activitybar") + statusbar = QStatusBar() + menubar = QMenuBar() + tool_btn_settings, tool_btn_theme, tool_btn_enable, tool_btn_disable, tool_btn_message_box = ( + QToolButton() for _ in range(5) + ) + + spacer = QToolButton() + + # Setup Actions + for action in self.actions_page: + action.setCheckable(True) + action_group_toolbar.addAction(action) + self.actions_page[0].setChecked(True) + + # Setup Widgets + spacer.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) + spacer.setEnabled(False) + + activitybar.setMovable(False) + activitybar.addActions(self.actions_page) + activitybar.addWidget(spacer) + activitybar.addWidget(tool_btn_settings) + + tool_btn_settings.setIcon(QIcon("icons:settings_24dp.svg")) + tool_btn_settings.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + tool_btn_enable.setDefaultAction(self.action_enable) + tool_btn_disable.setDefaultAction(self.action_disable) + tool_btn_message_box.setIcon(QIcon("icons:announcement_24dp.svg")) + tool_btn_message_box.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup) + tool_btn_theme.setIcon(QIcon("icons:contrast_24dp.svg")) + tool_btn_theme.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup) + + self.toolbar.addActions( + (self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog) + ) + self.toolbar.addSeparator() + self.toolbar.addWidget(QLabel("Popup")) + self.toolbar.addWidget(tool_btn_message_box) + self.toolbar.addWidget(tool_btn_theme) + + statusbar.addPermanentWidget(tool_btn_enable) + statusbar.addPermanentWidget(tool_btn_disable) + statusbar.showMessage("Enable") + + menu_toggle = menubar.addMenu("&Toggle") + menu_toggle.addActions((self.action_enable, self.action_disable)) + menu_theme = menubar.addMenu("&Theme") + menu_theme.addActions(self.actions_theme) + menu_dialog = menubar.addMenu("&Dialog") + menu_option = menubar.addMenu("&Option") + menu_option.addActions(self.actions_corner_radius) + menu_dialog.addActions( + (self.action_open_folder, self.action_open_color_dialog, self.action_open_font_dialog) + ) + menu_message_box = menu_dialog.addMenu("&Messages") + menu_message_box.addActions(self.actions_message_box) + + tool_btn_settings.setMenu(menu_toggle) + tool_btn_theme.setMenu(menu_theme) + tool_btn_message_box.setMenu(menu_message_box) + + self.action_enable.setEnabled(False) + + # Layout + for ui in (WidgetsUI, DockUI, FrameUI, MdiUI, IconsUi): + container = QWidget() + ui().setup_ui(container) + self.stack_widget.addWidget(container) + + self.central_window.setCentralWidget(self.stack_widget) + self.central_window.addToolBar(self.toolbar) + + main_win.setCentralWidget(self.central_window) + main_win.addToolBar(Qt.ToolBarArea.LeftToolBarArea, activitybar) + main_win.setMenuBar(menubar) + main_win.setStatusBar(statusbar) + + +class WidgetGallery(QMainWindow): + """The main window class of example app.""" + + def __init__(self) -> None: + """Initialize the WidgetGallery class.""" + super().__init__() + QDir.addSearchPath("icons", f"{get_qdarktheme_root_path().as_posix()}/widget_gallery/svg") + self._ui = _WidgetGalleryUI() + self._ui.setup_ui(self) + self._theme = "dark" + self._corner_shape = "rounded" + + # Signal + self._ui.action_open_folder.triggered.connect( + lambda: QFileDialog.getOpenFileName( + self, "Open File", options=QFileDialog.Option.DontUseNativeDialog + ) + ) + self._ui.action_open_color_dialog.triggered.connect( + lambda: QColorDialog.getColor( + parent=self, options=QColorDialog.ColorDialogOption.DontUseNativeDialog + ) + ) + self._ui.action_open_font_dialog.triggered.connect( + lambda: QFontDialog.getFont( + QFont(), parent=self, options=QFontDialog.FontDialogOption.DontUseNativeDialog + ) + ) + self._ui.action_enable.triggered.connect(self._toggle_state) + self._ui.action_disable.triggered.connect(self._toggle_state) + for action in self._ui.actions_theme: + action.triggered.connect(self._change_theme) + for action in self._ui.actions_page: + action.triggered.connect(self._change_page) + for action in self._ui.actions_message_box: + action.triggered.connect(self._popup_message_box) + for action in self._ui.actions_corner_radius: + action.triggered.connect(self._change_corner_radius) + + @Slot() + def _change_page(self) -> None: + action_name: str = self.sender().text() # type: ignore + if "widgets" in action_name: + index = 0 + elif "dock" in action_name: + index = 1 + elif "frame" in action_name: + index = 2 + elif "mdi" in action_name: + index = 3 + else: + index = 4 + self._ui.stack_widget.setCurrentIndex(index) + + @Slot() + def _toggle_state(self) -> None: + state: str = self.sender().text() # type: ignore + self._ui.central_window.centralWidget().setEnabled(state == "Enable") + self._ui.toolbar.setEnabled(state == "Enable") + self._ui.action_enable.setEnabled(state == "Disable") + self._ui.action_disable.setEnabled(state == "Enable") + self.statusBar().showMessage(state) + + @Slot() + def _change_theme(self) -> None: + self._theme = self.sender().text() # type: ignore + qdarktheme.setup_theme(self._theme, self._corner_shape) + + @Slot() + def _change_corner_radius(self) -> None: + self._corner_shape: str = self.sender().text() # type: ignore + qdarktheme.setup_theme(self._theme, self._corner_shape) + + @Slot() + def _popup_message_box(self) -> None: + action_name: str = self.sender().text() # type: ignore + if "question" in action_name: + QMessageBox.question(self, "Question", "Question") + elif "information" in action_name: + QMessageBox.information(self, "Information", "Information") + elif "warning" in action_name: + QMessageBox.warning(self, "Warning", "Warning") + elif "critical" in action_name: + QMessageBox.critical(self, "Critical", "Critical") diff --git a/style/README.md b/style/README.md index d4572428..21f699a1 100644 --- a/style/README.md +++ b/style/README.md @@ -1,130 +1,130 @@ -# Styles - -This folder contains the style data of PyQtDarkTheme. If you have a better idea for this style, edit this folder and create PR. - -## How to build style to qdarktheme module - -You need to run the following command to add the style changes to qdarktheme. - -``` sh -python -m tools.build_styles -``` - -When using VSCode, you can build the style resource and show the widget gallery together by running a "Check style" task. - -Even if you forget to add the style changes, pre-commit or GitHub actions will automatically build and add them. - -## Colors - -Currently, the supporting color format is only hexadecimal notations: #RGB, #RGBA, #RRGGBB, and #RRGGBBAA. - -## Theme colors - -Default color maps for dark/light theme are in `colors/themes/{theme_name}.json`. - -Colors that depend on specific colors like `background>textarea` adjust darkness, lightness and transparency. - -If you think it's best to use additional theme colors id for more highly customizable, you need to edit `colors/themes/{theme_name}.json` and `colors/themes/validate.json` to add color id for specific widgets or components. - -## Accent colors - -MacOS features accent colors. qdarktheme can detect these accent colors and set the primary color to this accent color. The accent color setting file is `style/colors/os_accent.json`. - -### About validate.json - -`colors/themes/validate.json` is json schema for `colors/themes/{theme_name}.json`. Code completion applies to `colors/themes/{theme_name}.json` if you are using VSCode. Properties **groups**, **{color_id}.group** and **{color_id}.description** are used for automatic generation of color theme documentation. - -## Icon - -Currently, PyQtDarkTheme uses only SVG for styling. All SVG icons are in `svg/`. Most of the icons use Google [Material Design Icons](https://github.com/google/material-design-icons). - -### Material Design Icons - -All SVG icons of Material Design Icons are automatically downloaded from [material-icons](https://github.com/marella/material-icons), saved in `svg/material`, and always kept up-to-date by GitHub actions. You don't need to download icons manually. -If you want to add new material icons to this style, add the icon name and style to the `svg/material_design_icons.json` list. -> **Warning** -> -> Don't edit `svg/material` manually. - -### Original Icons - -PyQtDarkTheme also has own SVG icons in `svg/original`. You can add and edit its icons manually. -> **Warning** -> -> PyQtDarkTheme supports simple SVG like following code. -> -> ```svg -> -> ``` -> -> Don't use properties like `fill`, `fill-opacity` and `transform` which use in qdarktheme module. - -### Override Qt standard Icons - -Qt has standard icons. `qdarktheme.setup_theme` can override its icons to custom icons by using QProxyStyle. You can edit custom icons in `svg/new_standard_icons.json`. - -## Style Sheet - -`base.qss` defines widget size/roundness, border size, padding, margin, icon, etc. - -For more information about Qt Style Sheet, see the QT official site: [Qt Style Sheets](https://doc.qt.io/qt-6/stylesheet.html). - -Also `base.qss` supports templates like [jinja2](https://jinja.palletsprojects.com/en/3.1.x/). - -### Template examples - -1. Dynamic color - - This template output the color of `background` id. - - ``` plain text - {{ background|color }} - ``` - -1. Dynamic color with child ID - - This template output the color of `primary>selection.background` id. - - ``` plain text - {{ primary|color(state="selection.background") }} - ``` - -1. Dynamic icon url - - This template output the system absolute url of `east.svg`. You can use the svg file name in the `svg/` for the id of the url. - - ``` plain text - {{ primary|color|url(id="east") }} - ``` - -1. Dynamic rotating icon url - - ``` plain text - {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }} - ``` - -1. Dynamic radius of corner - - ``` plain text - {{ corner-shape|corner(size=2) }} - ``` - -1. Dependence on system environment - - ``` plain text - {{ |env(value="popupMode=MenuButtonPopup", version="<6.0.0", qt="PySide2") }} - ``` - -For more filter information, see `qdarktheme/_filter.py`. - -### QPalette - -`palette.template.py` defines factory function of QPalette set theme colors for PyQtDarkTheme. This file use templates. - -### Template example - -Dynamic color of QPalette format - -``` plain text -{{ background|color(state="popup")|palette }} -``` +# Styles + +This folder contains the style data of PyQtDarkTheme. If you have a better idea for this style, edit this folder and create PR. + +## How to build style to qdarktheme module + +You need to run the following command to add the style changes to qdarktheme. + +``` sh +python -m tools.build_styles +``` + +When using VSCode, you can build the style resource and show the widget gallery together by running a "Check style" task. + +Even if you forget to add the style changes, pre-commit or GitHub actions will automatically build and add them. + +## Colors + +Currently, the supporting color format is only hexadecimal notations: #RGB, #RGBA, #RRGGBB, and #RRGGBBAA. + +## Theme colors + +Default color maps for dark/light theme are in `colors/themes/{theme_name}.json`. + +Colors that depend on specific colors like `background>textarea` adjust darkness, lightness and transparency. + +If you think it's best to use additional theme colors id for more highly customizable, you need to edit `colors/themes/{theme_name}.json` and `colors/themes/validate.json` to add color id for specific widgets or components. + +## Accent colors + +MacOS features accent colors. qdarktheme can detect these accent colors and set the primary color to this accent color. The accent color setting file is `style/colors/os_accent.json`. + +### About validate.json + +`colors/themes/validate.json` is json schema for `colors/themes/{theme_name}.json`. Code completion applies to `colors/themes/{theme_name}.json` if you are using VSCode. Properties **groups**, **{color_id}.group** and **{color_id}.description** are used for automatic generation of color theme documentation. + +## Icon + +Currently, PyQtDarkTheme uses only SVG for styling. All SVG icons are in `svg/`. Most of the icons use Google [Material Design Icons](https://github.com/google/material-design-icons). + +### Material Design Icons + +All SVG icons of Material Design Icons are automatically downloaded from [material-icons](https://github.com/marella/material-icons), saved in `svg/material`, and always kept up-to-date by GitHub actions. You don't need to download icons manually. +If you want to add new material icons to this style, add the icon name and style to the `svg/material_design_icons.json` list. +> **Warning** +> +> Don't edit `svg/material` manually. + +### Original Icons + +PyQtDarkTheme also has own SVG icons in `svg/original`. You can add and edit its icons manually. +> **Warning** +> +> PyQtDarkTheme supports simple SVG like following code. +> +> ```svg +> +> ``` +> +> Don't use properties like `fill`, `fill-opacity` and `transform` which use in qdarktheme module. + +### Override Qt standard Icons + +Qt has standard icons. `qdarktheme.setup_theme` can override its icons to custom icons by using QProxyStyle. You can edit custom icons in `svg/new_standard_icons.json`. + +## Style Sheet + +`base.qss` defines widget size/roundness, border size, padding, margin, icon, etc. + +For more information about Qt Style Sheet, see the QT official site: [Qt Style Sheets](https://doc.qt.io/qt-6/stylesheet.html). + +Also `base.qss` supports templates like [jinja2](https://jinja.palletsprojects.com/en/3.1.x/). + +### Template examples + +1. Dynamic color + + This template output the color of `background` id. + + ``` plain text + {{ background|color }} + ``` + +1. Dynamic color with child ID + + This template output the color of `primary>selection.background` id. + + ``` plain text + {{ primary|color(state="selection.background") }} + ``` + +1. Dynamic icon url + + This template output the system absolute url of `east.svg`. You can use the svg file name in the `svg/` for the id of the url. + + ``` plain text + {{ primary|color|url(id="east") }} + ``` + +1. Dynamic rotating icon url + + ``` plain text + {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }} + ``` + +1. Dynamic radius of corner + + ``` plain text + {{ corner-shape|corner(size=2) }} + ``` + +1. Dependence on system environment + + ``` plain text + {{ |env(value="popupMode=MenuButtonPopup", version="<6.0.0", qt="PySide2") }} + ``` + +For more filter information, see `qdarktheme/_filter.py`. + +### QPalette + +`palette.template.py` defines factory function of QPalette set theme colors for PyQtDarkTheme. This file use templates. + +### Template example + +Dynamic color of QPalette format + +``` plain text +{{ background|color(state="popup")|palette }} +``` diff --git a/style/base.qss b/style/base.qss index a00e5a82..83b825db 100644 --- a/style/base.qss +++ b/style/base.qss @@ -1,1334 +1,1334 @@ -/* QWidget ---------------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QWidget { - background: {{ background|color }}; - color: {{ foreground|color }}; - selection-color: {{ foreground|color }}; - selection-background-color: {{ primary|color(state="selection.background") }}; -} -QWidget:disabled { - color: {{ foreground|color(state="disabled") }}; - selection-background-color: {{ foreground|color(state="disabledSelectionBackground") }}; - selection-color: {{ foreground|color(state="disabled") }}; -} -QWidget:focus { - outline: none; -} -/* Top level widget ------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QCheckBox:!window, -QRadioButton:!window, -QPushButton:!window, -QLabel:!window, -QLCDNumber:!window { - background: transparent; -} - -QMdiSubWindow > QCheckBox:!window, -QMdiSubWindow > QRadioButton:!window, -QMdiSubWindow > QPushButton:!window, -QMdiSubWindow > QLabel:!window, -QMdiSubWindow > QLCDNumber:!window { - background: {{ background|color }}; -} - -/* QMainWindow ------------------------------------------------------------ - -This adjusts the splitter in the dock widget, not QSplitter -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmainwindow - ---------------------------------------------------------------------------- */ -QMainWindow::separator { - width: 4px; - height: 4px; - background: {{ border|color }}; -} -QMainWindow::separator:hover, -QMainWindow::separator:pressed { - background: {{ primary|color }}; -} - -/* QToolTip --------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtooltip - ---------------------------------------------------------------------------- */ -QToolTip { - background: {{ background|color(state="popup") }}; - color: {{ foreground|color }}; -} - -/* QSizeGrip -------------------------------------------------------------- - -There is no size grip in modern apps. So we hide size grip. -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsizegrip - ---------------------------------------------------------------------------- */ -QSizeGrip { - width: 0; - height: 0; - image: none; -} - -/* QStatusBar ------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qstatusbar - ---------------------------------------------------------------------------- */ -QStatusBar { - background: {{ statusBar.background|color }}; -} -QStatusBar::item { - /* Remove white border line on Windows */ - border: none; -} - -QStatusBar QWidget { - background: transparent; - padding: 3px; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -/* -`QStatusBar QWidget` selector also override QSizeGrip rule. -The QSizeGrip should be hidden on this qss, but the padding is set by `QStatusBar QWidget`. -Remove padding of QSizeGrip after `QStatusBar QWidget` sets `padding: 3px`. -*/ -QStatusBar > .QSizeGrip { - padding: 0; -} -QStatusBar QWidget:hover { - background: {{ statusBarItem.hoverBackground|color }}; -} -QStatusBar QWidget:pressed, -QStatusBar QWidget:checked { - background: {{ statusBarItem.activeBackground|color }}; -} - -/* QCheckBox QRadioButton QGroupBox --------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcheckbox -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qradiobutton -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox - ---------------------------------------------------------------------------- */ -QCheckBox, -QRadioButton { - border-top: 2px solid transparent; - border-bottom: 2px solid transparent; -} -QCheckBox:hover, -QRadioButton:hover { - border-bottom: 2px solid {{ primary|color }}; -} - -QGroupBox { - font-weight: bold; - margin-top: 8px; - padding: 2px 1px 1px 1px; - border-radius: {{ corner-shape|corner(size=4) }}px; - border: 1px solid {{ border|color }}; -} -QGroupBox::title { - subcontrol-origin: margin; - subcontrol-position: top left; - left: 7px; - margin: 0 2px 0 3px; -} -QGroupBox:flat { - border-color: transparent; -} - -/* QMenuBar --------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenubar - ---------------------------------------------------------------------------- */ -QMenuBar { - padding: 2px; - border-bottom: 1px solid {{ border|color }}; - background: {{ background|color }}; -} -QMenuBar::item { - background: transparent; - padding: 4px; -} -QMenuBar::item:selected { - padding: 4px; - border-radius: {{ corner-shape|corner(size=4) }}px; - background: {{ menubar.selectionBackground|color }}; -} -QMenuBar::item:pressed { - padding: 4px; - margin-bottom: 0; - padding-bottom: 0; -} - -/* QToolBar --------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbar - ---------------------------------------------------------------------------- */ -QToolBar { - padding: 1px; - font-weight: bold; - spacing: 2px; - margin: 1px; - background: {{ toolbar.background|color }}; - /* QToolBar must override `border-style` to set the style. */ - border-style: none; -} -QToolBar::handle:horizontal { - width: 20px; - image: {{ foreground|color(state="icon")|url(id="drag_indicator") }}; -} -QToolBar::handle:vertical { - height: 20px; - image: {{ foreground|color(state="icon")|url(id="drag_indicator", rotate=90) }}; -} -QToolBar::handle:horizontal:disabled { - image: {{ foreground|color(state="disabled")|url(id="drag_indicator") }}; -} -QToolBar::handle:vertical:disabled { - image: {{ foreground|color(state="disabled")|url(id="drag_indicator", rotate=90) }}; -} -QToolBar::separator { - background: {{ border|color }}; -} -QToolBar::separator:horizontal { - width: 2px; - margin: 0 6px; -} -QToolBar::separator:vertical { - height: 2px; - margin: 6px 0; -} - -QToolBar > QToolButton { - background: transparent; - padding: 3px; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QToolBar > QToolButton:hover, -QToolBar > QToolButton::menu-button:hover { - background: {{ toolbar.hoverBackground|color }}; -} -QToolBar > QToolButton::menu-button { - border-top-right-radius: {{ corner-shape|corner(size=4) }}px; - border-bottom-right-radius: {{ corner-shape|corner(size=4) }}px; -} -QToolBar > QToolButton:pressed, -QToolBar > QToolButton::menu-button:pressed:enabled, -QToolBar > QToolButton:checked:enabled { - background: {{ toolbar.activeBackground|color }}; -} - -QToolBar > QWidget { - background: transparent; -} - -/* QMenu ------------------------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu - ---------------------------------------------------------------------------- */ -QMenu { - background: {{ background|color(state="popup") }}; - padding: 8px 0; - {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} - {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version=">=6.4.1", os="Darwin") }} -} -QMenu::separator { - margin: 4px 0; - height: 1px; - background: {{ border|color }}; -} -QMenu::item { - padding: 4px 19px; -} -QMenu::item:selected { - background: {{ popupItem.selectionBackground|color }}; -} -QMenu::icon { - padding-left: 10px; - width: 14px; - height: 14px; -} -QMenu::right-arrow { - margin: 2px; - padding-left: 12px; - height: 20px; - width: 20px; - image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; -} -QMenu::right-arrow:disabled { - image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; -} - -/* QScrollBar ------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qscrollbar - ---------------------------------------------------------------------------- */ -QScrollBar { - background: {{ scrollbar.background|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; - {{ |env(value="background: transparent", os="Darwin")}} -} -QScrollBar:horizontal { - height: 14px; - {{ |env(value="height: 7px;", os="Darwin") }} -} -QScrollBar:vertical { - width: 14px; - {{ |env(value="width: 7px;", os="Darwin") }} -} -QScrollBar::handle { - background: {{ scrollbarSlider.background|color }}; - border-radius: {{ corner-shape|corner(size=3) }}px; -} -QScrollBar::handle:hover { - background: {{ scrollbarSlider.hoverBackground|color }}; -} -QScrollBar::handle:pressed { - background: {{ scrollbarSlider.activeBackground|color }}; -} -QScrollBar::handle:disabled { - background: {{ scrollbarSlider.disabledBackground|color }}; -} -QScrollBar::handle:horizontal { - min-width: 8px; - margin: 4px 14px; - {{ |env(value="margin: 0;", os="Darwin") }} -} -QScrollBar::handle:horizontal:hover { - margin: 2px 14px; - {{ |env(value="margin: 0;", os="Darwin") }} -} -QScrollBar::handle:vertical { - min-height: 8px; - margin: 14px 4px; - {{ |env(value="margin: 0;", os="Darwin") }} -} -QScrollBar::handle:vertical:hover { - margin: 14px 2px; - {{ |env(value="margin: 0;", os="Darwin") }} -} -/* -Hide QScrollBar background. -The `sub-page` and `add-page` are not colored by default on most OS, but are colored on Windows. -*/ -QScrollBar::sub-page, -QScrollBar::add-page { - background: transparent; -} -QScrollBar::sub-line, -QScrollBar::add-line { - background: transparent; - {{ |env(value="width: 0; height: 0", os="Darwin") }} -} -QScrollBar::up-arrow:enabled { - image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up") }}; -} -QScrollBar::right-arrow:enabled { - image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=90) }}; -} -QScrollBar::down-arrow:enabled { - image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=180) }}; -} -QScrollBar::left-arrow:enabled { - image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=270) }}; -} -QScrollBar::up-arrow:hover { - image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up") }}; -} -QScrollBar::right-arrow:hover { - image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=90) }}; -} -QScrollBar::down-arrow:hover { - image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=180) }}; -} -QScrollBar::left-arrow:hover { - image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=270) }}; -} - -/* QProgressBar ----------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qprogressbar - ---------------------------------------------------------------------------- */ -QProgressBar { - text-align: center; - border: 1px solid {{ border|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QProgressBar::chunk { - background: {{ primary|color(state="progressBar.background") }}; - border-radius: {{ corner-shape|corner(size=3) }}px; -} -QProgressBar::chunk:disabled { - background: {{ foreground|color(state="progressBar.disabledBackground") }}; -} - -/* QPushButton ------------------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qpushbutton - ---------------------------------------------------------------------------- */ -QPushButton { - color: {{ primary|color }}; - border: 1px solid {{ border|color }}; - padding: 4px 8px; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QPushButton:flat, -QPushButton:default { - border: none; - padding: 5px 9px; -} -QPushButton:default { - color: {{ background|color }}; - background: {{ primary|color }}; -} -QPushButton:hover { - background: {{ primary|color(state="button.hoverBackground") }}; -} -QPushButton:pressed { - background: {{ primary|color(state="button.activeBackground") }}; -} -QPushButton:checked:enabled { - background: {{ primary|color(state="button.activeBackground") }}; -} -QPushButton:default:hover { - background: {{ primary|color(state="defaultButton.hoverBackground") }}; -} -QPushButton:default:pressed, -QPushButton:default:checked { - background: {{ primary|color(state="defaultButton.activeBackground") }}; -} -QPushButton:default:disabled, -QPushButton:default:checked:disabled { - background: {{ foreground|color(state="defaultButton.disabledBackground") }}; -} - -/* QDialogButtonBox ------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QDialogButtonBox { - dialogbuttonbox-buttons-have-icons: 0; -} - -QDialogButtonBox QPushButton { - min-width: 65px; -} - -/* QToolButton ------------------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbutton - ---------------------------------------------------------------------------- */ -QToolButton { - background: transparent; - padding: 5px; - spacing: 2px; - border-radius: {{ corner-shape|corner(size=2) }}px; -} -QToolButton:hover, -QToolButton::menu-button:hover { - background: {{ primary|color(state="button.hoverBackground") }}; -} -QToolButton:pressed, -QToolButton:checked:pressed, -QToolButton::menu-button:pressed:enabled { - background: {{ primary|color(state="button.activeBackground") }}; -} -QToolButton:selected:enabled, -QToolButton:checked:enabled { - background: {{ primary|color(state="button.activeBackground") }}; -} -QToolButton::menu-indicator { - height: 18px; - width: 18px; - top: 6px; - left: 3px; - image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; -} -QToolButton::menu-indicator:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; -} -QToolButton::menu-arrow { - /* Remove default arrow icon on Qt5 */ - image: unset; -} -QToolButton::menu-button { - subcontrol-origin: margin; - width: 17px; - border-top-right-radius: {{ corner-shape|corner(size=2) }}px; - border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; - image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; -} -QToolButton::menu-button:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; -} - -/* MenuButtonPopup */ -QToolButton[ -{{ |env(value="popupMode=MenuButtonPopup", version="<6.0.0", qt="PySide2") }} -{{ |env(value="popupMode=\\"1\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="popupMode=MenuButtonPopup", version=">=6.0.0") }} -] { - padding-right: 1px; - margin-right: 18px; - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -/* QComboBox -------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox - ---------------------------------------------------------------------------- */ -QComboBox { - min-height: 1.5em; - padding: 0 8px 0 4px; - background: {{ input.background|color }}; - border: 1px solid {{ border|color(state="input") }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QComboBox:focus, -QComboBox:open { - border-color: {{ primary|color }}; -} -QComboBox::drop-down { - margin: 2px 2px 2px -6px; - border-radius: {{ corner-shape|corner(size=4) }}; /* This remove default style. */ -} -QComboBox::drop-down:editable:hover { - background: {{ inputButton.hoverBackground|color }}; -} -QComboBox::down-arrow { - image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; -} -QComboBox::down-arrow:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; -} -QComboBox::down-arrow:editable:open { - image: {{ foreground|color(state="icon")|url(id="expand_less") }}; -} -QComboBox::down-arrow:editable:open:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less") }}; -} -/* Setting background color of selected item when editable is false. */ -QComboBox::item:selected { - border: none; /* This remove the border of indicator. */ - background: {{ primary|color(state="list.selectionBackground") }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} - -/* QAbstractItemView in QComboBox is NoFrame. Override default settings and show border. */ -QComboBox QListView[ -{{ |env(value="frameShape=\\"0\\"", version="<6.0.0")}} -{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} -] { - margin: 0; - padding: 4px; - background: {{ background|color(state="popup") }}; - {{ primary|color(state="list.selectionBackground")|env(value="selection-background-color: ${};", version="<6.0.0") }} - border-radius: 0; - {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} - {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version=">=6.4.1", os="Darwin") }} -} -QComboBox QListView::item { - border-radius: {{ corner-shape|corner(size=4) }}px; -} - - -/* QSlider ---------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qslider - ---------------------------------------------------------------------------- */ -QSlider { - padding: 2px 0; -} -QSlider::groove { - border-radius: {{ corner-shape|corner(size=2) }}px; -} -QSlider::groove:horizontal { - height: 4px; -} -QSlider::groove:vertical { - width: 4px; -} -/* Horizontal slider active line is `::sub-page`. */ -/* Vertical slider active line is `::add-page`. */ -QSlider::sub-page:horizontal, -QSlider::add-page:vertical, -QSlider::handle { - background: {{ primary|color }}; -} -QSlider::sub-page:horizontal:disabled, -QSlider::add-page:vertical:disabled, -QSlider::handle:disabled { - background: {{ foreground|color(state="slider.disabledBackground") }}; -} -QSlider::add-page:horizontal, -QSlider::sub-page:vertical { - background: {{ foreground|color(state="sliderTrack.inactiveBackground") }}; -} -QSlider::handle:hover, -QSlider::handle:pressed { - background: {{ primary|color(state="sliderHandle.activeBackground") }}; -} -QSlider::handle:horizontal { - width: 16px; - height: 8px; - margin: -6px 0; - border-radius: 8px; -} -QSlider::handle:vertical { - width: 8px; - height: 16px; - margin: 0 -6px; - border-radius: 8px; -} - -/* QTabWidget QTabBar ----------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtabwidget-and-qtabbar - ---------------------------------------------------------------------------- */ -QTabWidget::pane { - border: 1px solid {{ border|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QTabBar { - /* - Not filling background color when drawBase property is true. So need to set drawBase to false. - Default QTabBar is no need following declaration, but QMdiArea is need. - */ - qproperty-drawBase: 0; -} -QTabBar::close-button { - image: {{ foreground|color(state="icon")|url(id="close") }}; -} -QTabBar::close-button:hover { - background: {{ tabCloseButton.hoverBackground|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QTabBar::close-button:!selected { - image: {{ foreground|color(state="icon.unfocused")|url(id="close") }}; -} -QTabBar::close-button:disabled { - image: {{ foreground|color(state="disabled")|url(id="close") }}; -} -QTabBar::tab { - padding: 3px; - border-style: solid; -} -QTabBar::tab:hover, -QTabBar::tab:selected:hover:enabled { - background: {{ tab.hoverBackground|color }}; -} -QTabBar::tab:selected:enabled { - color: {{ primary|color }}; - background: {{ tab.activeBackground|color }}; - border-color: {{ primary|color }}; -} -QTabBar::tab:selected:disabled, -QTabBar::tab:only-one:selected:enabled { - border-color: {{ border|color }}; -} -QTabBar::tab:top { - border-bottom-width: 2px; - margin: 3px 6px 0 0; - border-top-left-radius: {{ corner-shape|corner(size=2) }}px; - border-top-right-radius: {{ corner-shape|corner(size=2) }}px; -} -QTabBar::tab:bottom { - border-top-width: 2px; - margin: 0 6px 3px 0; - border-bottom-left-radius: {{ corner-shape|corner(size=2) }}px; - border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; -} -QTabBar::tab:left { - border-right-width: 2px; - margin: 0 0 6px 3px; - border-top-left-radius: {{ corner-shape|corner(size=2) }}px; - border-bottom-left-radius: {{ corner-shape|corner(size=2) }}px; -} -QTabBar::tab:right { - border-left-width: 2px; - margin-bottom: 6px; - margin: 0 3px 6px 0; - border-top-right-radius: {{ corner-shape|corner(size=2) }}px; - border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; -} -QTabBar::tab:top:first, -QTabBar::tab:top:only-one, -QTabBar::tab:bottom:first, -QTabBar::tab:bottom:only-one { - margin-left: 2px; -} -QTabBar::tab:top:last, -QTabBar::tab:top:only-one, -QTabBar::tab:bottom:last, -QTabBar::tab:bottom:only-one { - margin-right: 2px; -} -QTabBar::tab:left:first, -QTabBar::tab:left:only-one, -QTabBar::tab:right:first, -QTabBar::tab:right:only-one { - margin-top: 2px; -} -QTabBar::tab:left:last, -QTabBar::tab:left:only-one, -QTabBar::tab:right:last, -QTabBar::tab:right:only-one { - margin-bottom: 2px; -} - -/* QDockWidget ------------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QDockWidget { - border: 1px solid {{ border|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QDockWidget::title { - /* Better size for title bar */ - padding: 3px; - spacing: 4px; - background: {{ background|color(state="title") }}; -} -/* -`QDockWidget::close-button` and `QDockWidget::float-button` is not QToolButton. -We need to set the button style again. -*/ -QDockWidget::close-button, -QDockWidget::float-button { - border-radius: {{ corner-shape|corner(size=2) }}px; -} -QDockWidget::close-button:hover, -QDockWidget::float-button:hover { - background: {{ primary|color(state="button.hoverBackground") }}; -} -QDockWidget::close-button:pressed, -QDockWidget::float-button:pressed { - background: {{ primary|color(state="button.activeBackground") }}; -} - -/* QFrame ----------------------------------------------------------------- - -https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qframe - ---------------------------------------------------------------------------- */ -QFrame { - border: 1px solid {{ border|color }}; - padding: 1px; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -.QFrame { - padding: 0; -} -/* NoFrame */ -QFrame[ -{{ |env(value="frameShape=NoFrame", version="<6.0.0", qt="PySide2") }} -{{ |env(value="frameShape=\\"0\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} -] { - border-color: transparent; - padding: 0; -} -.QFrame[ -{{ |env(value="frameShape=NoFrame", version="<6.0.0", qt="PySide2") }} -{{ |env(value="frameShape=\\"0\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} -] { - border: none; -} -/* Panel */ -QFrame[ -{{ |env(value="frameShape=Panel", version="<6.0.0", qt="PySide2") }} -{{ |env(value="frameShape=\\"2\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="frameShape=Panel", version=">=6.0.0") }} -] { - border-color: {{ background|color(state="panel") }}; - background: {{ background|color(state="panel") }}; -} -/* HLine */ -QFrame[ -{{ |env(value="frameShape=HLine", version="<6.0.0", qt="PySide2") }} -{{ |env(value="frameShape=\\"4\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="frameShape=HLine", version=">=6.0.0") }} -] { - max-height: 2px; - border: none; - background: {{ border|color }}; -} -/* VLine */ -QFrame[ -{{ |env(value="frameShape=VLine", version="<6.0.0", qt="PySide2") }} -{{ |env(value="frameShape=\\"5\\"", version="<6.0.0", qt="PyQt5") }} -{{ |env(value="frameShape=VLine", version=">=6.0.0") }} -] { - max-width: 2px; - border: none; - background: {{ border|color }}; -} -/* QLCDNumber ------------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QLCDNumber { - min-width: 2em; - margin: 2px; -} - -/* QToolBox --------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbox - ---------------------------------------------------------------------------- */ -QToolBox::tab { - background: {{ background|color(state="title") }}; - border-bottom: 2px solid {{ border|color }}; - border-top-left-radius: {{ corner-shape|corner(size=4) }}px; - border-top-right-radius: {{ corner-shape|corner(size=4) }}px; -} -QToolBox::tab:selected:enabled { - border-bottom-color: {{ primary|color }}; -} - -/* QSplitter -------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsplitter - ---------------------------------------------------------------------------- */ -QSplitter::handle { - background: {{ border|color }}; - margin: 1px 3px; -} -QSplitter::handle:hover { - background: {{ primary|color }}; -} -QSplitter::handle:horizontal { - width: 5px; - image: {{ foreground|color(state="icon")|url(id="horizontal_rule", rotate=90) }}; -} -QSplitter::handle:horizontal:disabled { - image: {{ foreground|color(state="disabled")|url(id="horizontal_rule", rotate=90) }}; -} -QSplitter::handle:vertical { - height: 5px; - image: {{ foreground|color(state="icon")|url(id="horizontal_rule") }}; -} -QSplitter::handle:vertical:disabled { - image: {{ foreground|color(state="disabled")|url(id="horizontal_rule") }}; -} - -/* -QSplitterHandle is the QSplitter handle class. -[QSplitter::handle:hover] is enabled by the following settings. -Warning: This setting is not mentioned in the documentation. -*/ -QSplitterHandle::item:hover {} - -/* QAbstractScrollArea ---------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qabstractscrollarea - ---------------------------------------------------------------------------- */ -QAbstractScrollArea { - margin: 1px; -} -QAbstractScrollArea::corner { - background: transparent; -} - -/* Fix a bug where widgets in scroll area would hide the scroll area border. */ -QAbstractScrollArea > .QWidget { - background: transparent; -} -QAbstractScrollArea > .QWidget > .QWidget { - background: transparent; -} - -/* QMdiArea QMdiSubWindow ---------------------------------------------------- - ---------------------------------------------------------------------------- */ -QMdiArea { - qproperty-background: {{ background|color(state="panel") }}; - border-radius: 0; -} - -QMdiSubWindow { - background: {{ background|color }}; - border: 1px solid; - padding: 0 3px; -} -QMdiSubWindow > QWidget { - border: 1px solid {{ border|color }}; -} - -/* QTextEdit QPlainTextEdit------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-specific-widgets - ---------------------------------------------------------------------------- */ -QTextEdit, QPlainTextEdit { - background: {{ background|color(state="textarea") }}; -} -QTextEdit:focus, -QTextEdit:selected, -QPlainTextEdit:focus, -QPlainTextEdit:selected { - border: 1px solid {{ primary|color }}; - selection-background-color: {{ primary|color(state="textarea.selectionBackground") }}; -} - -/* In version +5.15, [!active] state is disabled. */ -/* In version -5.15, [!focus] state is disabled. */ -QTextEdit:!focus, -QPlainTextEdit:!focus { - {{ textarea.inactiveSelectionBackground|color|env(value="selection-background-color: ${}", version=">=5.15.0") }} -} -QTextEdit:!active, -QPlainTextEdit:!active { - {{ textarea.inactiveSelectionBackground|color|env(value="selection-background-color: ${}", version="<5.15.0") }} -} - -/* QAbstractItemView ------------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox - ---------------------------------------------------------------------------- */ -/* Control QAbstractItemView selection and alternate colors with Pseudo-States instead of properties. */ -QAbstractItemView { - padding: 0; - alternate-background-color: transparent; - selection-background-color: transparent; -} -QAbstractItemView:disabled { - selection-background-color: transparent; -} -QAbstractItemView::item:alternate, -QAbstractItemView::branch:alternate { - background: {{ list.alternateBackground|color }}; -} -QAbstractItemView::item:selected, -QAbstractItemView::branch:selected { - background: {{ primary|color(state="list.selectionBackground") }}; -} -QAbstractItemView::item:selected:!active, -QAbstractItemView::branch:selected:!active { - background: {{ primary|color(state="list.inactiveSelectionBackground") }}; -} - -QAbstractItemView QLineEdit, -QAbstractItemView QAbstractSpinBox, -QAbstractItemView QAbstractButton { - padding: 0; - margin: 1px; -} - -/* QListView QTreeView --------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlistview -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtreeview - ---------------------------------------------------------------------------- */ -QListView { - padding: 1px -} -QListView, -QTreeView { - background: {{ background|color(state="list") }}; -} -QListView::item:!selected:hover, -QTreeView::item:!selected:hover, -QTreeView::branch:!selected:hover { - background: {{ list.hoverBackground|color }}; -} -QTreeView::branch:!selected:hover, -QTreeView::branch:alternate, -QTreeView::branch:selected, -QTreeView::branch:selected:!active { - {{ |env(value="background: transparent;", version=">=6.4.1") }} -} -QTreeView::branch { - border-image: {{ tree.inactiveIndentGuidesStroke|color|url(id="vertical_line") }} 0; -} -QTreeView::branch:active { - border-image: {{ tree.indentGuidesStroke|color(state="icon")|url(id="vertical_line") }} 0; -} -QTreeView::branch:has-siblings:adjoins-item, -QTreeView::branch:!has-children:!has-siblings:adjoins-item { - border-image: unset; -} -QTreeView::branch:has-children:!has-siblings:closed, -QTreeView::branch:closed:has-children:has-siblings { - border-image: unset; - image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; -} -QTreeView::branch:has-children:!has-siblings:closed:disabled, -QTreeView::branch:closed:has-children:has-siblings:disabled { - image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; -} -QTreeView::branch:open:has-children:!has-siblings, -QTreeView::branch:open:has-children:has-siblings { - border-image: unset; - image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; -} -QTreeView::branch:open:has-children:!has-siblings:disabled, -QTreeView::branch:open:has-children:has-siblings:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; -} -QTreeView > QHeaderView { - background: {{ background|color(state="list") }}; -} -QTreeView > QHeaderView::section { - background: {{ treeSectionHeader.background|color }}; -} - -/* -Following arrow settings are for QColumnView. -QColumnView::left-arrow and QColumnView::right-arrow are not working. -*/ -QListView::left-arrow { - margin: -2px; - image: {{ foreground|color(state="icon.unfocused")|url(id="chevron_right", rotate=180) }}; -} -QListView::right-arrow { - margin: -2px; - image: {{ foreground|color(state="icon.unfocused")|url(id="chevron_right") }}; -} -QListView::left-arrow:selected:enabled { - image: {{ foreground|color(state="icon")|url(id="chevron_right", rotate=180) }}; -} -QListView::right-arrow:selected:enabled { - image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; -} -QListView::left-arrow:disabled { - image: {{ foreground|color(state="disabled")|url(id="chevron_right", rotate=180) }}; -} -QListView::right-arrow:disabled { - image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; -} - -/* QColumnView ------------------------------------------------------------ - ---------------------------------------------------------------------------- */ -QColumnView { - background: {{ background|color(state="list") }} -} -QColumnViewGrip { - margin: -4px; - background: {{ background|color(state="list") }}; - image: {{ foreground|color(state="icon")|url(id="drag_handle", rotate=90) }} -} -QColumnViewGrip:disabled { - image: {{ foreground|color(state="disabled")|url(id="drag_handle", rotate=90) }} -} - -/* QTableView ------------------------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtableview - ---------------------------------------------------------------------------- */ -QTableView { - gridline-color: {{ tableSectionHeader.background|color }}; - background: {{ background|color(state="table") }}; - {{ primary|color(state="table.selectionBackground")|env(value="selection-background-color: ${};", version=">=6.4.1") }} - {{ table.alternateBackground|color|env(value="alternate-background-color: ${};", version=">=6.4.1") }} -} -QTableView:!active { - {{ primary|color(state="table.inactiveSelectionBackground")|env(value="selection-background-color: ${};", version="<6.4.1") }} -} -QTableView::item:alternate { - {{ table.alternateBackground|color|env(value="background: ${};", version="<6.4.1") }} -} -QTableView::item:selected { - {{ primary|color(state="table.selectionBackground")|env(value="background: ${};", version="<6.4.1") }} -} - -QTableView QTableCornerButton::section { - margin: 0 1px 1px 0; - background: {{ tableSectionHeader.background|color }}; - border-top-left-radius: {{ corner-shape|corner(size=2) }}px; -} -QTableView QTableCornerButton::section:pressed { - background: {{ primary|color(state="table.selectionBackground") }}; -} - -QTableView > QHeaderView { - background: {{ background|color(state="table") }}; - border-radius: {{ corner-shape|corner(size=3) }}; -} -QTableView > QHeaderView::section { - background: {{ tableSectionHeader.background|color }} -} - -/* QHeaderView ------------------------------------------------------------ - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qheaderview - ---------------------------------------------------------------------------- */ -QHeaderView { - margin: 0; /* Override QAbstractScrollArea rule. */ - border: none; /* Override QFrame rule. */ -} -QHeaderView::section { - border: none; /* This hide default grid. */ - background: {{ treeSectionHeader.background|color }}; - /* Apply left padding to no padding header section. */ - padding-left: 4px; -} -QHeaderView::section:horizontal { - margin-right: 1px; /* This make grid of section. */ -} -QHeaderView::section:vertical { - margin-bottom: 1px; /* This make section grid. */ -} -QHeaderView::section:on:enabled, -QHeaderView::section:on:pressed { - color: {{ primary|color }}; -} -QHeaderView::section:last, -QHeaderView::section:only-one { - margin: 0; -} -QHeaderView::down-arrow:horizontal { - margin-left: -19px; - subcontrol-position: center right; - image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; -} -QHeaderView::down-arrow:horizontal:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; -} -QHeaderView::up-arrow:horizontal { - margin-left: -19px; - subcontrol-position: center right; - image: {{ foreground|color(state="icon")|url(id="expand_less") }}; -} -QHeaderView::up-arrow:horizontal:disabled { - image: {{ foreground|color(state="disabled")|url(id="expand_less") }}; -} -/* -Longer text in the vertical QHeaderView section makes the section space too large. -This is because a transparent arrow appears on the vertical QHeaderView. -Remove the vertical QHeaderView arrow, and remove the section space. -*/ -QHeaderView::down-arrow:vertical, -QHeaderView::up-arrow:vertical { - width: 0; - height: 0; -} - -/* QCalendarWidget -------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QCalendarWidget > .QWidget { - background: {{ background|color(state="table") }}; - border-bottom: 1px solid {{ border|color }}; - border-top-left-radius: {{ corner-shape|corner(size=4) }}px; - border-top-right-radius: {{ corner-shape|corner(size=4) }}px; -} -QCalendarWidget > .QWidget > QWidget { - padding: 1px; -} -QCalendarWidget .QWidget > QToolButton { - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QCalendarWidget > QTableView { - margin: 0; - border: none; - border-radius: {{ corner-shape|corner(size=4) }}px; - border-top-left-radius: 0; - border-top-right-radius: 0; - alternate-background-color: {{ table.alternateBackground|color }}; - {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} - /* Fix 207 */ - {{ primary|color(state="table.selectionBackground")|env(value="selection-background-color: ${};", version="<6.0.0") }} -} - -/* QAbstractSpinBox QLineEdit---------------------------------------------- - -examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlineedit - ---------------------------------------------------------------------------- */ -QLineEdit, -QAbstractSpinBox { - padding: 3px 4px; - /* Adjust the min-height of QLineEdit to the height of the characters. */ - min-height: 1em; - border: 1px solid {{ border|color(state="input") }}; - background: {{ input.background|color }}; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QLineEdit:focus, -QAbstractSpinBox:focus { - border-color: {{ primary|color }}; -} -QAbstractSpinBox::up-button, -QAbstractSpinBox::down-button { - subcontrol-position: center right; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QAbstractSpinBox::up-button:hover:on, -QAbstractSpinBox::down-button:hover:on { - background: {{ inputButton.hoverBackground|color }}; -} -QAbstractSpinBox::up-button { - bottom: 5px; - right: 4px; -} -QAbstractSpinBox::up-arrow:on { - image: {{ foreground|color(state="icon")|url(id="arrow_drop_up") }}; -} -QAbstractSpinBox::up-arrow:disabled, -QAbstractSpinBox::up-arrow:off { - image: {{ foreground|color(state="disabled")|url(id="arrow_drop_up") }}; -} -QAbstractSpinBox::down-button { - top: 5px; - right: 4px; -} -QAbstractSpinBox::down-arrow:on { - image: {{ foreground|color(state="icon")|url(id="arrow_drop_up", rotate=180) }}; -} -QAbstractSpinBox::down-arrow:disabled, -QAbstractSpinBox::down-arrow:off { - image: {{ foreground|color(state="disabled")|url(id="arrow_drop_up", rotate=180) }}; -} - -/* QDateTimeEdit ---------------------------------------------------------- - ---------------------------------------------------------------------------- */ -QDateTimeEdit::drop-down { - padding-right: 4px; - width: 16px; - image: {{ foreground|color(state="icon")|url(id="calendar_today") }}; -} -QDateTimeEdit::drop-down:disabled { - image: {{ foreground|color(state="disabled")|url(id="calendar_today") }}; -} -QDateTimeEdit::down-arrow[calendarPopup=true] { - image: none; -} - -/* QFileDialog QFontDialog ------------------------------------------------ - ---------------------------------------------------------------------------- */ -QFileDialog QFrame { - border: none; -} - -QFontDialog QListView { - min-height: 60px; -} - -/* Check ------------------------------------------------------------------ - ---------------------------------------------------------------------------- */ -QComboBox::indicator, -QMenu::indicator { - width: 18px; - height: 18px; -} -QMenu::indicator { - background: {{ popupItem.checkbox.background|color }}; - margin-left: 3px; - border-radius: {{ corner-shape|corner(size=4) }}px; -} -QComboBox::indicator:checked, -QMenu::indicator:checked { - image: {{ foreground|color(state="icon")|url(id="check") }}; -} - -/* Check indicator -------------------------------------------------------- - -document: https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-sub-controls - ---------------------------------------------------------------------------- */ -QCheckBox, -QRadioButton { - spacing: 8px; -} -QGroupBox::title, -QAbstractItemView::item { - spacing: 6px; -} -QCheckBox::indicator, -QGroupBox::indicator, -QAbstractItemView::indicator, -QRadioButton::indicator { - height: 18px; - width: 18px; -} -QCheckBox::indicator, -QGroupBox::indicator, -QAbstractItemView::indicator { - image: {{ foreground|color(state="icon")|url(id="check_box_outline_blank") }}; -} -QCheckBox::indicator:unchecked:disabled, -QGroupBox::indicator:unchecked:disabled, -QAbstractItemView::indicator:unchecked:disabled { - image: {{ foreground|color(state="disabled")|url(id="check_box_outline_blank") }}; -} -QCheckBox::indicator:checked, -QGroupBox::indicator:checked, -QAbstractItemView::indicator:checked { - image: {{ primary|color|url(id="check_box") }}; -} -QCheckBox::indicator:checked:disabled, -QGroupBox::indicator:checked:disabled, -QAbstractItemView::indicator:checked:disabled { - image: {{ foreground|color(state="disabled")|url(id="check_box") }}; -} -QCheckBox::indicator:indeterminate, -QAbstractItemView::indicator:indeterminate { - image: {{ primary|color|url(id="indeterminate_check_box") }}; -} -QCheckBox::indicator:indeterminate:disabled, -QAbstractItemView::indicator:indeterminate:disabled { - image: {{ foreground|color(state="disabled")|url(id="indeterminate_check_box") }}; -} - -QRadioButton::indicator:unchecked { - image: {{ foreground|color(state="icon")|url(id="radio_button_unchecked") }}; -} -QRadioButton::indicator:unchecked:disabled { - image: {{ foreground|color(state="disabled")|url(id="radio_button_unchecked") }}; -} -QRadioButton::indicator:checked { - image: {{ primary|color|url(id="radio_button_checked") }}; -} -QRadioButton::indicator:checked:disabled { - image: {{ foreground|color(state="disabled")|url(id="radio_button_checked") }}; -} - -/* PyQtGraph -=========================================================================== */ - -/* PlotWidget ------------------------------------------------------------- - ---------------------------------------------------------------------------- */ -PlotWidget { - /* Fix cut labels in plots - https://github.com/ColinDuquesnoy/QDarkStyleSheet/issues/134 */ - padding: 0; -} - -/* ParameterTree ---------------------------------------------------------- - ---------------------------------------------------------------------------- */ - -ParameterTree > .QWidget > .QWidget > .QWidget > QComboBox{ - min-height: 1.2em; -} -ParameterTree::item, -ParameterTree > .QWidget { - background: {{ background|color(state="list") }}; -} +/* QWidget ---------------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QWidget { + background: {{ background|color }}; + color: {{ foreground|color }}; + selection-color: {{ foreground|color }}; + selection-background-color: {{ primary|color(state="selection.background") }}; +} +QWidget:disabled { + color: {{ foreground|color(state="disabled") }}; + selection-background-color: {{ foreground|color(state="disabledSelectionBackground") }}; + selection-color: {{ foreground|color(state="disabled") }}; +} +QWidget:focus { + outline: none; +} +/* Top level widget ------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QCheckBox:!window, +QRadioButton:!window, +QPushButton:!window, +QLabel:!window, +QLCDNumber:!window { + background: transparent; +} + +QMdiSubWindow > QCheckBox:!window, +QMdiSubWindow > QRadioButton:!window, +QMdiSubWindow > QPushButton:!window, +QMdiSubWindow > QLabel:!window, +QMdiSubWindow > QLCDNumber:!window { + background: {{ background|color }}; +} + +/* QMainWindow ------------------------------------------------------------ + +This adjusts the splitter in the dock widget, not QSplitter +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmainwindow + +--------------------------------------------------------------------------- */ +QMainWindow::separator { + width: 4px; + height: 4px; + background: {{ border|color }}; +} +QMainWindow::separator:hover, +QMainWindow::separator:pressed { + background: {{ primary|color }}; +} + +/* QToolTip --------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtooltip + +--------------------------------------------------------------------------- */ +QToolTip { + background: {{ background|color(state="popup") }}; + color: {{ foreground|color }}; +} + +/* QSizeGrip -------------------------------------------------------------- + +There is no size grip in modern apps. So we hide size grip. +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsizegrip + +--------------------------------------------------------------------------- */ +QSizeGrip { + width: 0; + height: 0; + image: none; +} + +/* QStatusBar ------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qstatusbar + +--------------------------------------------------------------------------- */ +QStatusBar { + background: {{ statusBar.background|color }}; +} +QStatusBar::item { + /* Remove white border line on Windows */ + border: none; +} + +QStatusBar QWidget { + background: transparent; + padding: 3px; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +/* +`QStatusBar QWidget` selector also override QSizeGrip rule. +The QSizeGrip should be hidden on this qss, but the padding is set by `QStatusBar QWidget`. +Remove padding of QSizeGrip after `QStatusBar QWidget` sets `padding: 3px`. +*/ +QStatusBar > .QSizeGrip { + padding: 0; +} +QStatusBar QWidget:hover { + background: {{ statusBarItem.hoverBackground|color }}; +} +QStatusBar QWidget:pressed, +QStatusBar QWidget:checked { + background: {{ statusBarItem.activeBackground|color }}; +} + +/* QCheckBox QRadioButton QGroupBox --------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcheckbox +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qradiobutton +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qgroupbox + +--------------------------------------------------------------------------- */ +QCheckBox, +QRadioButton { + border-top: 2px solid transparent; + border-bottom: 2px solid transparent; +} +QCheckBox:hover, +QRadioButton:hover { + border-bottom: 2px solid {{ primary|color }}; +} + +QGroupBox { + font-weight: bold; + margin-top: 8px; + padding: 2px 1px 1px 1px; + border-radius: {{ corner-shape|corner(size=4) }}px; + border: 1px solid {{ border|color }}; +} +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 7px; + margin: 0 2px 0 3px; +} +QGroupBox:flat { + border-color: transparent; +} + +/* QMenuBar --------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenubar + +--------------------------------------------------------------------------- */ +QMenuBar { + padding: 2px; + border-bottom: 1px solid {{ border|color }}; + background: {{ background|color }}; +} +QMenuBar::item { + background: transparent; + padding: 4px; +} +QMenuBar::item:selected { + padding: 4px; + border-radius: {{ corner-shape|corner(size=4) }}px; + background: {{ menubar.selectionBackground|color }}; +} +QMenuBar::item:pressed { + padding: 4px; + margin-bottom: 0; + padding-bottom: 0; +} + +/* QToolBar --------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbar + +--------------------------------------------------------------------------- */ +QToolBar { + padding: 1px; + font-weight: bold; + spacing: 2px; + margin: 1px; + background: {{ toolbar.background|color }}; + /* QToolBar must override `border-style` to set the style. */ + border-style: none; +} +QToolBar::handle:horizontal { + width: 20px; + image: {{ foreground|color(state="icon")|url(id="drag_indicator") }}; +} +QToolBar::handle:vertical { + height: 20px; + image: {{ foreground|color(state="icon")|url(id="drag_indicator", rotate=90) }}; +} +QToolBar::handle:horizontal:disabled { + image: {{ foreground|color(state="disabled")|url(id="drag_indicator") }}; +} +QToolBar::handle:vertical:disabled { + image: {{ foreground|color(state="disabled")|url(id="drag_indicator", rotate=90) }}; +} +QToolBar::separator { + background: {{ border|color }}; +} +QToolBar::separator:horizontal { + width: 2px; + margin: 0 6px; +} +QToolBar::separator:vertical { + height: 2px; + margin: 6px 0; +} + +QToolBar > QToolButton { + background: transparent; + padding: 3px; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QToolBar > QToolButton:hover, +QToolBar > QToolButton::menu-button:hover { + background: {{ toolbar.hoverBackground|color }}; +} +QToolBar > QToolButton::menu-button { + border-top-right-radius: {{ corner-shape|corner(size=4) }}px; + border-bottom-right-radius: {{ corner-shape|corner(size=4) }}px; +} +QToolBar > QToolButton:pressed, +QToolBar > QToolButton::menu-button:pressed:enabled, +QToolBar > QToolButton:checked:enabled { + background: {{ toolbar.activeBackground|color }}; +} + +QToolBar > QWidget { + background: transparent; +} + +/* QMenu ------------------------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qmenu + +--------------------------------------------------------------------------- */ +QMenu { + background: {{ background|color(state="popup") }}; + padding: 8px 0; + {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} + {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version=">=6.4.1", os="Darwin") }} +} +QMenu::separator { + margin: 4px 0; + height: 1px; + background: {{ border|color }}; +} +QMenu::item { + padding: 4px 19px; +} +QMenu::item:selected { + background: {{ popupItem.selectionBackground|color }}; +} +QMenu::icon { + padding-left: 10px; + width: 14px; + height: 14px; +} +QMenu::right-arrow { + margin: 2px; + padding-left: 12px; + height: 20px; + width: 20px; + image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; +} +QMenu::right-arrow:disabled { + image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; +} + +/* QScrollBar ------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qscrollbar + +--------------------------------------------------------------------------- */ +QScrollBar { + background: {{ scrollbar.background|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; + {{ |env(value="background: transparent", os="Darwin")}} +} +QScrollBar:horizontal { + height: 14px; + {{ |env(value="height: 7px;", os="Darwin") }} +} +QScrollBar:vertical { + width: 14px; + {{ |env(value="width: 7px;", os="Darwin") }} +} +QScrollBar::handle { + background: {{ scrollbarSlider.background|color }}; + border-radius: {{ corner-shape|corner(size=3) }}px; +} +QScrollBar::handle:hover { + background: {{ scrollbarSlider.hoverBackground|color }}; +} +QScrollBar::handle:pressed { + background: {{ scrollbarSlider.activeBackground|color }}; +} +QScrollBar::handle:disabled { + background: {{ scrollbarSlider.disabledBackground|color }}; +} +QScrollBar::handle:horizontal { + min-width: 8px; + margin: 4px 14px; + {{ |env(value="margin: 0;", os="Darwin") }} +} +QScrollBar::handle:horizontal:hover { + margin: 2px 14px; + {{ |env(value="margin: 0;", os="Darwin") }} +} +QScrollBar::handle:vertical { + min-height: 8px; + margin: 14px 4px; + {{ |env(value="margin: 0;", os="Darwin") }} +} +QScrollBar::handle:vertical:hover { + margin: 14px 2px; + {{ |env(value="margin: 0;", os="Darwin") }} +} +/* +Hide QScrollBar background. +The `sub-page` and `add-page` are not colored by default on most OS, but are colored on Windows. +*/ +QScrollBar::sub-page, +QScrollBar::add-page { + background: transparent; +} +QScrollBar::sub-line, +QScrollBar::add-line { + background: transparent; + {{ |env(value="width: 0; height: 0", os="Darwin") }} +} +QScrollBar::up-arrow:enabled { + image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up") }}; +} +QScrollBar::right-arrow:enabled { + image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=90) }}; +} +QScrollBar::down-arrow:enabled { + image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=180) }}; +} +QScrollBar::left-arrow:enabled { + image: {{ scrollbarSlider.background|color|url(id="arrow_drop_up", rotate=270) }}; +} +QScrollBar::up-arrow:hover { + image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up") }}; +} +QScrollBar::right-arrow:hover { + image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=90) }}; +} +QScrollBar::down-arrow:hover { + image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=180) }}; +} +QScrollBar::left-arrow:hover { + image: {{ scrollbarSlider.activeBackground|color|url(id="arrow_drop_up", rotate=270) }}; +} + +/* QProgressBar ----------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qprogressbar + +--------------------------------------------------------------------------- */ +QProgressBar { + text-align: center; + border: 1px solid {{ border|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QProgressBar::chunk { + background: {{ primary|color(state="progressBar.background") }}; + border-radius: {{ corner-shape|corner(size=3) }}px; +} +QProgressBar::chunk:disabled { + background: {{ foreground|color(state="progressBar.disabledBackground") }}; +} + +/* QPushButton ------------------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qpushbutton + +--------------------------------------------------------------------------- */ +QPushButton { + color: {{ primary|color }}; + border: 1px solid {{ border|color }}; + padding: 4px 8px; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QPushButton:flat, +QPushButton:default { + border: none; + padding: 5px 9px; +} +QPushButton:default { + color: {{ background|color }}; + background: {{ primary|color }}; +} +QPushButton:hover { + background: {{ primary|color(state="button.hoverBackground") }}; +} +QPushButton:pressed { + background: {{ primary|color(state="button.activeBackground") }}; +} +QPushButton:checked:enabled { + background: {{ primary|color(state="button.activeBackground") }}; +} +QPushButton:default:hover { + background: {{ primary|color(state="defaultButton.hoverBackground") }}; +} +QPushButton:default:pressed, +QPushButton:default:checked { + background: {{ primary|color(state="defaultButton.activeBackground") }}; +} +QPushButton:default:disabled, +QPushButton:default:checked:disabled { + background: {{ foreground|color(state="defaultButton.disabledBackground") }}; +} + +/* QDialogButtonBox ------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QDialogButtonBox { + dialogbuttonbox-buttons-have-icons: 0; +} + +QDialogButtonBox QPushButton { + min-width: 65px; +} + +/* QToolButton ------------------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbutton + +--------------------------------------------------------------------------- */ +QToolButton { + background: transparent; + padding: 5px; + spacing: 2px; + border-radius: {{ corner-shape|corner(size=2) }}px; +} +QToolButton:hover, +QToolButton::menu-button:hover { + background: {{ primary|color(state="button.hoverBackground") }}; +} +QToolButton:pressed, +QToolButton:checked:pressed, +QToolButton::menu-button:pressed:enabled { + background: {{ primary|color(state="button.activeBackground") }}; +} +QToolButton:selected:enabled, +QToolButton:checked:enabled { + background: {{ primary|color(state="button.activeBackground") }}; +} +QToolButton::menu-indicator { + height: 18px; + width: 18px; + top: 6px; + left: 3px; + image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; +} +QToolButton::menu-indicator:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; +} +QToolButton::menu-arrow { + /* Remove default arrow icon on Qt5 */ + image: unset; +} +QToolButton::menu-button { + subcontrol-origin: margin; + width: 17px; + border-top-right-radius: {{ corner-shape|corner(size=2) }}px; + border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; + image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; +} +QToolButton::menu-button:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; +} + +/* MenuButtonPopup */ +QToolButton[ +{{ |env(value="popupMode=MenuButtonPopup", version="<6.0.0", qt="PySide2") }} +{{ |env(value="popupMode=\\"1\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="popupMode=MenuButtonPopup", version=">=6.0.0") }} +] { + padding-right: 1px; + margin-right: 18px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +/* QComboBox -------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox + +--------------------------------------------------------------------------- */ +QComboBox { + min-height: 1.5em; + padding: 0 8px 0 4px; + background: {{ input.background|color }}; + border: 1px solid {{ border|color(state="input") }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QComboBox:focus, +QComboBox:open { + border-color: {{ primary|color }}; +} +QComboBox::drop-down { + margin: 2px 2px 2px -6px; + border-radius: {{ corner-shape|corner(size=4) }}; /* This remove default style. */ +} +QComboBox::drop-down:editable:hover { + background: {{ inputButton.hoverBackground|color }}; +} +QComboBox::down-arrow { + image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; +} +QComboBox::down-arrow:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; +} +QComboBox::down-arrow:editable:open { + image: {{ foreground|color(state="icon")|url(id="expand_less") }}; +} +QComboBox::down-arrow:editable:open:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less") }}; +} +/* Setting background color of selected item when editable is false. */ +QComboBox::item:selected { + border: none; /* This remove the border of indicator. */ + background: {{ primary|color(state="list.selectionBackground") }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} + +/* QAbstractItemView in QComboBox is NoFrame. Override default settings and show border. */ +QComboBox QListView[ +{{ |env(value="frameShape=\\"0\\"", version="<6.0.0")}} +{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} +] { + margin: 0; + padding: 4px; + background: {{ background|color(state="popup") }}; + {{ primary|color(state="list.selectionBackground")|env(value="selection-background-color: ${};", version="<6.0.0") }} + border-radius: 0; + {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} + {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version=">=6.4.1", os="Darwin") }} +} +QComboBox QListView::item { + border-radius: {{ corner-shape|corner(size=4) }}px; +} + + +/* QSlider ---------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qslider + +--------------------------------------------------------------------------- */ +QSlider { + padding: 2px 0; +} +QSlider::groove { + border-radius: {{ corner-shape|corner(size=2) }}px; +} +QSlider::groove:horizontal { + height: 4px; +} +QSlider::groove:vertical { + width: 4px; +} +/* Horizontal slider active line is `::sub-page`. */ +/* Vertical slider active line is `::add-page`. */ +QSlider::sub-page:horizontal, +QSlider::add-page:vertical, +QSlider::handle { + background: {{ primary|color }}; +} +QSlider::sub-page:horizontal:disabled, +QSlider::add-page:vertical:disabled, +QSlider::handle:disabled { + background: {{ foreground|color(state="slider.disabledBackground") }}; +} +QSlider::add-page:horizontal, +QSlider::sub-page:vertical { + background: {{ foreground|color(state="sliderTrack.inactiveBackground") }}; +} +QSlider::handle:hover, +QSlider::handle:pressed { + background: {{ primary|color(state="sliderHandle.activeBackground") }}; +} +QSlider::handle:horizontal { + width: 16px; + height: 8px; + margin: -6px 0; + border-radius: 8px; +} +QSlider::handle:vertical { + width: 8px; + height: 16px; + margin: 0 -6px; + border-radius: 8px; +} + +/* QTabWidget QTabBar ----------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtabwidget-and-qtabbar + +--------------------------------------------------------------------------- */ +QTabWidget::pane { + border: 1px solid {{ border|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QTabBar { + /* + Not filling background color when drawBase property is true. So need to set drawBase to false. + Default QTabBar is no need following declaration, but QMdiArea is need. + */ + qproperty-drawBase: 0; +} +QTabBar::close-button { + image: {{ foreground|color(state="icon")|url(id="close") }}; +} +QTabBar::close-button:hover { + background: {{ tabCloseButton.hoverBackground|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QTabBar::close-button:!selected { + image: {{ foreground|color(state="icon.unfocused")|url(id="close") }}; +} +QTabBar::close-button:disabled { + image: {{ foreground|color(state="disabled")|url(id="close") }}; +} +QTabBar::tab { + padding: 3px; + border-style: solid; +} +QTabBar::tab:hover, +QTabBar::tab:selected:hover:enabled { + background: {{ tab.hoverBackground|color }}; +} +QTabBar::tab:selected:enabled { + color: {{ primary|color }}; + background: {{ tab.activeBackground|color }}; + border-color: {{ primary|color }}; +} +QTabBar::tab:selected:disabled, +QTabBar::tab:only-one:selected:enabled { + border-color: {{ border|color }}; +} +QTabBar::tab:top { + border-bottom-width: 2px; + margin: 3px 6px 0 0; + border-top-left-radius: {{ corner-shape|corner(size=2) }}px; + border-top-right-radius: {{ corner-shape|corner(size=2) }}px; +} +QTabBar::tab:bottom { + border-top-width: 2px; + margin: 0 6px 3px 0; + border-bottom-left-radius: {{ corner-shape|corner(size=2) }}px; + border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; +} +QTabBar::tab:left { + border-right-width: 2px; + margin: 0 0 6px 3px; + border-top-left-radius: {{ corner-shape|corner(size=2) }}px; + border-bottom-left-radius: {{ corner-shape|corner(size=2) }}px; +} +QTabBar::tab:right { + border-left-width: 2px; + margin-bottom: 6px; + margin: 0 3px 6px 0; + border-top-right-radius: {{ corner-shape|corner(size=2) }}px; + border-bottom-right-radius: {{ corner-shape|corner(size=2) }}px; +} +QTabBar::tab:top:first, +QTabBar::tab:top:only-one, +QTabBar::tab:bottom:first, +QTabBar::tab:bottom:only-one { + margin-left: 2px; +} +QTabBar::tab:top:last, +QTabBar::tab:top:only-one, +QTabBar::tab:bottom:last, +QTabBar::tab:bottom:only-one { + margin-right: 2px; +} +QTabBar::tab:left:first, +QTabBar::tab:left:only-one, +QTabBar::tab:right:first, +QTabBar::tab:right:only-one { + margin-top: 2px; +} +QTabBar::tab:left:last, +QTabBar::tab:left:only-one, +QTabBar::tab:right:last, +QTabBar::tab:right:only-one { + margin-bottom: 2px; +} + +/* QDockWidget ------------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QDockWidget { + border: 1px solid {{ border|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QDockWidget::title { + /* Better size for title bar */ + padding: 3px; + spacing: 4px; + background: {{ background|color(state="title") }}; +} +/* +`QDockWidget::close-button` and `QDockWidget::float-button` is not QToolButton. +We need to set the button style again. +*/ +QDockWidget::close-button, +QDockWidget::float-button { + border-radius: {{ corner-shape|corner(size=2) }}px; +} +QDockWidget::close-button:hover, +QDockWidget::float-button:hover { + background: {{ primary|color(state="button.hoverBackground") }}; +} +QDockWidget::close-button:pressed, +QDockWidget::float-button:pressed { + background: {{ primary|color(state="button.activeBackground") }}; +} + +/* QFrame ----------------------------------------------------------------- + +https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qframe + +--------------------------------------------------------------------------- */ +QFrame { + border: 1px solid {{ border|color }}; + padding: 1px; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +.QFrame { + padding: 0; +} +/* NoFrame */ +QFrame[ +{{ |env(value="frameShape=NoFrame", version="<6.0.0", qt="PySide2") }} +{{ |env(value="frameShape=\\"0\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} +] { + border-color: transparent; + padding: 0; +} +.QFrame[ +{{ |env(value="frameShape=NoFrame", version="<6.0.0", qt="PySide2") }} +{{ |env(value="frameShape=\\"0\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="frameShape=NoFrame", version=">=6.0.0") }} +] { + border: none; +} +/* Panel */ +QFrame[ +{{ |env(value="frameShape=Panel", version="<6.0.0", qt="PySide2") }} +{{ |env(value="frameShape=\\"2\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="frameShape=Panel", version=">=6.0.0") }} +] { + border-color: {{ background|color(state="panel") }}; + background: {{ background|color(state="panel") }}; +} +/* HLine */ +QFrame[ +{{ |env(value="frameShape=HLine", version="<6.0.0", qt="PySide2") }} +{{ |env(value="frameShape=\\"4\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="frameShape=HLine", version=">=6.0.0") }} +] { + max-height: 2px; + border: none; + background: {{ border|color }}; +} +/* VLine */ +QFrame[ +{{ |env(value="frameShape=VLine", version="<6.0.0", qt="PySide2") }} +{{ |env(value="frameShape=\\"5\\"", version="<6.0.0", qt="PyQt5") }} +{{ |env(value="frameShape=VLine", version=">=6.0.0") }} +] { + max-width: 2px; + border: none; + background: {{ border|color }}; +} +/* QLCDNumber ------------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QLCDNumber { + min-width: 2em; + margin: 2px; +} + +/* QToolBox --------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtoolbox + +--------------------------------------------------------------------------- */ +QToolBox::tab { + background: {{ background|color(state="title") }}; + border-bottom: 2px solid {{ border|color }}; + border-top-left-radius: {{ corner-shape|corner(size=4) }}px; + border-top-right-radius: {{ corner-shape|corner(size=4) }}px; +} +QToolBox::tab:selected:enabled { + border-bottom-color: {{ primary|color }}; +} + +/* QSplitter -------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qsplitter + +--------------------------------------------------------------------------- */ +QSplitter::handle { + background: {{ border|color }}; + margin: 1px 3px; +} +QSplitter::handle:hover { + background: {{ primary|color }}; +} +QSplitter::handle:horizontal { + width: 5px; + image: {{ foreground|color(state="icon")|url(id="horizontal_rule", rotate=90) }}; +} +QSplitter::handle:horizontal:disabled { + image: {{ foreground|color(state="disabled")|url(id="horizontal_rule", rotate=90) }}; +} +QSplitter::handle:vertical { + height: 5px; + image: {{ foreground|color(state="icon")|url(id="horizontal_rule") }}; +} +QSplitter::handle:vertical:disabled { + image: {{ foreground|color(state="disabled")|url(id="horizontal_rule") }}; +} + +/* +QSplitterHandle is the QSplitter handle class. +[QSplitter::handle:hover] is enabled by the following settings. +Warning: This setting is not mentioned in the documentation. +*/ +QSplitterHandle::item:hover {} + +/* QAbstractScrollArea ---------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qabstractscrollarea + +--------------------------------------------------------------------------- */ +QAbstractScrollArea { + margin: 1px; +} +QAbstractScrollArea::corner { + background: transparent; +} + +/* Fix a bug where widgets in scroll area would hide the scroll area border. */ +QAbstractScrollArea > .QWidget { + background: transparent; +} +QAbstractScrollArea > .QWidget > .QWidget { + background: transparent; +} + +/* QMdiArea QMdiSubWindow ---------------------------------------------------- + +--------------------------------------------------------------------------- */ +QMdiArea { + qproperty-background: {{ background|color(state="panel") }}; + border-radius: 0; +} + +QMdiSubWindow { + background: {{ background|color }}; + border: 1px solid; + padding: 0 3px; +} +QMdiSubWindow > QWidget { + border: 1px solid {{ border|color }}; +} + +/* QTextEdit QPlainTextEdit------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-specific-widgets + +--------------------------------------------------------------------------- */ +QTextEdit, QPlainTextEdit { + background: {{ background|color(state="textarea") }}; +} +QTextEdit:focus, +QTextEdit:selected, +QPlainTextEdit:focus, +QPlainTextEdit:selected { + border: 1px solid {{ primary|color }}; + selection-background-color: {{ primary|color(state="textarea.selectionBackground") }}; +} + +/* In version +5.15, [!active] state is disabled. */ +/* In version -5.15, [!focus] state is disabled. */ +QTextEdit:!focus, +QPlainTextEdit:!focus { + {{ textarea.inactiveSelectionBackground|color|env(value="selection-background-color: ${}", version=">=5.15.0") }} +} +QTextEdit:!active, +QPlainTextEdit:!active { + {{ textarea.inactiveSelectionBackground|color|env(value="selection-background-color: ${}", version="<5.15.0") }} +} + +/* QAbstractItemView ------------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qcombobox + +--------------------------------------------------------------------------- */ +/* Control QAbstractItemView selection and alternate colors with Pseudo-States instead of properties. */ +QAbstractItemView { + padding: 0; + alternate-background-color: transparent; + selection-background-color: transparent; +} +QAbstractItemView:disabled { + selection-background-color: transparent; +} +QAbstractItemView::item:alternate, +QAbstractItemView::branch:alternate { + background: {{ list.alternateBackground|color }}; +} +QAbstractItemView::item:selected, +QAbstractItemView::branch:selected { + background: {{ primary|color(state="list.selectionBackground") }}; +} +QAbstractItemView::item:selected:!active, +QAbstractItemView::branch:selected:!active { + background: {{ primary|color(state="list.inactiveSelectionBackground") }}; +} + +QAbstractItemView QLineEdit, +QAbstractItemView QAbstractSpinBox, +QAbstractItemView QAbstractButton { + padding: 0; + margin: 1px; +} + +/* QListView QTreeView --------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlistview +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtreeview + +--------------------------------------------------------------------------- */ +QListView { + padding: 1px +} +QListView, +QTreeView { + background: {{ background|color(state="list") }}; +} +QListView::item:!selected:hover, +QTreeView::item:!selected:hover, +QTreeView::branch:!selected:hover { + background: {{ list.hoverBackground|color }}; +} +QTreeView::branch:!selected:hover, +QTreeView::branch:alternate, +QTreeView::branch:selected, +QTreeView::branch:selected:!active { + {{ |env(value="background: transparent;", version=">=6.4.1") }} +} +QTreeView::branch { + border-image: {{ tree.inactiveIndentGuidesStroke|color|url(id="vertical_line") }} 0; +} +QTreeView::branch:active { + border-image: {{ tree.indentGuidesStroke|color(state="icon")|url(id="vertical_line") }} 0; +} +QTreeView::branch:has-siblings:adjoins-item, +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: unset; +} +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: unset; + image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; +} +QTreeView::branch:has-children:!has-siblings:closed:disabled, +QTreeView::branch:closed:has-children:has-siblings:disabled { + image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; +} +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: unset; + image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; +} +QTreeView::branch:open:has-children:!has-siblings:disabled, +QTreeView::branch:open:has-children:has-siblings:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; +} +QTreeView > QHeaderView { + background: {{ background|color(state="list") }}; +} +QTreeView > QHeaderView::section { + background: {{ treeSectionHeader.background|color }}; +} + +/* +Following arrow settings are for QColumnView. +QColumnView::left-arrow and QColumnView::right-arrow are not working. +*/ +QListView::left-arrow { + margin: -2px; + image: {{ foreground|color(state="icon.unfocused")|url(id="chevron_right", rotate=180) }}; +} +QListView::right-arrow { + margin: -2px; + image: {{ foreground|color(state="icon.unfocused")|url(id="chevron_right") }}; +} +QListView::left-arrow:selected:enabled { + image: {{ foreground|color(state="icon")|url(id="chevron_right", rotate=180) }}; +} +QListView::right-arrow:selected:enabled { + image: {{ foreground|color(state="icon")|url(id="chevron_right") }}; +} +QListView::left-arrow:disabled { + image: {{ foreground|color(state="disabled")|url(id="chevron_right", rotate=180) }}; +} +QListView::right-arrow:disabled { + image: {{ foreground|color(state="disabled")|url(id="chevron_right") }}; +} + +/* QColumnView ------------------------------------------------------------ + +--------------------------------------------------------------------------- */ +QColumnView { + background: {{ background|color(state="list") }} +} +QColumnViewGrip { + margin: -4px; + background: {{ background|color(state="list") }}; + image: {{ foreground|color(state="icon")|url(id="drag_handle", rotate=90) }} +} +QColumnViewGrip:disabled { + image: {{ foreground|color(state="disabled")|url(id="drag_handle", rotate=90) }} +} + +/* QTableView ------------------------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qtableview + +--------------------------------------------------------------------------- */ +QTableView { + gridline-color: {{ tableSectionHeader.background|color }}; + background: {{ background|color(state="table") }}; + {{ primary|color(state="table.selectionBackground")|env(value="selection-background-color: ${};", version=">=6.4.1") }} + {{ table.alternateBackground|color|env(value="alternate-background-color: ${};", version=">=6.4.1") }} +} +QTableView:!active { + {{ primary|color(state="table.inactiveSelectionBackground")|env(value="selection-background-color: ${};", version="<6.4.1") }} +} +QTableView::item:alternate { + {{ table.alternateBackground|color|env(value="background: ${};", version="<6.4.1") }} +} +QTableView::item:selected { + {{ primary|color(state="table.selectionBackground")|env(value="background: ${};", version="<6.4.1") }} +} + +QTableView QTableCornerButton::section { + margin: 0 1px 1px 0; + background: {{ tableSectionHeader.background|color }}; + border-top-left-radius: {{ corner-shape|corner(size=2) }}px; +} +QTableView QTableCornerButton::section:pressed { + background: {{ primary|color(state="table.selectionBackground") }}; +} + +QTableView > QHeaderView { + background: {{ background|color(state="table") }}; + border-radius: {{ corner-shape|corner(size=3) }}; +} +QTableView > QHeaderView::section { + background: {{ tableSectionHeader.background|color }} +} + +/* QHeaderView ------------------------------------------------------------ + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qheaderview + +--------------------------------------------------------------------------- */ +QHeaderView { + margin: 0; /* Override QAbstractScrollArea rule. */ + border: none; /* Override QFrame rule. */ +} +QHeaderView::section { + border: none; /* This hide default grid. */ + background: {{ treeSectionHeader.background|color }}; + /* Apply left padding to no padding header section. */ + padding-left: 4px; +} +QHeaderView::section:horizontal { + margin-right: 1px; /* This make grid of section. */ +} +QHeaderView::section:vertical { + margin-bottom: 1px; /* This make section grid. */ +} +QHeaderView::section:on:enabled, +QHeaderView::section:on:pressed { + color: {{ primary|color }}; +} +QHeaderView::section:last, +QHeaderView::section:only-one { + margin: 0; +} +QHeaderView::down-arrow:horizontal { + margin-left: -19px; + subcontrol-position: center right; + image: {{ foreground|color(state="icon")|url(id="expand_less", rotate=180) }}; +} +QHeaderView::down-arrow:horizontal:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less", rotate=180) }}; +} +QHeaderView::up-arrow:horizontal { + margin-left: -19px; + subcontrol-position: center right; + image: {{ foreground|color(state="icon")|url(id="expand_less") }}; +} +QHeaderView::up-arrow:horizontal:disabled { + image: {{ foreground|color(state="disabled")|url(id="expand_less") }}; +} +/* +Longer text in the vertical QHeaderView section makes the section space too large. +This is because a transparent arrow appears on the vertical QHeaderView. +Remove the vertical QHeaderView arrow, and remove the section space. +*/ +QHeaderView::down-arrow:vertical, +QHeaderView::up-arrow:vertical { + width: 0; + height: 0; +} + +/* QCalendarWidget -------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QCalendarWidget > .QWidget { + background: {{ background|color(state="table") }}; + border-bottom: 1px solid {{ border|color }}; + border-top-left-radius: {{ corner-shape|corner(size=4) }}px; + border-top-right-radius: {{ corner-shape|corner(size=4) }}px; +} +QCalendarWidget > .QWidget > QWidget { + padding: 1px; +} +QCalendarWidget .QWidget > QToolButton { + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QCalendarWidget > QTableView { + margin: 0; + border: none; + border-radius: {{ corner-shape|corner(size=4) }}px; + border-top-left-radius: 0; + border-top-right-radius: 0; + alternate-background-color: {{ table.alternateBackground|color }}; + {{ corner-shape|corner(size=4)|env(value="border-radius: ${}px;", version="<6.0.0", os="Darwin") }} + /* Fix 207 */ + {{ primary|color(state="table.selectionBackground")|env(value="selection-background-color: ${};", version="<6.0.0") }} +} + +/* QAbstractSpinBox QLineEdit---------------------------------------------- + +examples: https://doc.qt.io/qt-5/stylesheet-examples.html#customizing-qlineedit + +--------------------------------------------------------------------------- */ +QLineEdit, +QAbstractSpinBox { + padding: 3px 4px; + /* Adjust the min-height of QLineEdit to the height of the characters. */ + min-height: 1em; + border: 1px solid {{ border|color(state="input") }}; + background: {{ input.background|color }}; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QLineEdit:focus, +QAbstractSpinBox:focus { + border-color: {{ primary|color }}; +} +QAbstractSpinBox::up-button, +QAbstractSpinBox::down-button { + subcontrol-position: center right; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QAbstractSpinBox::up-button:hover:on, +QAbstractSpinBox::down-button:hover:on { + background: {{ inputButton.hoverBackground|color }}; +} +QAbstractSpinBox::up-button { + bottom: 5px; + right: 4px; +} +QAbstractSpinBox::up-arrow:on { + image: {{ foreground|color(state="icon")|url(id="arrow_drop_up") }}; +} +QAbstractSpinBox::up-arrow:disabled, +QAbstractSpinBox::up-arrow:off { + image: {{ foreground|color(state="disabled")|url(id="arrow_drop_up") }}; +} +QAbstractSpinBox::down-button { + top: 5px; + right: 4px; +} +QAbstractSpinBox::down-arrow:on { + image: {{ foreground|color(state="icon")|url(id="arrow_drop_up", rotate=180) }}; +} +QAbstractSpinBox::down-arrow:disabled, +QAbstractSpinBox::down-arrow:off { + image: {{ foreground|color(state="disabled")|url(id="arrow_drop_up", rotate=180) }}; +} + +/* QDateTimeEdit ---------------------------------------------------------- + +--------------------------------------------------------------------------- */ +QDateTimeEdit::drop-down { + padding-right: 4px; + width: 16px; + image: {{ foreground|color(state="icon")|url(id="calendar_today") }}; +} +QDateTimeEdit::drop-down:disabled { + image: {{ foreground|color(state="disabled")|url(id="calendar_today") }}; +} +QDateTimeEdit::down-arrow[calendarPopup=true] { + image: none; +} + +/* QFileDialog QFontDialog ------------------------------------------------ + +--------------------------------------------------------------------------- */ +QFileDialog QFrame { + border: none; +} + +QFontDialog QListView { + min-height: 60px; +} + +/* Check ------------------------------------------------------------------ + +--------------------------------------------------------------------------- */ +QComboBox::indicator, +QMenu::indicator { + width: 18px; + height: 18px; +} +QMenu::indicator { + background: {{ popupItem.checkbox.background|color }}; + margin-left: 3px; + border-radius: {{ corner-shape|corner(size=4) }}px; +} +QComboBox::indicator:checked, +QMenu::indicator:checked { + image: {{ foreground|color(state="icon")|url(id="check") }}; +} + +/* Check indicator -------------------------------------------------------- + +document: https://doc.qt.io/qt-5/stylesheet-reference.html#list-of-sub-controls + +--------------------------------------------------------------------------- */ +QCheckBox, +QRadioButton { + spacing: 8px; +} +QGroupBox::title, +QAbstractItemView::item { + spacing: 6px; +} +QCheckBox::indicator, +QGroupBox::indicator, +QAbstractItemView::indicator, +QRadioButton::indicator { + height: 18px; + width: 18px; +} +QCheckBox::indicator, +QGroupBox::indicator, +QAbstractItemView::indicator { + image: {{ foreground|color(state="icon")|url(id="check_box_outline_blank") }}; +} +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled, +QAbstractItemView::indicator:unchecked:disabled { + image: {{ foreground|color(state="disabled")|url(id="check_box_outline_blank") }}; +} +QCheckBox::indicator:checked, +QGroupBox::indicator:checked, +QAbstractItemView::indicator:checked { + image: {{ primary|color|url(id="check_box") }}; +} +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled, +QAbstractItemView::indicator:checked:disabled { + image: {{ foreground|color(state="disabled")|url(id="check_box") }}; +} +QCheckBox::indicator:indeterminate, +QAbstractItemView::indicator:indeterminate { + image: {{ primary|color|url(id="indeterminate_check_box") }}; +} +QCheckBox::indicator:indeterminate:disabled, +QAbstractItemView::indicator:indeterminate:disabled { + image: {{ foreground|color(state="disabled")|url(id="indeterminate_check_box") }}; +} + +QRadioButton::indicator:unchecked { + image: {{ foreground|color(state="icon")|url(id="radio_button_unchecked") }}; +} +QRadioButton::indicator:unchecked:disabled { + image: {{ foreground|color(state="disabled")|url(id="radio_button_unchecked") }}; +} +QRadioButton::indicator:checked { + image: {{ primary|color|url(id="radio_button_checked") }}; +} +QRadioButton::indicator:checked:disabled { + image: {{ foreground|color(state="disabled")|url(id="radio_button_checked") }}; +} + +/* PyQtGraph +=========================================================================== */ + +/* PlotWidget ------------------------------------------------------------- + +--------------------------------------------------------------------------- */ +PlotWidget { + /* Fix cut labels in plots + https://github.com/ColinDuquesnoy/QDarkStyleSheet/issues/134 */ + padding: 0; +} + +/* ParameterTree ---------------------------------------------------------- + +--------------------------------------------------------------------------- */ + +ParameterTree > .QWidget > .QWidget > .QWidget > QComboBox{ + min-height: 1.2em; +} +ParameterTree::item, +ParameterTree > .QWidget { + background: {{ background|color(state="list") }}; +} diff --git a/style/colors/os_accent.json b/style/colors/os_accent.json index 76e1303c..98b5ef60 100644 --- a/style/colors/os_accent.json +++ b/style/colors/os_accent.json @@ -1,22 +1,22 @@ -{ - "dark": { - "graphite": "#898a8f", - "red": "#f6685e", - "orange": "#ff9800", - "yellow": "#ffeb3b", - "green": "#4caf50", - "blue": "#8ab4f7", - "purple": "#af52bf", - "pink": "#c7457f" - }, - "light": { - "graphite": "#898a8f", - "red": "#f44336", - "orange": "#ff9800", - "yellow": "#f4c65f", - "green": "#4caf50", - "blue": "#1a73e8", - "purple": "#9c27b0", - "pink": "#c7457f" - } -} +{ + "dark": { + "graphite": "#898a8f", + "red": "#f6685e", + "orange": "#ff9800", + "yellow": "#ffeb3b", + "green": "#4caf50", + "blue": "#8ab4f7", + "purple": "#af52bf", + "pink": "#c7457f" + }, + "light": { + "graphite": "#898a8f", + "red": "#f44336", + "orange": "#ff9800", + "yellow": "#f4c65f", + "green": "#4caf50", + "blue": "#1a73e8", + "purple": "#9c27b0", + "pink": "#c7457f" + } +} diff --git a/style/colors/themes/dark.json b/style/colors/themes/dark.json index ade2eb62..99ae2a92 100644 --- a/style/colors/themes/dark.json +++ b/style/colors/themes/dark.json @@ -1,134 +1,134 @@ -{ - "input.background": "#3f4042", - "inputButton.hoverBackground": "#ffffff25", - "list.alternateBackground": "#ffffff0c", - "list.hoverBackground": "#ffffff13", - "linkVisited": "#c58af8", - "menubar.selectionBackground": "#ffffff25", - "popupItem.checkbox.background": "#ffffff19", - "popupItem.selectionBackground": "#ffffff22", - "scrollbar.background": "#ffffff10", - "scrollbarSlider.activeBackground": "#ffffff60", - "scrollbarSlider.background": "#ffffff30", - "scrollbarSlider.disabledBackground": "#ffffff15", - "scrollbarSlider.hoverBackground": "#ffffff45", - "statusBar.background": "#2a2b2e", - "statusBarItem.activeBackground": "#ffffff34", - "statusBarItem.hoverBackground": "#ffffff22", - "tab.activeBackground": "#ffffff00", - "tab.hoverBackground": "#ffffff18", - "tabCloseButton.hoverBackground": "#ffffff25", - "table.alternateBackground": "#ffffff15", - "tableSectionHeader.background": "#3f4042", - "textarea.inactiveSelectionBackground": "#ffffff20", - "toolbar.activeBackground": "#ffffff34", - "toolbar.background": "#333333", - "toolbar.hoverBackground": "#ffffff22", - "tree.inactiveIndentGuidesStroke": "#ffffff35", - "tree.indentGuidesStroke": "#ffffff60", - "treeSectionHeader.background": "#3f4042", - "background": { - "base": "#202124", - "list": {}, - "panel": { - "darken": 0.3 - }, - "popup": { - "lighten": 0.3 - }, - "table": { - "darken": 0.5 - }, - "textarea": { - "darken": 0.13 - }, - "title": { - "darken": 0.3 - } - }, - "border": { - "base": "#3f4042", - "input": { - "transparent": 0 - } - }, - "foreground": { - "base": "#e4e7eb", - "defaultButton.disabledBackground": { - "transparent": 0.2 - }, - "disabledSelectionBackground": { - "transparent": 0.2 - }, - "disabled": { - "transparent": 0.4 - }, - "icon": { - "darken": 0.01 - }, - "icon.unfocused": { - "transparent": 0.6 - }, - "input.placeholder": { - "transparent": 0.6 - }, - "progressBar.disabledBackground": { - "transparent": 0.2 - }, - "slider.disabledBackground": { - "transparent": 0.2 - }, - "sliderTrack.inactiveBackground": { - "transparent": 0.1 - } - }, - "primary": { - "base": "#8ab4f7", - "button.activeBackground": { - "transparent": 0.23, - "darken": 0.14 - }, - "button.hoverBackground": { - "transparent": 0.11, - "darken": 0.1 - }, - "defaultButton.activeBackground": { - "darken": 0.12 - }, - "defaultButton.hoverBackground": { - "darken": 0.06 - }, - "list.inactiveSelectionBackground": { - "transparent": 0.15, - "lighten": 0.2 - }, - "list.selectionBackground": { - "transparent": 0.4, - "darken": 0.2 - }, - "progressBar.background": { - "darken": 0.1 - }, - "selection.background": { - "transparent": 0.4, - "darken": 0.2, - "lighten": 0.1 - }, - "sliderHandle.activeBackground": { - "darken": 0.09 - }, - "table.selectionBackground": { - "transparent": 0.55, - "darken": 0.2 - }, - "table.inactiveSelectionBackground": { - "transparent": 0.18, - "lighten": 0.2 - }, - "textarea.selectionBackground": { - "transparent": 0.4, - "darken": 0.2, - "lighten": 0.1 - } - } -} +{ + "input.background": "#3f4042", + "inputButton.hoverBackground": "#ffffff25", + "list.alternateBackground": "#ffffff0c", + "list.hoverBackground": "#ffffff13", + "linkVisited": "#c58af8", + "menubar.selectionBackground": "#ffffff25", + "popupItem.checkbox.background": "#ffffff19", + "popupItem.selectionBackground": "#ffffff22", + "scrollbar.background": "#ffffff10", + "scrollbarSlider.activeBackground": "#ffffff60", + "scrollbarSlider.background": "#ffffff30", + "scrollbarSlider.disabledBackground": "#ffffff15", + "scrollbarSlider.hoverBackground": "#ffffff45", + "statusBar.background": "#2a2b2e", + "statusBarItem.activeBackground": "#ffffff34", + "statusBarItem.hoverBackground": "#ffffff22", + "tab.activeBackground": "#ffffff00", + "tab.hoverBackground": "#ffffff18", + "tabCloseButton.hoverBackground": "#ffffff25", + "table.alternateBackground": "#ffffff15", + "tableSectionHeader.background": "#3f4042", + "textarea.inactiveSelectionBackground": "#ffffff20", + "toolbar.activeBackground": "#ffffff34", + "toolbar.background": "#333333", + "toolbar.hoverBackground": "#ffffff22", + "tree.inactiveIndentGuidesStroke": "#ffffff35", + "tree.indentGuidesStroke": "#ffffff60", + "treeSectionHeader.background": "#3f4042", + "background": { + "base": "#202124", + "list": {}, + "panel": { + "darken": 0.3 + }, + "popup": { + "lighten": 0.3 + }, + "table": { + "darken": 0.5 + }, + "textarea": { + "darken": 0.13 + }, + "title": { + "darken": 0.3 + } + }, + "border": { + "base": "#3f4042", + "input": { + "transparent": 0 + } + }, + "foreground": { + "base": "#e4e7eb", + "defaultButton.disabledBackground": { + "transparent": 0.2 + }, + "disabledSelectionBackground": { + "transparent": 0.2 + }, + "disabled": { + "transparent": 0.4 + }, + "icon": { + "darken": 0.01 + }, + "icon.unfocused": { + "transparent": 0.6 + }, + "input.placeholder": { + "transparent": 0.6 + }, + "progressBar.disabledBackground": { + "transparent": 0.2 + }, + "slider.disabledBackground": { + "transparent": 0.2 + }, + "sliderTrack.inactiveBackground": { + "transparent": 0.1 + } + }, + "primary": { + "base": "#8ab4f7", + "button.activeBackground": { + "transparent": 0.23, + "darken": 0.14 + }, + "button.hoverBackground": { + "transparent": 0.11, + "darken": 0.1 + }, + "defaultButton.activeBackground": { + "darken": 0.12 + }, + "defaultButton.hoverBackground": { + "darken": 0.06 + }, + "list.inactiveSelectionBackground": { + "transparent": 0.15, + "lighten": 0.2 + }, + "list.selectionBackground": { + "transparent": 0.4, + "darken": 0.2 + }, + "progressBar.background": { + "darken": 0.1 + }, + "selection.background": { + "transparent": 0.4, + "darken": 0.2, + "lighten": 0.1 + }, + "sliderHandle.activeBackground": { + "darken": 0.09 + }, + "table.selectionBackground": { + "transparent": 0.55, + "darken": 0.2 + }, + "table.inactiveSelectionBackground": { + "transparent": 0.18, + "lighten": 0.2 + }, + "textarea.selectionBackground": { + "transparent": 0.4, + "darken": 0.2, + "lighten": 0.1 + } + } +} diff --git a/style/colors/themes/light.json b/style/colors/themes/light.json index 21a398f0..2d9caf96 100644 --- a/style/colors/themes/light.json +++ b/style/colors/themes/light.json @@ -1,130 +1,130 @@ -{ - "input.background": "#f8f9fa", - "inputButton.hoverBackground": "#00000018", - "list.alternateBackground": "#00000009", - "list.hoverBackground": "#00000013", - "linkVisited": "#660098", - "menubar.selectionBackground": "#00000020", - "popupItem.checkbox.background": "#00000019", - "popupItem.selectionBackground": "#00000022", - "scrollbar.background": "#00000010", - "scrollbarSlider.activeBackground": "#00000060", - "scrollbarSlider.background": "#00000040", - "scrollbarSlider.disabledBackground": "#00000015", - "scrollbarSlider.hoverBackground": "#00000050", - "statusBar.background": "#dfe1e5", - "statusBarItem.activeBackground": "#00000024", - "statusBarItem.hoverBackground": "#00000015", - "tab.activeBackground": "#00000000", - "tab.hoverBackground": "#00000015", - "tabCloseButton.hoverBackground": "#00000020", - "table.alternateBackground": "#00000012", - "tableSectionHeader.background": "#dadce0", - "textarea.inactiveSelectionBackground": "#00000015", - "toolbar.activeBackground": "#00000024", - "toolbar.background": "#ebebeb", - "toolbar.hoverBackground": "#00000015", - "tree.inactiveIndentGuidesStroke": "#00000030", - "tree.indentGuidesStroke": "#00000050", - "treeSectionHeader.background": "#dadce0", - "background": { - "base": "#f8f9fa", - "list": {}, - "panel": { - "lighten": 0.5 - }, - "popup": { - "lighten": 0.2 - }, - "table": { - "lighten": 0.5 - }, - "textarea": { - "lighten": 0.25 - }, - "title": { - "darken": 0.04 - } - }, - "border": { - "base": "#dadce0", - "input": {} - }, - "foreground": { - "base": "#4d5157", - "defaultButton.disabledBackground": { - "transparent": 0.25 - }, - "disabled": { - "transparent": 0.4 - }, - "disabledSelectionBackground": { - "transparent": 0.25 - }, - "icon": { - "darken": 0.05 - }, - "icon.unfocused": { - "transparent": 0.6 - }, - "input.placeholder": { - "transparent": 0.6 - }, - "progressBar.disabledBackground": { - "transparent": 0.25 - }, - "slider.disabledBackground": { - "transparent": 0.25 - }, - "sliderTrack.inactiveBackground": { - "transparent": 0.2 - } - }, - "primary": { - "base": "#1a73e8", - "button.activeBackground": { - "transparent": 0.24, - "darken": 0.03 - }, - "button.hoverBackground": { - "transparent": 0.1, - "darken": 0.03 - }, - "defaultButton.activeBackground": { - "lighten": 0.3 - }, - "defaultButton.hoverBackground": { - "lighten": 0.1 - }, - "list.inactiveSelectionBackground": { - "transparent": 0.09, - "darken": 0.43 - }, - "list.selectionBackground": { - "transparent": 0.35, - "lighten": 0.2 - }, - "progressBar.background": { - "lighten": 0.2 - }, - "selection.background": { - "transparent": 0.5, - "lighten": 0.3 - }, - "sliderHandle.activeBackground": { - "lighten": 0.2 - }, - "table.selectionBackground": { - "transparent": 0.5, - "lighten": 0.1 - }, - "table.inactiveSelectionBackground": { - "transparent": 0.09, - "darken": 0.43 - }, - "textarea.selectionBackground": { - "transparent": 0.5, - "lighten": 0.3 - } - } -} +{ + "input.background": "#f8f9fa", + "inputButton.hoverBackground": "#00000018", + "list.alternateBackground": "#00000009", + "list.hoverBackground": "#00000013", + "linkVisited": "#660098", + "menubar.selectionBackground": "#00000020", + "popupItem.checkbox.background": "#00000019", + "popupItem.selectionBackground": "#00000022", + "scrollbar.background": "#00000010", + "scrollbarSlider.activeBackground": "#00000060", + "scrollbarSlider.background": "#00000040", + "scrollbarSlider.disabledBackground": "#00000015", + "scrollbarSlider.hoverBackground": "#00000050", + "statusBar.background": "#dfe1e5", + "statusBarItem.activeBackground": "#00000024", + "statusBarItem.hoverBackground": "#00000015", + "tab.activeBackground": "#00000000", + "tab.hoverBackground": "#00000015", + "tabCloseButton.hoverBackground": "#00000020", + "table.alternateBackground": "#00000012", + "tableSectionHeader.background": "#dadce0", + "textarea.inactiveSelectionBackground": "#00000015", + "toolbar.activeBackground": "#00000024", + "toolbar.background": "#ebebeb", + "toolbar.hoverBackground": "#00000015", + "tree.inactiveIndentGuidesStroke": "#00000030", + "tree.indentGuidesStroke": "#00000050", + "treeSectionHeader.background": "#dadce0", + "background": { + "base": "#f8f9fa", + "list": {}, + "panel": { + "lighten": 0.5 + }, + "popup": { + "lighten": 0.2 + }, + "table": { + "lighten": 0.5 + }, + "textarea": { + "lighten": 0.25 + }, + "title": { + "darken": 0.04 + } + }, + "border": { + "base": "#dadce0", + "input": {} + }, + "foreground": { + "base": "#4d5157", + "defaultButton.disabledBackground": { + "transparent": 0.25 + }, + "disabled": { + "transparent": 0.4 + }, + "disabledSelectionBackground": { + "transparent": 0.25 + }, + "icon": { + "darken": 0.05 + }, + "icon.unfocused": { + "transparent": 0.6 + }, + "input.placeholder": { + "transparent": 0.6 + }, + "progressBar.disabledBackground": { + "transparent": 0.25 + }, + "slider.disabledBackground": { + "transparent": 0.25 + }, + "sliderTrack.inactiveBackground": { + "transparent": 0.2 + } + }, + "primary": { + "base": "#1a73e8", + "button.activeBackground": { + "transparent": 0.24, + "darken": 0.03 + }, + "button.hoverBackground": { + "transparent": 0.1, + "darken": 0.03 + }, + "defaultButton.activeBackground": { + "lighten": 0.3 + }, + "defaultButton.hoverBackground": { + "lighten": 0.1 + }, + "list.inactiveSelectionBackground": { + "transparent": 0.09, + "darken": 0.43 + }, + "list.selectionBackground": { + "transparent": 0.35, + "lighten": 0.2 + }, + "progressBar.background": { + "lighten": 0.2 + }, + "selection.background": { + "transparent": 0.5, + "lighten": 0.3 + }, + "sliderHandle.activeBackground": { + "lighten": 0.2 + }, + "table.selectionBackground": { + "transparent": 0.5, + "lighten": 0.1 + }, + "table.inactiveSelectionBackground": { + "transparent": 0.09, + "darken": 0.43 + }, + "textarea.selectionBackground": { + "transparent": 0.5, + "lighten": 0.3 + } + } +} diff --git a/style/colors/themes/validate.json b/style/colors/themes/validate.json index 0ed50b56..46a324bb 100644 --- a/style/colors/themes/validate.json +++ b/style/colors/themes/validate.json @@ -1,451 +1,451 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema", - "type": "object", - "title": "The json schema for validating color map file", - "description": "The root schema comprises the entire JSON document.", - "definitions": { - "color_code": { - "type": "string", - "format": "color" - }, - "color_style": { - "type": "object", - "additionalProperties": false, - "properties": { - "transparent": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "darken": { - "type": "number", - "minimum": 0, - "maximum": 1 - }, - "lighten": { - "type": "number", - "minimum": 0, - "maximum": 1 - } - } - } - }, - "additionalProperties": false, - "groups": { - "Base colors": "Base color for application.", - "Button control": "Colors for QPushButton, QToolButton and QDockWidget title button.", - "Input": "Colors for QAbstractSpinBox, QSpinBox, QComboBox and QLineEdit.", - "Link colors": "Colors for link.", - "Lists and trees": "Colors for QListView/QListWidget and QTreeView/QTreeWidget and QColumnView.", - "Menubar": "Colors for QMenuBar.", - "Popup": "Colors for QMenu and QToolTip.", - "Progress bar": "Colors for QProgressBar.", - "Scrollbar control": "Colors for QScrollBar.", - "Slider control": "Colors for QSlider.", - "Statusbar colors": "Colors for QStatusBar.", - "Tabs": "Colors for QTabBar.", - "Textarea": "Colors for QTextEdit, QTextBrowser and QPlainTextEdit.", - "Table": "Colors for QTableView/QTableWidget.", - "Title": "Colors for title components.", - "Toolbar": "Colors for QToolBar.", - "Panel": "Colors for panel." - }, - "properties": { - "input.background": { - "description": "Background color for QAbstractSpinBox, QComboBox and QLineEdit.", - "group": "Input", - "$ref": "#/definitions/color_code" - }, - "inputButton.hoverBackground": { - "description": "Hover background color of button in QAbstractSpinBox.", - "group": "Input", - "$ref": "#/definitions/color_code" - }, - "linkVisited": { - "description": "Foreground color for already visited hyperlinks.", - "group": "Link colors", - "$ref": "#/definitions/color_code" - }, - "list.alternateBackground": { - "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView alternating rows.", - "group": "Lists and trees", - "$ref": "#/definitions/color_code" - }, - "list.hoverBackground": { - "description": "QListView/QListWidget, QTreeView/QTreeWidget and QColumnView background color when hovering over items using the mouse.", - "group": "Lists and trees", - "$ref": "#/definitions/color_code" - }, - "menubar.selectionBackground": { - "description": "Background color of the selected menu item in the QMenuBar.", - "group": "Menubar", - "$ref": "#/definitions/color_code" - }, - "popupItem.checkbox.background": { - "description": "Background color of QCheckBox in QMenu.", - "group": "Popup", - "$ref": "#/definitions/color_code" - }, - "popupItem.selectionBackground": { - "description": "Background color of the selected item in the QMenu.", - "group": "Popup", - "$ref": "#/definitions/color_code" - }, - "scrollbar.background": { - "description": "QScrollBar background color. Available on OS other than mac.", - "group": "Scrollbar control", - "$ref": "#/definitions/color_code" - }, - "scrollbarSlider.activeBackground": { - "description": "QScrollbar slider background color when clicked on.", - "group": "Scrollbar control", - "$ref": "#/definitions/color_code" - }, - "scrollbarSlider.background": { - "description": "QScrollbar slider background color.", - "group": "Scrollbar control", - "$ref": "#/definitions/color_code" - }, - "scrollbarSlider.disabledBackground": { - "description": "Scrollbar slider background color when disabled.", - "group": "Scrollbar control", - "$ref": "#/definitions/color_code" - }, - "scrollbarSlider.hoverBackground": { - "description": "QScrollbar slider background color when hovering.", - "group": "Scrollbar control", - "$ref": "#/definitions/color_code" - }, - "statusBar.background": { - "description": "QStatusBar background color.", - "group": "Statusbar colors", - "$ref": "#/definitions/color_code" - }, - "statusBarItem.activeBackground": { - "description": "QStatusBar item background color when clicking.", - "group": "Statusbar colors", - "$ref": "#/definitions/color_code" - }, - "statusBarItem.hoverBackground": { - "description": "QStatusBar item background color when hovering.", - "group": "Statusbar colors", - "$ref": "#/definitions/color_code" - }, - "tab.activeBackground": { - "description": "Active Tab background color in an QTabBar.", - "group": "Tabs", - "$ref": "#/definitions/color_code" - }, - "tab.hoverBackground": { - "description": "Tab background color when hovering in an QTabBar.", - "group": "Tabs", - "$ref": "#/definitions/color_code" - }, - "tabCloseButton.hoverBackground": { - "description": "Background color of close button in an QTabBar.", - "group": "Tabs", - "$ref": "#/definitions/color_code" - }, - "table.alternateBackground": { - "description": "Background color for QTableView/QTableWidget alternating rows.", - "group": "Table", - "$ref": "#/definitions/color_code" - }, - "tableSectionHeader.background": { - "description": "QTableView/QTableWidget section header background color.", - "group": "Table", - "$ref": "#/definitions/color_code" - }, - "textarea.inactiveSelectionBackground": { - "description": "QTextEdit, QTextBrowser and QPlainTextEdit background color for the selected item when the widget is inactive.", - "group": "Textarea", - "$ref": "#/definitions/color_code" - }, - "toolbar.activeBackground": { - "description": "QToolBar button background color when clicking.", - "group": "Toolbar", - "$ref": "#/definitions/color_code" - }, - "toolbar.background": { - "description": "QToolBar background color.", - "group": "Toolbar", - "$ref": "#/definitions/color_code" - }, - "toolbar.hoverBackground": { - "description": "QToolbar background when hovering over items using the mouse.", - "group": "Toolbar", - "$ref": "#/definitions/color_code" - }, - "tree.inactiveIndentGuidesStroke": { - "description": "QTreeView stroke color for indent guides when QTreeView is inactive.", - "group": "Lists and trees", - "$ref": "#/definitions/color_code" - }, - "tree.indentGuidesStroke": { - "description": "QTreeView stroke color for indent guides.", - "group": "Lists and trees", - "$ref": "#/definitions/color_code" - }, - "treeSectionHeader.background": { - "description": "QTreeView section header background color.", - "group": "Lists and trees", - "$ref": "#/definitions/color_code" - }, - "background": { - "type": "object", - "additionalProperties": false, - "properties": { - "base": { - "description": "Overall background color. This color is only used if not overridden by a widget.", - "group": "Base colors", - "$ref": "#/definitions/color_code" - }, - "list": { - "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView.", - "group": "Lists and trees", - "$ref": "#/definitions/color_style" - }, - "panel": { - "description": "Background color of QFrame when the frameShape property is Panel.", - "group": "Panel", - "$ref": "#/definitions/color_style" - }, - "popup": { - "description": "Background color for QToolTip, QMenu and dropdown of QComboBox.", - "group": "Popup", - "$ref": "#/definitions/color_style" - }, - "table": { - "description": "QTableView/QTableWidget background color.", - "group": "Table", - "$ref": "#/definitions/color_style" - }, - "textarea": { - "description": "QTextEdit/QPlainTextEdit background color.", - "group": "Textarea", - "$ref": "#/definitions/color_style" - }, - "title": { - "description": "Background color for QDockWidget title and QToolBox tab.", - "group": "Title", - "$ref": "#/definitions/color_style" - } - }, - "required": [ - "base", - "list", - "panel", - "popup", - "table", - "textarea", - "title" - ] - }, - "border": { - "type": "object", - "additionalProperties": false, - "properties": { - "base": { - "description": "Overall border color. This color is only used if not overridden by a widget.", - "group": "Base colors", - "$ref": "#/definitions/color_code" - }, - "input": { - "description": "Border color for QAbstractSpinBox, QComboBox and QLineEdit.", - "group": "Input", - "$ref": "#/definitions/color_style" - } - }, - "required": [ - "base", - "input" - ] - }, - "foreground": { - "type": "object", - "additionalProperties": false, - "properties": { - "base": { - "description": "Overall foreground color. This color is only used if not overridden by a widget.", - "group": "Base colors", - "$ref": "#/definitions/color_code" - }, - "defaultButton.disabledBackground": { - "description": "Default QPushButton background color when disabled.", - "group": "Button control", - "$ref": "#/definitions/color_style" - }, - "disabled": { - "description": "Overall foreground color for disabled elements. This color is only used if not overridden by a widget.", - "group": "Base colors", - "$ref": "#/definitions/color_style" - }, - "disabledSelectionBackground": { - "description": "Overall background color for the selected item when widgets are disabled.", - "group": "Base colors", - "$ref": "#/definitions/color_style" - }, - "icon": { - "description": "The default color for icons.", - "group": "Base colors", - "$ref": "#/definitions/color_style" - }, - "icon.unfocused": { - "description": "The default color for icons when unfocused. Currently, this color is only used for unselected tab close button of QTabBar.", - "group": "Base colors", - "$ref": "#/definitions/color_style" - }, - "input.placeholder": { - "description": "QLineEdit and QComboBox(editable) foreground color for placeholder text.", - "group": "Input", - "$ref": "#/definitions/color_style" - }, - "progressBar.disabledBackground": { - "description": "Active indicator color of QProgressBar when disabled.", - "group": "Progress bar", - "$ref": "#/definitions/color_style" - }, - "slider.disabledBackground": { - "description": "Active track and handle color of QSlider when disabled.", - "group": "Slider control", - "$ref": "#/definitions/color_style" - }, - "sliderTrack.inactiveBackground": { - "description": "Inactive track color of QSlider.", - "group": "Slider control", - "$ref": "#/definitions/color_style" - } - }, - "required": [ - "base", - "defaultButton.disabledBackground", - "disabled", - "disabledSelectionBackground", - "icon", - "icon.unfocused", - "input.placeholder", - "progressBar.disabledBackground", - "slider.disabledBackground", - "sliderTrack.inactiveBackground" - ] - }, - "primary": { - "type": "object", - "additionalProperties": false, - "properties": { - "base": { - "description": "Accent color.", - "group": "Base colors", - "$ref": "#/definitions/color_code" - }, - "button.activeBackground": { - "description": "Background color for QPushButton, QToolButton and button on QDockWidget title when the widget is active(pressed or checked).", - "group": "Button control", - "$ref": "#/definitions/color_style" - }, - "button.hoverBackground": { - "description": "Background color for QPushButton, QToolButton and button on QDockWidget title when hovering over buttons using the mouse.", - "group": "Button control", - "$ref": "#/definitions/color_style" - }, - "defaultButton.activeBackground": { - "description": "QPushButton when the widget is active(pressed or checked).", - "group": "Button control", - "$ref": "#/definitions/color_style" - }, - "defaultButton.hoverBackground": { - "description": "Background color for default QPushButton when hovering over buttons using the mouse.", - "group": "Button control", - "$ref": "#/definitions/color_style" - }, - "list.inactiveSelectionBackground": { - "description": "QListView/QListWidget, QTreeView/QTreeWidget and QColumnView background color for the selected item when the widget is inactive.", - "group": "Lists and trees", - "$ref": "#/definitions/color_style" - }, - "list.selectionBackground": { - "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView when selected item or texts.", - "group": "Lists and trees", - "$ref": "#/definitions/color_style" - }, - "progressBar.background": { - "description": "Active indicator color of QProgressBar.", - "group": "Progress bar", - "$ref": "#/definitions/color_style" - }, - "selection.background": { - "description": "Overall background color when selected item or texts. This color is only used if not overridden by a widget.", - "group": "Base colors", - "$ref": "#/definitions/color_style" - }, - "sliderHandle.activeBackground": { - "description": "QSlider handle color when pressing or hovering over the handle using the mouse.", - "group": "Slider control", - "$ref": "#/definitions/color_style" - }, - "table.selectionBackground": { - "description": "QTableView/QTableWidget background color when selected item.", - "group": "Table", - "$ref": "#/definitions/color_style" - }, - "table.inactiveSelectionBackground": { - "description": "QTableView/QTableWidget background color for the selected item when the widget is inactive.", - "group": "Table", - "$ref": "#/definitions/color_style" - }, - "textarea.selectionBackground": { - "description": "Background color of text selections in the QTextEdit, QTextBrowser and QPlainTextEdit.", - "group": "Textarea", - "$ref": "#/definitions/color_style" - } - }, - "required": [ - "base", - "button.activeBackground", - "button.hoverBackground", - "defaultButton.activeBackground", - "defaultButton.hoverBackground", - "list.inactiveSelectionBackground", - "list.selectionBackground", - "progressBar.background", - "selection.background", - "sliderHandle.activeBackground", - "table.selectionBackground", - "table.inactiveSelectionBackground", - "textarea.selectionBackground" - ] - } - }, - "required": [ - "background", - "border", - "foreground", - "input.background", - "inputButton.hoverBackground", - "list.alternateBackground", - "list.hoverBackground", - "linkVisited", - "popupItem.checkbox.background", - "popupItem.selectionBackground", - "primary", - "scrollbar.background", - "scrollbarSlider.activeBackground", - "scrollbarSlider.disabledBackground", - "scrollbarSlider.hoverBackground", - "statusBar.background", - "statusBarItem.activeBackground", - "statusBarItem.hoverBackground", - "tab.activeBackground", - "tab.hoverBackground", - "tabCloseButton.hoverBackground", - "table.alternateBackground", - "tableSectionHeader.background", - "textarea.inactiveSelectionBackground", - "toolbar.activeBackground", - "toolbar.background", - "toolbar.hoverBackground", - "tree.inactiveIndentGuidesStroke", - "tree.indentGuidesStroke", - "treeSectionHeader.background" - ] -} +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "title": "The json schema for validating color map file", + "description": "The root schema comprises the entire JSON document.", + "definitions": { + "color_code": { + "type": "string", + "format": "color" + }, + "color_style": { + "type": "object", + "additionalProperties": false, + "properties": { + "transparent": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "darken": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lighten": { + "type": "number", + "minimum": 0, + "maximum": 1 + } + } + } + }, + "additionalProperties": false, + "groups": { + "Base colors": "Base color for application.", + "Button control": "Colors for QPushButton, QToolButton and QDockWidget title button.", + "Input": "Colors for QAbstractSpinBox, QSpinBox, QComboBox and QLineEdit.", + "Link colors": "Colors for link.", + "Lists and trees": "Colors for QListView/QListWidget and QTreeView/QTreeWidget and QColumnView.", + "Menubar": "Colors for QMenuBar.", + "Popup": "Colors for QMenu and QToolTip.", + "Progress bar": "Colors for QProgressBar.", + "Scrollbar control": "Colors for QScrollBar.", + "Slider control": "Colors for QSlider.", + "Statusbar colors": "Colors for QStatusBar.", + "Tabs": "Colors for QTabBar.", + "Textarea": "Colors for QTextEdit, QTextBrowser and QPlainTextEdit.", + "Table": "Colors for QTableView/QTableWidget.", + "Title": "Colors for title components.", + "Toolbar": "Colors for QToolBar.", + "Panel": "Colors for panel." + }, + "properties": { + "input.background": { + "description": "Background color for QAbstractSpinBox, QComboBox and QLineEdit.", + "group": "Input", + "$ref": "#/definitions/color_code" + }, + "inputButton.hoverBackground": { + "description": "Hover background color of button in QAbstractSpinBox.", + "group": "Input", + "$ref": "#/definitions/color_code" + }, + "linkVisited": { + "description": "Foreground color for already visited hyperlinks.", + "group": "Link colors", + "$ref": "#/definitions/color_code" + }, + "list.alternateBackground": { + "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView alternating rows.", + "group": "Lists and trees", + "$ref": "#/definitions/color_code" + }, + "list.hoverBackground": { + "description": "QListView/QListWidget, QTreeView/QTreeWidget and QColumnView background color when hovering over items using the mouse.", + "group": "Lists and trees", + "$ref": "#/definitions/color_code" + }, + "menubar.selectionBackground": { + "description": "Background color of the selected menu item in the QMenuBar.", + "group": "Menubar", + "$ref": "#/definitions/color_code" + }, + "popupItem.checkbox.background": { + "description": "Background color of QCheckBox in QMenu.", + "group": "Popup", + "$ref": "#/definitions/color_code" + }, + "popupItem.selectionBackground": { + "description": "Background color of the selected item in the QMenu.", + "group": "Popup", + "$ref": "#/definitions/color_code" + }, + "scrollbar.background": { + "description": "QScrollBar background color. Available on OS other than mac.", + "group": "Scrollbar control", + "$ref": "#/definitions/color_code" + }, + "scrollbarSlider.activeBackground": { + "description": "QScrollbar slider background color when clicked on.", + "group": "Scrollbar control", + "$ref": "#/definitions/color_code" + }, + "scrollbarSlider.background": { + "description": "QScrollbar slider background color.", + "group": "Scrollbar control", + "$ref": "#/definitions/color_code" + }, + "scrollbarSlider.disabledBackground": { + "description": "Scrollbar slider background color when disabled.", + "group": "Scrollbar control", + "$ref": "#/definitions/color_code" + }, + "scrollbarSlider.hoverBackground": { + "description": "QScrollbar slider background color when hovering.", + "group": "Scrollbar control", + "$ref": "#/definitions/color_code" + }, + "statusBar.background": { + "description": "QStatusBar background color.", + "group": "Statusbar colors", + "$ref": "#/definitions/color_code" + }, + "statusBarItem.activeBackground": { + "description": "QStatusBar item background color when clicking.", + "group": "Statusbar colors", + "$ref": "#/definitions/color_code" + }, + "statusBarItem.hoverBackground": { + "description": "QStatusBar item background color when hovering.", + "group": "Statusbar colors", + "$ref": "#/definitions/color_code" + }, + "tab.activeBackground": { + "description": "Active Tab background color in an QTabBar.", + "group": "Tabs", + "$ref": "#/definitions/color_code" + }, + "tab.hoverBackground": { + "description": "Tab background color when hovering in an QTabBar.", + "group": "Tabs", + "$ref": "#/definitions/color_code" + }, + "tabCloseButton.hoverBackground": { + "description": "Background color of close button in an QTabBar.", + "group": "Tabs", + "$ref": "#/definitions/color_code" + }, + "table.alternateBackground": { + "description": "Background color for QTableView/QTableWidget alternating rows.", + "group": "Table", + "$ref": "#/definitions/color_code" + }, + "tableSectionHeader.background": { + "description": "QTableView/QTableWidget section header background color.", + "group": "Table", + "$ref": "#/definitions/color_code" + }, + "textarea.inactiveSelectionBackground": { + "description": "QTextEdit, QTextBrowser and QPlainTextEdit background color for the selected item when the widget is inactive.", + "group": "Textarea", + "$ref": "#/definitions/color_code" + }, + "toolbar.activeBackground": { + "description": "QToolBar button background color when clicking.", + "group": "Toolbar", + "$ref": "#/definitions/color_code" + }, + "toolbar.background": { + "description": "QToolBar background color.", + "group": "Toolbar", + "$ref": "#/definitions/color_code" + }, + "toolbar.hoverBackground": { + "description": "QToolbar background when hovering over items using the mouse.", + "group": "Toolbar", + "$ref": "#/definitions/color_code" + }, + "tree.inactiveIndentGuidesStroke": { + "description": "QTreeView stroke color for indent guides when QTreeView is inactive.", + "group": "Lists and trees", + "$ref": "#/definitions/color_code" + }, + "tree.indentGuidesStroke": { + "description": "QTreeView stroke color for indent guides.", + "group": "Lists and trees", + "$ref": "#/definitions/color_code" + }, + "treeSectionHeader.background": { + "description": "QTreeView section header background color.", + "group": "Lists and trees", + "$ref": "#/definitions/color_code" + }, + "background": { + "type": "object", + "additionalProperties": false, + "properties": { + "base": { + "description": "Overall background color. This color is only used if not overridden by a widget.", + "group": "Base colors", + "$ref": "#/definitions/color_code" + }, + "list": { + "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView.", + "group": "Lists and trees", + "$ref": "#/definitions/color_style" + }, + "panel": { + "description": "Background color of QFrame when the frameShape property is Panel.", + "group": "Panel", + "$ref": "#/definitions/color_style" + }, + "popup": { + "description": "Background color for QToolTip, QMenu and dropdown of QComboBox.", + "group": "Popup", + "$ref": "#/definitions/color_style" + }, + "table": { + "description": "QTableView/QTableWidget background color.", + "group": "Table", + "$ref": "#/definitions/color_style" + }, + "textarea": { + "description": "QTextEdit/QPlainTextEdit background color.", + "group": "Textarea", + "$ref": "#/definitions/color_style" + }, + "title": { + "description": "Background color for QDockWidget title and QToolBox tab.", + "group": "Title", + "$ref": "#/definitions/color_style" + } + }, + "required": [ + "base", + "list", + "panel", + "popup", + "table", + "textarea", + "title" + ] + }, + "border": { + "type": "object", + "additionalProperties": false, + "properties": { + "base": { + "description": "Overall border color. This color is only used if not overridden by a widget.", + "group": "Base colors", + "$ref": "#/definitions/color_code" + }, + "input": { + "description": "Border color for QAbstractSpinBox, QComboBox and QLineEdit.", + "group": "Input", + "$ref": "#/definitions/color_style" + } + }, + "required": [ + "base", + "input" + ] + }, + "foreground": { + "type": "object", + "additionalProperties": false, + "properties": { + "base": { + "description": "Overall foreground color. This color is only used if not overridden by a widget.", + "group": "Base colors", + "$ref": "#/definitions/color_code" + }, + "defaultButton.disabledBackground": { + "description": "Default QPushButton background color when disabled.", + "group": "Button control", + "$ref": "#/definitions/color_style" + }, + "disabled": { + "description": "Overall foreground color for disabled elements. This color is only used if not overridden by a widget.", + "group": "Base colors", + "$ref": "#/definitions/color_style" + }, + "disabledSelectionBackground": { + "description": "Overall background color for the selected item when widgets are disabled.", + "group": "Base colors", + "$ref": "#/definitions/color_style" + }, + "icon": { + "description": "The default color for icons.", + "group": "Base colors", + "$ref": "#/definitions/color_style" + }, + "icon.unfocused": { + "description": "The default color for icons when unfocused. Currently, this color is only used for unselected tab close button of QTabBar.", + "group": "Base colors", + "$ref": "#/definitions/color_style" + }, + "input.placeholder": { + "description": "QLineEdit and QComboBox(editable) foreground color for placeholder text.", + "group": "Input", + "$ref": "#/definitions/color_style" + }, + "progressBar.disabledBackground": { + "description": "Active indicator color of QProgressBar when disabled.", + "group": "Progress bar", + "$ref": "#/definitions/color_style" + }, + "slider.disabledBackground": { + "description": "Active track and handle color of QSlider when disabled.", + "group": "Slider control", + "$ref": "#/definitions/color_style" + }, + "sliderTrack.inactiveBackground": { + "description": "Inactive track color of QSlider.", + "group": "Slider control", + "$ref": "#/definitions/color_style" + } + }, + "required": [ + "base", + "defaultButton.disabledBackground", + "disabled", + "disabledSelectionBackground", + "icon", + "icon.unfocused", + "input.placeholder", + "progressBar.disabledBackground", + "slider.disabledBackground", + "sliderTrack.inactiveBackground" + ] + }, + "primary": { + "type": "object", + "additionalProperties": false, + "properties": { + "base": { + "description": "Accent color.", + "group": "Base colors", + "$ref": "#/definitions/color_code" + }, + "button.activeBackground": { + "description": "Background color for QPushButton, QToolButton and button on QDockWidget title when the widget is active(pressed or checked).", + "group": "Button control", + "$ref": "#/definitions/color_style" + }, + "button.hoverBackground": { + "description": "Background color for QPushButton, QToolButton and button on QDockWidget title when hovering over buttons using the mouse.", + "group": "Button control", + "$ref": "#/definitions/color_style" + }, + "defaultButton.activeBackground": { + "description": "QPushButton when the widget is active(pressed or checked).", + "group": "Button control", + "$ref": "#/definitions/color_style" + }, + "defaultButton.hoverBackground": { + "description": "Background color for default QPushButton when hovering over buttons using the mouse.", + "group": "Button control", + "$ref": "#/definitions/color_style" + }, + "list.inactiveSelectionBackground": { + "description": "QListView/QListWidget, QTreeView/QTreeWidget and QColumnView background color for the selected item when the widget is inactive.", + "group": "Lists and trees", + "$ref": "#/definitions/color_style" + }, + "list.selectionBackground": { + "description": "Background color for QListView/QListWidget, QTreeView/QTreeWidget and QColumnView when selected item or texts.", + "group": "Lists and trees", + "$ref": "#/definitions/color_style" + }, + "progressBar.background": { + "description": "Active indicator color of QProgressBar.", + "group": "Progress bar", + "$ref": "#/definitions/color_style" + }, + "selection.background": { + "description": "Overall background color when selected item or texts. This color is only used if not overridden by a widget.", + "group": "Base colors", + "$ref": "#/definitions/color_style" + }, + "sliderHandle.activeBackground": { + "description": "QSlider handle color when pressing or hovering over the handle using the mouse.", + "group": "Slider control", + "$ref": "#/definitions/color_style" + }, + "table.selectionBackground": { + "description": "QTableView/QTableWidget background color when selected item.", + "group": "Table", + "$ref": "#/definitions/color_style" + }, + "table.inactiveSelectionBackground": { + "description": "QTableView/QTableWidget background color for the selected item when the widget is inactive.", + "group": "Table", + "$ref": "#/definitions/color_style" + }, + "textarea.selectionBackground": { + "description": "Background color of text selections in the QTextEdit, QTextBrowser and QPlainTextEdit.", + "group": "Textarea", + "$ref": "#/definitions/color_style" + } + }, + "required": [ + "base", + "button.activeBackground", + "button.hoverBackground", + "defaultButton.activeBackground", + "defaultButton.hoverBackground", + "list.inactiveSelectionBackground", + "list.selectionBackground", + "progressBar.background", + "selection.background", + "sliderHandle.activeBackground", + "table.selectionBackground", + "table.inactiveSelectionBackground", + "textarea.selectionBackground" + ] + } + }, + "required": [ + "background", + "border", + "foreground", + "input.background", + "inputButton.hoverBackground", + "list.alternateBackground", + "list.hoverBackground", + "linkVisited", + "popupItem.checkbox.background", + "popupItem.selectionBackground", + "primary", + "scrollbar.background", + "scrollbarSlider.activeBackground", + "scrollbarSlider.disabledBackground", + "scrollbarSlider.hoverBackground", + "statusBar.background", + "statusBarItem.activeBackground", + "statusBarItem.hoverBackground", + "tab.activeBackground", + "tab.hoverBackground", + "tabCloseButton.hoverBackground", + "table.alternateBackground", + "tableSectionHeader.background", + "textarea.inactiveSelectionBackground", + "toolbar.activeBackground", + "toolbar.background", + "toolbar.hoverBackground", + "tree.inactiveIndentGuidesStroke", + "tree.indentGuidesStroke", + "treeSectionHeader.background" + ] +} diff --git a/style/palette.template.py b/style/palette.template.py index 9477c1be..84b23b31 100644 --- a/style/palette.template.py +++ b/style/palette.template.py @@ -1,114 +1,114 @@ -"""Module loading QPalette.""" -from __future__ import annotations - -from functools import partial - -from qdarktheme._template.engine import Template - - -def q_palette(mk_template: partial[Template], color_map: dict[str, str | dict], for_stylesheet: bool): - """Generate QPalette.""" - from qdarktheme.qtpy.QtGui import QColor, QPalette - - def _mk_q_color(text: str): - template = mk_template(text) - color_format = template.render(color_map) - return QColor(color_format) - - palette = QPalette() - - if not for_stylesheet: - # base - palette.setColor(QPalette.ColorRole.WindowText, _mk_q_color("{{ foreground|color|palette }}")) - palette.setColor( - QPalette.ColorRole.Button, _mk_q_color("{{ treeSectionHeader.background|color|palette }}") - ) - palette.setColor(QPalette.ColorRole.ButtonText, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor(QPalette.ColorRole.Base, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Window, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Highlight, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor( - QPalette.ColorRole.HighlightedText, _mk_q_color("{{ background|color|palette }}") - ) - palette.setColor( - QPalette.ColorRole.AlternateBase, - _mk_q_color("{{ list.alternateBackground|color|palette }}"), - ) - palette.setColor( - QPalette.ColorRole.ToolTipBase, _mk_q_color('{{ background|color(state="popup")|palette }}') - ) - palette.setColor(QPalette.ColorRole.ToolTipText, _mk_q_color("{{ foreground|color|palette }}")) - if hasattr(QPalette.ColorRole, "Foreground"): - palette.setColor( - QPalette.ColorRole.Foreground, # type: ignore - _mk_q_color("{{ foreground|color|palette }}"), - ) - - palette.setColor(QPalette.ColorRole.Light, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Midlight, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Dark, _mk_q_color("{{ background|color|palette }}")) - palette.setColor(QPalette.ColorRole.Mid, _mk_q_color("{{ border|color|palette }}")) - palette.setColor(QPalette.ColorRole.Shadow, _mk_q_color("{{ border|color|palette }}")) - - # disabled - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.WindowText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.ButtonText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Highlight, - _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.HighlightedText, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - - # inactive - palette.setColor( - QPalette.ColorGroup.Inactive, - QPalette.ColorRole.Highlight, - _mk_q_color('{{ primary|color(state="list.inactiveSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Inactive, - QPalette.ColorRole.HighlightedText, - _mk_q_color("{{ foreground|color|palette }}"), - ) - - palette.setColor( - QPalette.ColorRole.Text, _mk_q_color('{{ foreground|color(state="icon")|palette }}') - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Text, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - palette.setColor(QPalette.ColorRole.Link, _mk_q_color("{{ primary|color|palette }}")) - palette.setColor(QPalette.ColorRole.LinkVisited, _mk_q_color("{{ linkVisited|color|palette }}")) - if hasattr(QPalette.ColorRole, "PlaceholderText"): - palette.setColor( - QPalette.ColorRole.PlaceholderText, - _mk_q_color('{{ foreground|color(state="input.placeholder")|palette }}'), - ) - - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.Link, - _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), - ) - palette.setColor( - QPalette.ColorGroup.Disabled, - QPalette.ColorRole.LinkVisited, - _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), - ) - - return palette +"""Module loading QPalette.""" +from __future__ import annotations + +from functools import partial + +from qdarktheme._template.engine import Template + + +def q_palette(mk_template: partial[Template], color_map: dict[str, str | dict], for_stylesheet: bool): + """Generate QPalette.""" + from qdarktheme.qtpy.QtGui import QColor, QPalette + + def _mk_q_color(text: str): + template = mk_template(text) + color_format = template.render(color_map) + return QColor(color_format) + + palette = QPalette() + + if not for_stylesheet: + # base + palette.setColor(QPalette.ColorRole.WindowText, _mk_q_color("{{ foreground|color|palette }}")) + palette.setColor( + QPalette.ColorRole.Button, _mk_q_color("{{ treeSectionHeader.background|color|palette }}") + ) + palette.setColor(QPalette.ColorRole.ButtonText, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor(QPalette.ColorRole.Base, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Window, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Highlight, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor( + QPalette.ColorRole.HighlightedText, _mk_q_color("{{ background|color|palette }}") + ) + palette.setColor( + QPalette.ColorRole.AlternateBase, + _mk_q_color("{{ list.alternateBackground|color|palette }}"), + ) + palette.setColor( + QPalette.ColorRole.ToolTipBase, _mk_q_color('{{ background|color(state="popup")|palette }}') + ) + palette.setColor(QPalette.ColorRole.ToolTipText, _mk_q_color("{{ foreground|color|palette }}")) + if hasattr(QPalette.ColorRole, "Foreground"): + palette.setColor( + QPalette.ColorRole.Foreground, # type: ignore + _mk_q_color("{{ foreground|color|palette }}"), + ) + + palette.setColor(QPalette.ColorRole.Light, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Midlight, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Dark, _mk_q_color("{{ background|color|palette }}")) + palette.setColor(QPalette.ColorRole.Mid, _mk_q_color("{{ border|color|palette }}")) + palette.setColor(QPalette.ColorRole.Shadow, _mk_q_color("{{ border|color|palette }}")) + + # disabled + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.WindowText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.ButtonText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Highlight, + _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.HighlightedText, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + + # inactive + palette.setColor( + QPalette.ColorGroup.Inactive, + QPalette.ColorRole.Highlight, + _mk_q_color('{{ primary|color(state="list.inactiveSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Inactive, + QPalette.ColorRole.HighlightedText, + _mk_q_color("{{ foreground|color|palette }}"), + ) + + palette.setColor( + QPalette.ColorRole.Text, _mk_q_color('{{ foreground|color(state="icon")|palette }}') + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Text, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + palette.setColor(QPalette.ColorRole.Link, _mk_q_color("{{ primary|color|palette }}")) + palette.setColor(QPalette.ColorRole.LinkVisited, _mk_q_color("{{ linkVisited|color|palette }}")) + if hasattr(QPalette.ColorRole, "PlaceholderText"): + palette.setColor( + QPalette.ColorRole.PlaceholderText, + _mk_q_color('{{ foreground|color(state="input.placeholder")|palette }}'), + ) + + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.Link, + _mk_q_color('{{ foreground|color(state="disabledSelectionBackground")|palette }}'), + ) + palette.setColor( + QPalette.ColorGroup.Disabled, + QPalette.ColorRole.LinkVisited, + _mk_q_color('{{ foreground|color(state="disabled")|palette }}'), + ) + + return palette diff --git a/style/svg/material_design_icons.json b/style/svg/material_design_icons.json index ea367e0c..43b590c9 100644 --- a/style/svg/material_design_icons.json +++ b/style/svg/material_design_icons.json @@ -1,54 +1,54 @@ -{ - "arrow_drop_up": "round", - "arrow_upward": "round", - "calendar_today": "round", - "cancel": "round", - "check_box_outline_blank": "round", - "check_box": "round", - "check_circle": "round", - "check": "round", - "chevron_right": "round", - "circle": "outlined", - "cleaning_services": "round", - "close": "round", - "create_new_folder": "round", - "delete": "round", - "done_all": "round", - "double_arrow": "round", - "drag_handle": "round", - "drag_indicator": "round", - "drive_file_move": "round", - "drive_file_move_rtl": "round", - "east": "round", - "expand_less": "round", - "fast_forward": "round", - "fast_rewind": "round", - "flip_to_front": "round", - "fullscreen": "round", - "grid_view": "round", - "help": "round", - "home": "round", - "horizontal_rule": "round", - "indeterminate_check_box": "round", - "info": "round", - "launch": "round", - "list": "round", - "minimize": "round", - "not_interested": "round", - "pause": "round", - "play_arrow": "round", - "question_mark": "round", - "radio_button_checked": "round", - "radio_button_unchecked": "round", - "refresh": "round", - "restart_alt": "round", - "save": "round", - "search": "round", - "security": "round", - "skip_next": "round", - "skip_previous": "round", - "stop": "round", - "visibility_off": "round", - "volume_mute": "round", - "volume_up": "round" -} +{ + "arrow_drop_up": "round", + "arrow_upward": "round", + "calendar_today": "round", + "cancel": "round", + "check_box_outline_blank": "round", + "check_box": "round", + "check_circle": "round", + "check": "round", + "chevron_right": "round", + "circle": "outlined", + "cleaning_services": "round", + "close": "round", + "create_new_folder": "round", + "delete": "round", + "done_all": "round", + "double_arrow": "round", + "drag_handle": "round", + "drag_indicator": "round", + "drive_file_move": "round", + "drive_file_move_rtl": "round", + "east": "round", + "expand_less": "round", + "fast_forward": "round", + "fast_rewind": "round", + "flip_to_front": "round", + "fullscreen": "round", + "grid_view": "round", + "help": "round", + "home": "round", + "horizontal_rule": "round", + "indeterminate_check_box": "round", + "info": "round", + "launch": "round", + "list": "round", + "minimize": "round", + "not_interested": "round", + "pause": "round", + "play_arrow": "round", + "question_mark": "round", + "radio_button_checked": "round", + "radio_button_unchecked": "round", + "refresh": "round", + "restart_alt": "round", + "save": "round", + "search": "round", + "security": "round", + "skip_next": "round", + "skip_previous": "round", + "stop": "round", + "visibility_off": "round", + "volume_mute": "round", + "volume_up": "round" +} diff --git a/style/svg/new_standard_icons.json b/style/svg/new_standard_icons.json index 0d9328ca..eb3d2123 100644 --- a/style/svg/new_standard_icons.json +++ b/style/svg/new_standard_icons.json @@ -1,294 +1,294 @@ -{ - "SP_ArrowBack": { - "id": "arrow_upward", - "rotate": 270, - "qss": { - "property": "backward-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_ArrowDown": { - "id": "arrow_upward", - "rotate": 180 - }, - "SP_ArrowForward": { - "id": "arrow_upward", - "rotate": 90, - "qss": { - "property": "forward-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_ArrowLeft": { - "id": "arrow_upward", - "rotate": 270, - "qss": { - "property": "leftarrow-icon", - "widgets": [ - "QCalendarWidget" - ] - } - }, - "SP_ArrowRight": { - "id": "arrow_upward", - "rotate": 90, - "qss": { - "property": "rightarrow-icon", - "widgets": [ - "QCalendarWidget" - ] - } - }, - "SP_ArrowUp": { - "id": "arrow_upward" - }, - "SP_BrowserReload": { - "id": "refresh" - }, - "SP_BrowserStop": { - "id": "close" - }, - "SP_CommandLink": { - "id": "east", - "qss": { - "property": "qproperty-icon", - "widgets": [ - "QCommandLinkButton" - ] - } - }, - "SP_DialogAbortButton": { - "id": "not_interested" - }, - "SP_DialogApplyButton": { - "id": "check_circle" - }, - "SP_DialogCancelButton": { - "id": "cancel" - }, - "SP_DialogCloseButton": { - "id": "close" - }, - "SP_DialogDiscardButton": { - "id": "delete" - }, - "SP_DialogHelpButton": { - "id": "help" - }, - "SP_DialogIgnoreButton": { - "id": "visibility_off" - }, - "SP_DialogNoButton": { - "id": "not_interested" - }, - "SP_DialogNoToAllButton": { - "id": "close" - }, - "SP_DialogOkButton": { - "id": "check" - }, - "SP_DialogOpenButton": { - "id": "launch" - }, - "SP_DialogResetButton": { - "id": "cleaning_services" - }, - "SP_DialogSaveButton": { - "id": "save" - }, - "SP_DialogSaveAllButton": { - "id": "save" - }, - "SP_DialogYesButton": { - "id": "circle" - }, - "SP_DialogYesToAllButton": { - "id": "done_all" - }, - "SP_DirHomeIcon": { - "id": "home" - }, - "SP_DockWidgetCloseButton": { - "id": "close" - }, - "SP_FileDialogBack": { - "id": "arrow_upward", - "rotate": 270 - }, - "SP_FileDialogContentsView": { - "id": "search" - }, - "SP_FileDialogDetailedView": { - "id": "list", - "qss": { - "property": "filedialog-detailedview-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_FileDialogEnd": { - "id": "drive_file_move_rtl" - }, - "SP_FileDialogInfoView": { - "id": "info" - }, - "SP_FileDialogListView": { - "id": "grid_view", - "qss": { - "property": "filedialog-listview-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_FileDialogNewFolder": { - "id": "create_new_folder", - "qss": { - "property": "filedialog-new-directory-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_FileDialogToParent": { - "id": "arrow_upward", - "qss": { - "property": "filedialog-parent-directory-icon", - "widgets": [ - "QFileDialog" - ] - } - }, - "SP_FileDialogStart": { - "id": "drive_file_move" - }, - "SP_LineEditClearButton": { - "id": "close", - "qss": { - "property": "lineedit-clear-button-icon", - "widgets": [ - "QLineEdit" - ] - } - }, - "SP_MediaPlay": { - "id": "play_arrow" - }, - "SP_MediaPause": { - "id": "pause" - }, - "SP_MediaSeekForward": { - "id": "fast_forward" - }, - "SP_MediaSeekBackward": { - "id": "fast_rewind" - }, - "SP_MediaSkipBackward": { - "id": "skip_previous" - }, - "SP_MediaSkipForward": { - "id": "skip_next" - }, - "SP_MediaStop": { - "id": "stop" - }, - "SP_MediaVolume": { - "id": "volume_up" - }, - "SP_MediaVolumeMuted": { - "id": "volume_mute" - }, - "SP_MessageBoxQuestion": { - "id": "help", - "os": [ - "Darwin", - "Linux" - ] - }, - "SP_DialogRetryButton": { - "id": "refresh" - }, - "SP_TabCloseButton": { - "id": "close" - }, - "SP_TitleBarCloseButton": { - "id": "close", - "qss": { - "property": "titlebar-close-icon", - "widgets": [ - "QDockWidget", - "QMdiSubWindow" - ] - } - }, - "SP_TitleBarContextHelpButton": { - "id": "question_mark" - }, - "SP_TitleBarMaxButton": { - "id": "fullscreen", - "qss": { - "property": "titlebar-maximize-icon", - "widgets": [ - "QMdiSubWindow" - ] - } - }, - "SP_TitleBarMinButton": { - "id": "minimize", - "qss": { - "property": "titlebar-minimize-icon", - "widgets": [ - "QMdiSubWindow" - ] - } - }, - "SP_TitleBarNormalButton": { - "id": "flip_to_front", - "qss": { - "property": "titlebar-normal-icon", - "widgets": [ - "QDockWidget", - "QMdiSubWindow" - ] - } - }, - "SP_ToolBarHorizontalExtensionButton": { - "id": "double_arrow", - "qss": { - "property": "qproperty-icon", - "widgets": [ - "QToolBarExtension" - ] - } - }, - "SP_TitleBarShadeButton": { - "id": "chevron_right", - "rotate": "270" - }, - "SP_TitleBarUnshadeButton": { - "id": "chevron_right", - "rotate": "90" - }, - "SP_ToolBarVerticalExtensionButton": { - "id": "double_arrow", - "rotate": 90 - }, - "SP_TrashIcon": { - "id": "delete", - "os": [ - "Windows" - ] - }, - "SP_VistaShield": { - "id": "security", - "os": [ - "Darwin", - "Linux" - ] - } -} +{ + "SP_ArrowBack": { + "id": "arrow_upward", + "rotate": 270, + "qss": { + "property": "backward-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_ArrowDown": { + "id": "arrow_upward", + "rotate": 180 + }, + "SP_ArrowForward": { + "id": "arrow_upward", + "rotate": 90, + "qss": { + "property": "forward-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_ArrowLeft": { + "id": "arrow_upward", + "rotate": 270, + "qss": { + "property": "leftarrow-icon", + "widgets": [ + "QCalendarWidget" + ] + } + }, + "SP_ArrowRight": { + "id": "arrow_upward", + "rotate": 90, + "qss": { + "property": "rightarrow-icon", + "widgets": [ + "QCalendarWidget" + ] + } + }, + "SP_ArrowUp": { + "id": "arrow_upward" + }, + "SP_BrowserReload": { + "id": "refresh" + }, + "SP_BrowserStop": { + "id": "close" + }, + "SP_CommandLink": { + "id": "east", + "qss": { + "property": "qproperty-icon", + "widgets": [ + "QCommandLinkButton" + ] + } + }, + "SP_DialogAbortButton": { + "id": "not_interested" + }, + "SP_DialogApplyButton": { + "id": "check_circle" + }, + "SP_DialogCancelButton": { + "id": "cancel" + }, + "SP_DialogCloseButton": { + "id": "close" + }, + "SP_DialogDiscardButton": { + "id": "delete" + }, + "SP_DialogHelpButton": { + "id": "help" + }, + "SP_DialogIgnoreButton": { + "id": "visibility_off" + }, + "SP_DialogNoButton": { + "id": "not_interested" + }, + "SP_DialogNoToAllButton": { + "id": "close" + }, + "SP_DialogOkButton": { + "id": "check" + }, + "SP_DialogOpenButton": { + "id": "launch" + }, + "SP_DialogResetButton": { + "id": "cleaning_services" + }, + "SP_DialogSaveButton": { + "id": "save" + }, + "SP_DialogSaveAllButton": { + "id": "save" + }, + "SP_DialogYesButton": { + "id": "circle" + }, + "SP_DialogYesToAllButton": { + "id": "done_all" + }, + "SP_DirHomeIcon": { + "id": "home" + }, + "SP_DockWidgetCloseButton": { + "id": "close" + }, + "SP_FileDialogBack": { + "id": "arrow_upward", + "rotate": 270 + }, + "SP_FileDialogContentsView": { + "id": "search" + }, + "SP_FileDialogDetailedView": { + "id": "list", + "qss": { + "property": "filedialog-detailedview-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_FileDialogEnd": { + "id": "drive_file_move_rtl" + }, + "SP_FileDialogInfoView": { + "id": "info" + }, + "SP_FileDialogListView": { + "id": "grid_view", + "qss": { + "property": "filedialog-listview-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_FileDialogNewFolder": { + "id": "create_new_folder", + "qss": { + "property": "filedialog-new-directory-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_FileDialogToParent": { + "id": "arrow_upward", + "qss": { + "property": "filedialog-parent-directory-icon", + "widgets": [ + "QFileDialog" + ] + } + }, + "SP_FileDialogStart": { + "id": "drive_file_move" + }, + "SP_LineEditClearButton": { + "id": "close", + "qss": { + "property": "lineedit-clear-button-icon", + "widgets": [ + "QLineEdit" + ] + } + }, + "SP_MediaPlay": { + "id": "play_arrow" + }, + "SP_MediaPause": { + "id": "pause" + }, + "SP_MediaSeekForward": { + "id": "fast_forward" + }, + "SP_MediaSeekBackward": { + "id": "fast_rewind" + }, + "SP_MediaSkipBackward": { + "id": "skip_previous" + }, + "SP_MediaSkipForward": { + "id": "skip_next" + }, + "SP_MediaStop": { + "id": "stop" + }, + "SP_MediaVolume": { + "id": "volume_up" + }, + "SP_MediaVolumeMuted": { + "id": "volume_mute" + }, + "SP_MessageBoxQuestion": { + "id": "help", + "os": [ + "Darwin", + "Linux" + ] + }, + "SP_DialogRetryButton": { + "id": "refresh" + }, + "SP_TabCloseButton": { + "id": "close" + }, + "SP_TitleBarCloseButton": { + "id": "close", + "qss": { + "property": "titlebar-close-icon", + "widgets": [ + "QDockWidget", + "QMdiSubWindow" + ] + } + }, + "SP_TitleBarContextHelpButton": { + "id": "question_mark" + }, + "SP_TitleBarMaxButton": { + "id": "fullscreen", + "qss": { + "property": "titlebar-maximize-icon", + "widgets": [ + "QMdiSubWindow" + ] + } + }, + "SP_TitleBarMinButton": { + "id": "minimize", + "qss": { + "property": "titlebar-minimize-icon", + "widgets": [ + "QMdiSubWindow" + ] + } + }, + "SP_TitleBarNormalButton": { + "id": "flip_to_front", + "qss": { + "property": "titlebar-normal-icon", + "widgets": [ + "QDockWidget", + "QMdiSubWindow" + ] + } + }, + "SP_ToolBarHorizontalExtensionButton": { + "id": "double_arrow", + "qss": { + "property": "qproperty-icon", + "widgets": [ + "QToolBarExtension" + ] + } + }, + "SP_TitleBarShadeButton": { + "id": "chevron_right", + "rotate": "270" + }, + "SP_TitleBarUnshadeButton": { + "id": "chevron_right", + "rotate": "90" + }, + "SP_ToolBarVerticalExtensionButton": { + "id": "double_arrow", + "rotate": 90 + }, + "SP_TrashIcon": { + "id": "delete", + "os": [ + "Windows" + ] + }, + "SP_VistaShield": { + "id": "security", + "os": [ + "Darwin", + "Linux" + ] + } +} diff --git a/tests/test_color.py b/tests/test_color.py index 72047814..427cbb8a 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -1,121 +1,121 @@ -"""Test for the color manager.""" -import re - -import pytest - -from qdarktheme._color import _HSLA, _RGBA, Color - - -@pytest.mark.parametrize( - ("hsla", "rgba"), - [ - ((0, 0, 0, 0), (0, 0, 0, 0)), - ((0, 0, 0, 1), (0, 0, 0, 1)), - ((0, 0, 1, 1), (255, 255, 255, 1)), - ((0, 1, 0.5, 1), (255, 0, 0, 1)), - ((120, 1, 0.5, 1), (0, 255, 0, 1)), - ((240, 1, 0.5, 1), (0, 0, 255, 1)), - ((60, 1, 0.5, 1), (255, 255, 0, 1)), - ((180, 1, 0.5, 1), (0, 255, 255, 1)), - ((300, 1, 0.5, 1), (255, 0, 255, 1)), - ((0, 0, 0.753, 1), (192, 192, 192, 1)), - ((0, 0, 0.502, 1), (128, 128, 128, 1)), - ((0, 1, 0.251, 1), (128, 0, 0, 1)), - ((60, 1, 0.251, 1), (128, 128, 0, 1)), - ((120, 1, 0.251, 1), (0, 128, 0, 1)), - ((300, 1, 0.251, 1), (128, 0, 128, 1)), - ((180, 1, 0.251, 1), (0, 128, 128, 1)), - ((240, 1, 0.251, 1), (0, 0, 128, 1)), - ], -) -def test_hsla_from_rgba(hsla, rgba) -> None: - """Verify that converting from rgba to hsla correctly.""" - assert _HSLA(*hsla).to_rgba() == _RGBA(*rgba) - - -@pytest.mark.parametrize( - ("rgba", "hsla"), - [ - ((0, 0, 0, 0), (0, 0, 0, 0)), - ((0, 0, 0, 1), (0, 0, 0, 1)), - ((255, 255, 255, 1), (0, 0, 1, 1)), - ((255, 0, 0, 1), (0, 1, 0.5, 1)), - ((0, 255, 0, 1), (120, 1, 0.5, 1)), - ((0, 0, 255, 1), (240, 1, 0.5, 1)), - ((255, 255, 0, 1), (60, 1, 0.5, 1)), - ((0, 255, 255, 1), (180, 1, 0.5, 1)), - ((255, 0, 255, 1), (300, 1, 0.5, 1)), - ((192, 192, 192, 1), (0, 0, 0.753, 1)), - ((128, 128, 128, 1), (0, 0, 0.502, 1)), - ((128, 0, 0, 1), (0, 1, 0.251, 1)), - ((128, 128, 0, 1), (60, 1, 0.251, 1)), - ((0, 128, 0, 1), (120, 1, 0.251, 1)), - ((128, 0, 128, 1), (300, 1, 0.251, 1)), - ((0, 128, 128, 1), (180, 1, 0.251, 1)), - ((0, 0, 128, 1), (240, 1, 0.251, 1)), - ], -) -def test_rgba_from_hsla(rgba, hsla) -> None: - """Verify that converting from hsla to rgba correctly.""" - assert _HSLA.from_rgba(_RGBA(*rgba)) == _HSLA(*hsla) - - -@pytest.mark.parametrize( - ("hex", "rgba"), - [ - ("#000000", (0, 0, 0, 1)), - ("#FFFFFF", (255, 255, 255, 1)), - ("#FF00FF", (255, 0, 255, 1)), - ("#C0C0C0", (192, 192, 192, 1)), - ("#808080", (128, 128, 128, 1)), - ("#800000", (128, 0, 0, 1)), - ("#808000", (128, 128, 0, 1)), - ("#008000", (0, 128, 0, 1)), - ("#800080", (128, 0, 128, 1)), - ("#008080", (0, 128, 128, 1)), - ("#000080", (0, 0, 128, 1)), - ("#010203", (1, 2, 3, 1)), - ("#040506", (4, 5, 6, 1)), - ("#070809", (7, 8, 9, 1)), - ("#0a0A0a", (10, 10, 10, 1)), - ("#0b0B0b", (11, 11, 11, 1)), - ("#0c0C0c", (12, 12, 12, 1)), - ("#0d0D0d", (13, 13, 13, 1)), - ("#0e0E0e", (14, 14, 14, 1)), - ("#0f0F0f", (15, 15, 15, 1)), - ("#a0A0a0", (160, 160, 160, 1)), - ("#CFA", (204, 255, 170, 1)), - ("#CFA8", (204, 255, 170, 0.533)), - ], -) -def test_rgba_from_hex(hex, rgba) -> None: - """Verify that converting from hex to rgba correctly.""" - assert Color.from_hex(hex).rgba == _RGBA(*rgba) - - -@pytest.mark.parametrize( - "wrong_hex", - [ - ("#"), - ("#1"), - ("#12"), - ("#12345"), - ("#1234567"), - ("#123456789"), - ("123#"), - ("#00000g"), - ("#he00ff"), - ], -) -def test_color_from_wrong_hex(wrong_hex) -> None: - """Verify we raise ValueError when using wrong hexadecimal notations.""" - with pytest.raises( - ValueError, - match=re.escape( - f'invalid hex color format: "{wrong_hex}". ' - "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " - "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " - "(0-9, a-f or A-F)." - ), - ): - Color.from_hex(wrong_hex) +"""Test for the color manager.""" +import re + +import pytest + +from qdarktheme._color import _HSLA, _RGBA, Color + + +@pytest.mark.parametrize( + ("hsla", "rgba"), + [ + ((0, 0, 0, 0), (0, 0, 0, 0)), + ((0, 0, 0, 1), (0, 0, 0, 1)), + ((0, 0, 1, 1), (255, 255, 255, 1)), + ((0, 1, 0.5, 1), (255, 0, 0, 1)), + ((120, 1, 0.5, 1), (0, 255, 0, 1)), + ((240, 1, 0.5, 1), (0, 0, 255, 1)), + ((60, 1, 0.5, 1), (255, 255, 0, 1)), + ((180, 1, 0.5, 1), (0, 255, 255, 1)), + ((300, 1, 0.5, 1), (255, 0, 255, 1)), + ((0, 0, 0.753, 1), (192, 192, 192, 1)), + ((0, 0, 0.502, 1), (128, 128, 128, 1)), + ((0, 1, 0.251, 1), (128, 0, 0, 1)), + ((60, 1, 0.251, 1), (128, 128, 0, 1)), + ((120, 1, 0.251, 1), (0, 128, 0, 1)), + ((300, 1, 0.251, 1), (128, 0, 128, 1)), + ((180, 1, 0.251, 1), (0, 128, 128, 1)), + ((240, 1, 0.251, 1), (0, 0, 128, 1)), + ], +) +def test_hsla_from_rgba(hsla, rgba) -> None: + """Verify that converting from rgba to hsla correctly.""" + assert _HSLA(*hsla).to_rgba() == _RGBA(*rgba) + + +@pytest.mark.parametrize( + ("rgba", "hsla"), + [ + ((0, 0, 0, 0), (0, 0, 0, 0)), + ((0, 0, 0, 1), (0, 0, 0, 1)), + ((255, 255, 255, 1), (0, 0, 1, 1)), + ((255, 0, 0, 1), (0, 1, 0.5, 1)), + ((0, 255, 0, 1), (120, 1, 0.5, 1)), + ((0, 0, 255, 1), (240, 1, 0.5, 1)), + ((255, 255, 0, 1), (60, 1, 0.5, 1)), + ((0, 255, 255, 1), (180, 1, 0.5, 1)), + ((255, 0, 255, 1), (300, 1, 0.5, 1)), + ((192, 192, 192, 1), (0, 0, 0.753, 1)), + ((128, 128, 128, 1), (0, 0, 0.502, 1)), + ((128, 0, 0, 1), (0, 1, 0.251, 1)), + ((128, 128, 0, 1), (60, 1, 0.251, 1)), + ((0, 128, 0, 1), (120, 1, 0.251, 1)), + ((128, 0, 128, 1), (300, 1, 0.251, 1)), + ((0, 128, 128, 1), (180, 1, 0.251, 1)), + ((0, 0, 128, 1), (240, 1, 0.251, 1)), + ], +) +def test_rgba_from_hsla(rgba, hsla) -> None: + """Verify that converting from hsla to rgba correctly.""" + assert _HSLA.from_rgba(_RGBA(*rgba)) == _HSLA(*hsla) + + +@pytest.mark.parametrize( + ("hex", "rgba"), + [ + ("#000000", (0, 0, 0, 1)), + ("#FFFFFF", (255, 255, 255, 1)), + ("#FF00FF", (255, 0, 255, 1)), + ("#C0C0C0", (192, 192, 192, 1)), + ("#808080", (128, 128, 128, 1)), + ("#800000", (128, 0, 0, 1)), + ("#808000", (128, 128, 0, 1)), + ("#008000", (0, 128, 0, 1)), + ("#800080", (128, 0, 128, 1)), + ("#008080", (0, 128, 128, 1)), + ("#000080", (0, 0, 128, 1)), + ("#010203", (1, 2, 3, 1)), + ("#040506", (4, 5, 6, 1)), + ("#070809", (7, 8, 9, 1)), + ("#0a0A0a", (10, 10, 10, 1)), + ("#0b0B0b", (11, 11, 11, 1)), + ("#0c0C0c", (12, 12, 12, 1)), + ("#0d0D0d", (13, 13, 13, 1)), + ("#0e0E0e", (14, 14, 14, 1)), + ("#0f0F0f", (15, 15, 15, 1)), + ("#a0A0a0", (160, 160, 160, 1)), + ("#CFA", (204, 255, 170, 1)), + ("#CFA8", (204, 255, 170, 0.533)), + ], +) +def test_rgba_from_hex(hex, rgba) -> None: + """Verify that converting from hex to rgba correctly.""" + assert Color.from_hex(hex).rgba == _RGBA(*rgba) + + +@pytest.mark.parametrize( + "wrong_hex", + [ + ("#"), + ("#1"), + ("#12"), + ("#12345"), + ("#1234567"), + ("#123456789"), + ("123#"), + ("#00000g"), + ("#he00ff"), + ], +) +def test_color_from_wrong_hex(wrong_hex) -> None: + """Verify we raise ValueError when using wrong hexadecimal notations.""" + with pytest.raises( + ValueError, + match=re.escape( + f'invalid hex color format: "{wrong_hex}". ' + "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " + "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " + "(0-9, a-f or A-F)." + ), + ): + Color.from_hex(wrong_hex) diff --git a/tests/test_filters.py b/tests/test_filters.py index 52583768..cb56a8bf 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,72 +1,72 @@ -"""Tests for the main program.""" -import platform - -import pytest - -from qdarktheme._color import Color -from qdarktheme._template import filter -from qdarktheme.qtpy.qt_compat import QT_API -from qdarktheme.qtpy.qt_version import __version__ - -CURRENT_QT_VERSION = f'=={"10.0.0" if __version__ is None else __version__}' -ANOTHER_QT_VERSION = f'=={"6.0.0" if CURRENT_QT_VERSION == "5.15.0" else "5.15.0"}' -CURRENT_QT_API = "PySide6" if QT_API is None else QT_API -ANOTHER_QT_API = "PyQt5" if CURRENT_QT_API == "PySide6" else "PySide6" -CURRENT_OS = platform.system() -ANOTHER_OS = "Linux" if CURRENT_OS.lower() == "windows" else "windows" - - -@pytest.mark.parametrize( - ("text", "output", "qt_version", "qt_api", "os_name", "expected_result"), - [ - ("", "background: red;", None, None, None, "background: red;"), - ("blue", "background: red;", None, None, None, "background: red;"), - ("red", "background: ${};", None, None, None, "background: red;"), - ("", "background: ${};", None, None, None, "background: ;"), - ("red", "background: ${};", CURRENT_QT_VERSION, None, None, "background: red;"), - ("red", "background: ${};", ANOTHER_QT_VERSION, None, None, ""), - ("red", "background: ${};", None, CURRENT_QT_API, None, "background: red;"), - ("red", "background: ${};", None, ANOTHER_QT_API, None, ""), - ("red", "background: ${};", None, None, CURRENT_OS, "background: red;"), - ("red", "background: ${};", None, None, ANOTHER_OS, ""), - ("red", "background: ${};", CURRENT_QT_VERSION, CURRENT_QT_API, CURRENT_OS, "background: red;"), - ("red", "background: ${};", ANOTHER_QT_VERSION, CURRENT_QT_API, CURRENT_OS, ""), - ("red", "background: ${};", CURRENT_QT_VERSION, ANOTHER_QT_API, CURRENT_OS, ""), - ("red", "background: ${};", CURRENT_QT_VERSION, CURRENT_QT_API, ANOTHER_OS, ""), - ], -) -def test_env_filter(text, output, qt_version, qt_api, os_name, expected_result) -> None: - """Verify that the function `_Filter.env()` runs successfully when inputting various arguments.""" - assert filter.env(text, output, qt_version, qt_api, os_name) == expected_result - - -@pytest.mark.parametrize( - ("color_info", "color_state", "expected_result"), - [ - ("#121212", None, Color.from_hex("#121212")._to_hex()), - ({"base": "#121212"}, None, Color.from_hex("#121212")._to_hex()), - ( - {"base": "#121212", "test state": {"transparent": 0.5}}, - "test state", - Color.from_hex("#121212").transparent(0.5)._to_hex(), - ), - ( - {"base": "#121212", "test state": {"darken": 0.5}}, - "test state", - Color.from_hex("#121212").darken(0.5)._to_hex(), - ), - ( - {"base": "#121212", "test state": {"lighten": 0.5}}, - "test state", - Color.from_hex("#121212").lighten(0.5)._to_hex(), - ), - ( - {"base": "#121212", "test state": {"transparent": 0.5, "darken": 0.5, "lighten": 0.5}}, - "test state", - Color.from_hex("#121212").transparent(0.5).darken(0.5).lighten(0.5)._to_hex(), - ), - ], -) -def test_color_filter(color_info, color_state, expected_result) -> None: - """Verify that the function `_Filter.color()` runs successfully when inputting various arguments.""" - assert filter.color(color_info, color_state)._to_hex() == expected_result +"""Tests for the main program.""" +import platform + +import pytest + +from qdarktheme._color import Color +from qdarktheme._template import filter +from qdarktheme.qtpy.qt_compat import QT_API +from qdarktheme.qtpy.qt_version import __version__ + +CURRENT_QT_VERSION = f'=={"10.0.0" if __version__ is None else __version__}' +ANOTHER_QT_VERSION = f'=={"6.0.0" if CURRENT_QT_VERSION == "5.15.0" else "5.15.0"}' +CURRENT_QT_API = "PySide6" if QT_API is None else QT_API +ANOTHER_QT_API = "PyQt5" if CURRENT_QT_API == "PySide6" else "PySide6" +CURRENT_OS = platform.system() +ANOTHER_OS = "Linux" if CURRENT_OS.lower() == "windows" else "windows" + + +@pytest.mark.parametrize( + ("text", "output", "qt_version", "qt_api", "os_name", "expected_result"), + [ + ("", "background: red;", None, None, None, "background: red;"), + ("blue", "background: red;", None, None, None, "background: red;"), + ("red", "background: ${};", None, None, None, "background: red;"), + ("", "background: ${};", None, None, None, "background: ;"), + ("red", "background: ${};", CURRENT_QT_VERSION, None, None, "background: red;"), + ("red", "background: ${};", ANOTHER_QT_VERSION, None, None, ""), + ("red", "background: ${};", None, CURRENT_QT_API, None, "background: red;"), + ("red", "background: ${};", None, ANOTHER_QT_API, None, ""), + ("red", "background: ${};", None, None, CURRENT_OS, "background: red;"), + ("red", "background: ${};", None, None, ANOTHER_OS, ""), + ("red", "background: ${};", CURRENT_QT_VERSION, CURRENT_QT_API, CURRENT_OS, "background: red;"), + ("red", "background: ${};", ANOTHER_QT_VERSION, CURRENT_QT_API, CURRENT_OS, ""), + ("red", "background: ${};", CURRENT_QT_VERSION, ANOTHER_QT_API, CURRENT_OS, ""), + ("red", "background: ${};", CURRENT_QT_VERSION, CURRENT_QT_API, ANOTHER_OS, ""), + ], +) +def test_env_filter(text, output, qt_version, qt_api, os_name, expected_result) -> None: + """Verify that the function `_Filter.env()` runs successfully when inputting various arguments.""" + assert filter.env(text, output, qt_version, qt_api, os_name) == expected_result + + +@pytest.mark.parametrize( + ("color_info", "color_state", "expected_result"), + [ + ("#121212", None, Color.from_hex("#121212")._to_hex()), + ({"base": "#121212"}, None, Color.from_hex("#121212")._to_hex()), + ( + {"base": "#121212", "test state": {"transparent": 0.5}}, + "test state", + Color.from_hex("#121212").transparent(0.5)._to_hex(), + ), + ( + {"base": "#121212", "test state": {"darken": 0.5}}, + "test state", + Color.from_hex("#121212").darken(0.5)._to_hex(), + ), + ( + {"base": "#121212", "test state": {"lighten": 0.5}}, + "test state", + Color.from_hex("#121212").lighten(0.5)._to_hex(), + ), + ( + {"base": "#121212", "test state": {"transparent": 0.5, "darken": 0.5, "lighten": 0.5}}, + "test state", + Color.from_hex("#121212").transparent(0.5).darken(0.5).lighten(0.5)._to_hex(), + ), + ], +) +def test_color_filter(color_info, color_state, expected_result) -> None: + """Verify that the function `_Filter.color()` runs successfully when inputting various arguments.""" + assert filter.color(color_info, color_state)._to_hex() == expected_result diff --git a/tests/test_qdarktheme.py b/tests/test_qdarktheme.py index 807fc4ed..b3efb96a 100644 --- a/tests/test_qdarktheme.py +++ b/tests/test_qdarktheme.py @@ -1,115 +1,115 @@ -"""Tests for the main program.""" -import re - -import pytest - -import qdarktheme - - -@pytest.mark.parametrize( - ("theme", "corner_shape", "custom_colors"), - [ - # Test theme - ("dark", "rounded", None), - ("light", "rounded", None), - # Test corner_shape - ("dark", "sharp", None), - # Test corner_shape and custom_colors - ("dark", "rounded", {}), - ("dark", "rounded", {"foreground": "#112233"}), - ("dark", "sharp", {"foreground": "#112233"}), - ("dark", "rounded", {"foreground>icon": "#112233"}), - ("dark", "rounded", {"input.background": "#112233"}), - # Test color code - ("dark", "rounded", {"foreground": "#123"}), # hex size 3 - ("dark", "rounded", {"foreground": "#1234"}), # hex size 4 - ("dark", "rounded", {"foreground": "#12345678"}), # hex size 8 - # Test automatic theme - ("auto", "rounded", None), - ("auto", "rounded", {"foreground": "#112233"}), - ("auto", "rounded", {"[dark]": {"foreground": "#112233"}}), - ("auto", "rounded", {"foreground": "#112233", "[dark]": {"foreground": "#112233"}}), - ("auto", "rounded", {"foreground": "#112233", "[light]": {"foreground": "#112233"}}), - ( - "auto", - "rounded", - {"[dark]": {"foreground": "#112233"}, "[light]": {"foreground": "#112233"}}, - ), - ], -) -def test_load_stylesheet(theme, corner_shape, custom_colors) -> None: - """Verify that the function `load_stylesheet()` runs successfully when using various arguments.""" - qdarktheme.load_stylesheet(theme, corner_shape, custom_colors) - - -def test_load_stylesheet_with_wrong_theme() -> None: - """Verify we raise ValueError when using wrong theme name.""" - with pytest.raises(ValueError, match='invalid argument, not a dark, light or auto: "wrong_value"'): - qdarktheme.load_stylesheet("wrong_value") - - -def test_load_stylesheet_with_wrong_corner_shape() -> None: - """Verify we raise ValueError when using wrong corner shape name.""" - with pytest.raises(ValueError, match='invalid argument, not a rounded or sharp: "wrong_value"'): - qdarktheme.load_stylesheet(corner_shape="wrong_value") - - -def test_load_stylesheet_with_wrong_color_format() -> None: - """Verify we raise ValueError when using wrong color format.""" - with pytest.raises( - ValueError, - match=re.escape( - 'invalid hex color format: "wrong color code". ' - "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " - "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " - "(0-9, a-f or A-F)." - ), - ): - qdarktheme.load_stylesheet(custom_colors={"input.background": "wrong color code"}) - - -def test_load_stylesheet_with_wrong_custom_colors() -> None: - """Verify we raise ValueError when using wrong custom colors.""" - with pytest.raises( - ValueError, - match=re.escape( - 'invalid value for argument custom_colors, not a dict type: "#1234" of "[dark]" key.' - ), - ): - qdarktheme.load_stylesheet(custom_colors={"[dark]": "#1234"}) - - -@pytest.mark.parametrize( - "wrong_color_id", - [ - ("wrong id"), - ("background>wrong child key"), - ("background>wrong key>wrong key"), - ], -) -def test_load_stylesheet_with_wrong_color_id(wrong_color_id) -> None: - """Verify we raise ValueError when using wrong color id.""" - with pytest.raises(KeyError): - qdarktheme.load_stylesheet(custom_colors={wrong_color_id: "#121212"}) - - -def test_load_stylesheet_when_failing_to_detect_system_theme(mocker) -> None: - """Verify `load_stylesheet(theme="auto")` works when failing to detect system theme.""" - mocker.patch("darkdetect.theme", return_value=None) - qdarktheme.load_stylesheet("auto") - - -def test_clear_cache() -> None: - """Verify `clear_cache()`.""" - qdarktheme.load_stylesheet() - qdarktheme.clear_cache() - # Test function when there is no cache. - qdarktheme.clear_cache() - - -def test_get_themes() -> None: - """Verify `get_themes()` works. - - get_themes() is not called from other functions, so it should be tested. - """ - qdarktheme.get_themes() +"""Tests for the main program.""" +import re + +import pytest + +import qdarktheme + + +@pytest.mark.parametrize( + ("theme", "corner_shape", "custom_colors"), + [ + # Test theme + ("dark", "rounded", None), + ("light", "rounded", None), + # Test corner_shape + ("dark", "sharp", None), + # Test corner_shape and custom_colors + ("dark", "rounded", {}), + ("dark", "rounded", {"foreground": "#112233"}), + ("dark", "sharp", {"foreground": "#112233"}), + ("dark", "rounded", {"foreground>icon": "#112233"}), + ("dark", "rounded", {"input.background": "#112233"}), + # Test color code + ("dark", "rounded", {"foreground": "#123"}), # hex size 3 + ("dark", "rounded", {"foreground": "#1234"}), # hex size 4 + ("dark", "rounded", {"foreground": "#12345678"}), # hex size 8 + # Test automatic theme + ("auto", "rounded", None), + ("auto", "rounded", {"foreground": "#112233"}), + ("auto", "rounded", {"[dark]": {"foreground": "#112233"}}), + ("auto", "rounded", {"foreground": "#112233", "[dark]": {"foreground": "#112233"}}), + ("auto", "rounded", {"foreground": "#112233", "[light]": {"foreground": "#112233"}}), + ( + "auto", + "rounded", + {"[dark]": {"foreground": "#112233"}, "[light]": {"foreground": "#112233"}}, + ), + ], +) +def test_load_stylesheet(theme, corner_shape, custom_colors) -> None: + """Verify that the function `load_stylesheet()` runs successfully when using various arguments.""" + qdarktheme.load_stylesheet(theme, corner_shape, custom_colors) + + +def test_load_stylesheet_with_wrong_theme() -> None: + """Verify we raise ValueError when using wrong theme name.""" + with pytest.raises(ValueError, match='invalid argument, not a dark, light or auto: "wrong_value"'): + qdarktheme.load_stylesheet("wrong_value") + + +def test_load_stylesheet_with_wrong_corner_shape() -> None: + """Verify we raise ValueError when using wrong corner shape name.""" + with pytest.raises(ValueError, match='invalid argument, not a rounded or sharp: "wrong_value"'): + qdarktheme.load_stylesheet(corner_shape="wrong_value") + + +def test_load_stylesheet_with_wrong_color_format() -> None: + """Verify we raise ValueError when using wrong color format.""" + with pytest.raises( + ValueError, + match=re.escape( + 'invalid hex color format: "wrong color code". ' + "Only support following hexadecimal notations: #RGB, #RGBA, #RRGGBB and #RRGGBBAA. " + "R (red), G (green), B (blue), and A (alpha) are hexadecimal characters " + "(0-9, a-f or A-F)." + ), + ): + qdarktheme.load_stylesheet(custom_colors={"input.background": "wrong color code"}) + + +def test_load_stylesheet_with_wrong_custom_colors() -> None: + """Verify we raise ValueError when using wrong custom colors.""" + with pytest.raises( + ValueError, + match=re.escape( + 'invalid value for argument custom_colors, not a dict type: "#1234" of "[dark]" key.' + ), + ): + qdarktheme.load_stylesheet(custom_colors={"[dark]": "#1234"}) + + +@pytest.mark.parametrize( + "wrong_color_id", + [ + ("wrong id"), + ("background>wrong child key"), + ("background>wrong key>wrong key"), + ], +) +def test_load_stylesheet_with_wrong_color_id(wrong_color_id) -> None: + """Verify we raise ValueError when using wrong color id.""" + with pytest.raises(KeyError): + qdarktheme.load_stylesheet(custom_colors={wrong_color_id: "#121212"}) + + +def test_load_stylesheet_when_failing_to_detect_system_theme(mocker) -> None: + """Verify `load_stylesheet(theme="auto")` works when failing to detect system theme.""" + mocker.patch("darkdetect.theme", return_value=None) + qdarktheme.load_stylesheet("auto") + + +def test_clear_cache() -> None: + """Verify `clear_cache()`.""" + qdarktheme.load_stylesheet() + qdarktheme.clear_cache() + # Test function when there is no cache. + qdarktheme.clear_cache() + + +def test_get_themes() -> None: + """Verify `get_themes()` works. + + get_themes() is not called from other functions, so it should be tested. + """ + qdarktheme.get_themes() diff --git a/tests/test_qdarktheme_with_qt.py b/tests/test_qdarktheme_with_qt.py index e7706f13..5665beba 100644 --- a/tests/test_qdarktheme_with_qt.py +++ b/tests/test_qdarktheme_with_qt.py @@ -1,92 +1,92 @@ -"""Tests for the main program with Qt.""" -import platform -import re - -import pytest - -import qdarktheme -from qdarktheme.qtpy.QtGui import QGuiApplication, QPalette - - -@pytest.mark.parametrize( - ("theme", "custom_colors"), - [ - # Test theme - ("dark", None), - ("light", None), - # Test theme and custom_colors - ("dark", {}), - ("dark", {"foreground": "#112233"}), - ("dark", {"foreground>icon": "#112233"}), - # Test color code - ("dark", {"foreground": "#112"}), - ("dark", {"foreground": "#11223344"}), - ("dark", {"foreground": "#1122"}), - # Test automatic theme - ("auto", None), - ("auto", {"foreground": "#112233"}), - ("auto", {"[dark]": {"foreground": "#112233"}}), - ("auto", {"foreground": "#112233", "[dark]": {"foreground": "#112233"}}), - ("auto", {"foreground": "#112233", "[light]": {"foreground": "#112233"}}), - ("auto", {"[dark]": {"foreground": "#112233"}, "[light]": {"foreground": "#112233"}}), - ], -) -def test_load_palette(theme, custom_colors) -> None: - """Verify that the function `load_stylesheet()` runs successfully when using various arguments.""" - qdarktheme.load_palette(theme, custom_colors) - - -def test_apply_stylesheet_to_qt_app(qapp: QGuiApplication) -> None: - """Verify that the function `load_stylesheet()` runs without error.""" - qapp.setStyleSheet(qdarktheme.load_stylesheet()) # type: ignore - - -def test_apply_palette_to_qt_app(qapp: QGuiApplication) -> None: - """Verify that the function `load_palette()` runs without error.""" - qapp.setPalette(qdarktheme.load_palette()) - - -@pytest.mark.parametrize( - ("theme", "additional_qss"), - [ - ("dark", None), - ("light", None), - ("dark", "QWidget{color: red;}"), - ], -) -def test_setup_theme(qapp, theme, additional_qss) -> None: - """Verify that the function `setup_theme()` runs without error.""" - qdarktheme.setup_theme(theme, additional_qss=additional_qss) - - -def test_enable_high_dpi(qapp) -> None: - """Verify that the function `enable_high_dpi()` runs without error.""" - qdarktheme.enable_hi_dpi() - - -def test_stop_sync(qapp) -> None: - """Verify that the function `stop_sync()` runs without error.""" - qdarktheme.setup_theme("auto") - qdarktheme.stop_sync() - - -def test_setup_theme_without_qapp(mocker) -> None: - """Verify we raise Exception when qapp is none.""" - mocker.patch("qdarktheme.qtpy.QtCore.QCoreApplication.instance", return_value=None) - with pytest.raises( - Exception, match=re.escape("setup_theme() must be called after instantiation of QApplication.") - ): - qdarktheme.setup_theme() - - -if platform.system() == "Darwin": - - def test_theme_event_filter(qapp: QGuiApplication, mocker) -> None: - """Verify that the internal class `ThemeEventFilter` runs without error.""" - qdarktheme.setup_theme("auto") - mocker.patch("darkdetect.theme", return_value="light") - qapp.setPalette(QPalette()) - mocker.patch("darkdetect.theme", return_value="dark") - qapp.setPalette(QPalette()) - mocker.patch("qdarktheme._os_appearance.accent", return_value="red") - qapp.setPalette(QPalette()) +"""Tests for the main program with Qt.""" +import platform +import re + +import pytest + +import qdarktheme +from qdarktheme.qtpy.QtGui import QGuiApplication, QPalette + + +@pytest.mark.parametrize( + ("theme", "custom_colors"), + [ + # Test theme + ("dark", None), + ("light", None), + # Test theme and custom_colors + ("dark", {}), + ("dark", {"foreground": "#112233"}), + ("dark", {"foreground>icon": "#112233"}), + # Test color code + ("dark", {"foreground": "#112"}), + ("dark", {"foreground": "#11223344"}), + ("dark", {"foreground": "#1122"}), + # Test automatic theme + ("auto", None), + ("auto", {"foreground": "#112233"}), + ("auto", {"[dark]": {"foreground": "#112233"}}), + ("auto", {"foreground": "#112233", "[dark]": {"foreground": "#112233"}}), + ("auto", {"foreground": "#112233", "[light]": {"foreground": "#112233"}}), + ("auto", {"[dark]": {"foreground": "#112233"}, "[light]": {"foreground": "#112233"}}), + ], +) +def test_load_palette(theme, custom_colors) -> None: + """Verify that the function `load_stylesheet()` runs successfully when using various arguments.""" + qdarktheme.load_palette(theme, custom_colors) + + +def test_apply_stylesheet_to_qt_app(qapp: QGuiApplication) -> None: + """Verify that the function `load_stylesheet()` runs without error.""" + qapp.setStyleSheet(qdarktheme.load_stylesheet()) # type: ignore + + +def test_apply_palette_to_qt_app(qapp: QGuiApplication) -> None: + """Verify that the function `load_palette()` runs without error.""" + qapp.setPalette(qdarktheme.load_palette()) + + +@pytest.mark.parametrize( + ("theme", "additional_qss"), + [ + ("dark", None), + ("light", None), + ("dark", "QWidget{color: red;}"), + ], +) +def test_setup_theme(qapp, theme, additional_qss) -> None: + """Verify that the function `setup_theme()` runs without error.""" + qdarktheme.setup_theme(theme, additional_qss=additional_qss) + + +def test_enable_high_dpi(qapp) -> None: + """Verify that the function `enable_high_dpi()` runs without error.""" + qdarktheme.enable_hi_dpi() + + +def test_stop_sync(qapp) -> None: + """Verify that the function `stop_sync()` runs without error.""" + qdarktheme.setup_theme("auto") + qdarktheme.stop_sync() + + +def test_setup_theme_without_qapp(mocker) -> None: + """Verify we raise Exception when qapp is none.""" + mocker.patch("qdarktheme.qtpy.QtCore.QCoreApplication.instance", return_value=None) + with pytest.raises( + Exception, match=re.escape("setup_theme() must be called after instantiation of QApplication.") + ): + qdarktheme.setup_theme() + + +if platform.system() == "Darwin": + + def test_theme_event_filter(qapp: QGuiApplication, mocker) -> None: + """Verify that the internal class `ThemeEventFilter` runs without error.""" + qdarktheme.setup_theme("auto") + mocker.patch("darkdetect.theme", return_value="light") + qapp.setPalette(QPalette()) + mocker.patch("darkdetect.theme", return_value="dark") + qapp.setPalette(QPalette()) + mocker.patch("qdarktheme._os_appearance.accent", return_value="red") + qapp.setPalette(QPalette()) diff --git a/tests/test_svg.py b/tests/test_svg.py index 271c2dd1..5bf71f05 100644 --- a/tests/test_svg.py +++ b/tests/test_svg.py @@ -1,47 +1,47 @@ -"""Test for the SVG manager.""" -import pytest - -from qdarktheme._color import Color -from qdarktheme._icon.svg import Svg - - -@pytest.mark.parametrize( - ("rgba", "rotate", "svg_source", "expected_result"), - [ - ((0, 0, 0, 255), 0, '', ''), - ( - (0, 0, 0, 0), - 0, - '', - '', - ), - ( - (0, 0, 0, 255), - 90, - '', - '', - ), - ], -) -def test_svg(mocker, rgba, rotate, svg_source, expected_result): - """Verify that Svg class build correct SVG.""" - mocker.patch("qdarktheme._icon.svg._svg_resources", return_value={"dummy": svg_source}) - assert str(Svg("dummy").colored(Color.from_rgba(*rgba)).rotate(rotate)) == expected_result - - -def test_svg_colored_chain(): - """Verify that ``Svg.colored`` chain build correct SVG.""" - svg1 = Svg("arrow_upward").colored(Color.from_hex("#ff0000")) - svg2 = Svg("arrow_upward").colored(Color.from_hex("#ffffff55")).colored(Color.from_hex("#ff0000")) - assert str(svg1) == str(svg2) - - svg3 = Svg("arrow_upward").colored(Color.from_hex("#ffffff30")) - svg4 = Svg("arrow_upward").colored(Color.from_hex("#ffffff90")).colored(Color.from_hex("#ffffff30")) - assert str(svg3) == str(svg4) - - -def test_svg_rotate_chain(): - """Verify that ``Svg.rotate`` chain build correct SVG.""" - svg1 = Svg("arrow_upward").rotate(90) - svg2 = Svg("arrow_upward").rotate(180).rotate(90) - assert str(svg1) == str(svg2) +"""Test for the SVG manager.""" +import pytest + +from qdarktheme._color import Color +from qdarktheme._icon.svg import Svg + + +@pytest.mark.parametrize( + ("rgba", "rotate", "svg_source", "expected_result"), + [ + ((0, 0, 0, 255), 0, '', ''), + ( + (0, 0, 0, 0), + 0, + '', + '', + ), + ( + (0, 0, 0, 255), + 90, + '', + '', + ), + ], +) +def test_svg(mocker, rgba, rotate, svg_source, expected_result): + """Verify that Svg class build correct SVG.""" + mocker.patch("qdarktheme._icon.svg._svg_resources", return_value={"dummy": svg_source}) + assert str(Svg("dummy").colored(Color.from_rgba(*rgba)).rotate(rotate)) == expected_result + + +def test_svg_colored_chain(): + """Verify that ``Svg.colored`` chain build correct SVG.""" + svg1 = Svg("arrow_upward").colored(Color.from_hex("#ff0000")) + svg2 = Svg("arrow_upward").colored(Color.from_hex("#ffffff55")).colored(Color.from_hex("#ff0000")) + assert str(svg1) == str(svg2) + + svg3 = Svg("arrow_upward").colored(Color.from_hex("#ffffff30")) + svg4 = Svg("arrow_upward").colored(Color.from_hex("#ffffff90")).colored(Color.from_hex("#ffffff30")) + assert str(svg3) == str(svg4) + + +def test_svg_rotate_chain(): + """Verify that ``Svg.rotate`` chain build correct SVG.""" + svg1 = Svg("arrow_upward").rotate(90) + svg2 = Svg("arrow_upward").rotate(180).rotate(90) + assert str(svg1) == str(svg2) diff --git a/tests/test_template_engine.py b/tests/test_template_engine.py index 55c9ffcf..65b929c5 100644 --- a/tests/test_template_engine.py +++ b/tests/test_template_engine.py @@ -1,51 +1,51 @@ -"""Tests for the template engine.""" -import pytest - -from qdarktheme._template.engine import Template - - -@pytest.mark.parametrize( - ("text_with_placeholder", "replacements", "filters", "expected_result"), - [ - ("{{ background }}", {"background": "red"}, {}, "red"), - ("background-color: {{ background }};", {"background": "red"}, {}, "background-color: red;"), - ( - "border: {{ size }}px {{ style }} {{ background }};", - {"size": "2", "style": "solid", "background": "grey"}, - {}, - "border: 2px solid grey;", - ), - # Test without placeholder - ("background: blue;", {"background": "red"}, {}, "background: blue;"), - # Test int - ("{{ 100 }}", {}, {}, "100"), - # Test float - ("{{ 0.5 }}", {}, {}, "0.5"), - # Test filter - ("{{ size|filter }}", {"size": "5"}, {"filter": lambda a: f"{a}px"}, "5px"), - # Test chan filters - ( - "{{ size|filter1|filter2 }}", - {"size": "5"}, - {"filter1": lambda a: f"{a}px", "filter2": lambda a: f"{a};"}, - "5px;", - ), - ], -) -def test_template_engine(text_with_placeholder, replacements, filters, expected_result) -> None: - """Verify template engine runs without error when using various arguments.""" - assert Template(text_with_placeholder, filters).render(replacements) == expected_result - - -@pytest.mark.parametrize( - ("text_with_placeholder", "replacements"), - [ - ("{{ background }}", {}), - ("{{ background }}", {"border": "#FFFFFF"}), - ("background-color: {{ background }};", {"border": "#FFFFFF"}), - ], -) -def test_template_engine_wrong_replacements(text_with_placeholder, replacements) -> None: - """Verify we raise AssertionError when using wrong replacements.""" - with pytest.raises(AssertionError): - Template(text_with_placeholder, {}).render(replacements) +"""Tests for the template engine.""" +import pytest + +from qdarktheme._template.engine import Template + + +@pytest.mark.parametrize( + ("text_with_placeholder", "replacements", "filters", "expected_result"), + [ + ("{{ background }}", {"background": "red"}, {}, "red"), + ("background-color: {{ background }};", {"background": "red"}, {}, "background-color: red;"), + ( + "border: {{ size }}px {{ style }} {{ background }};", + {"size": "2", "style": "solid", "background": "grey"}, + {}, + "border: 2px solid grey;", + ), + # Test without placeholder + ("background: blue;", {"background": "red"}, {}, "background: blue;"), + # Test int + ("{{ 100 }}", {}, {}, "100"), + # Test float + ("{{ 0.5 }}", {}, {}, "0.5"), + # Test filter + ("{{ size|filter }}", {"size": "5"}, {"filter": lambda a: f"{a}px"}, "5px"), + # Test chan filters + ( + "{{ size|filter1|filter2 }}", + {"size": "5"}, + {"filter1": lambda a: f"{a}px", "filter2": lambda a: f"{a};"}, + "5px;", + ), + ], +) +def test_template_engine(text_with_placeholder, replacements, filters, expected_result) -> None: + """Verify template engine runs without error when using various arguments.""" + assert Template(text_with_placeholder, filters).render(replacements) == expected_result + + +@pytest.mark.parametrize( + ("text_with_placeholder", "replacements"), + [ + ("{{ background }}", {}), + ("{{ background }}", {"border": "#FFFFFF"}), + ("background-color: {{ background }};", {"border": "#FFFFFF"}), + ], +) +def test_template_engine_wrong_replacements(text_with_placeholder, replacements) -> None: + """Verify we raise AssertionError when using wrong replacements.""" + with pytest.raises(AssertionError): + Template(text_with_placeholder, {}).render(replacements) diff --git a/tests/test_util.py b/tests/test_util.py index e7b544fa..6b817d4e 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -1,54 +1,54 @@ -"""Test utility methods in qdarktheme/util.py.""" -import pytest - -from qdarktheme._util import analyze_version_str, multi_replace - - -def test_multi_replace() -> None: - """Test `multi_replace()`.""" - target = "Dark theme for PySide and PyQt." - empty = {} - multi_replace(target, empty) - - replacements = {"Dark": "Light", "PySide": "PyQt", "PyQt": "PySide"} - multi_replace(target, replacements) - - -def test_analyze_version_str() -> None: - """Test `analyze_version_str()`.""" - # Test "==" - assert analyze_version_str(target_version="5.0.0", version_text="==5.0.0") - assert not analyze_version_str(target_version="5.1.2", version_text="==5.0.0") - # Test "!=" - assert analyze_version_str(target_version="5.0.0", version_text="!=6.0.1") - assert not analyze_version_str(target_version="6.0.1", version_text="!=6.0.1") - # Test ">=" - assert analyze_version_str(target_version="5.12.0", version_text=">=5.10.5") - assert analyze_version_str(target_version="5.10.5", version_text=">=5.10.5") - assert not analyze_version_str(target_version="5.9.1", version_text=">=5.10.5") - # Test ">" - assert analyze_version_str(target_version="5.12.0", version_text=">5.10.5") - assert not analyze_version_str(target_version="5.10.5", version_text=">5.10.5") - assert not analyze_version_str(target_version="5.9.1", version_text=">5.10.5") - # Test "<=" - assert analyze_version_str(target_version="6.2.2", version_text="<=6.3.1") - assert analyze_version_str(target_version="6.3.1", version_text="<=6.3.1") - assert not analyze_version_str(target_version="6.4.0", version_text="<=6.3.1") - # Test "<" - assert analyze_version_str(target_version="6.2.2", version_text="<6.3.1") - assert not analyze_version_str(target_version="6.3.1", version_text="<6.3.1") - assert not analyze_version_str(target_version="6.4.0", version_text="<6.3.1") - - -@pytest.mark.parametrize( - ("target_version", "version_text"), - [ - ("6.2.2", "5.12.0"), - ("6.2.2", "a5.12.0"), - ("6.2.2", "-5.12.0"), - ], -) -def test_analyze_wrong_version_str(target_version, version_text) -> None: - """Verify we raise ValueError when using wrong version_str.""" - with pytest.raises(AssertionError): - analyze_version_str(target_version, version_text) +"""Test utility methods in qdarktheme/util.py.""" +import pytest + +from qdarktheme._util import analyze_version_str, multi_replace + + +def test_multi_replace() -> None: + """Test `multi_replace()`.""" + target = "Dark theme for PySide and PyQt." + empty = {} + multi_replace(target, empty) + + replacements = {"Dark": "Light", "PySide": "PyQt", "PyQt": "PySide"} + multi_replace(target, replacements) + + +def test_analyze_version_str() -> None: + """Test `analyze_version_str()`.""" + # Test "==" + assert analyze_version_str(target_version="5.0.0", version_text="==5.0.0") + assert not analyze_version_str(target_version="5.1.2", version_text="==5.0.0") + # Test "!=" + assert analyze_version_str(target_version="5.0.0", version_text="!=6.0.1") + assert not analyze_version_str(target_version="6.0.1", version_text="!=6.0.1") + # Test ">=" + assert analyze_version_str(target_version="5.12.0", version_text=">=5.10.5") + assert analyze_version_str(target_version="5.10.5", version_text=">=5.10.5") + assert not analyze_version_str(target_version="5.9.1", version_text=">=5.10.5") + # Test ">" + assert analyze_version_str(target_version="5.12.0", version_text=">5.10.5") + assert not analyze_version_str(target_version="5.10.5", version_text=">5.10.5") + assert not analyze_version_str(target_version="5.9.1", version_text=">5.10.5") + # Test "<=" + assert analyze_version_str(target_version="6.2.2", version_text="<=6.3.1") + assert analyze_version_str(target_version="6.3.1", version_text="<=6.3.1") + assert not analyze_version_str(target_version="6.4.0", version_text="<=6.3.1") + # Test "<" + assert analyze_version_str(target_version="6.2.2", version_text="<6.3.1") + assert not analyze_version_str(target_version="6.3.1", version_text="<6.3.1") + assert not analyze_version_str(target_version="6.4.0", version_text="<6.3.1") + + +@pytest.mark.parametrize( + ("target_version", "version_text"), + [ + ("6.2.2", "5.12.0"), + ("6.2.2", "a5.12.0"), + ("6.2.2", "-5.12.0"), + ], +) +def test_analyze_wrong_version_str(target_version, version_text) -> None: + """Verify we raise ValueError when using wrong version_str.""" + with pytest.raises(AssertionError): + analyze_version_str(target_version, version_text) diff --git a/tests/test_widget_gallery.py b/tests/test_widget_gallery.py index ebd74ac4..c7d16303 100644 --- a/tests/test_widget_gallery.py +++ b/tests/test_widget_gallery.py @@ -1,31 +1,31 @@ -"""Test WidgetGallery.""" -import pytest -from pytestqt.qtbot import QtBot - -from qdarktheme.widget_gallery.__main__ import WidgetGallery - - -@pytest.fixture() -def widget_gallery(qtbot: QtBot) -> WidgetGallery: - """Create test instance of WidgetGallery.""" - widget_gallery = WidgetGallery() - qtbot.add_widget(widget_gallery) - widget_gallery.show() - return widget_gallery - - -def test_actions(widget_gallery: WidgetGallery, monkeypatch: pytest.MonkeyPatch) -> None: - """Ensure the actions work correctly.""" - from qdarktheme.qtpy.QtWidgets import QMessageBox - - for message_type in ("question", "information", "warning", "critical"): - monkeypatch.setattr(QMessageBox, message_type, lambda a, b, c: (a, b, c)) - - actions = [widget_gallery._ui.action_enable, widget_gallery._ui.action_disable] - actions += widget_gallery._ui.actions_page - actions += widget_gallery._ui.actions_theme - actions += widget_gallery._ui.actions_message_box - actions += widget_gallery._ui.actions_message_box - actions += widget_gallery._ui.actions_corner_radius - for action in actions: - action.triggered.emit() +"""Test WidgetGallery.""" +import pytest +from pytestqt.qtbot import QtBot + +from qdarktheme.widget_gallery.__main__ import WidgetGallery + + +@pytest.fixture() +def widget_gallery(qtbot: QtBot) -> WidgetGallery: + """Create test instance of WidgetGallery.""" + widget_gallery = WidgetGallery() + qtbot.add_widget(widget_gallery) + widget_gallery.show() + return widget_gallery + + +def test_actions(widget_gallery: WidgetGallery, monkeypatch: pytest.MonkeyPatch) -> None: + """Ensure the actions work correctly.""" + from qdarktheme.qtpy.QtWidgets import QMessageBox + + for message_type in ("question", "information", "warning", "critical"): + monkeypatch.setattr(QMessageBox, message_type, lambda a, b, c: (a, b, c)) + + actions = [widget_gallery._ui.action_enable, widget_gallery._ui.action_disable] + actions += widget_gallery._ui.actions_page + actions += widget_gallery._ui.actions_theme + actions += widget_gallery._ui.actions_message_box + actions += widget_gallery._ui.actions_message_box + actions += widget_gallery._ui.actions_corner_radius + for action in actions: + action.triggered.emit() diff --git a/tools/__init__.py b/tools/__init__.py index 35359be4..b2b2a973 100644 --- a/tools/__init__.py +++ b/tools/__init__.py @@ -1 +1 @@ -"""Tools for PyQtDarkTheme.""" +"""Tools for PyQtDarkTheme.""" diff --git a/tools/_util.py b/tools/_util.py index 83a68e28..ed5b4a89 100644 --- a/tools/_util.py +++ b/tools/_util.py @@ -1,21 +1,21 @@ -"""Utility methods for tools.""" -from __future__ import annotations - -import inspect -from pathlib import Path - -import tools - - -def get_project_root_path() -> Path: - """Return the project root path. - - Returns: - Project root path. - """ - return Path(inspect.getfile(tools)).parent.parent - - -def get_style_path() -> Path: - """Return the style root path.""" - return Path(__file__).parent.parent / "style" +"""Utility methods for tools.""" +from __future__ import annotations + +import inspect +from pathlib import Path + +import tools + + +def get_project_root_path() -> Path: + """Return the project root path. + + Returns: + Project root path. + """ + return Path(inspect.getfile(tools)).parent.parent + + +def get_style_path() -> Path: + """Return the style root path.""" + return Path(__file__).parent.parent / "style" diff --git a/tools/build_styles/README.md b/tools/build_styles/README.md index b68db6f0..849e2af4 100644 --- a/tools/build_styles/README.md +++ b/tools/build_styles/README.md @@ -1,17 +1,17 @@ -# Build style resources - -This tool build template stylesheet, svg, QPalette script and dark/light color values. -The resources are output to `{projectRootFolder}/qdarktheme/_resources`. - -## Output format - -``` shell -resources -├── __init__.py -├── _color_values.py -├── _palette.py -├── _svg.py -└── _template_stylesheet.py -``` - -Original data is in `{projectRootFolder}/style`. For more information about styles, see the `{projectRootFolder}/style/README.md`. +# Build style resources + +This tool build template stylesheet, svg, QPalette script and dark/light color values. +The resources are output to `{projectRootFolder}/qdarktheme/_resources`. + +## Output format + +``` shell +resources +├── __init__.py +├── _color_values.py +├── _palette.py +├── _svg.py +└── _template_stylesheet.py +``` + +Original data is in `{projectRootFolder}/style`. For more information about styles, see the `{projectRootFolder}/style/README.md`. diff --git a/tools/build_styles/__main__.py b/tools/build_styles/__main__.py index 5a730679..9e82b5a9 100644 --- a/tools/build_styles/__main__.py +++ b/tools/build_styles/__main__.py @@ -1,5 +1,5 @@ -"""Module allowing for `python -m tools.build_styles`.""" -from tools.build_styles._main import main - -if __name__ == "__main__": - main() +"""Module allowing for `python -m tools.build_styles`.""" +from tools.build_styles._main import main + +if __name__ == "__main__": + main() diff --git a/tools/build_styles/_main.py b/tools/build_styles/_main.py index 76d56af4..1c1aabef 100644 --- a/tools/build_styles/_main.py +++ b/tools/build_styles/_main.py @@ -1,238 +1,238 @@ -"""Main module for building style resources for qdarktheme.""" -from __future__ import annotations - -import json -import logging -import re -import shutil -from collections import defaultdict -from filecmp import cmpfiles -from pathlib import Path -from pprint import pformat -from tempfile import TemporaryDirectory - -from tools import material_icons -from tools._util import get_style_path - -logging.basicConfig(level=logging.INFO) - -_ROOT_INIT_DOC = '''"""Package including resources. - -**Warning** - -This package created programmatically. All changes made in this file will be lost! -Created by the `PyQtDarkTheme/tools/build_styles`. - -""" -''' - - -def _get_dist_path() -> Path: - return Path(__file__).parent.parent.parent / "qdarktheme" / "_resources" - - -def _remove_qss_comment(stylesheet: str) -> str: - """Remove qss comment from the stylesheet string.""" - stylesheet = re.sub(r" */\*[\s\S]*?\*/", "", stylesheet) - # Change multi blank lines to one blank line. - return re.sub(r"\n\s*\n", "\n", stylesheet) - - -def _mk_root_init_file(output: Path, themes: list[str], doc_string: str) -> None: - themes.append("auto") - - code = f"{doc_string}\n" - code += "from qdarktheme._resources import " - code += "colors, palette, stylesheets, svg\n\n" - code += f"""THEMES = {str(tuple(themes)).replace("'", '"')}\n""" - (output / "__init__.py").write_text(code) - - -def _mk_svg_resource(svg_dir: Path, output: Path): - material_icons.reflect_icon_conf_changes() - - svg_resources = {svg_file.stem: svg_file.read_text() for svg_file in svg_dir.glob("*/*.svg")} - for name, svg_resource in svg_resources.items(): - svg_resources[name] = re.compile(r'xmlns="[\s\S]*?" ').sub("", svg_resource) - - code = '"""SVG resource."""\n\n' - code += 'SVG_RESOURCES = """\n' - code += json.dumps(svg_resources, sort_keys=True).replace('\\"', '\\\\"') - code += '\n""" # noqa: E501\n' - output.write_text(code) - - -def _mk_standard_icon_map(icon_map_file: Path, output: Path): - standard_icons: dict[str, dict] = json.loads(icon_map_file.read_text()) - - for standard_icon in standard_icons.values(): - if "qss" in standard_icon: - del standard_icon["qss"] - - incompatible_names = ( - "SP_LineEditClearButton", - "SP_DialogYesToAllButton", - "SP_DialogNoToAllButton", - "SP_DialogSaveAllButton", - "SP_DialogAbortButton", - "SP_DialogRetryButton", - "SP_DialogIgnoreButton", - "SP_RestoreDefaultsButton", - "SP_TabCloseButton", - ) - incompatible_standard_icons = { - name: standard_icons.pop(name) for name in list(standard_icons) if name in incompatible_names - } - - icon_map_code = pformat(standard_icons, sort_dicts=True, indent=4) - for icon_name in standard_icons.keys(): - icon_map_code = icon_map_code.replace(f"'{icon_name}'", f"QStyle.StandardPixmap.{icon_name}") - icon_map_code = icon_map_code[0] + "\n " + icon_map_code[1:-1] + ",\n" + icon_map_code[-1] - - add_icon_to_map_code = "" - for icon_name in sorted(incompatible_standard_icons.keys()): - add_icon_to_map_code += f'\nif hasattr(QStyle.StandardPixmap, "{icon_name}"):\n' - add_icon_to_map_code += f" NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.{icon_name}]" - add_icon_to_map_code += ( - f" = {incompatible_standard_icons[icon_name]} # type: ignore # noqa: E501\n" - ) - - code = '"""Icon map that overrides standard icons."""\n' - code += "from qdarktheme.qtpy.QtWidgets import QStyle\n\n" - code += "NEW_STANDARD_ICON_MAP = " - code += icon_map_code.replace("'", '"') + "\n" - code += add_icon_to_map_code.replace("'", '"') - output.write_text(code) - - -def _create_icon_definition(icon_id: str, rotate: int | None, qss_property: str) -> str: - url_placeholder = f'foreground|color(state="icon")|url(id="{icon_id}"' - if rotate is not None: - url_placeholder += f",rotate={rotate}" - url_placeholder += ")" - if qss_property == "lineedit-clear-button-icon": - return f' {{{{{url_placeholder}|env(value="{qss_property}:${{}};",version=">=6.0.0")}}}}' - return f"{qss_property}:{{{{{url_placeholder}}}}}" - - -def _mk_template_standard_icon_stylesheet(icon_map_file: Path) -> str: - standard_icons: dict[str, dict] = json.loads(icon_map_file.read_text()) - definitions_by_selector: defaultdict[tuple[str], set[str]] = defaultdict(set) - - for value in standard_icons.values(): - if "qss" not in value: - continue - qss_widgets: tuple[str, ...] = tuple(sorted(value["qss"]["widgets"])) - definition = _create_icon_definition( - icon_id=value["id"], - rotate=value.get("rotate"), - qss_property=value["qss"]["property"], - ) - definitions_by_selector[qss_widgets].add(definition) - - qss = "" - for selector, definitions in sorted(definitions_by_selector.items()): - qss += ",".join(selector) + "{" - qss += ";".join(sorted(definitions)) + "}" - return qss - - -def _mk_template_stylesheet(base_stylesheet_file: Path, icon_map_file: Path, output: Path): - base_qss = base_stylesheet_file.read_text() - base_qss = _remove_qss_comment(base_qss) - base_qss = base_qss.replace("\n", "") - base_qss = re.sub(r" \s* ", " ", base_qss) - base_qss = re.sub(r"{{.*?}}", lambda match: match.group().replace(" ", ""), base_qss) - base_qss = base_qss.replace(";}", "}").replace("{ ", "{") - base_qss = base_qss.replace("{{{", "{ {{") - base_qss = base_qss.replace(": ", ":").replace("; ", ";") - - code = '"""Template stylesheet."""\n\n' - code += f"TEMPLATE_STYLESHEET = '{base_qss}' # noqa: E501\n" - code += "TEMPLATE_STANDARD_ICONS_STYLESHEET = '" - code += _mk_template_standard_icon_stylesheet(icon_map_file) - code += "' # noqa: E501\n" - output.write_text(code) - - -def _mk_palette_file(template_palette_path: Path, output: Path): - template_palette = template_palette_path.read_text() - output.write_text(template_palette) - - -def _mk_color_resource(color_values: dict[str, dict], accent_colors_file: Path, output: Path): - accent_colors: dict[str, str] = json.loads(accent_colors_file.read_text()) - - code = '"""Default color values."""\n\n' - code += "THEME_COLOR_VALUES = {\n" - for theme, color_value in sorted(color_values.items()): - code += f""" "{theme}": '{json.dumps(color_value, sort_keys=True)}', # noqa: E501\n""" - code += "}\n" - code += "ACCENT_COLORS = " - code += ( - json.dumps(accent_colors, sort_keys=True, indent=4) - .replace("\n}", ",\n}") - .replace('"\n', '",\n') - ) - code += "\n" - output.write_text(code) - - -def _build_styles(build_path: Path) -> None: - style_path = get_style_path() - theme_paths = [ - path for path in style_path.glob("colors/themes/*.json") if path.name != "validate.json" - ] - themes = sorted(path.stem for path in theme_paths) - color_values = {path.stem: json.loads(path.read_bytes()) for path in theme_paths} - - _mk_color_resource( - color_values=color_values, - accent_colors_file=style_path / "colors/os_accent.json", - output=build_path / "colors.py", - ) - _mk_palette_file(style_path / "palette.template.py", output=build_path / "palette.py") - _mk_svg_resource(style_path / "svg", output=build_path / "svg.py") - _mk_standard_icon_map( - style_path / "svg/new_standard_icons.json", output=build_path / "standard_icons.py" - ) - _mk_template_stylesheet( - style_path / "base.qss", - style_path / "svg/new_standard_icons.json", - output=build_path / "stylesheets.py", - ) - _mk_root_init_file(build_path, themes, _ROOT_INIT_DOC) - - -def _compare_all_files(dir1: Path, dir2: Path) -> list[str]: - """Check if the contents of the files with the same name in the two directories are the same. - - Args: - dir1: The directory containing files. - dir2: The directory containing files. - - Returns: - A list of file names with different contents. - """ - target_files = {str(path.relative_to(dir2)) for path in dir2.glob("**/*") if path.is_file()} - _, mismatch_path, err_path = cmpfiles(dir1, dir2, target_files) - return mismatch_path + err_path - - -def main() -> None: - """Build style resources for qdarktheme.""" - dist_dir_path = _get_dist_path() - - with TemporaryDirectory() as temp_dir: - _build_styles(Path(temp_dir)) - # Refresh dist dir - changed_files = _compare_all_files(dist_dir_path, Path(temp_dir)) - if len(changed_files) == 0: - logging.info("There is no change of qdarktheme module") - return - - shutil.rmtree(dist_dir_path, ignore_errors=True) - shutil.copytree(temp_dir, dist_dir_path) - - logging.info("Build finished!") - logging.info("Changed some contents in qdarktheme module: %s", changed_files) +"""Main module for building style resources for qdarktheme.""" +from __future__ import annotations + +import json +import logging +import re +import shutil +from collections import defaultdict +from filecmp import cmpfiles +from pathlib import Path +from pprint import pformat +from tempfile import TemporaryDirectory + +from tools import material_icons +from tools._util import get_style_path + +logging.basicConfig(level=logging.INFO) + +_ROOT_INIT_DOC = '''"""Package including resources. + +**Warning** + +This package created programmatically. All changes made in this file will be lost! +Created by the `PyQtDarkTheme/tools/build_styles`. + +""" +''' + + +def _get_dist_path() -> Path: + return Path(__file__).parent.parent.parent / "qdarktheme" / "_resources" + + +def _remove_qss_comment(stylesheet: str) -> str: + """Remove qss comment from the stylesheet string.""" + stylesheet = re.sub(r" */\*[\s\S]*?\*/", "", stylesheet) + # Change multi blank lines to one blank line. + return re.sub(r"\n\s*\n", "\n", stylesheet) + + +def _mk_root_init_file(output: Path, themes: list[str], doc_string: str) -> None: + themes.append("auto") + + code = f"{doc_string}\n" + code += "from qdarktheme._resources import " + code += "colors, palette, stylesheets, svg\n\n" + code += f"""THEMES = {str(tuple(themes)).replace("'", '"')}\n""" + (output / "__init__.py").write_text(code) + + +def _mk_svg_resource(svg_dir: Path, output: Path): + material_icons.reflect_icon_conf_changes() + + svg_resources = {svg_file.stem: svg_file.read_text() for svg_file in svg_dir.glob("*/*.svg")} + for name, svg_resource in svg_resources.items(): + svg_resources[name] = re.compile(r'xmlns="[\s\S]*?" ').sub("", svg_resource) + + code = '"""SVG resource."""\n\n' + code += 'SVG_RESOURCES = """\n' + code += json.dumps(svg_resources, sort_keys=True).replace('\\"', '\\\\"') + code += '\n""" # noqa: E501\n' + output.write_text(code) + + +def _mk_standard_icon_map(icon_map_file: Path, output: Path): + standard_icons: dict[str, dict] = json.loads(icon_map_file.read_text()) + + for standard_icon in standard_icons.values(): + if "qss" in standard_icon: + del standard_icon["qss"] + + incompatible_names = ( + "SP_LineEditClearButton", + "SP_DialogYesToAllButton", + "SP_DialogNoToAllButton", + "SP_DialogSaveAllButton", + "SP_DialogAbortButton", + "SP_DialogRetryButton", + "SP_DialogIgnoreButton", + "SP_RestoreDefaultsButton", + "SP_TabCloseButton", + ) + incompatible_standard_icons = { + name: standard_icons.pop(name) for name in list(standard_icons) if name in incompatible_names + } + + icon_map_code = pformat(standard_icons, sort_dicts=True, indent=4) + for icon_name in standard_icons.keys(): + icon_map_code = icon_map_code.replace(f"'{icon_name}'", f"QStyle.StandardPixmap.{icon_name}") + icon_map_code = icon_map_code[0] + "\n " + icon_map_code[1:-1] + ",\n" + icon_map_code[-1] + + add_icon_to_map_code = "" + for icon_name in sorted(incompatible_standard_icons.keys()): + add_icon_to_map_code += f'\nif hasattr(QStyle.StandardPixmap, "{icon_name}"):\n' + add_icon_to_map_code += f" NEW_STANDARD_ICON_MAP[QStyle.StandardPixmap.{icon_name}]" + add_icon_to_map_code += ( + f" = {incompatible_standard_icons[icon_name]} # type: ignore # noqa: E501\n" + ) + + code = '"""Icon map that overrides standard icons."""\n' + code += "from qdarktheme.qtpy.QtWidgets import QStyle\n\n" + code += "NEW_STANDARD_ICON_MAP = " + code += icon_map_code.replace("'", '"') + "\n" + code += add_icon_to_map_code.replace("'", '"') + output.write_text(code) + + +def _create_icon_definition(icon_id: str, rotate: int | None, qss_property: str) -> str: + url_placeholder = f'foreground|color(state="icon")|url(id="{icon_id}"' + if rotate is not None: + url_placeholder += f",rotate={rotate}" + url_placeholder += ")" + if qss_property == "lineedit-clear-button-icon": + return f' {{{{{url_placeholder}|env(value="{qss_property}:${{}};",version=">=6.0.0")}}}}' + return f"{qss_property}:{{{{{url_placeholder}}}}}" + + +def _mk_template_standard_icon_stylesheet(icon_map_file: Path) -> str: + standard_icons: dict[str, dict] = json.loads(icon_map_file.read_text()) + definitions_by_selector: defaultdict[tuple[str], set[str]] = defaultdict(set) + + for value in standard_icons.values(): + if "qss" not in value: + continue + qss_widgets: tuple[str, ...] = tuple(sorted(value["qss"]["widgets"])) + definition = _create_icon_definition( + icon_id=value["id"], + rotate=value.get("rotate"), + qss_property=value["qss"]["property"], + ) + definitions_by_selector[qss_widgets].add(definition) + + qss = "" + for selector, definitions in sorted(definitions_by_selector.items()): + qss += ",".join(selector) + "{" + qss += ";".join(sorted(definitions)) + "}" + return qss + + +def _mk_template_stylesheet(base_stylesheet_file: Path, icon_map_file: Path, output: Path): + base_qss = base_stylesheet_file.read_text() + base_qss = _remove_qss_comment(base_qss) + base_qss = base_qss.replace("\n", "") + base_qss = re.sub(r" \s* ", " ", base_qss) + base_qss = re.sub(r"{{.*?}}", lambda match: match.group().replace(" ", ""), base_qss) + base_qss = base_qss.replace(";}", "}").replace("{ ", "{") + base_qss = base_qss.replace("{{{", "{ {{") + base_qss = base_qss.replace(": ", ":").replace("; ", ";") + + code = '"""Template stylesheet."""\n\n' + code += f"TEMPLATE_STYLESHEET = '{base_qss}' # noqa: E501\n" + code += "TEMPLATE_STANDARD_ICONS_STYLESHEET = '" + code += _mk_template_standard_icon_stylesheet(icon_map_file) + code += "' # noqa: E501\n" + output.write_text(code) + + +def _mk_palette_file(template_palette_path: Path, output: Path): + template_palette = template_palette_path.read_text() + output.write_text(template_palette) + + +def _mk_color_resource(color_values: dict[str, dict], accent_colors_file: Path, output: Path): + accent_colors: dict[str, str] = json.loads(accent_colors_file.read_text()) + + code = '"""Default color values."""\n\n' + code += "THEME_COLOR_VALUES = {\n" + for theme, color_value in sorted(color_values.items()): + code += f""" "{theme}": '{json.dumps(color_value, sort_keys=True)}', # noqa: E501\n""" + code += "}\n" + code += "ACCENT_COLORS = " + code += ( + json.dumps(accent_colors, sort_keys=True, indent=4) + .replace("\n}", ",\n}") + .replace('"\n', '",\n') + ) + code += "\n" + output.write_text(code) + + +def _build_styles(build_path: Path) -> None: + style_path = get_style_path() + theme_paths = [ + path for path in style_path.glob("colors/themes/*.json") if path.name != "validate.json" + ] + themes = sorted(path.stem for path in theme_paths) + color_values = {path.stem: json.loads(path.read_bytes()) for path in theme_paths} + + _mk_color_resource( + color_values=color_values, + accent_colors_file=style_path / "colors/os_accent.json", + output=build_path / "colors.py", + ) + _mk_palette_file(style_path / "palette.template.py", output=build_path / "palette.py") + _mk_svg_resource(style_path / "svg", output=build_path / "svg.py") + _mk_standard_icon_map( + style_path / "svg/new_standard_icons.json", output=build_path / "standard_icons.py" + ) + _mk_template_stylesheet( + style_path / "base.qss", + style_path / "svg/new_standard_icons.json", + output=build_path / "stylesheets.py", + ) + _mk_root_init_file(build_path, themes, _ROOT_INIT_DOC) + + +def _compare_all_files(dir1: Path, dir2: Path) -> list[str]: + """Check if the contents of the files with the same name in the two directories are the same. + + Args: + dir1: The directory containing files. + dir2: The directory containing files. + + Returns: + A list of file names with different contents. + """ + target_files = {str(path.relative_to(dir2)) for path in dir2.glob("**/*") if path.is_file()} + _, mismatch_path, err_path = cmpfiles(dir1, dir2, target_files) + return mismatch_path + err_path + + +def main() -> None: + """Build style resources for qdarktheme.""" + dist_dir_path = _get_dist_path() + + with TemporaryDirectory() as temp_dir: + _build_styles(Path(temp_dir)) + # Refresh dist dir + changed_files = _compare_all_files(dist_dir_path, Path(temp_dir)) + if len(changed_files) == 0: + logging.info("There is no change of qdarktheme module") + return + + shutil.rmtree(dist_dir_path, ignore_errors=True) + shutil.copytree(temp_dir, dist_dir_path) + + logging.info("Build finished!") + logging.info("Changed some contents in qdarktheme module: %s", changed_files) diff --git a/tools/build_theme_color_doc/__init__.py b/tools/build_theme_color_doc/__init__.py index 8b743db1..9ea43cd0 100644 --- a/tools/build_theme_color_doc/__init__.py +++ b/tools/build_theme_color_doc/__init__.py @@ -1 +1 @@ -"""Package building theme color documentation.""" +"""Package building theme color documentation.""" diff --git a/tools/build_theme_color_doc/__main__.py b/tools/build_theme_color_doc/__main__.py index 4676f8b9..79f88c19 100644 --- a/tools/build_theme_color_doc/__main__.py +++ b/tools/build_theme_color_doc/__main__.py @@ -1,5 +1,5 @@ -"""Module allowing for `python -m tools.build_theme_color_doc`.""" -from tools.build_theme_color_doc.main import main - -if __name__ == "__main__": - main() +"""Module allowing for `python -m tools.build_theme_color_doc`.""" +from tools.build_theme_color_doc.main import main + +if __name__ == "__main__": + main() diff --git a/tools/build_theme_color_doc/main.py b/tools/build_theme_color_doc/main.py index 9a3a7535..d30b169c 100644 --- a/tools/build_theme_color_doc/main.py +++ b/tools/build_theme_color_doc/main.py @@ -1,155 +1,155 @@ -"""Module building theme color documentation.""" -from __future__ import annotations - -import json -from dataclasses import dataclass -from importlib import resources -from pathlib import Path - -from qdarktheme._color import Color -from tools._util import get_style_path - -_DEFAULT_DARK_COLORS: dict = json.loads((get_style_path() / "colors/themes/dark.json").read_bytes()) -_DEFAULT_LIGHT_COLORS: dict = json.loads((get_style_path() / "colors/themes/light.json").read_bytes()) - - -@dataclass -class _ThemeColor: - id: str - default_dark_value: str - default_light_value: str - description: str - image_name: str | None - inherited_by: list[str] | None = None - inherits: str | None = None - - -def _to_hex(base_color: str, color_info: dict[str, float]) -> str: - color = Color.from_hex(base_color) - if color_info.get("transparent"): - color = color.transparent(color_info["transparent"]) - if color_info.get("darken"): - color = color.darken(color_info["darken"]) - if color_info.get("lighten"): - color = color.lighten(color_info["lighten"]) - return f"#{color._to_hex()}" - - -def _parse_theme_color_files() -> dict[str, list[_ThemeColor]]: - validate_property: dict = json.loads( - (get_style_path() / "colors/themes/validate.json").read_bytes() - ) - theme_color_properties: dict[str, dict] = validate_property["properties"] - groups: dict[str, str] = validate_property["groups"] - - theme_colors: dict[str, list[_ThemeColor]] = {group: [] for group in groups.keys()} - for id, theme_color_property in theme_color_properties.items(): - inherited_theme_color_properties: dict[str, dict] | None = theme_color_property.get( - "properties" - ) - if inherited_theme_color_properties is not None: - # Parse base theme color - base_color_property = inherited_theme_color_properties.pop("base") - theme_colors[base_color_property["group"]].append( - _ThemeColor( - id, - _DEFAULT_DARK_COLORS[id]["base"], - _DEFAULT_LIGHT_COLORS[id]["base"], - base_color_property["description"], - base_color_property.get("image_name"), - list(inherited_theme_color_properties.keys()), - ) - ) - # Parse inherited theme color - for ( - inherited_id, - inherited_theme_color_property, - ) in inherited_theme_color_properties.items(): - theme_colors[inherited_theme_color_property["group"]].append( - _ThemeColor( - f"{id}>{inherited_id}", - _to_hex( - _DEFAULT_DARK_COLORS[id]["base"], _DEFAULT_DARK_COLORS[id][inherited_id] - ), - _to_hex( - _DEFAULT_LIGHT_COLORS[id]["base"], _DEFAULT_LIGHT_COLORS[id][inherited_id] - ), - inherited_theme_color_property["description"], - inherited_theme_color_property.get("image_name"), - inherits=id, - ) - ) - else: - # Parse single theme color - theme_colors[theme_color_property["group"]].append( - _ThemeColor( - id, - _DEFAULT_DARK_COLORS[id], - _DEFAULT_LIGHT_COLORS[id], - theme_color_property["description"], - theme_color_property.get("image_name"), - ) - ) - return theme_colors - - -def _mk_group_section(title, description) -> str: - doc_text = title + "\n" - doc_text += "^" * len(title) + "\n\n" - doc_text += description + "\n" - return doc_text - - -def _mk_color_section(theme_color: _ThemeColor) -> str: - doc_text = "\n" - doc_text += f"- ``{theme_color.id}``\n\n" - doc_text += f" {theme_color.description}\n\n" - doc_text += " .. list-table::\n" - doc_text += " :stub-columns: 1\n\n" - if theme_color.inherits is not None: - doc_text += " * - Inherits\n" - doc_text += " - " + f"``{theme_color.inherits}``\n" - if theme_color.inherited_by is not None: - doc_text += " * - Inherited by\n" - doc_text += " -" - for inherited_by in theme_color.inherited_by: - doc_text += f" ``{inherited_by}``" - doc_text += "\n" - doc_text += " * - Default(light)\n" - doc_text += " - " + theme_color.default_light_value + "\n" - doc_text += " * - Default(dark)\n" - doc_text += " - " + theme_color.default_dark_value + "\n" - if theme_color.image_name is not None: - doc_text += " * - View\n" - doc_text += " - .. image:: ../../_static/theme_color_api/images/" + theme_color.image_name - doc_text += "\n" - doc_text += " :class:dark-light\n\n" - return doc_text - - -def _mk_color_list_section(theme_colors: dict[str, list[_ThemeColor]]) -> str: - validate_property: dict = json.loads( - (get_style_path() / "colors/themes/validate.json").read_bytes() - ) - groups: dict[str, str] = validate_property["groups"] - doc_text = "\n" - for group_name, group_description in sorted(groups.items()): - doc_text += _mk_group_section(group_name, group_description) - for theme_color in theme_colors[group_name]: - doc_text += _mk_color_section(theme_color) - doc_text += "\n" - return doc_text - - -def _mk_theme_color_doc(doc_template: str, color_list_section: str) -> str: - return doc_template + color_list_section - - -def main() -> None: - """Build theme color documentation.""" - output_path = Path(__file__).parent.parent.parent / "docs/source/reference/theme_color.rst" - doc_template = resources.read_text("tools.build_theme_color_doc", "theme_color.template.rst") - theme_colors = _parse_theme_color_files() - color_list_section = _mk_color_list_section(theme_colors) - theme_color_doc = _mk_theme_color_doc(doc_template, color_list_section) - output_path.write_text(theme_color_doc) +"""Module building theme color documentation.""" +from __future__ import annotations + +import json +from dataclasses import dataclass +from importlib import resources +from pathlib import Path + +from qdarktheme._color import Color +from tools._util import get_style_path + +_DEFAULT_DARK_COLORS: dict = json.loads((get_style_path() / "colors/themes/dark.json").read_bytes()) +_DEFAULT_LIGHT_COLORS: dict = json.loads((get_style_path() / "colors/themes/light.json").read_bytes()) + + +@dataclass +class _ThemeColor: + id: str + default_dark_value: str + default_light_value: str + description: str + image_name: str | None + inherited_by: list[str] | None = None + inherits: str | None = None + + +def _to_hex(base_color: str, color_info: dict[str, float]) -> str: + color = Color.from_hex(base_color) + if color_info.get("transparent"): + color = color.transparent(color_info["transparent"]) + if color_info.get("darken"): + color = color.darken(color_info["darken"]) + if color_info.get("lighten"): + color = color.lighten(color_info["lighten"]) + return f"#{color._to_hex()}" + + +def _parse_theme_color_files() -> dict[str, list[_ThemeColor]]: + validate_property: dict = json.loads( + (get_style_path() / "colors/themes/validate.json").read_bytes() + ) + theme_color_properties: dict[str, dict] = validate_property["properties"] + groups: dict[str, str] = validate_property["groups"] + + theme_colors: dict[str, list[_ThemeColor]] = {group: [] for group in groups.keys()} + for id, theme_color_property in theme_color_properties.items(): + inherited_theme_color_properties: dict[str, dict] | None = theme_color_property.get( + "properties" + ) + if inherited_theme_color_properties is not None: + # Parse base theme color + base_color_property = inherited_theme_color_properties.pop("base") + theme_colors[base_color_property["group"]].append( + _ThemeColor( + id, + _DEFAULT_DARK_COLORS[id]["base"], + _DEFAULT_LIGHT_COLORS[id]["base"], + base_color_property["description"], + base_color_property.get("image_name"), + list(inherited_theme_color_properties.keys()), + ) + ) + # Parse inherited theme color + for ( + inherited_id, + inherited_theme_color_property, + ) in inherited_theme_color_properties.items(): + theme_colors[inherited_theme_color_property["group"]].append( + _ThemeColor( + f"{id}>{inherited_id}", + _to_hex( + _DEFAULT_DARK_COLORS[id]["base"], _DEFAULT_DARK_COLORS[id][inherited_id] + ), + _to_hex( + _DEFAULT_LIGHT_COLORS[id]["base"], _DEFAULT_LIGHT_COLORS[id][inherited_id] + ), + inherited_theme_color_property["description"], + inherited_theme_color_property.get("image_name"), + inherits=id, + ) + ) + else: + # Parse single theme color + theme_colors[theme_color_property["group"]].append( + _ThemeColor( + id, + _DEFAULT_DARK_COLORS[id], + _DEFAULT_LIGHT_COLORS[id], + theme_color_property["description"], + theme_color_property.get("image_name"), + ) + ) + return theme_colors + + +def _mk_group_section(title, description) -> str: + doc_text = title + "\n" + doc_text += "^" * len(title) + "\n\n" + doc_text += description + "\n" + return doc_text + + +def _mk_color_section(theme_color: _ThemeColor) -> str: + doc_text = "\n" + doc_text += f"- ``{theme_color.id}``\n\n" + doc_text += f" {theme_color.description}\n\n" + doc_text += " .. list-table::\n" + doc_text += " :stub-columns: 1\n\n" + if theme_color.inherits is not None: + doc_text += " * - Inherits\n" + doc_text += " - " + f"``{theme_color.inherits}``\n" + if theme_color.inherited_by is not None: + doc_text += " * - Inherited by\n" + doc_text += " -" + for inherited_by in theme_color.inherited_by: + doc_text += f" ``{inherited_by}``" + doc_text += "\n" + doc_text += " * - Default(light)\n" + doc_text += " - " + theme_color.default_light_value + "\n" + doc_text += " * - Default(dark)\n" + doc_text += " - " + theme_color.default_dark_value + "\n" + if theme_color.image_name is not None: + doc_text += " * - View\n" + doc_text += " - .. image:: ../../_static/theme_color_api/images/" + theme_color.image_name + doc_text += "\n" + doc_text += " :class:dark-light\n\n" + return doc_text + + +def _mk_color_list_section(theme_colors: dict[str, list[_ThemeColor]]) -> str: + validate_property: dict = json.loads( + (get_style_path() / "colors/themes/validate.json").read_bytes() + ) + groups: dict[str, str] = validate_property["groups"] + doc_text = "\n" + for group_name, group_description in sorted(groups.items()): + doc_text += _mk_group_section(group_name, group_description) + for theme_color in theme_colors[group_name]: + doc_text += _mk_color_section(theme_color) + doc_text += "\n" + return doc_text + + +def _mk_theme_color_doc(doc_template: str, color_list_section: str) -> str: + return doc_template + color_list_section + + +def main() -> None: + """Build theme color documentation.""" + output_path = Path(__file__).parent.parent.parent / "docs/source/reference/theme_color.rst" + doc_template = resources.read_text("tools.build_theme_color_doc", "theme_color.template.rst") + theme_colors = _parse_theme_color_files() + color_list_section = _mk_color_list_section(theme_colors) + theme_color_doc = _mk_theme_color_doc(doc_template, color_list_section) + output_path.write_text(theme_color_doc) diff --git a/tools/build_theme_color_doc/theme_color.template.rst b/tools/build_theme_color_doc/theme_color.template.rst index ad0364a6..3ed5a211 100644 --- a/tools/build_theme_color_doc/theme_color.template.rst +++ b/tools/build_theme_color_doc/theme_color.template.rst @@ -1,41 +1,41 @@ -Theme Color -=========== - -You can customize your theme color with the argument ``custom_colors`` of ``qdarktheme.setup_theme``, ``qdarktheme.load_stylesheet`` and ``qdarktheme.load_palette``. - -.. code-block:: python - - import qdarktheme - - qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) - -To customize a specific theme only, use the following syntax: - -.. code-block:: python - - import qdarktheme - - qdarktheme.setup_theme( - custom_colors={ - "[dark]": { - "primary": "#D0BCFF", - } - } - ) - -Color formats -------------- - -+--------------------------------------+--------------------------------------+ -| Format | Example | -+======================================+======================================+ -| Case-insensitive hex RGB or RGBA | - ``'#0f0f0f'`` | -| string. | - ``'#0f0f0f80'`` | -+--------------------------------------+--------------------------------------+ -| Case-insensitive RGB or RGBA string | - ``'#abc'`` as ``'#aabbcc'`` | -| equivalent hex shorthand of | - ``'#fb1'`` as ``'#ffbb11'`` | -| duplicated characters. | - ``'#e35f'`` as ``'#ee3355ff'`` | -+--------------------------------------+--------------------------------------+ - -List of customizable colors ---------------------------- +Theme Color +=========== + +You can customize your theme color with the argument ``custom_colors`` of ``qdarktheme.setup_theme``, ``qdarktheme.load_stylesheet`` and ``qdarktheme.load_palette``. + +.. code-block:: python + + import qdarktheme + + qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}) + +To customize a specific theme only, use the following syntax: + +.. code-block:: python + + import qdarktheme + + qdarktheme.setup_theme( + custom_colors={ + "[dark]": { + "primary": "#D0BCFF", + } + } + ) + +Color formats +------------- + ++--------------------------------------+--------------------------------------+ +| Format | Example | ++======================================+======================================+ +| Case-insensitive hex RGB or RGBA | - ``'#0f0f0f'`` | +| string. | - ``'#0f0f0f80'`` | ++--------------------------------------+--------------------------------------+ +| Case-insensitive RGB or RGBA string | - ``'#abc'`` as ``'#aabbcc'`` | +| equivalent hex shorthand of | - ``'#fb1'`` as ``'#ffbb11'`` | +| duplicated characters. | - ``'#e35f'`` as ``'#ee3355ff'`` | ++--------------------------------------+--------------------------------------+ + +List of customizable colors +--------------------------- diff --git a/tools/capture.py b/tools/capture.py index 7623c18a..fff8cb65 100644 --- a/tools/capture.py +++ b/tools/capture.py @@ -1,45 +1,45 @@ -"""Script capturing the image of widget_gallery.""" -from __future__ import annotations - -import argparse -import os -import platform -import sys - -import qdarktheme -from qdarktheme.qtpy.QtCore import QTimer, Slot -from qdarktheme.qtpy.QtGui import QGuiApplication -from qdarktheme.qtpy.QtWidgets import QApplication -from qdarktheme.widget_gallery.main_window import WidgetGallery - - -def _parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="This program capture images of WidgetGallery.") - parser.add_argument("--name", help="Image name") - return parser.parse_args() - - -class _Application(QApplication): - def __init__(self, img_name: str) -> None: - super().__init__(sys.argv) - self._img_name = img_name.replace("~=", "-") - self._gallery = WidgetGallery() - self._gallery.show() - - @Slot() - def _capture_window_img(self) -> None: - for theme in qdarktheme.get_themes(): - if theme == "auto": - continue - qdarktheme.setup_theme(theme) - self._gallery.setGeometry(QGuiApplication.primaryScreen().geometry()) - self._gallery.grab().save(f"{self._img_name}-{theme}.png") - self.exit() - - -if __name__ == "__main__": - if platform.system() == "Linux": - os.environ["QT_QPA_PLATFORM"] = "offscreen" - app = _Application(_parse_args().name) - QTimer.singleShot(10, app._capture_window_img) - app.exec() +"""Script capturing the image of widget_gallery.""" +from __future__ import annotations + +import argparse +import os +import platform +import sys + +import qdarktheme +from qdarktheme.qtpy.QtCore import QTimer, Slot +from qdarktheme.qtpy.QtGui import QGuiApplication +from qdarktheme.qtpy.QtWidgets import QApplication +from qdarktheme.widget_gallery.main_window import WidgetGallery + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="This program capture images of WidgetGallery.") + parser.add_argument("--name", help="Image name") + return parser.parse_args() + + +class _Application(QApplication): + def __init__(self, img_name: str) -> None: + super().__init__(sys.argv) + self._img_name = img_name.replace("~=", "-") + self._gallery = WidgetGallery() + self._gallery.show() + + @Slot() + def _capture_window_img(self) -> None: + for theme in qdarktheme.get_themes(): + if theme == "auto": + continue + qdarktheme.setup_theme(theme) + self._gallery.setGeometry(QGuiApplication.primaryScreen().geometry()) + self._gallery.grab().save(f"{self._img_name}-{theme}.png") + self.exit() + + +if __name__ == "__main__": + if platform.system() == "Linux": + os.environ["QT_QPA_PLATFORM"] = "offscreen" + app = _Application(_parse_args().name) + QTimer.singleShot(10, app._capture_window_img) + app.exec() diff --git a/tools/check_build_package.py b/tools/check_build_package.py index 8702abea..e1781881 100644 --- a/tools/check_build_package.py +++ b/tools/check_build_package.py @@ -1,45 +1,45 @@ -"""Module for checking tag validation in GitHub auto release action.""" -import argparse -import logging -import sys -from importlib.metadata import version - -import qdarktheme - -logging.basicConfig(level=logging.INFO) - - -def _parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="This program check package built by poetry.") - parser.add_argument( - "--tag-version", - help="Version name of the pushed tag", - required=True, - ) - return parser.parse_args() - - -def _test_qdarktheme() -> None: - for theme in qdarktheme.get_themes(): - qdarktheme.load_stylesheet(theme) - - -def _main() -> None: - args = _parse_args() - git_tag_v: str = args.tag_version.replace("v", "") - package_v = version("pyqtdarktheme") - if git_tag_v == package_v == qdarktheme.__version__: - logging.info("The package version, module version and tag version are the same.") - else: - logging.info("The version names of package and tag are different.") - logging.info("tag version(GitHub tags) : ", git_tag_v) - logging.info("package version(pyproject.toml) : ", package_v) - logging.info("module version(__version__) : ", qdarktheme.__version__) - sys.exit(1) - - _test_qdarktheme() - logging.info("Test finished successfully!") - - -if __name__ == "__main__": - _main() +"""Module for checking tag validation in GitHub auto release action.""" +import argparse +import logging +import sys +from importlib.metadata import version + +import qdarktheme + +logging.basicConfig(level=logging.INFO) + + +def _parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="This program check package built by poetry.") + parser.add_argument( + "--tag-version", + help="Version name of the pushed tag", + required=True, + ) + return parser.parse_args() + + +def _test_qdarktheme() -> None: + for theme in qdarktheme.get_themes(): + qdarktheme.load_stylesheet(theme) + + +def _main() -> None: + args = _parse_args() + git_tag_v: str = args.tag_version.replace("v", "") + package_v = version("pyqtdarktheme") + if git_tag_v == package_v == qdarktheme.__version__: + logging.info("The package version, module version and tag version are the same.") + else: + logging.info("The version names of package and tag are different.") + logging.info("tag version(GitHub tags) : ", git_tag_v) + logging.info("package version(pyproject.toml) : ", package_v) + logging.info("module version(__version__) : ", qdarktheme.__version__) + sys.exit(1) + + _test_qdarktheme() + logging.info("Test finished successfully!") + + +if __name__ == "__main__": + _main() diff --git a/tools/material_icons.py b/tools/material_icons.py index 3ef4dd95..7e0b5e17 100644 --- a/tools/material_icons.py +++ b/tools/material_icons.py @@ -1,62 +1,62 @@ -"""Module for downloading material design icons.""" -import json -import logging -from urllib import request - -from tools._util import get_style_path - -logging.basicConfig(level=logging.INFO) - - -def _get_available_icons() -> dict[str, str]: - material_icons_info_file = get_style_path() / "svg/material_design_icons.json" - return json.loads(material_icons_info_file.read_text()) - - -def _download_icon(name: str, style: str) -> None: - """Download Google material deign icon. - - Use assets of https://github.com/marella/material-design-icons. - """ - url = f"https://raw.githubusercontent.com/marella/material-design-icons/main/svg/{style}/{name}.svg" - material_icons_dir = get_style_path() / "svg" / "material" - material_icons_dir.mkdir(parents=True, exist_ok=True) - output_path = material_icons_dir / f"{name}.svg" - logging.info("Downloading: %s", url) - with request.urlopen(url) as response: - svg: str = response.read().decode() - output_path.write_text(svg) - - -def _download_missing_icons() -> None: - """Download missing material design icons.""" - for name, style in _get_available_icons().items(): - if not (get_style_path() / f"svg/material/{name}.svg").exists(): - _download_icon(name, style) - - -def _remove_unused_icons() -> None: - """Remove unused material design icons.""" - available_icons = _get_available_icons() - for svg_path in (get_style_path() / "svg/material").glob("*.svg"): - if svg_path.stem not in available_icons: - logging.info("Removing: %s", svg_path) - svg_path.unlink() - - -def reflect_icon_conf_changes() -> None: - """Reflect changes of ``style/svg/material_design_icons.json``.""" - _download_missing_icons() - _remove_unused_icons() - - -def _download_all() -> None: - """Download all icon files to use in PyQtDarkTheme styles.""" - for name, style in _get_available_icons().items(): - _download_icon(name, style) - - -def update_icons() -> None: - """Update all material design icons.""" - _download_all() - reflect_icon_conf_changes() +"""Module for downloading material design icons.""" +import json +import logging +from urllib import request + +from tools._util import get_style_path + +logging.basicConfig(level=logging.INFO) + + +def _get_available_icons() -> dict[str, str]: + material_icons_info_file = get_style_path() / "svg/material_design_icons.json" + return json.loads(material_icons_info_file.read_text()) + + +def _download_icon(name: str, style: str) -> None: + """Download Google material deign icon. + + Use assets of https://github.com/marella/material-design-icons. + """ + url = f"https://raw.githubusercontent.com/marella/material-design-icons/main/svg/{style}/{name}.svg" + material_icons_dir = get_style_path() / "svg" / "material" + material_icons_dir.mkdir(parents=True, exist_ok=True) + output_path = material_icons_dir / f"{name}.svg" + logging.info("Downloading: %s", url) + with request.urlopen(url) as response: + svg: str = response.read().decode() + output_path.write_text(svg) + + +def _download_missing_icons() -> None: + """Download missing material design icons.""" + for name, style in _get_available_icons().items(): + if not (get_style_path() / f"svg/material/{name}.svg").exists(): + _download_icon(name, style) + + +def _remove_unused_icons() -> None: + """Remove unused material design icons.""" + available_icons = _get_available_icons() + for svg_path in (get_style_path() / "svg/material").glob("*.svg"): + if svg_path.stem not in available_icons: + logging.info("Removing: %s", svg_path) + svg_path.unlink() + + +def reflect_icon_conf_changes() -> None: + """Reflect changes of ``style/svg/material_design_icons.json``.""" + _download_missing_icons() + _remove_unused_icons() + + +def _download_all() -> None: + """Download all icon files to use in PyQtDarkTheme styles.""" + for name, style in _get_available_icons().items(): + _download_icon(name, style) + + +def update_icons() -> None: + """Update all material design icons.""" + _download_all() + reflect_icon_conf_changes()