Source code for qdarkstyle

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""The most complete dark/light style sheet for Qt applications (Qt4, Qt5,
PySide, PySide2, PyQt4, PyQt5, QtPy, PyQtGraph, Qt.Py) for Python 2/3 and C++.

Python 2, as well as Qt4 (PyQt4 and PySide), will not be supported anymore.
They still there as it is, but no back-compatibility, fixes, nor features
will be implemented.

We still preparing the portability to Qt6 since we need changes in
`QtPy <https://github.com/spyder-ide/qtpy>`__ dependency project.

Check the `documentation <https://qdarkstylesheet.readthedocs.io/en/stable>`__
to see how to set the desirable theme palette.

This module provides a function to load the stylesheets transparently
with the right resources file.

First, start importing our module

.. code-block:: python

    import qdarkstyle

Then you can get stylesheet provided by QDarkStyle for various Qt wrappers
as shown below

.. code-block:: python

    # PySide
    dark_stylesheet = qdarkstyle.load_stylesheet_pyside()
    # PySide 2
    dark_stylesheet = qdarkstyle.load_stylesheet_pyside2()
    # PyQt4
    dark_stylesheet = qdarkstyle.load_stylesheet_pyqt()
    # PyQt5
    dark_stylesheet = qdarkstyle.load_stylesheet_pyqt5()

Alternatively, from environment variables provided by QtPy, PyQtGraph, Qt.Py

.. code-block:: python

    # QtPy
    dark_stylesheet = qdarkstyle.load_stylesheet()
    # PyQtGraph
    dark_stylesheet = qdarkstyle.load_stylesheet(qt_api=os.environ('PYQTGRAPH_QT_LIB'))
    # Qt.Py
    dark_stylesheet = qdarkstyle.load_stylesheet(qt_api=Qt.__binding__)

Finally, set your QApplication with it

.. code-block:: python

    app.setStyleSheet(dark_stylesheet)

Enjoy!

