Source code for qdarktheme._style_loader

"""Module for loading style data for Qt."""
from __future__ import annotations

import json
import shutil
import warnings
from functools import partial

from qdarktheme import __version__, _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.COLOR_VALUES[theme])
    except KeyError:
        raise ValueError(f'invalid argument, not a dark or light: "{theme}"') from None


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


[docs]def load_stylesheet( theme: str = "dark", corner_shape: str = "rounded", custom_colors: dict[str, str | dict[str, str]] | None = None, *, default_theme: str = "dark", border: str | None = None, ) -> 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 system theme. If failed to detect system theme, use the theme set in argument ``default_theme``. 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. border: The corner shape. There are `rounded` and `sharp` shape. This argument is deprecated since v1.2.0. Please use `corner_shape` instead. This argument override value of argument `corner_shape`. 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) 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 border not in (None, "rounded", "sharp"): raise ValueError(f'invalid argument, not a rounded or sharp: "{border}"') if border is not None: warnings.warn( 'deprecated argument, "border" is deprecated since v2.0.0. ' 'Please use "corner_shape" instead.', FutureWarning, ) corner_shape = border if custom_colors is not None: _marge_colors(color_values, custom_colors, theme) get_cash_root_path(__version__).mkdir(parents=True, exist_ok=True) # Build stylesheet template = Template( _resources.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)
[docs]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")
[docs]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.mk_q_palette(mk_template, color_values, for_stylesheet)
[docs]def get_themes() -> tuple[str, ...]: """Return available theme names. Returns: Tuple of available theme names. """ return _resources.THEMES