- added qdarktheme package into the code
@@ -11,6 +11,7 @@ CHANGELOG for FlatCAM Evo beta
|
||||
|
||||
- fixed preprocessors issue where the start GCode was not added due of trying to access an object that did not exist (because its name was not changed to reflect the changes in other parts on the app)
|
||||
- updated the requirements.txt file
|
||||
- added qdarktheme package into the code
|
||||
|
||||
27.09.2023
|
||||
|
||||
|
||||
37
appMain.py
@@ -47,9 +47,9 @@ import socket
|
||||
|
||||
import tkinter as tk
|
||||
|
||||
import qdarktheme
|
||||
import qdarktheme.themes.dark.stylesheet as qdarksheet
|
||||
import qdarktheme.themes.light.stylesheet as qlightsheet
|
||||
import libs.qdarktheme
|
||||
import libs.qdarktheme.themes.dark.stylesheet as qdarksheet
|
||||
import libs.qdarktheme.themes.light.stylesheet as qlightsheet
|
||||
|
||||
from typing import Union
|
||||
|
||||
@@ -628,11 +628,11 @@ class App(QtCore.QObject):
|
||||
elif self.options["global_theme"] == 'light':
|
||||
self.resource_location = 'assets/resources'
|
||||
qlightsheet.STYLE_SHEET = light_style_sheet.L_STYLE_SHEET
|
||||
self.qapp.setStyleSheet(qdarktheme.load_stylesheet('light'))
|
||||
self.qapp.setStyleSheet(libs.qdarktheme.load_stylesheet('light'))
|
||||
else:
|
||||
self.resource_location = 'assets/resources/dark_resources'
|
||||
qdarksheet.STYLE_SHEET = dark_style_sheet.D_STYLE_SHEET
|
||||
self.qapp.setStyleSheet(qdarktheme.load_stylesheet())
|
||||
self.qapp.setStyleSheet(libs.qdarktheme.load_stylesheet())
|
||||
|
||||
# ############################################################################################################
|
||||
# ################################### Set LOG verbosity ######################################################
|
||||
@@ -801,7 +801,6 @@ class App(QtCore.QObject):
|
||||
self.ui = MainGUI(self)
|
||||
# ########################
|
||||
|
||||
|
||||
# decide if to show or hide the Notebook side of the screen at startup
|
||||
if self.options["global_project_at_startup"] is True:
|
||||
self.ui.splitter.setSizes([1, 1])
|
||||
@@ -956,7 +955,7 @@ class App(QtCore.QObject):
|
||||
self.hover_shapes = ShapeCollectionLegacy(obj=self, app=self, name='hover')
|
||||
|
||||
# Storage for Selection shapes
|
||||
self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self.app, name="selection")
|
||||
self.sel_shapes = ShapeCollectionLegacy(obj=self, app=self, name="selection")
|
||||
# #############################################################################################################
|
||||
|
||||
end_plot_time = time.time()
|
||||
@@ -4142,9 +4141,9 @@ class App(QtCore.QObject):
|
||||
"""
|
||||
Called for converting a Geometry object from single-geo to multi-geo.
|
||||
Single-geo Geometry objects store their geometry data into self.solid_geometry.
|
||||
Multi-geo Geometry objects store their geometry data into the self.tools dictionary, each key (a tool actually)
|
||||
having as a value another dictionary. This value dictionary has one of its keys 'solid_geometry' which holds
|
||||
the solid-geometry of that tool.
|
||||
Multi-geo Geometry objects store their geometry data into the `self.tools` dictionary, each key
|
||||
(a tool actually) having as a value another dictionary. This value dictionary has
|
||||
one of its keys 'solid_geometry' which holds the solid-geometry of that tool.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
@@ -4833,7 +4832,7 @@ class App(QtCore.QObject):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if type(dia_box_location) == tuple:
|
||||
if isinstance(dia_box_location, tuple):
|
||||
dia_box_location = str(dia_box_location)
|
||||
else:
|
||||
dia_box_location = None
|
||||
@@ -5296,7 +5295,7 @@ class App(QtCore.QObject):
|
||||
else:
|
||||
default_data[opt_key] = self.options[opt_key]
|
||||
|
||||
if type(self.options["tools_mill_tooldia"]) == float:
|
||||
if isinstance(self.options["tools_mill_tooldia"], float):
|
||||
tools_diameters = [self.options["tools_mill_tooldia"]]
|
||||
else:
|
||||
try:
|
||||
@@ -7240,7 +7239,7 @@ class App(QtCore.QObject):
|
||||
outline = self.options['global_sel_line'][:-2] + str(hex(int(1.0 * 255)))[2:]
|
||||
|
||||
self.sel_objects_list.append(
|
||||
self.sel_shapes.add(b_sel_rect,color=outline, face_color=face, update=True, layer=0, tolerance=None)
|
||||
self.sel_shapes.add(b_sel_rect, color=outline, face_color=face, update=True, layer=0, tolerance=None)
|
||||
)
|
||||
if self.use_3d_engine is False:
|
||||
self.sel_shapes.redraw()
|
||||
@@ -7350,17 +7349,17 @@ class App(QtCore.QObject):
|
||||
return
|
||||
|
||||
obj = self.collection.get_active()
|
||||
if type(obj) == GeometryObject:
|
||||
if isinstance(obj, GeometryObject):
|
||||
self.f_handlers.on_file_exportdxf()
|
||||
elif type(obj) == ExcellonObject:
|
||||
elif isinstance(obj, ExcellonObject):
|
||||
self.f_handlers.on_file_saveexcellon()
|
||||
elif type(obj) == CNCJobObject:
|
||||
elif isinstance(obj, CNCJobObject):
|
||||
obj.on_exportgcode_button_click()
|
||||
elif type(obj) == GerberObject:
|
||||
elif isinstance(obj, GerberObject):
|
||||
self.f_handlers.on_file_savegerber()
|
||||
elif type(obj) == ScriptObject:
|
||||
elif isinstance(obj, ScriptObject):
|
||||
self.f_handlers.on_file_savescript()
|
||||
elif type(obj) == DocumentObject:
|
||||
elif isinstance(obj, DocumentObject):
|
||||
self.f_handlers.on_file_savedocument()
|
||||
|
||||
def obj_move(self):
|
||||
|
||||
@@ -12,8 +12,6 @@ from appGUI import VisPyPatches
|
||||
from appGUI.GUIElements import FCMessageBox
|
||||
|
||||
from multiprocessing import freeze_support
|
||||
# import copyreg
|
||||
# import types
|
||||
|
||||
MIN_VERSION_MAJOR = 3
|
||||
MIN_VERSION_MINOR = 6
|
||||
|
||||
6
libs/qdarktheme/__init__.py
Normal 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
@@ -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
|
||||
19
libs/qdarktheme/qtpy/QtCore/__init__.py
Normal 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
|
||||
20
libs/qdarktheme/qtpy/QtGui/__init__.py
Normal 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
|
||||
13
libs/qdarktheme/qtpy/QtSvg/__init__.py
Normal 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
|
||||
50
libs/qdarktheme/qtpy/QtWidgets/__init__.py
Normal 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
|
||||
11
libs/qdarktheme/qtpy/__init__.py
Normal 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.")
|
||||
82
libs/qdarktheme/qtpy/qt_compat.py
Normal 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()
|
||||
18
libs/qdarktheme/qtpy/qt_version.py
Normal 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
|
||||
45
libs/qdarktheme/themes/__init__.py
Normal 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")
|
||||
1
libs/qdarktheme/themes/dark/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Package containing the resources for dark theme."""
|
||||
45
libs/qdarktheme/themes/dark/palette.py
Normal 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
|
||||
1097
libs/qdarktheme/themes/dark/stylesheet.py
Normal file
5
libs/qdarktheme/themes/dark/svg.py
Normal file
1
libs/qdarktheme/themes/light/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Package containing the resources for light theme."""
|
||||
45
libs/qdarktheme/themes/light/palette.py
Normal 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
|
||||
1097
libs/qdarktheme/themes/light/stylesheet.py
Normal file
5
libs/qdarktheme/themes/light/svg.py
Normal file
67
libs/qdarktheme/util.py
Normal 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)
|
||||
8
libs/qdarktheme/widget_gallery/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
"""Example of PyQtDarkTheme use for Qt applications.
|
||||
|
||||
To check example app, run:
|
||||
|
||||
```shell
|
||||
python -m qdarktheme.widget_gallery
|
||||
```
|
||||
"""
|
||||
17
libs/qdarktheme/widget_gallery/__main__.py
Normal 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()
|
||||
204
libs/qdarktheme/widget_gallery/mainwindow.py
Normal 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")
|
||||
1
libs/qdarktheme/widget_gallery/svg/announcement_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/circle_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/clear_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/contrast_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/crop_din_24dp.svg
Normal 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 |
@@ -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 |
@@ -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 |
1
libs/qdarktheme/widget_gallery/svg/folder_open_24dp.svg
Normal 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 |
@@ -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 |
1
libs/qdarktheme/widget_gallery/svg/palette_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/settings_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/svg/widgets_24dp.svg
Normal 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 |
1
libs/qdarktheme/widget_gallery/ui/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Package including ui for WidgetGallery."""
|
||||
40
libs/qdarktheme/widget_gallery/ui/dock_ui.py
Normal 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)
|
||||
75
libs/qdarktheme/widget_gallery/ui/frame_ui.py
Normal 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)
|
||||
293
libs/qdarktheme/widget_gallery/ui/widgets_ui.py
Normal 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)
|
||||
@@ -47,7 +47,7 @@ pyopengl
|
||||
pyqt6>=6.1.0
|
||||
freetype-py
|
||||
vispy>=0.9.0
|
||||
pyqtdarktheme==1.1.1
|
||||
# pyqtdarktheme==1.1.1
|
||||
|
||||
svgtrace
|
||||
|
||||
|
||||