"""

# Standard library imports
import logging
import os
import platform
import sys
import warnings

# Local imports
from qdarkstyle.dark.palette import DarkPalette
from qdarkstyle.light.palette import LightPalette
from qdarkstyle.palette import Palette

__version__ = "3.1"

_logger = logging.getLogger(__name__)


# Dir names
REPO_PATH = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
EXAMPLE_PATH = os.path.join(REPO_PATH, 'example')
IMAGES_PATH = os.path.join(REPO_PATH, 'docs', 'images')
PACKAGE_PATH = os.path.join(REPO_PATH, 'qdarkstyle')
QSS_PATH = os.path.join(PACKAGE_PATH, 'qss')
SVG_PATH = os.path.join(PACKAGE_PATH, 'svg')

# File names
QSS_FILE = 'style.qss'
QRC_FILE = QSS_FILE.replace('.qss', '.qrc')

MAIN_SCSS_FILE = 'main.scss'
STYLES_SCSS_FILE = '_styles.scss'
VARIABLES_SCSS_FILE = '_variables.scss'

# File paths
STYLES_SCSS_FILEPATH = os.path.join(QSS_PATH, STYLES_SCSS_FILE)

# Todo: check if we are deprecate all those functions or keep them
DEPRECATION_MSG = '''This function will be deprecated in v3.0.
Please, set the wanted binding by using QtPy environment variable QT_API,
then use load_stylesheet() or use load_stylesheet()
passing the argument qt_api='wanted_binding'.'''


[docs]def _apply_os_patches(palette): """ Apply OS-only specific stylesheet pacthes. Returns: str: stylesheet string (css). """ os_fix = "" if platform.system().lower() == 'darwin': # See issue #12, #267 os_fix = ''' QDockWidget::title {{ background-color: {color}; text-align: center; height: 12px; }} QTabBar::close-button {{ padding: 2px; }} '''.format(color=palette.COLOR_BACKGROUND_4) # Only open the QSS file if any patch is needed if os_fix: _logger.info("Found OS patches to be applied.") return os_fix
[docs]def _apply_binding_patches(): """ Apply binding-only specific stylesheet patches for the same OS. Returns: str: stylesheet string (css). """ binding_fix = "" if binding_fix: _logger.info("Found binding patches to be applied.") return binding_fix
[docs]def _apply_version_patches(qt_version): """ Apply version-only specific stylesheet patches for the same binding. Args: qt_version (str): Qt string version. Returns: str: stylesheet string (css). """ version_fix = "" major, minor, patch = qt_version.split('.') major, minor, patch = int(major), int(minor), int(patch) if major == 5 and minor >= 14: # See issue #214 version_fix = ''' QMenu::item { padding: 4px 24px 4px 6px; } ''' if version_fix: _logger.info("Found version patches to be applied.") return version_fix
[docs]def _apply_application_patches(QCoreApplication, QPalette, QColor, palette): """ Apply application level fixes on the QPalette. The import names args must be passed here because the import is done inside the load_stylesheet() function, as QtPy is only imported in that moment for setting reasons. """ # See issue #139 color = palette.COLOR_ACCENT_3 qcolor = QColor(color) # Todo: check if it is qcoreapplication indeed app = QCoreApplication.instance() _logger.info("Found application patches to be applied.") if app: app_palette = app.palette() app_palette.setColor(QPalette.Normal, QPalette.Link, qcolor) app.setPalette(app_palette) else: _logger.warning("No QCoreApplication instance found. " "Application patches not applied. " "You have to call load_stylesheet function after " "instantiation of QApplication to take effect. ")
[docs]def _load_stylesheet(qt_api='', palette=None): """ Load the stylesheet based on QtPy abstraction layer environment variable. If the argument is not passed, it uses the current QT_API environment variable to make the imports of Qt bindings. If passed, it sets this variable then make the imports. Args: qt_api (str): qt binding name to set QT_API environment variable. Default is ''. Possible values are pyside, pyside2 pyqt4, pyqt5. Not case sensitive. palette (Palette): Palette class that inherits from Palette. Note: - Note that the variable QT_API is read when first imported. So, pay attention to the import order. - If you are using another abstraction layer, i.e PyQtGraph to do imports on Qt things you must set both to use the same Qt binding (PyQt, PySide). - OS, binding and binding version number, and application specific patches are applied in this order. Returns: str: stylesheet string (css). """ if qt_api: os.environ['QT_API'] = qt_api # Import is made after setting QT_API from qtpy.QtCore import QCoreApplication, QFile, QTextStream from qtpy.QtGui import QColor, QPalette from qtpy import QT_VERSION # Then we import resources - binary qrc content if palette is None: from qdarkstyle.dark import darkstyle_rc palette = DarkPalette elif palette.ID == 'dark': from qdarkstyle.dark import darkstyle_rc palette = DarkPalette elif palette.ID == 'light': from qdarkstyle.light import lightstyle_rc palette = LightPalette else: print("Not recognized ID for palette! Exiting!") sys.exit(1) # Thus, by importing the binary we can access the resources package_dir = os.path.basename(PACKAGE_PATH) palette_dir = os.path.join(package_dir, palette.ID) qss_rc_path = ":" + os.path.join(palette_dir, palette.ID + QSS_FILE) _logger.debug("Reading QSS file in: %s" % qss_rc_path) # It gets the qss file from compiled style_rc that was imported, # not from the file QSS as we are using resources qss_file = QFile(qss_rc_path) if qss_file.exists(): qss_file.open(QFile.ReadOnly | QFile.Text) text_stream = QTextStream(qss_file) stylesheet = text_stream.readAll() _logger.info("QSS file successfully loaded.") else: stylesheet = "" # Todo: check this raise type and add to docs raise FileNotFoundError("Unable to find QSS file '{}' " "in resources.".format(qss_rc_path)) _logger.debug("Checking patches for being applied.") # Todo: check execution order for these functions # 1. Apply OS specific patches stylesheet += _apply_os_patches(palette) # 2. Apply binding specific patches stylesheet += _apply_binding_patches() # 3. Apply binding version specific patches stylesheet += _apply_version_patches(QT_VERSION) # 4. Apply palette fix. See issue #139 _apply_application_patches(QCoreApplication, QPalette, QColor, palette) return stylesheet
[docs]def load_stylesheet(*args, **kwargs): """ Load the stylesheet. Takes care of importing the rc module. Args: pyside (bool): True to load the PySide (or PySide2) rc file, False to load the PyQt4 (or PyQt5) rc file. Default is False. or qt_api (str): Qt binding name to set QT_API environment variable. Default is '', i.e PyQt5 the default QtPy binding. Possible values are pyside, pyside2 pyqt4, pyqt5. Not case sensitive. or palette (Palette): Class (not instance) that inherits from Palette. Raises: TypeError: If arguments do not match: type, keyword name nor quantity. Returns: str: the stylesheet string. """ stylesheet = "" arg = None try: arg = args[0] except IndexError: # It is already none pass # Get palette palette = kwargs.get('palette', None) # Number of arguments are wrong if (kwargs and args) or len(args) > 2 or len(kwargs) > 2: raise TypeError("load_stylesheet() takes zero, one or two arguments: " "(new) string type qt_api='pyqt5' or " "(old) boolean type pyside='False' or " "(new) palette type palette=Palette.") # No arguments if not kwargs and not args: stylesheet = _load_stylesheet(qt_api='pyqt5') # Old API arguments elif 'pyside' in kwargs or isinstance(arg, bool): pyside = kwargs.get('pyside', arg) if pyside: stylesheet = _load_stylesheet(qt_api='pyside2', palette=palette) if not stylesheet: stylesheet = _load_stylesheet(qt_api='pyside', palette=palette) else: stylesheet = _load_stylesheet(qt_api='pyqt5', palette=palette) if not stylesheet: stylesheet = _load_stylesheet(qt_api='pyqt4', palette=palette) # Deprecation warning only for old API warnings.warning(DEPRECATION_MSG, DeprecationWarning) # New API arguments elif 'qt_api' in kwargs or isinstance(arg, str): qt_api = kwargs.get('qt_api', arg) stylesheet = _load_stylesheet(qt_api=qt_api, palette=palette) # Palette arg elif 'palette' in kwargs or issubclass(arg, Palette): palette_arg = kwargs.get('palette', arg) stylesheet = _load_stylesheet(palette=palette_arg) # Wrong API arguments name or type else: raise TypeError("load_stylesheet() takes only zero, one or two arguments: " "(new) string type qt_api='pyqt5' or " "(new) palette type palette=Palette or " "(old) boolean type pyside='False'.") return stylesheet
[docs]def load_stylesheet_pyside(): """ Load the stylesheet for use in a PySide application. Returns: str: the stylesheet string. """ return _load_stylesheet(qt_api='pyside')
[docs]def load_stylesheet_pyside2(): """ Load the stylesheet for use in a PySide2 application. Returns: str: the stylesheet string. """ return _load_stylesheet(qt_api='pyside2')
[docs]def load_stylesheet_pyqt(): """ Load the stylesheet for use in a PyQt4 application. Returns: str: the stylesheet string. """ return _load_stylesheet(qt_api='pyqt4')
[docs]def load_stylesheet_pyqt5(): """ Load the stylesheet for use in a PyQt5 application. Returns: str: the stylesheet string. """ return _load_stylesheet(qt_api='pyqt5')
# Deprecation Warning --------------------------------------------------------
[docs]def load_stylesheet_from_environment(is_pyqtgraph=False): """ Load the stylesheet from QT_API (or PYQTGRAPH_QT_LIB) environment variable. Args: is_pyqtgraph (bool): True if it is to be set using PYQTGRAPH_QT_LIB. Raises: KeyError: if PYQTGRAPH_QT_LIB does not exist. Returns: str: the stylesheet string. """ warnings.warning(DEPRECATION_MSG, DeprecationWarning) if is_pyqtgraph: stylesheet = _load_stylesheet(qt_api=os.environ.get('PYQTGRAPH_QT_LIB', None)) else: stylesheet = _load_stylesheet() return stylesheet
# Deprecated ----------------------------------------------------------------