/ src / addon / config.py
config.py
  1  import collections
  2  import json
  3  from pathlib import Path
  4  from typing import Any, Iterable, List
  5  
  6  from aqt import QComboBox, QPushButton, colors
  7  from aqt.theme import theme_manager
  8  from aqt.utils import openLink, tooltip
  9  
 10  from .ankiaddonconfig import ConfigLayout, ConfigManager, ConfigWindow
 11  from .colors import recolor_python, recolor_web
 12  from .migrate import maybe_migrate_config
 13  
 14  THEMES_DIR = Path(__file__).parent / "themes"
 15  
 16  
 17  conf = ConfigManager()
 18  
 19  
 20  def open_web(url: str) -> None:
 21      openLink(f"https://{url}")
 22  
 23  
 24  def on_save() -> None:
 25      conf.save()
 26      recolor_python()
 27      recolor_web()
 28  
 29  
 30  def with_window(conf_window: ConfigWindow) -> None:
 31      conf_window.setWindowTitle("recolor-debloated Settings")
 32      conf_window.setMinimumWidth(500)
 33      conf_window.execute_on_save(on_save)
 34      conf_window.after_advanced_save_hook.append(lambda: maybe_migrate_config(conf))
 35  
 36  
 37  def color_idx() -> int:
 38      return 2 if theme_manager.night_mode else 1
 39  
 40  
 41  def populate_tab(layout: ConfigLayout, conf_keys: List[str]) -> None:
 42      for conf_key in conf_keys:
 43          name = conf.get(f"colors.{conf_key}.0")
 44          anki_color = getattr(colors, conf_key, None)
 45          description = anki_color["comment"] if anki_color is not None else None
 46          layout.color_input(
 47              f"colors.{conf_key}.{color_idx()}", name, tooltip=description, opacity=True
 48          )
 49      layout.stretch()
 50  
 51  
 52  def main_tab(conf_window: ConfigWindow) -> None:
 53      conf_keys = [
 54          "FG",
 55          "FG_SUBTLE",
 56          "FG_DISABLED",
 57          "FG_FAINT",
 58          "FG_LINK",
 59          "CANVAS",
 60          "CANVAS_ELEVATED",
 61          "CANVAS_INSET",
 62          "CANVAS_OVERLAY",
 63          "CANVAS_CODE",
 64          "BORDER",
 65          "BORDER_SUBTLE",
 66          "BORDER_STRONG",
 67          "BORDER_FOCUS",
 68      ]
 69      tab = conf_window.add_tab("Main")
 70      populate_tab(tab, conf_keys)
 71  
 72  
 73  def buttons_tab(conf_window: ConfigWindow) -> None:
 74      conf_keys = [
 75          "BUTTON_BG",
 76          "BUTTON_HOVER",
 77          "BUTTON_HOVER_BORDER",
 78          "BUTTON_DISABLED",
 79      ]
 80      tab = conf_window.add_tab("Buttons")
 81      populate_tab(tab, conf_keys)
 82  
 83  
 84  def cards_tab(conf_window: ConfigWindow) -> None:
 85      conf_keys = [
 86          "STATE_NEW",
 87          "STATE_LEARN",
 88          "STATE_REVIEW",
 89          "STATE_SUSPENDED",
 90          "STATE_BURIED",
 91          "FLAG_1",
 92          "FLAG_2",
 93          "FLAG_3",
 94          "FLAG_4",
 95          "FLAG_5",
 96          "FLAG_6",
 97          "FLAG_7",
 98          "ACCENT_CARD",
 99          "ACCENT_NOTE",
100      ]
101      tab = conf_window.add_tab("Cards")
102      populate_tab(tab, conf_keys)
103  
104  
105  def misc_tab(conf_window: ConfigWindow) -> None:
106      conf_keys = [
107          "HIGHLIGHT_BG",
108          "HIGHLIGHT_FG",
109          "SELECTED_BG",
110          "SELECTED_FG",
111          "ACCENT_DANGER",
112          "SHADOW",
113          "SHADOW_INSET",
114          "SHADOW_SUBTLE",
115          "SHADOW_FOCUS",
116          "SCROLLBAR_BG",
117          "SCROLLBAR_BG_ACTIVE",
118          "SCROLLBAR_BG_HOVER",
119      ]
120      tab = conf_window.add_tab("Misc")
121      populate_tab(tab, conf_keys)
122  
123  
124  def themes_list() -> list[str]:
125      themes: list[str] = []
126      for child in sorted(THEMES_DIR.iterdir()):
127          if child.is_file() and child.suffix == ".json":
128              themes.append(child.stem)
129      return themes
130  
131  
132  def replace_conf_color(conf: ConfigManager, theme_json: Any, dark_mode: bool) -> None:
133      modeidx = 2 if dark_mode else 1
134  
135      for color in theme_json["colors"]:
136          conf[f"colors.{color}.{modeidx}"] = theme_json["colors"][color][modeidx]
137  
138  
139  def apply_theme(conf_window: ConfigWindow, theme: str) -> None:
140      theme_path = THEMES_DIR / f"{theme}.json"
141      theme_json = json.loads(theme_path.read_text())
142  
143      # Dark mode or universal
144      if not theme.startswith("(dark)"):
145          replace_conf_color(conf, theme_json, False)
146      # Light mode or universal
147      if not theme.startswith("(light)"):
148          replace_conf_color(conf, theme_json, True)
149  
150      conf_window.update_widgets()
151      conf_window.main_tab.setCurrentIndex(0)
152  
153      tooltip(f"Applied theme: {theme}<br />Press save to save changes")
154  
155  
156  def themes_tab(conf_window: ConfigWindow) -> None:
157      tab = conf_window.add_tab("Themes")
158      tab.space(10)
159  
160      tab.text(
161          "Note: You may need to toggle dark mode in Anki preferences to see changes",
162          multiline=True,
163      )
164  
165      apply_lay = tab.hlayout()
166      apply_lay.text("Themes:")
167      apply_lay.space(10)
168  
169      combobox = QComboBox(conf_window)        
170      combobox.insertItems(0, themes_list())
171      
172      apply_lay.addWidget(combobox)
173  
174      btn = QPushButton("Apply theme", conf_window)
175      btn.clicked.connect(lambda _: apply_theme(conf_window, combobox.currentText()))
176      apply_lay.addWidget(btn)
177      apply_lay.stretch()
178  
179      tab.stretch()
180  
181  
182  conf.use_custom_window()
183  conf.on_window_open(with_window)
184  conf.add_config_tab(main_tab)
185  conf.add_config_tab(buttons_tab)
186  conf.add_config_tab(cards_tab)
187  conf.add_config_tab(misc_tab)
188  conf.add_config_tab(themes_tab)