2020-01-23 06:08:10 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2021-11-24 22:17:41 +01:00
|
|
|
import enum
|
2020-01-23 06:08:10 +01:00
|
|
|
import platform
|
2021-11-25 10:10:57 +01:00
|
|
|
import subprocess
|
2021-02-05 06:26:12 +01:00
|
|
|
from dataclasses import dataclass
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-11-24 22:17:41 +01:00
|
|
|
import aqt
|
2021-11-25 10:10:57 +01:00
|
|
|
from anki.utils import is_lin, is_mac, is_win
|
|
|
|
from aqt import QApplication, colors, gui_hooks
|
2021-10-07 08:26:29 +02:00
|
|
|
from aqt.qt import (
|
|
|
|
QColor,
|
|
|
|
QGuiApplication,
|
|
|
|
QIcon,
|
|
|
|
QPainter,
|
|
|
|
QPalette,
|
|
|
|
QPixmap,
|
|
|
|
QStyleFactory,
|
|
|
|
Qt,
|
|
|
|
)
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class ColoredIcon:
|
|
|
|
path: str
|
|
|
|
# (day, night)
|
2021-10-03 10:59:42 +02:00
|
|
|
color: tuple[str, str]
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
def current_color(self, night_mode: bool) -> str:
|
|
|
|
if night_mode:
|
|
|
|
return self.color[1]
|
|
|
|
else:
|
|
|
|
return self.color[0]
|
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def with_color(self, color: tuple[str, str]) -> ColoredIcon:
|
2021-02-05 06:26:12 +01:00
|
|
|
return ColoredIcon(path=self.path, color=color)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
|
2021-11-24 22:17:41 +01:00
|
|
|
class Theme(enum.IntEnum):
|
|
|
|
FOLLOW_SYSTEM = 0
|
|
|
|
LIGHT = 1
|
|
|
|
DARK = 2
|
|
|
|
|
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
class ThemeManager:
|
2020-01-31 04:14:16 +01:00
|
|
|
_night_mode_preference = False
|
2021-10-03 10:59:42 +02:00
|
|
|
_icon_cache_light: dict[str, QIcon] = {}
|
|
|
|
_icon_cache_dark: dict[str, QIcon] = {}
|
2020-01-23 06:08:10 +01:00
|
|
|
_icon_size = 128
|
2021-10-03 10:59:42 +02:00
|
|
|
_dark_mode_available: bool | None = None
|
|
|
|
default_palette: QPalette | None = None
|
2021-11-24 22:17:41 +01:00
|
|
|
_default_style: str | None = None
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-05-24 03:31:56 +02:00
|
|
|
# Qt applies a gradient to the buttons in dark mode
|
|
|
|
# from about #505050 to #606060.
|
|
|
|
DARK_MODE_BUTTON_BG_MIDPOINT = "#555555"
|
|
|
|
|
2020-01-31 04:14:16 +01:00
|
|
|
def macos_dark_mode(self) -> bool:
|
2020-04-15 13:37:16 +02:00
|
|
|
"True if the user has night mode on, and has forced native widgets."
|
2021-11-25 00:06:16 +01:00
|
|
|
if not is_mac:
|
2020-01-31 05:12:37 +01:00
|
|
|
return False
|
|
|
|
|
2021-02-04 10:07:34 +01:00
|
|
|
if not self._night_mode_preference:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if self._dark_mode_available is None:
|
2021-11-24 22:17:41 +01:00
|
|
|
self._dark_mode_available = set_macos_dark_mode(True)
|
2021-02-04 10:07:34 +01:00
|
|
|
|
2020-04-15 13:37:16 +02:00
|
|
|
from aqt import mw
|
|
|
|
|
2021-02-04 10:07:34 +01:00
|
|
|
return self._dark_mode_available and mw.pm.dark_mode_widgets()
|
2020-01-31 04:14:16 +01:00
|
|
|
|
|
|
|
def get_night_mode(self) -> bool:
|
2020-04-15 13:37:16 +02:00
|
|
|
return self._night_mode_preference
|
2020-01-31 04:14:16 +01:00
|
|
|
|
|
|
|
def set_night_mode(self, val: bool) -> None:
|
|
|
|
self._night_mode_preference = val
|
2020-02-07 04:19:41 +01:00
|
|
|
self._update_stat_colors()
|
2020-01-31 04:14:16 +01:00
|
|
|
|
|
|
|
night_mode = property(get_night_mode, set_night_mode)
|
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def icon_from_resources(self, path: str | ColoredIcon) -> QIcon:
|
2020-01-23 06:08:10 +01:00
|
|
|
"Fetch icon from Qt resources, and invert if in night mode."
|
2020-01-31 04:30:12 +01:00
|
|
|
if self.night_mode:
|
|
|
|
cache = self._icon_cache_light
|
|
|
|
else:
|
|
|
|
cache = self._icon_cache_dark
|
2021-02-05 06:26:12 +01:00
|
|
|
|
|
|
|
if isinstance(path, str):
|
|
|
|
key = path
|
|
|
|
else:
|
|
|
|
key = f"{path.path}-{path.color}"
|
|
|
|
|
|
|
|
icon = cache.get(key)
|
2020-01-23 06:08:10 +01:00
|
|
|
if icon:
|
|
|
|
return icon
|
|
|
|
|
2021-02-05 06:26:12 +01:00
|
|
|
if isinstance(path, str):
|
|
|
|
# default black/white
|
|
|
|
icon = QIcon(path)
|
|
|
|
if self.night_mode:
|
|
|
|
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
|
|
|
|
img.invertPixels()
|
|
|
|
icon = QIcon(QPixmap(img))
|
|
|
|
else:
|
|
|
|
# specified colours
|
|
|
|
icon = QIcon(path.path)
|
2021-03-17 05:51:59 +01:00
|
|
|
pixmap = icon.pixmap(16)
|
|
|
|
painter = QPainter(pixmap)
|
2021-10-05 05:53:01 +02:00
|
|
|
painter.setCompositionMode(
|
|
|
|
QPainter.CompositionMode.CompositionMode_SourceIn
|
|
|
|
)
|
2021-03-17 05:51:59 +01:00
|
|
|
painter.fillRect(pixmap.rect(), QColor(path.current_color(self.night_mode)))
|
2021-02-05 06:26:12 +01:00
|
|
|
painter.end()
|
2021-03-17 05:51:59 +01:00
|
|
|
icon = QIcon(pixmap)
|
2021-02-05 06:26:12 +01:00
|
|
|
return icon
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2020-01-31 04:30:12 +01:00
|
|
|
return cache.setdefault(path, icon)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def body_class(self, night_mode: bool | None = None) -> str:
|
2020-01-23 09:05:55 +01:00
|
|
|
"Returns space-separated class list for platform/theme."
|
|
|
|
classes = []
|
2021-11-25 00:06:16 +01:00
|
|
|
if is_win:
|
2020-01-23 09:05:55 +01:00
|
|
|
classes.append("isWin")
|
2021-11-25 00:06:16 +01:00
|
|
|
elif is_mac:
|
2020-01-23 09:05:55 +01:00
|
|
|
classes.append("isMac")
|
|
|
|
else:
|
|
|
|
classes.append("isLin")
|
2020-07-31 06:47:17 +02:00
|
|
|
|
|
|
|
if night_mode is None:
|
|
|
|
night_mode = self.night_mode
|
|
|
|
if night_mode:
|
2020-01-30 22:00:01 +01:00
|
|
|
classes.extend(["nightMode", "night_mode"])
|
2020-01-31 04:39:52 +01:00
|
|
|
if self.macos_dark_mode():
|
|
|
|
classes.append("macos-dark-mode")
|
2020-01-23 09:05:55 +01:00
|
|
|
return " ".join(classes)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2020-07-31 06:47:17 +02:00
|
|
|
def body_classes_for_card_ord(
|
2021-10-03 10:59:42 +02:00
|
|
|
self, card_ord: int, night_mode: bool | None = None
|
2020-07-31 06:47:17 +02:00
|
|
|
) -> str:
|
2020-01-23 06:08:10 +01:00
|
|
|
"Returns body classes used when showing a card."
|
2020-07-31 06:47:17 +02:00
|
|
|
return f"card card{card_ord+1} {self.body_class(night_mode)}"
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def color(self, colors: tuple[str, str]) -> str:
|
2021-02-05 09:50:01 +01:00
|
|
|
"""Given day/night colors, return the correct one for the current theme."""
|
2020-08-27 08:12:30 +02:00
|
|
|
idx = 1 if self.night_mode else 0
|
2021-02-05 09:50:01 +01:00
|
|
|
return colors[idx]
|
2021-02-05 06:26:12 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
def qcolor(self, colors: tuple[str, str]) -> QColor:
|
2021-02-05 09:50:01 +01:00
|
|
|
return QColor(self.color(colors))
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-11-24 22:17:41 +01:00
|
|
|
def _determine_night_mode(self) -> bool:
|
|
|
|
theme = aqt.mw.pm.theme()
|
|
|
|
if theme == Theme.LIGHT:
|
|
|
|
return False
|
|
|
|
elif theme == Theme.DARK:
|
|
|
|
return True
|
|
|
|
else:
|
2021-11-25 00:06:16 +01:00
|
|
|
if is_win:
|
2021-11-24 22:17:41 +01:00
|
|
|
return get_windows_dark_mode()
|
2021-11-25 00:06:16 +01:00
|
|
|
elif is_mac:
|
2021-11-24 22:17:41 +01:00
|
|
|
return get_macos_dark_mode()
|
|
|
|
else:
|
2021-11-24 23:41:15 +01:00
|
|
|
return get_linux_dark_mode()
|
2021-11-24 22:17:41 +01:00
|
|
|
|
|
|
|
def apply_style_if_system_style_changed(self) -> None:
|
|
|
|
theme = aqt.mw.pm.theme()
|
|
|
|
if theme != Theme.FOLLOW_SYSTEM:
|
|
|
|
return
|
|
|
|
if self._determine_night_mode() != self.night_mode:
|
|
|
|
self.apply_style()
|
|
|
|
|
|
|
|
def apply_style(self) -> None:
|
|
|
|
"Apply currently configured style."
|
|
|
|
app = aqt.mw.app
|
|
|
|
self.night_mode = self._determine_night_mode()
|
|
|
|
if not self.default_palette:
|
|
|
|
self.default_palette = QGuiApplication.palette()
|
|
|
|
self._default_style = app.style().objectName()
|
2020-01-23 06:08:10 +01:00
|
|
|
self._apply_palette(app)
|
|
|
|
self._apply_style(app)
|
2021-11-24 22:17:41 +01:00
|
|
|
gui_hooks.theme_did_change()
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
def _apply_style(self, app: QApplication) -> None:
|
|
|
|
buf = ""
|
|
|
|
|
2021-11-25 00:06:16 +01:00
|
|
|
if is_win and platform.release() == "10":
|
2021-11-24 22:17:41 +01:00
|
|
|
# day mode is missing a bottom border; background must be
|
|
|
|
# also set for border to apply
|
|
|
|
buf += f"""
|
|
|
|
QMenuBar {{
|
|
|
|
border-bottom: 1px solid {self.color(colors.BORDER)};
|
2022-01-16 04:45:16 +01:00
|
|
|
background: {self.color(colors.WINDOW_BG) if self.night_mode else "white"};
|
2021-11-24 22:17:41 +01:00
|
|
|
}}
|
2020-01-23 06:08:10 +01:00
|
|
|
"""
|
|
|
|
# qt bug? setting the above changes the browser sidebar
|
|
|
|
# to white as well, so set it back
|
2021-11-24 22:17:41 +01:00
|
|
|
buf += f"""
|
|
|
|
QTreeWidget {{
|
|
|
|
background: {self.color(colors.WINDOW_BG)};
|
|
|
|
}}
|
2020-01-23 06:08:10 +01:00
|
|
|
"""
|
|
|
|
|
2020-01-23 22:55:14 +01:00
|
|
|
if self.night_mode:
|
|
|
|
buf += """
|
|
|
|
QToolTip {
|
|
|
|
border: 0;
|
2020-01-26 00:13:38 +01:00
|
|
|
}
|
2020-01-23 22:55:14 +01:00
|
|
|
"""
|
|
|
|
|
2020-02-02 03:22:10 +01:00
|
|
|
if not self.macos_dark_mode():
|
|
|
|
buf += """
|
2021-10-03 10:59:42 +02:00
|
|
|
QScrollBar {{ background-color: {}; }}
|
|
|
|
QScrollBar::handle {{ background-color: {}; border-radius: 5px; }}
|
2020-02-02 03:22:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
QScrollBar:horizontal {{ height: 12px; }}
|
|
|
|
QScrollBar::handle:horizontal {{ min-width: 50px; }}
|
2020-02-02 03:22:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
QScrollBar:vertical {{ width: 12px; }}
|
|
|
|
QScrollBar::handle:vertical {{ min-height: 50px; }}
|
2020-02-02 03:22:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
QScrollBar::add-line {{
|
2020-02-02 03:22:10 +01:00
|
|
|
border: none;
|
|
|
|
background: none;
|
2021-10-03 10:59:42 +02:00
|
|
|
}}
|
2020-02-02 03:22:10 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
QScrollBar::sub-line {{
|
2020-02-02 03:22:10 +01:00
|
|
|
border: none;
|
|
|
|
background: none;
|
2021-10-03 10:59:42 +02:00
|
|
|
}}
|
2020-02-02 03:53:35 +01:00
|
|
|
|
2021-10-03 10:59:42 +02:00
|
|
|
QTabWidget {{ background-color: {}; }}
|
|
|
|
""".format(
|
2021-02-05 09:50:01 +01:00
|
|
|
self.color(colors.WINDOW_BG),
|
2020-10-06 06:35:21 +02:00
|
|
|
# fushion-button-hover-bg
|
|
|
|
"#656565",
|
2021-02-05 09:50:01 +01:00
|
|
|
self.color(colors.WINDOW_BG),
|
2020-02-02 04:09:02 +01:00
|
|
|
)
|
2020-02-02 03:22:10 +01:00
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
# allow addons to modify the styling
|
|
|
|
buf = gui_hooks.style_did_init(buf)
|
|
|
|
|
|
|
|
app.setStyleSheet(buf)
|
|
|
|
|
|
|
|
def _apply_palette(self, app: QApplication) -> None:
|
2021-11-24 22:17:41 +01:00
|
|
|
set_macos_dark_mode(self.night_mode)
|
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
if not self.night_mode:
|
2021-11-24 22:17:41 +01:00
|
|
|
app.setStyle(QStyleFactory.create(self._default_style)) # type: ignore
|
|
|
|
app.setPalette(self.default_palette)
|
2020-01-23 06:08:10 +01:00
|
|
|
return
|
|
|
|
|
2020-01-31 04:14:16 +01:00
|
|
|
if not self.macos_dark_mode():
|
|
|
|
app.setStyle(QStyleFactory.create("fusion")) # type: ignore
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
palette = QPalette()
|
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
text_fg = self.qcolor(colors.TEXT_FG)
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(QPalette.ColorRole.WindowText, text_fg)
|
|
|
|
palette.setColor(QPalette.ColorRole.ToolTipText, text_fg)
|
|
|
|
palette.setColor(QPalette.ColorRole.Text, text_fg)
|
|
|
|
palette.setColor(QPalette.ColorRole.ButtonText, text_fg)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
hlbg = self.qcolor(colors.HIGHLIGHT_BG)
|
2020-01-23 06:08:10 +01:00
|
|
|
hlbg.setAlpha(64)
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(
|
|
|
|
QPalette.ColorRole.HighlightedText, self.qcolor(colors.HIGHLIGHT_FG)
|
|
|
|
)
|
|
|
|
palette.setColor(QPalette.ColorRole.Highlight, hlbg)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
window_bg = self.qcolor(colors.WINDOW_BG)
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(QPalette.ColorRole.Window, window_bg)
|
|
|
|
palette.setColor(QPalette.ColorRole.AlternateBase, window_bg)
|
2020-02-02 03:53:35 +01:00
|
|
|
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(QPalette.ColorRole.Button, QColor("#454545"))
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
frame_bg = self.qcolor(colors.FRAME_BG)
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(QPalette.ColorRole.Base, frame_bg)
|
|
|
|
palette.setColor(QPalette.ColorRole.ToolTipBase, frame_bg)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
disabled_color = self.qcolor(colors.DISABLED)
|
2021-10-07 08:43:35 +02:00
|
|
|
palette.setColor(QPalette.ColorRole.PlaceholderText, disabled_color)
|
2021-10-05 05:53:01 +02:00
|
|
|
palette.setColor(
|
|
|
|
QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, disabled_color
|
|
|
|
)
|
|
|
|
palette.setColor(
|
|
|
|
QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, disabled_color
|
|
|
|
)
|
|
|
|
palette.setColor(
|
|
|
|
QPalette.ColorGroup.Disabled,
|
|
|
|
QPalette.ColorRole.HighlightedText,
|
|
|
|
disabled_color,
|
|
|
|
)
|
|
|
|
|
|
|
|
palette.setColor(QPalette.ColorRole.Link, self.qcolor(colors.LINK))
|
|
|
|
|
|
|
|
palette.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
app.setPalette(palette)
|
|
|
|
|
2020-02-27 03:11:00 +01:00
|
|
|
def _update_stat_colors(self) -> None:
|
2020-02-07 04:19:41 +01:00
|
|
|
import anki.stats as s
|
2020-02-07 08:55:26 +01:00
|
|
|
|
2021-02-05 09:50:01 +01:00
|
|
|
s.colLearn = self.color(colors.NEW_COUNT)
|
|
|
|
s.colRelearn = self.color(colors.LEARN_COUNT)
|
|
|
|
s.colCram = self.color(colors.SUSPENDED_BG)
|
|
|
|
s.colSusp = self.color(colors.SUSPENDED_BG)
|
|
|
|
s.colMature = self.color(colors.REVIEW_COUNT)
|
2021-10-14 11:22:47 +02:00
|
|
|
s._legacy_nightmode = self._night_mode_preference
|
2020-02-07 04:19:41 +01:00
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2021-11-25 10:10:57 +01:00
|
|
|
def get_windows_dark_mode() -> bool:
|
|
|
|
"True if Windows system is currently in dark mode."
|
|
|
|
if not is_win:
|
|
|
|
return False
|
|
|
|
|
|
|
|
from winreg import ( # pylint: disable=import-error
|
|
|
|
HKEY_CURRENT_USER,
|
|
|
|
OpenKey,
|
|
|
|
QueryValueEx,
|
|
|
|
)
|
|
|
|
|
|
|
|
key = OpenKey(
|
|
|
|
HKEY_CURRENT_USER,
|
|
|
|
r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize",
|
|
|
|
)
|
2022-01-16 05:07:28 +01:00
|
|
|
try:
|
|
|
|
return not QueryValueEx(key, "AppsUseLightTheme")[0]
|
|
|
|
except Exception as err:
|
|
|
|
# key reportedly missing or set to wrong type on some systems
|
|
|
|
return False
|
2021-11-25 10:10:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
def set_macos_dark_mode(enabled: bool) -> bool:
|
|
|
|
"True if setting successful."
|
2021-12-06 13:18:53 +01:00
|
|
|
from aqt._macos_helper import macos_helper
|
|
|
|
|
|
|
|
if not macos_helper:
|
2021-11-25 10:10:57 +01:00
|
|
|
return False
|
2021-12-06 13:18:53 +01:00
|
|
|
return macos_helper.set_darkmode_enabled(enabled)
|
2021-11-25 10:10:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_macos_dark_mode() -> bool:
|
|
|
|
"True if macOS system is currently in dark mode."
|
2021-12-06 13:18:53 +01:00
|
|
|
from aqt._macos_helper import macos_helper
|
|
|
|
|
|
|
|
if not macos_helper:
|
2021-11-25 10:10:57 +01:00
|
|
|
return False
|
2021-12-06 13:18:53 +01:00
|
|
|
return macos_helper.system_is_dark()
|
2021-11-25 10:10:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
def get_linux_dark_mode() -> bool:
|
|
|
|
"""True if Linux system is in dark mode.
|
|
|
|
This only works if the GTK theme name contains '-dark'"""
|
|
|
|
if not is_lin:
|
|
|
|
return False
|
|
|
|
try:
|
|
|
|
process = subprocess.run(
|
|
|
|
["gsettings", "get", "org.gnome.desktop.interface", "gtk-theme"],
|
|
|
|
check=True,
|
|
|
|
capture_output=True,
|
|
|
|
encoding="utf8",
|
|
|
|
)
|
|
|
|
except FileNotFoundError as e:
|
|
|
|
# swallow exceptions, as gsettings may not be installed
|
|
|
|
print(e)
|
|
|
|
return False
|
|
|
|
|
2022-01-24 10:48:32 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
# gsettings is installed, but cannot return a value
|
|
|
|
print(e)
|
|
|
|
return False
|
|
|
|
|
2021-11-25 10:10:57 +01:00
|
|
|
return "-dark" in process.stdout.lower()
|
|
|
|
|
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
theme_manager = ThemeManager()
|