2020-01-23 06:08:10 +01:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
|
|
|
import platform
|
2020-01-31 04:30:35 +01:00
|
|
|
import sys
|
2020-03-23 09:39:37 +01:00
|
|
|
from typing import Dict, Optional
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2020-01-23 09:05:55 +01:00
|
|
|
from anki.utils import isMac
|
2020-01-23 06:08:10 +01:00
|
|
|
from aqt import QApplication, gui_hooks, isWin
|
|
|
|
from aqt.colors import colors
|
2020-02-25 08:29:10 +01:00
|
|
|
from aqt.qt import QColor, QIcon, QPalette, QPixmap, QStyleFactory, Qt, qtminor
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
|
|
|
|
class ThemeManager:
|
2020-01-31 04:14:16 +01:00
|
|
|
_night_mode_preference = False
|
2020-01-31 04:30:12 +01:00
|
|
|
_icon_cache_light: Dict[str, QIcon] = {}
|
|
|
|
_icon_cache_dark: Dict[str, QIcon] = {}
|
2020-01-23 06:08:10 +01:00
|
|
|
_icon_size = 128
|
2020-03-23 09:39:37 +01:00
|
|
|
_macos_dark_mode_cached: Optional[bool] = None
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2020-01-31 04:14:16 +01:00
|
|
|
def macos_dark_mode(self) -> bool:
|
2020-01-31 04:30:35 +01:00
|
|
|
if not getattr(sys, "frozen", False):
|
|
|
|
return False
|
2020-01-31 05:12:37 +01:00
|
|
|
if not isMac:
|
|
|
|
return False
|
2020-02-26 07:53:35 +01:00
|
|
|
if qtminor < 13:
|
2020-02-25 06:46:39 +01:00
|
|
|
return False
|
2020-03-23 09:39:37 +01:00
|
|
|
if self._macos_dark_mode_cached is None:
|
|
|
|
import darkdetect # pylint: disable=import-error
|
2020-01-31 05:12:37 +01:00
|
|
|
|
2020-03-23 09:39:37 +01:00
|
|
|
# cache the value, as the interface gets messed up
|
|
|
|
# if the value changes after starting Anki
|
|
|
|
self._macos_dark_mode_cached = darkdetect.isDark() is True
|
|
|
|
return self._macos_dark_mode_cached
|
2020-01-31 04:14:16 +01:00
|
|
|
|
|
|
|
def get_night_mode(self) -> bool:
|
|
|
|
return self.macos_dark_mode() or self._night_mode_preference
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
def icon_from_resources(self, path: str) -> QIcon:
|
|
|
|
"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
|
|
|
|
icon = cache.get(path)
|
2020-01-23 06:08:10 +01:00
|
|
|
if icon:
|
|
|
|
return icon
|
|
|
|
|
|
|
|
icon = QIcon(path)
|
|
|
|
|
|
|
|
if self.night_mode:
|
|
|
|
img = icon.pixmap(self._icon_size, self._icon_size).toImage()
|
|
|
|
img.invertPixels()
|
|
|
|
icon = QIcon(QPixmap(img))
|
|
|
|
|
2020-01-31 04:30:12 +01:00
|
|
|
return cache.setdefault(path, icon)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
def body_class(self) -> str:
|
2020-01-23 09:05:55 +01:00
|
|
|
"Returns space-separated class list for platform/theme."
|
|
|
|
classes = []
|
|
|
|
if isWin:
|
|
|
|
classes.append("isWin")
|
|
|
|
elif isMac:
|
|
|
|
classes.append("isMac")
|
|
|
|
else:
|
|
|
|
classes.append("isLin")
|
|
|
|
if self.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
|
|
|
|
|
|
|
def body_classes_for_card_ord(self, card_ord: int) -> str:
|
|
|
|
"Returns body classes used when showing a card."
|
|
|
|
return f"card card{card_ord+1} {self.body_class()}"
|
|
|
|
|
|
|
|
def str_color(self, key: str) -> str:
|
|
|
|
"""Get a color defined in _vars.scss
|
|
|
|
|
|
|
|
If the colour is called '$day-frame-bg', key should be
|
|
|
|
'frame-bg'.
|
|
|
|
|
|
|
|
Returns the color as a string hex code or color name."""
|
|
|
|
prefix = self.night_mode and "night-" or "day-"
|
|
|
|
c = colors.get(prefix + key)
|
|
|
|
if c is None:
|
|
|
|
raise Exception("no such color:", key)
|
|
|
|
return c
|
|
|
|
|
|
|
|
def qcolor(self, key: str) -> QColor:
|
|
|
|
"""Get a color defined in _vars.scss as a QColor."""
|
|
|
|
return QColor(self.str_color(key))
|
|
|
|
|
|
|
|
def apply_style(self, app: QApplication) -> None:
|
|
|
|
self._apply_palette(app)
|
|
|
|
self._apply_style(app)
|
|
|
|
|
|
|
|
def _apply_style(self, app: QApplication) -> None:
|
|
|
|
buf = ""
|
|
|
|
|
|
|
|
if isWin and platform.release() == "10" and not self.night_mode:
|
|
|
|
# add missing bottom border to menubar
|
|
|
|
buf += """
|
|
|
|
QMenuBar {
|
|
|
|
border-bottom: 1px solid #aaa;
|
|
|
|
background: white;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
# qt bug? setting the above changes the browser sidebar
|
|
|
|
# to white as well, so set it back
|
|
|
|
buf += """
|
|
|
|
QTreeWidget {
|
|
|
|
background: #eee;
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
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 += """
|
|
|
|
QScrollBar { background-color: %s; }
|
|
|
|
QScrollBar::handle { background-color: %s; border-radius: 5px; }
|
|
|
|
|
|
|
|
QScrollBar:horizontal { height: 12px; }
|
|
|
|
QScrollBar::handle:horizontal { min-width: 50px; }
|
|
|
|
|
|
|
|
QScrollBar:vertical { width: 12px; }
|
|
|
|
QScrollBar::handle:vertical { min-height: 50px; }
|
|
|
|
|
|
|
|
QScrollBar::add-line {
|
|
|
|
border: none;
|
|
|
|
background: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
QScrollBar::sub-line {
|
|
|
|
border: none;
|
|
|
|
background: none;
|
|
|
|
}
|
2020-02-02 03:53:35 +01:00
|
|
|
|
|
|
|
QTabWidget { background-color: %s; }
|
2020-02-02 04:09:02 +01:00
|
|
|
""" % (
|
|
|
|
self.str_color("window-bg"),
|
|
|
|
colors.get("fusion-button-hover-bg"),
|
|
|
|
self.str_color("window-bg"),
|
|
|
|
)
|
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:
|
|
|
|
if not self.night_mode:
|
|
|
|
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()
|
|
|
|
|
|
|
|
text_fg = self.qcolor("text-fg")
|
|
|
|
palette.setColor(QPalette.WindowText, text_fg)
|
|
|
|
palette.setColor(QPalette.ToolTipText, text_fg)
|
|
|
|
palette.setColor(QPalette.Text, text_fg)
|
|
|
|
palette.setColor(QPalette.ButtonText, text_fg)
|
|
|
|
|
|
|
|
hlbg = self.qcolor("highlight-bg")
|
|
|
|
hlbg.setAlpha(64)
|
|
|
|
palette.setColor(QPalette.HighlightedText, self.qcolor("highlight-fg"))
|
|
|
|
palette.setColor(QPalette.Highlight, hlbg)
|
|
|
|
|
|
|
|
window_bg = self.qcolor("window-bg")
|
|
|
|
palette.setColor(QPalette.Window, window_bg)
|
|
|
|
palette.setColor(QPalette.AlternateBase, window_bg)
|
2020-02-02 03:53:35 +01:00
|
|
|
|
|
|
|
palette.setColor(QPalette.Button, QColor(colors.get("fusion-button-base-bg")))
|
2020-01-23 06:08:10 +01:00
|
|
|
|
2020-01-23 22:55:14 +01:00
|
|
|
frame_bg = self.qcolor("frame-bg")
|
|
|
|
palette.setColor(QPalette.Base, frame_bg)
|
|
|
|
palette.setColor(QPalette.ToolTipBase, frame_bg)
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
disabled_color = self.qcolor("disabled")
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.Text, disabled_color)
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.ButtonText, disabled_color)
|
|
|
|
palette.setColor(QPalette.Disabled, QPalette.HighlightedText, disabled_color)
|
|
|
|
|
|
|
|
palette.setColor(QPalette.Link, self.qcolor("link"))
|
|
|
|
|
|
|
|
palette.setColor(QPalette.BrightText, Qt.red)
|
|
|
|
|
|
|
|
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
|
|
|
|
2020-02-07 04:19:41 +01:00
|
|
|
s.colLearn = self.str_color("new-count")
|
|
|
|
s.colRelearn = self.str_color("learn-count")
|
|
|
|
s.colCram = self.str_color("suspended-bg")
|
|
|
|
s.colSusp = self.str_color("suspended-bg")
|
|
|
|
s.colMature = self.str_color("review-count")
|
|
|
|
|
2020-01-23 06:08:10 +01:00
|
|
|
|
|
|
|
theme_manager = ThemeManager()
|