- added qdarktheme package into the code

This commit is contained in:
Marius Stanciu
2023-10-19 13:49:14 +03:00
parent 656bec2e7c
commit 28dbb70126
42 changed files with 3548 additions and 22 deletions

View File

@@ -0,0 +1,6 @@
"""PyQtDarkTheme - A flat dark theme for PySide and PyQt.
- Repository: https://github.com/5yutan5/PyQtDarkTheme
- Documentation: https://pyqtdarktheme.readthedocs.io
"""
from libs.qdarktheme.main import __version__, clear_cache, get_themes, load_palette, load_stylesheet

251
libs/qdarktheme/main.py Normal file
View File

@@ -0,0 +1,251 @@
"""Main file of qdarktheme."""
from __future__ import annotations
import json
import platform
import re
import shutil
from pathlib import Path
from libs.qdarktheme.qtpy import __version__ as qt_version
from libs.qdarktheme.qtpy.qt_compat import QT_API
from libs.qdarktheme.util import OPERATORS, compare_v, get_logger, multi_replace
# Version of PyQtDarkTheme
__version__ = "1.1.1"
_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."
)
_RESOURCE_HOME_DIR = Path.home() / ".qdarktheme"
_RESOURCES_BASE_DIR = _RESOURCE_HOME_DIR / f"v{__version__}"
# Pattern
_PATTERN_RADIUS = re.compile(r"\$radius\{[\s\S]*?\}")
_PATTERN_ENV_PATCH = re.compile(r"\$env_patch\{[\s\S]*?\}")
def _build_svg_files(theme: str, theme_resources_dir: Path) -> None:
svg_resources_dir = theme_resources_dir / "svg"
if not svg_resources_dir.exists():
svg_resources_dir.mkdir()
else:
return
if theme == "dark":
from libs.qdarktheme.themes.dark.svg import SVG_RESOURCES
else:
from libs.qdarktheme.themes.light.svg import SVG_RESOURCES
for file_name, code in json.loads(SVG_RESOURCES).items():
(svg_resources_dir / f"{file_name}.svg").write_text(code)
def get_themes() -> tuple[str, ...]:
"""Return available theme list.
Returns:
Available themes.
"""
from libs.qdarktheme.themes import THEMES
return THEMES
def _replace_rounded(match: re.Match) -> str:
return match.group().replace("$radius{", "").replace("}", "")
def _replace_sharp(match: re.Match) -> str:
return _PATTERN_RADIUS.sub("0", match.group())
def _parse_radius(stylesheet: str, border: str = "rounded") -> dict[str, str]:
"""Parse `$radius{...}` placeholder in template stylesheet."""
matches = _PATTERN_RADIUS.finditer(stylesheet)
replace = _replace_rounded if border == "rounded" else _replace_sharp
return {match.group(): replace(match) for match in matches}
def _parse_env_patch(stylesheet: str) -> dict[str, str]:
"""Parse `$env_patch{...}` placeholder in template stylesheet.
Template stylesheet has `$env_patch{...}` symbol.
This symbol has json string and resolve the differences of the style between qt versions.
The json keys:
* version - the qt version and qualifier. Available qualifiers: [==, !=, >=, <=, >, <].
* qt - the name of qt binding.
* value - the qt stylesheet string
Args:
stylesheet: The qt stylesheet string.
Raises:
SyntaxError: If the version operator in version key of `$env_patch{...}` is wrong.
Returns:
The dictionary. Key is the text of $env_patch{...} symbol.
Value is the value of the `value` key in $env_patch.
"""
replacements: dict[str, str] = {}
for match in re.finditer(_PATTERN_ENV_PATCH, stylesheet):
match_text = match.group()
json_text = match_text.replace("$env_patch", "")
env_property: dict[str, str] = json.loads(json_text)
patch_version = env_property.get("version")
patch_qt = env_property.get("qt")
patch_os = env_property.get("os")
patch_value = env_property["value"]
results: list[bool] = []
# Parse version
if patch_version is not None:
for operator in OPERATORS:
if operator not in patch_version:
continue
version = patch_version.replace(operator, "")
results.append(compare_v(_qt_version, operator, version))
break
else:
raise SyntaxError(
f"invalid character in qualifier. Available qualifiers {list(OPERATORS.keys())}"
) from None
# Parse qt binding
if patch_qt is not None:
if QT_API is None:
results.append(False)
results.append(patch_qt.lower() == _qt_api.lower())
# Parse os
if patch_os is not None:
results.append(platform.system().lower() in patch_os.lower())
replacements[match_text] = patch_value if all(results) else ""
return replacements
def load_stylesheet(theme: str = "dark", border: str = "rounded") -> str:
"""Load the style sheet which looks like flat design. There are two themes, dark theme and light theme.
Args:
theme: The name of the theme. Available themes are "dark" and "light".
border: The border style. Available styles are "rounded" and "sharp".
Raises:
TypeError: If the arg of theme name is wrong.
Returns:
The stylesheet string for the given theme.
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"))
Change sharp frame.
Sharp Frame::
app = QApplication([])
app.setStyleSheet(qdarktheme.load_stylesheet(border="sharp"))
"""
if theme not in get_themes():
raise TypeError("The argument [theme] can only be specified as 'dark' or 'light'.") from None
if border not in ("rounded", "sharp"):
raise TypeError("The argument [border] can only be specified as 'rounded' or 'sharp'.")
theme_resources_dir = _RESOURCES_BASE_DIR / theme
theme_resources_dir.mkdir(parents=True, exist_ok=True)
_build_svg_files(theme, theme_resources_dir)
if theme == "dark":
from libs.qdarktheme.themes.dark.stylesheet import STYLE_SHEET
else:
from libs.qdarktheme.themes.light.stylesheet import STYLE_SHEET
# Build stylesheet
# Radius
replacements_radius = _parse_radius(STYLE_SHEET, border)
stylesheet = multi_replace(STYLE_SHEET, replacements_radius)
# Env
replacements_env = _parse_env_patch(stylesheet)
# Path
replacements_env["${path}"] = _RESOURCES_BASE_DIR.as_posix()
return multi_replace(stylesheet, replacements_env)
def clear_cache():
"""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:
shutil.rmtree(_RESOURCE_HOME_DIR)
_logger.info(f"The caches({_RESOURCE_HOME_DIR}) has been deleted")
except FileNotFoundError:
_logger.info("There is no caches")
def load_palette(theme: str = "dark"):
"""Load the QPalette for the dark or light theme.
Args:
theme: The name of the theme. Available theme are "dark" and "light".
Raises:
TypeError: If the arg name of theme 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"))
"""
if theme not in get_themes():
raise TypeError("The argument [theme] can only be specified as 'dark' or 'light'.") from None
if theme == "dark":
from libs.qdarktheme.themes.dark.palette import PALETTE
else:
from libs.qdarktheme.themes.light.palette import PALETTE
return PALETTE

View File

@@ -0,0 +1,19 @@
"""Module for QtCore."""
from libs.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 # noqa: F405
Signal = pyqtSignal # noqa: F405
elif QT_API == "PySide2":
from PySide2.QtCore import * # type: ignore # noqa: F403

View File

@@ -0,0 +1,20 @@
"""Module for QtGui."""
from libs.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

View File

@@ -0,0 +1,13 @@
"""Module for QtSvg."""
from libs.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

View File

@@ -0,0 +1,50 @@
"""Module for QtWidgets."""
from __future__ import annotations
from collections.abc import Sequence
from libs.qdarktheme.qtpy.qt_compat import QT_API
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.qdarktheme.qtpy.QtGui import QPalette
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)
def setStyleSheet(self, sheet: str) -> None: # noqa: N802
"""Override QApplication method."""
return super().setStyleSheet(sheet)
def setAttribute(self, attribute: Qt.ApplicationAttribute, on: bool = True) -> None: # noqa: N802
"""Override QApplication method."""
super().setAttribute(attribute, on)
def setPalette(self, palette: QPalette, className: str | None = None) -> None: # noqa: N802, N803
"""Override QApplication method."""
super().setPalette(palette, className)
QApplication = Application

View File

@@ -0,0 +1,11 @@
"""Package applying Qt compat of PyQt6, PySide6, PyQt5 and PySide2."""
from libs.qdarktheme.qtpy.qt_compat import QtImportError
from libs.qdarktheme.qtpy.qt_version import __version__
try:
from libs.qdarktheme.qtpy import QtCore, QtGui, QtSvg, QtWidgets
except ImportError:
from libs.qdarktheme.util import get_logger as __get_logger
__logger = __get_logger(__name__)
__logger.warning("Failed to import QtCore, QtGui, QtSvg and QtWidgets.")

View File

@@ -0,0 +1,82 @@
"""Module for Qt compat."""
from __future__ import annotations
import os
import sys
class QtImportError(ImportError):
"""Error raise if no bindings could be selected."""
pass
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()

View File

@@ -0,0 +1,18 @@
"""Module for detecting Qt version."""
from __future__ import annotations
from libs.qdarktheme.qtpy.qt_compat import QT_API
__version__: str | None = None
if QT_API == "PySide6":
from PySide6 import __version__ # type: ignore
elif QT_API == "PyQt6":
from PyQt6.QtCore import PYQT_VERSION_STR # type: ignore
__version__ = PYQT_VERSION_STR
elif QT_API == "PyQt5":
from PyQt5.QtCore import PYQT_VERSION_STR # type: ignore
__version__ = PYQT_VERSION_STR
elif QT_API == "PySide2":
from PySide2 import __version__ # type: ignore # noqa: F401

View File

@@ -0,0 +1,45 @@
"""Package including resources.
**Warning**
This package created programmatically. All changes made in this file will be lost!
Created by the `qdarktheme/tools/build_resources`.
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
"""
THEMES = ("dark", "light")

View File

@@ -0,0 +1 @@
"""Package containing the resources for dark theme."""

View File

@@ -0,0 +1,45 @@
"""Module loading QPalette."""
from libs.qdarktheme.qtpy.QtGui import QColor, QPalette
_palette = QPalette()
# base
_palette.setColor(QPalette.ColorRole.WindowText, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.Button, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Text, QColor("#eff1f1"))
_palette.setColor(QPalette.ColorRole.ButtonText, QColor("#8ab4f7"))
_palette.setColor(QPalette.ColorRole.Base, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Window, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Highlight, QColor("#8ab4f7"))
_palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.Link, QColor("#202124"))
_palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#292b2e"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#292a2d"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.LinkVisited, QColor("#c58af8"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#292a2d"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#e4e7eb"))
if hasattr(QPalette.ColorRole, "Foreground"):
_palette.setColor(QPalette.ColorRole.Foreground, QColor("#e4e7eb")) # type: ignore
if hasattr(QPalette.ColorRole, "PlaceholderText"):
_palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#8a8b8d"))
_palette.setColor(QPalette.ColorRole.Light, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Midlight, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Dark, QColor("#e4e7eb"))
_palette.setColor(QPalette.ColorRole.Mid, QColor("#3f4042"))
_palette.setColor(QPalette.ColorRole.Shadow, QColor("#3f4042"))
# disabled
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#3f4042"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor("#53575b"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Link, QColor("#697177"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.LinkVisited, QColor("#697177"))
# inactive
_palette.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Highlight, QColor("#393d41"))
PALETTE = _palette

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
"""Package containing the resources for light theme."""

View File

@@ -0,0 +1,45 @@
"""Module loading QPalette."""
from libs.qdarktheme.qtpy.QtGui import QColor, QPalette
_palette = QPalette()
# base
_palette.setColor(QPalette.ColorRole.WindowText, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.Button, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Text, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.ButtonText, QColor("#0081db"))
_palette.setColor(QPalette.ColorRole.Base, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Window, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Highlight, QColor("#0081db"))
_palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.Link, QColor("#f8f9fa"))
_palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#e9ecef"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#ffffff"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.LinkVisited, QColor("#660098"))
_palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#ffffff"))
_palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#4d5157"))
if hasattr(QPalette.ColorRole, "Foreground"):
_palette.setColor(QPalette.ColorRole.Foreground, QColor("#4d5157")) # type: ignore
if hasattr(QPalette.ColorRole, "PlaceholderText"):
_palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#696a6c"))
_palette.setColor(QPalette.ColorRole.Light, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Midlight, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Dark, QColor("#4d5157"))
_palette.setColor(QPalette.ColorRole.Mid, QColor("#dadce0"))
_palette.setColor(QPalette.ColorRole.Shadow, QColor("#dadce0"))
# disabled
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#dadce0"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor("#dadce0"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Link, QColor("#babdc2"))
_palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.LinkVisited, QColor("#babdc2"))
# inactive
_palette.setColor(QPalette.ColorGroup.Inactive, QPalette.ColorRole.Highlight, QColor("#e4e6f2"))
PALETTE = _palette

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

67
libs/qdarktheme/util.py Normal file
View File

@@ -0,0 +1,67 @@
"""Utility methods for qdarktheme."""
from __future__ import annotations
import inspect
import logging
import operator as ope
import re
from pathlib import Path
import libs.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_qdarktheme_root_path() -> Path:
"""Return the qdarktheme package root path.
Returns:
qdarktheme package root path.
"""
return Path(inspect.getfile(libs.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)

View File

@@ -0,0 +1,8 @@
"""Example of PyQtDarkTheme use for Qt applications.
To check example app, run:
```shell
python -m qdarktheme.widget_gallery
```
"""

View File

@@ -0,0 +1,17 @@
"""Module allowing for `python -m qdarktheme.widget_gallery`."""
import sys
import libs.qdarktheme
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.qdarktheme.qtpy.QtWidgets import QApplication
from libs.qdarktheme.widget_gallery.mainwindow import WidgetGallery
if __name__ == "__main__":
app = QApplication(sys.argv)
if hasattr(Qt.ApplicationAttribute, "AA_UseHighDpiPixmaps"): # Enable High DPI display with Qt5
app.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps) # type: ignore
win = WidgetGallery()
win.menuBar().setNativeMenuBar(False)
app.setStyleSheet(libs.qdarktheme.load_stylesheet())
win.show()
app.exec()

View File

@@ -0,0 +1,204 @@
"""Main module of widget gallery."""
import libs.qdarktheme
from libs.qdarktheme.qtpy.QtCore import QDir, Qt, Slot
from libs.qdarktheme.qtpy.QtGui import QAction, QActionGroup, QFont, QIcon
from libs.qdarktheme.qtpy.QtWidgets import (
QApplication,
QColorDialog,
QFileDialog,
QFontDialog,
QLabel,
QMainWindow,
QMenuBar,
QMessageBox,
QSizePolicy,
QStackedWidget,
QStatusBar,
QToolBar,
QToolButton,
QWidget,
)
from libs.qdarktheme.util import get_qdarktheme_root_path
from libs.qdarktheme.widget_gallery.ui.dock_ui import DockUI
from libs.qdarktheme.widget_gallery.ui.frame_ui import FrameUI
from libs.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 ["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"),
)
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):
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._border_radius = "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
else:
index = 2
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
QApplication.instance().setStyleSheet(libs.qdarktheme.load_stylesheet(self._theme, self._border_radius))
@Slot()
def _change_corner_radius(self) -> None:
self._border_radius: str = self.sender().text() # type: ignore
QApplication.instance().setStyleSheet(libs.qdarktheme.load_stylesheet(self._theme, self._border_radius))
@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")

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8 9c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1 4h-2v-2h2v2z"/></svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#008000"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,2C6.47,2,2,6.47,2,12c0,5.53,4.47,10,10,10s10-4.47,10-10C22,6.47,17.53,2,12,2z M12,20c-4.42,0-8-3.58-8-8 c0-4.42,3.58-8,8-8s8,3.58,8,8C20,16.42,16.42,20,12,20z"/></g></svg>

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FF0000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M18.3 5.71c-.39-.39-1.02-.39-1.41 0L12 10.59 7.11 5.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41L10.59 12 5.7 16.89c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 13.41l4.89 4.89c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z"/></svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,22c5.52,0,10-4.48,10-10S17.52,2,12,2S2,6.48,2,12S6.48,22,12,22z M13,4.07c3.94,0.49,7,3.85,7,7.93s-3.05,7.44-7,7.93 V4.07z"/></g></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/></svg>

After

Width:  |  Height:  |  Size: 249 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#ff1493"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19.66 3.99c-2.64-1.8-5.9-.96-7.66 1.1-1.76-2.06-5.02-2.91-7.66-1.1-1.4.96-2.28 2.58-2.34 4.29-.14 3.88 3.3 6.99 8.55 11.76l.1.09c.76.69 1.93.69 2.69-.01l.11-.1c5.25-4.76 8.68-7.87 8.55-11.75-.06-1.7-.94-3.32-2.34-4.28zM12.1 18.55l-.1.1-.1-.1C7.14 14.24 4 11.39 4 8.5 4 6.5 5.5 5 7.5 5c1.54 0 3.04.99 3.57 2.36h1.87C13.46 5.99 14.96 5 16.5 5c2 0 3.5 1.5 3.5 3.5 0 2.89-3.14 5.74-7.9 10.05z"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.89 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9c-1.11 0-2 .9-2 2v10c0 1.1.89 2 2 2h10c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-1 12h-8c-.55 0-1-.45-1-1V6c0-.55.45-1 1-1h8c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1zm-7 6h2v-2h-2v2zm-4 0h2v-2H7v2z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M20 6h-8l-1.41-1.41C10.21 4.21 9.7 4 9.17 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-1 12H5c-.55 0-1-.45-1-1V9c0-.55.45-1 1-1h14c.55 0 1 .45 1 1v8c0 .55-.45 1-1 1z"/></svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9.93 13.5h4.14L12 7.98 9.93 13.5zM20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-4.29 15.88l-.9-2.38H9.17l-.89 2.37c-.14.38-.5.63-.91.63-.68 0-1.15-.69-.9-1.32l4.25-10.81c.22-.53.72-.87 1.28-.87s1.06.34 1.27.87l4.25 10.81c.25.63-.22 1.32-.9 1.32-.4 0-.76-.25-.91-.62z"/></svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><g><rect fill="none" height="24" width="24"/></g><g><path d="M12,2C6.49,2,2,6.49,2,12s4.49,10,10,10c1.38,0,2.5-1.12,2.5-2.5c0-0.61-0.23-1.2-0.64-1.67c-0.08-0.1-0.13-0.21-0.13-0.33 c0-0.28,0.22-0.5,0.5-0.5H16c3.31,0,6-2.69,6-6C22,6.04,17.51,2,12,2z M17.5,13c-0.83,0-1.5-0.67-1.5-1.5c0-0.83,0.67-1.5,1.5-1.5 s1.5,0.67,1.5,1.5C19,12.33,18.33,13,17.5,13z M14.5,9C13.67,9,13,8.33,13,7.5C13,6.67,13.67,6,14.5,6S16,6.67,16,7.5 C16,8.33,15.33,9,14.5,9z M5,11.5C5,10.67,5.67,10,6.5,10S8,10.67,8,11.5C8,12.33,7.33,13,6.5,13S5,12.33,5,11.5z M11,7.5 C11,8.33,10.33,9,9.5,9S8,8.33,8,7.5C8,6.67,8.67,6,9.5,6S11,6.67,11,7.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 758 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><rect fill="none" height="24" width="24"/><path d="M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#d89e76"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 13v8h8v-8h-8zM3 21h8v-8H3v8zM3 3v8h8V3H3zm13.66-1.31L11 7.34 16.66 13l5.66-5.66-5.66-5.65z"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@@ -0,0 +1 @@
"""Package including ui for WidgetGallery."""

View File

@@ -0,0 +1,40 @@
"""Module setting up ui of dock window."""
from libs.qdarktheme.qtpy.QtCore import Qt
from libs.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)

View File

@@ -0,0 +1,75 @@
"""Module setting up ui of frame window."""
from libs.qdarktheme.qtpy.QtGui import QIcon
from libs.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)

View File

@@ -0,0 +1,293 @@
"""Module setting up ui of widgets window."""
from __future__ import annotations
from typing import Any
from libs.qdarktheme.qtpy.QtCore import QAbstractTableModel, QModelIndex, Qt
from libs.qdarktheme.qtpy.QtGui import QIcon, QTextOption
from libs.qdarktheme.qtpy.QtWidgets import (
QCheckBox,
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 = QToolButton(), QToolButton(), QToolButton()
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_text.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
tool_btn_text.setText("Text")
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)
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()
lineedit = 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)
lineedit.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_lineedit = QVBoxLayout()
v_layout_lineedit.addWidget(lineedit)
group_editable.setLayout(v_layout_lineedit)
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(self, section: int, orientation: Qt.Orientation, role: int = ...) -> Any: # noqa: N802
if role != Qt.ItemDataRole.DisplayRole:
return None
if orientation == Qt.Orientation.Horizontal:
return ["Normal", "Checkbox", "Spinbox", "LineEdit"][section]
return super().headerData(section, orientation, role)
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()
# Setup widgets
self.setCheckable(True)
self.setFlat(True)
tab_widget.setTabsClosable(True)
tab_widget.setMovable(True)
tab_text_edit.append("<b>PyQtDarkTheme</b>")
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.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_list.setAlternatingRowColors(True)
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)
# 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")
v_layout_main = QVBoxLayout(self)
v_layout_main.addWidget(tab_widget)
class _Group4(QGroupBox):
def __init__(self) -> None:
super().__init__("QToolBox")
# Widgets
toolbox = QToolBox()
slider = QSlider(Qt.Orientation.Horizontal)
dial_ticks = QDial()
progressbar = QProgressBar()
lcd_number = QLCDNumber()
# Setup widgets
self.setCheckable(True)
toolbox.addItem(slider, "Slider")
toolbox.addItem(dial_ticks, "Dial")
toolbox.addItem(progressbar, "Progress Bar")
toolbox.addItem(lcd_number, "LCD Number")
slider.setValue(50)
dial_ticks.setNotchesVisible(True)
progressbar.setValue(50)
lcd_number.setSegmentStyle(QLCDNumber.SegmentStyle.Flat)
lcd_number.display(123)
# Layout
v_layout = QVBoxLayout(self)
v_layout.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)