summaryrefslogtreecommitdiff
path: root/.local/share/Anki2/addons21/anki_reworked/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to '.local/share/Anki2/addons21/anki_reworked/__init__.py')
-rw-r--r--.local/share/Anki2/addons21/anki_reworked/__init__.py705
1 files changed, 705 insertions, 0 deletions
diff --git a/.local/share/Anki2/addons21/anki_reworked/__init__.py b/.local/share/Anki2/addons21/anki_reworked/__init__.py
new file mode 100644
index 0000000..81b2627
--- /dev/null
+++ b/.local/share/Anki2/addons21/anki_reworked/__init__.py
@@ -0,0 +1,705 @@
+import json
+### Custom util functions
+from .utils.modules import *
+### Logger for debuging
+from .utils.logger import logger
+
+from typing import Any, List, Optional
+from PyQt5.QtWidgets import QWidget
+from platform import system, version, release
+from ctypes import *
+# import the main window object (mw) from aqt
+from aqt import AnkiQt, DialogManager, mw
+from aqt.theme import theme_manager, colors
+from aqt import gui_hooks
+## QT dialog windows
+# Browser import legacy check (2.1.22)
+if module_exists("aqt.browser.browser"):
+ from aqt.browser.browser import Browser
+else:
+ from aqt.browser import Browser
+# DeckStats import legacy check
+if module_has_attribute("aqt.stats", "NewDeckStats"):
+ from aqt.stats import DeckStats, NewDeckStats
+else:
+ from aqt.stats import DeckStats
+from aqt.addcards import AddCards
+from aqt.editcurrent import EditCurrent
+from aqt.about import ClosableQDialog
+from aqt.preferences import Preferences
+from aqt.addons import AddonsDialog
+# FilteredDeckConfigDialog import non-legacy check
+if module_exists("aqt.filtered_deck"):
+ from aqt.filtered_deck import FilteredDeckConfigDialog
+# QT page views
+from aqt.toolbar import Toolbar, TopToolbar
+from aqt.deckbrowser import DeckBrowser, DeckBrowserBottomBar
+from aqt.overview import Overview, OverviewBottomBar
+from aqt.editor import Editor
+from aqt.reviewer import Reviewer, ReviewerBottomBar
+from aqt.webview import AnkiWebView, WebContent
+
+### Load config data here
+from .config import config, write_config, get_config
+
+## Addon compatibility fixes
+# More Overview Stats 2.1 addon compatibility fix
+addon_more_overview_stats_fix = config['addon_more_overview_stats']
+addon_advanced_review_bottom_bar = config['addon_advanced_review_bottom_bar']
+
+## Customization
+theme = config['theme']
+# Init script/file path
+from .utils.css_files import css_files_dir
+from .utils.themes import themes, write_theme, get_theme
+logger.debug(css_files_dir)
+logger.debug(themes)
+themes_parsed = get_theme(theme)
+color_mode = 2 if theme_manager.get_night_mode() else 1 # 1 = light and 2 = dark
+
+dwmapi = None
+## Darkmode windows titlebar thanks to miere43
+def set_dark_titlebar(window, dwmapi) -> None:
+ if dwmapi:
+ handler_window = c_void_p(int(window.winId()))
+ DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = c_int(19)
+ DWMWA_USE_IMMERSIVE_DARK_MODE = c_int(20)
+ windows_version = int(version().split('.')[2])
+ attribute = DWMWA_USE_IMMERSIVE_DARK_MODE if windows_version >= 18985 else DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1
+ if windows_version >= 17763 and int(release()) >= 10:
+ dwmapi.DwmSetWindowAttribute(handler_window, attribute, byref(c_int(1)), c_size_t(4))
+def set_dark_titlebar_qt(obj, dwmapi, fix=True) -> None:
+ if dwmapi and theme_manager.get_night_mode():
+ set_dark_titlebar(obj, dwmapi)
+ # Trick to refresh the titlebar after dark titlebar is set
+ if fix:
+ obj.showFullScreen()
+ obj.showNormal()
+if system() == "Windows" and theme_manager.get_night_mode():
+ dwmapi = WinDLL("dwmapi")
+ if dwmapi:
+ dwmapi.DwmSetWindowAttribute.argtypes = [c_void_p, c_int, c_void_p, c_size_t]
+ dwmapi.DwmSetWindowAttribute.restype = c_int
+ set_dark_titlebar(mw, dwmapi)
+logger.debug(dwmapi)
+
+### CSS injections
+def load_custom_style():
+ theme_colors = ""
+ for color_name in themes_parsed.get("colors"):
+ color = themes_parsed.get("colors").get(color_name)
+ if color[3]:
+ theme_colors += f"{color[3]}: {color[color_mode]};\n "
+ else:
+ theme_colors += f"--{color_name.lower().replace('_','-')}: {color[color_mode]};\n "
+ custom_style = """
+<style>
+ :root,
+ :root .isMac,
+ :root .isWin,
+ :root .isLin {
+ %s
+ }
+ html {
+ font-family: %s;
+ font-size: %spx;
+ }
+</style>
+ """ % (theme_colors, config["font"], config["font_size"])
+ return custom_style
+
+## Adds styling on the different webview contents, before the content is set
+def on_webview_will_set_content(web_content: WebContent, context: Optional[Any]) -> None:
+ logger.debug(context) # Logs content being loaded, find out the instance
+ web_content.css.append(css_files_dir['global']) # Global css
+ web_content.head += load_custom_style() # Custom styling
+ # Deckbrowser
+ if isinstance(context, DeckBrowser):
+ web_content.css.append(css_files_dir['DeckBrowser'])
+ # TopToolbar
+ elif isinstance(context, TopToolbar):
+ web_content.css.append(css_files_dir['TopToolbar'])
+ # BottomToolbar (Buttons)
+ elif isinstance(context, DeckBrowserBottomBar) or isinstance(context, OverviewBottomBar):
+ web_content.css.append(css_files_dir['BottomBar'])
+ # Overview
+ elif isinstance(context, Overview):
+ if addon_more_overview_stats_fix:
+ web_content.head += "<style>center > table tr:first-of-type {display: table-row; flex-direction: unset;}</style>"
+ web_content.css.append(css_files_dir['Overview'])
+ # Editor
+ elif isinstance(context, Editor):
+ web_content.css.append(css_files_dir['Editor'])
+ # Reviewer
+ elif isinstance(context, Reviewer):
+ web_content.css.append(css_files_dir['Reviewer'])
+ elif isinstance(context, ReviewerBottomBar):
+ if addon_advanced_review_bottom_bar:
+ #web_content.head += "<style>td.stat[align='left']:nth-of-type(2) {position: absolute; z-index: 1;}</style>"
+ web_content.body += "<script>const center = document.getElementById('outer');center.classList.add('arbb');</script>"
+ else:
+ web_content.css.append(css_files_dir['BottomBar'])
+ web_content.css.append(css_files_dir['ReviewerBottomBar'])
+ # Button padding bottom
+ web_content.body += "<div style='height: 9px; opacity: 0; pointer-events: none;'></div>"
+ web_content.body += "<div id='padFix' style='height: 30px; opacity: 0; pointer-events: none;'><script>const e = document.getElementById('padFix');e.parentElement.removeChild(e);</script></div>"
+ mw.bottomWeb.adjustHeightToFit()
+ # CardLayout
+ elif context_name_includes(context, "aqt.clayout.CardLayout"):
+ web_content.css.append(css_files_dir['CardLayout'])
+ ## Legacy webviews
+ # ResetRequired on card edit (legacy)
+ elif context_name_includes(context, "aqt.main.ResetRequired"):
+ web_content.css.append(css_files_dir['legacy'])
+gui_hooks.webview_will_set_content.append(on_webview_will_set_content)
+
+# TopToolbar styling fix through height change by adding <br> tag
+def redraw_toolbar() -> None:
+ # Reload the webview content with added <br/> tag, making the bar larger in height
+ mw.toolbar.web.setFixedHeight(60)
+ mw.toolbar.web.eval("""
+ var br = document.createElement("br");
+ document.body.appendChild(br);
+ """)
+
+ if 'Qt6' in QPalette.ColorRole.__module__:
+ mw.toolbar.web.eval("""
+ var div = document.createElement("div");
+ div.style.width = "5px";
+ div.style.height = "10px";
+ document.body.appendChild(div);
+ """)
+ # Auto adjust the height, then redraw the toolbar
+ mw.toolbar.web.adjustHeightToFit()
+ mw.toolbar.redraw()
+
+def redraw_toolbar_legacy(links: List[str], _: Toolbar) -> None:
+ # Utilizing the link hook, we inject <br/> tag through javascript
+ inject_br = """
+ <script>
+ while (document.body.querySelectorAll("br").length > 1)
+ document.body.querySelectorAll("br")[0].remove();
+ var br = document.createElement("br");
+ document.body.appendChild(br);
+ </script>
+ """
+ mw.toolbar.web.setFixedHeight(60)
+ links.append(inject_br)
+ mw.toolbar.web.adjustHeightToFit()
+
+if attribute_exists(gui_hooks, "main_window_did_init"):
+ pass
+ #gui_hooks.main_window_did_init.append(redraw_toolbar)
+elif attribute_exists(gui_hooks, "top_toolbar_did_init_links"):
+ gui_hooks.top_toolbar_did_init_links.append(redraw_toolbar_legacy)
+ pass
+
+# Dialog window styling
+def on_dialog_manager_did_open_dialog(dialog_manager: DialogManager, dialog_name: str, dialog_instance: QWidget) -> None:
+ logger.debug(dialog_name)
+ dialog: AnkiQt = dialog_manager._dialogs[dialog_name][1]
+ # If dwmapi found and nightmode is enabled, set dark titlebar to dialog window
+ set_dark_titlebar_qt(dialog, dwmapi)
+ # AddCards
+ if dialog_name == "AddCards":
+ context: AddCards = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QAddCards'], encoding='utf-8').read())
+ # Addons popup
+ elif dialog_name == "AddonsDialog":
+ context: AddonsDialog = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QAddonsDialog'], encoding='utf-8').read())
+ # Browser
+ elif dialog_name == "Browser":
+ context: Browser = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QBrowser'], encoding='utf-8').read())
+ pass
+ # EditCurrent
+ elif dialog_name == "EditCurrent":
+ context: EditCurrent = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QEditCurrent'], encoding='utf-8').read())
+ # FilteredDeckConfigDialog
+ elif module_exists("aqt.filtered_deck") and dialog_name == "FilteredDeckConfigDialog":
+ context: FilteredDeckConfigDialog = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QFilteredDeckConfigDialog'], encoding='utf-8').read())
+ # Statistics / NewDeckStats
+ elif dialog_name == "NewDeckStats":
+ context: NewDeckStats = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QNewDeckStats'], encoding='utf-8').read())
+ # About
+ elif dialog_name == "About":
+ context: ClosableQDialog = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QAbout'], encoding='utf-8').read())
+ # Preferences
+ elif dialog_name == "Preferences":
+ context: Preferences = dialog_manager._dialogs[dialog_name][1]
+ context.setStyleSheet(open(css_files_dir['QPreferences'], encoding='utf-8').read())
+ # sync_log - kore ha nani desu???
+ elif dialog_name == "sync_log":
+ pass
+
+if attribute_exists(gui_hooks, "dialog_manager_did_open_dialog"):
+ gui_hooks.dialog_manager_did_open_dialog.append(on_dialog_manager_did_open_dialog)
+else:
+ ## Legacy dialog window styling
+ # Sad monkey patch, instead of hooks :c
+ # setupDialogGC is being called on almost all dialog windows, utilizing this, the
+ # function is used as a type of hook to inject CSS styling on the QT instances
+ def monkey_setup_dialog_gc(obj: Any) -> None:
+ obj.finished.connect(lambda: mw.gcWindow(obj))
+ logger.debug(obj)
+ set_dark_titlebar_qt(obj, dwmapi)
+ # AddCards
+ if isinstance(obj, AddCards):
+ obj.setStyleSheet(open(css_files_dir['QAddCards'], encoding='utf-8').read())
+ # EditCurrent
+ elif isinstance(obj, EditCurrent):
+ obj.setStyleSheet(open(css_files_dir['QEditCurrent'], encoding='utf-8').read())
+ # Statistics / DeckStats
+ elif isinstance(obj, DeckStats):
+ obj.setStyleSheet(open(css_files_dir['QNewDeckStats'], encoding='utf-8').read())
+ # About
+ elif isinstance(obj, ClosableQDialog):
+ obj.setStyleSheet(open(css_files_dir['QAbout'], encoding='utf-8').read())
+ # Preferences
+ ## Haven't found a solution for preferences yet :c
+ mw.setupDialogGC = monkey_setup_dialog_gc # Should be rare enough for other addons to also patch this I hope.
+
+ # Addons popup
+ if attribute_exists(gui_hooks, "addons_dialog_will_show"):
+ def on_addons_dialog_will_show(dialog: AddonsDialog) -> None:
+ logger.debug(dialog)
+ set_dark_titlebar_qt(dialog, dwmapi)
+ dialog.setStyleSheet(open(css_files_dir['QAddonsDialog'], encoding='utf-8').read())
+ gui_hooks.addons_dialog_will_show.append(on_addons_dialog_will_show)
+ # Browser
+ if attribute_exists(gui_hooks, "browser_will_show"):
+ def on_browser_will_show(browser: Browser) -> None:
+ logger.debug(browser)
+ set_dark_titlebar_qt(browser, dwmapi)
+ browser.setStyleSheet(open(css_files_dir['QBrowser'], encoding='utf-8').read())
+ gui_hooks.browser_will_show.append(on_browser_will_show)
+
+## CONFIG DIALOG
+from aqt import mw, gui_hooks
+from aqt.qt import *
+from .config import config
+from aqt.utils import showInfo
+from aqt.webview import AnkiWebView
+
+class ThemeEditor(QDialog):
+ def __init__(self, parent, *args, **kwargs):
+ super().__init__(parent=parent or mw, *args, **kwargs)
+ self.config_editor = parent
+ self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowTitle(f'Anki-redesign Advanced Editor')
+ self.setSizePolicy(self.make_size_policy())
+ self.setMinimumSize(420, 420)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
+ set_dark_titlebar_qt(self, dwmapi, fix=False)
+ # Root layout
+ self.root_layout = QVBoxLayout(self)
+ # Main layout
+ self.layout = QVBoxLayout()
+ self.textedit = QTextEdit()
+ themes_plaintext = open(themes[theme], encoding='utf-8').read()
+ self.textedit.setPlainText(themes_plaintext)
+ self.layout.addWidget(self.textedit)
+ self.root_layout.addLayout(self.layout)
+ self.root_layout.addLayout(self.make_button_box())
+
+ def save_edit(self) -> None:
+ themes_parsed = json.loads(self.textedit.toPlainText())
+ write_theme(themes[theme], themes_parsed)
+ self.config_editor.update()
+ self.accept()
+
+ def make_button_box(self) -> QWidget:
+ def cancel():
+ button = QPushButton('Cancel')
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ qconnect(button.clicked, self.accept)
+ return button
+ def save():
+ button = QPushButton('Save')
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ button.setDefault(True)
+ button.setShortcut("Ctrl+Return")
+ button.clicked.connect(lambda _: self.save_edit())
+ return button
+
+ button_box = QHBoxLayout()
+ button_box.addStretch()
+ button_box.addWidget(cancel())
+ button_box.addWidget(save())
+ return button_box
+
+ def make_size_policy(self) -> QSizePolicy:
+ size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
+ size_policy.setHorizontalStretch(0)
+ size_policy.setVerticalStretch(0)
+ size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
+ return size_policy
+
+class ConfigDialog(QDialog):
+ def __init__(self, parent: QWidget, *args, **kwargs):
+ super().__init__(parent=parent or mw, *args, **kwargs)
+ self.setWindowModality(Qt.ApplicationModal)
+ self.setWindowTitle(f'Anki-redesign Configuration')
+ self.setSizePolicy(self.make_size_policy())
+ self.setMinimumSize(420, 580)
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
+ set_dark_titlebar_qt(self, dwmapi, fix=False)
+
+ # Color/theme
+ ## Loads theme color
+ self.theme_colors = themes_parsed.get("colors")
+ self.updates = []
+ self.theme_general = ["TEXT_FG", "WINDOW_BG", "FRAME_BG", "BUTTON_BG", "TOOLTIP_BG", "BORDER", "MEDIUM_BORDER", "FAINT_BORDER", "HIGHLIGHT_BG", "HIGHLIGHT_FG" , "LINK", "DISABLED", "SLIGHTLY_GREY_TEXT", "PRIMARY_COLOR"]
+ self.theme_decks = ["CURRENT_DECK", "NEW_COUNT", "LEARN_COUNT", "REVIEW_COUNT", "ZERO_COUNT"]
+ self.theme_browse = ["BURIED_FG", "SUSPENDED_FG", "MARKED_BG", "FLAG1_BG", "FLAG1_FG", "FLAG2_BG", "FLAG2_FG", "FLAG3_BG", "FLAG3_FG", "FLAG4_BG", "FLAG4_FG", "FLAG5_BG", "FLAG5_FG", "FLAG6_BG", "FLAG6_FG", "FLAG7_BG", "FLAG7_FG"]
+ self.theme_extra = []
+
+ # Root layout
+ self.root_layout = QVBoxLayout(self)
+ # Main layout
+ self.layout = QVBoxLayout()
+ ## Initialize tab screen
+ self.tabs = QTabWidget(objectName="tabs")
+ self.tabs.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+ self.tab_general = QWidget(objectName="general")
+ self.tab_general.setLayout(self.create_color_picker_layout(self.theme_general))
+ self.tab_decks = QWidget(objectName="decks")
+ self.tab_decks.setLayout(self.create_color_picker_layout(self.theme_decks))
+ self.tab_browse = QWidget(objectName="browse")
+ self.tab_browse.setLayout(self.create_color_picker_layout(self.theme_browse))
+ self.tab_extra = QWidget(objectName="extra")
+ self.tab_extra.setLayout(self.create_color_picker_layout(self.theme_extra))
+
+ self.tab_settings = QWidget(objectName="settings")
+ self.settings_layout = QFormLayout()
+ self.theme_label = QLabel("Theme:")
+ self.theme_label.setStyleSheet('QLabel { font-size: 14px; font-weight: bold }')
+ self.settings_layout.addRow(self.theme_label)
+ for key in themes:
+ self.radio = self.radio_button(key)
+ self.settings_layout.addRow(key, self.radio)
+ self.settings_layout.addRow(QLabel())
+
+ self.font_label = QLabel("Font:")
+ self.font_label.setStyleSheet('QLabel { font-size: 14px; font-weight: bold }')
+ self.settings_layout.addRow(self.font_label)
+ self.interface_font = QFontComboBox()
+ self.interface_font.setFixedWidth(200)
+ self.interface_font.setCurrentFont(QFont(config["font"]))
+ self.settings_layout.addRow(self.interface_font)
+
+ self.font_size = QSpinBox()
+ self.font_size.setFixedWidth(200)
+ self.font_size.setValue(config["font_size"])
+ self.font_size.setSuffix("px")
+ self.settings_layout.addRow(self.font_size)
+
+ self.settings_layout.addRow(QLabel())
+
+
+ self.fix_label = QLabel("Addon-Compatibility Fixes: ")
+ self.fix_label.setStyleSheet('QLabel { font-size: 14px; font-weight: bold }')
+ self.settings_layout.addRow(self.fix_label)
+ self.reload_theme = self.checkbox("theme_reload")
+ self.settings_layout.addRow("QT6 theme on-start reload fix", self.reload_theme)
+ self.addon_more_overview_stats_check = self.checkbox("addon_more_overview_stats")
+ self.settings_layout.addRow("More Overview Stats 21", self.addon_more_overview_stats_check)
+ self.addon_advanced_review_bottom_bar_check = self.checkbox("addon_advanced_review_bottom_bar")
+ self.settings_layout.addRow("Advanced Review Bottom Bar", self.addon_advanced_review_bottom_bar_check)
+
+ self.tab_settings.setLayout(self.settings_layout)
+
+ ## Add tabs
+ self.tabs.resize(300,200)
+ self.tabs.addTab(self.tab_settings,"Settings")
+ self.tabs.addTab(self.tab_general,"General")
+ self.tabs.addTab(self.tab_decks,"Decks")
+ self.tabs.addTab(self.tab_browse,"Browse")
+ #self.tabs.addTab(self.tab_extra,"Extra")
+
+ ## Add tabs to widget
+ self.layout.addWidget(self.tabs)
+
+ self.root_layout.addLayout(self.layout)
+ self.root_layout.addLayout(self.make_button_box())
+ self.setLayout(self.root_layout)
+ self.show()
+
+ def update(self) -> None:
+ global themes_parsed
+ themes_parsed = get_theme(theme)
+ self.theme_colors = themes_parsed.get("colors")
+ for update in self.updates:
+ update()
+
+ def checkbox(self, key: str) -> QCheckBox:
+ checkbox = QCheckBox()
+
+ def update() -> None:
+ value = config[key]
+ checkbox.setChecked(value)
+
+ self.updates.append(update)
+ update()
+ return checkbox
+
+ def radio_button(self, key: str) -> QRadioButton:
+ radio = QRadioButton()
+
+ def update() -> None:
+ if theme == key:
+ radio.setChecked(True)
+ elif radio.isChecked():
+ radio.setChecked(False)
+
+ def toggle(checked) -> None:
+ global theme
+ if checked:
+ theme = key
+ self.update()
+
+ self.updates.append(update)
+ radio.toggled.connect(lambda checked: toggle(checked))
+ update()
+ return radio
+
+ def color_input(self, key: str) -> QPushButton:
+ button = QPushButton()
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ button.setFixedWidth(25)
+ button.setFixedHeight(25)
+
+ color_dialog = QColorDialog(self)
+
+ def set_color(rgb: str) -> None:
+ # Check for valid color
+ color = QColor()
+ color.setNamedColor(rgb)
+ if not color.isValid():
+ return
+ # Update color
+ color_dialog.setCurrentColor(color)
+ button.setStyleSheet('QPushButton{ background-color: "%s"; border: none; border-radius: 2px}' % rgb)
+
+ def update() -> None:
+ rgb = self.theme_colors.get(key)[color_mode]
+ set_color(rgb)
+
+ def save(color: QColor) -> None:
+ rgb = color.name(QColor.NameFormat.HexRgb)
+ self.theme_colors[key][color_mode] = rgb
+ set_color(rgb)
+
+ self.updates.append(update)
+ color_dialog.colorSelected.connect(lambda color: save(color))
+ button.clicked.connect(lambda _: color_dialog.exec())
+ return button
+
+ def create_color_picker_layout(self, colors) -> None:
+ layout = QFormLayout()
+ for key in colors:
+ self.test = self.color_input(key)
+ layout.addRow(self.theme_colors.get(key)[0], self.test)
+ return layout
+
+ def theme_file_editor(self) -> None:
+ diag = ThemeEditor(self)
+ diag.show()
+
+ def make_button_box(self) -> QWidget:
+ def advanced():
+ button = QPushButton('Advanced')
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ qconnect(button.clicked, self.theme_file_editor)
+ return button
+ def cancel():
+ button = QPushButton('Cancel')
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ qconnect(button.clicked, self.accept)
+ return button
+ def save():
+ button = QPushButton('Save')
+ button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+ button.setDefault(True)
+ button.setShortcut("Ctrl+Return")
+ button.clicked.connect(lambda _: self.save())
+ return button
+
+ button_box = QHBoxLayout()
+ button_box.addWidget(advanced())
+ button_box.addStretch()
+ button_box.addWidget(cancel())
+ button_box.addWidget(save())
+ return button_box
+
+ def make_size_policy(self) -> QSizePolicy:
+ size_policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
+ size_policy.setHorizontalStretch(0)
+ size_policy.setVerticalStretch(0)
+ size_policy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
+ return size_policy
+
+ def save(self) -> None:
+ # Save settings and update config
+ global config, color_mode
+ config["font"] = self.interface_font.currentFont().family()
+ config["font_size"] = self.font_size.value()
+ config['addon_more_overview_stats'] = self.addon_more_overview_stats_check.isChecked()
+ config['addon_advanced_review_bottom_bar'] = self.addon_advanced_review_bottom_bar_check.isChecked()
+ config['theme_reload'] = self.reload_theme.isChecked()
+ config["theme"] = theme
+ write_config(config)
+ config = get_config()
+
+ # Write and update theme
+ color_mode = 2 if theme_manager.get_night_mode() else 1 # 1 = light and 2 = dark
+ themes_parsed["colors"] = self.theme_colors
+ write_theme(themes[theme], themes_parsed)
+ update_theme()
+
+ # mw.reset()
+ # ShowInfo for both new and legacy support
+ showInfo(_("Changes will take effect when you restart Anki."))
+ #showInfo(tr.preferences_changes_will_take_effect_when_you())
+ self.accept()
+
+def check_legacy_colors() -> None:
+ try:
+ _ = colors.items()
+ except:
+ return False
+ return True
+
+def refresh_all_windows() -> None:
+ # Redraw top toolbar
+ mw.toolbar.draw()
+ if attribute_exists(gui_hooks, "top_toolbar_did_init_links"):
+ gui_hooks.top_toolbar_did_init_links.append(lambda a,b: [redraw_toolbar_legacy(a,b), gui_hooks.top_toolbar_did_init_links.remove(print)])
+
+ # Redraw main body
+ if mw.state == "review":
+ mw.reviewer._initWeb()
+ if mw.reviewer.state == "question":
+ mw.reviewer._showQuestion()
+ else:
+ mw.reviewer._showAnswer()
+ elif mw.state == "overview":
+ mw.overview.refresh()
+ elif mw.state == "deckBrowser":
+ mw.deckBrowser.show()
+
+ if attribute_exists(gui_hooks, "top_toolbar_did_init_links"):
+ gui_hooks.top_toolbar_did_init_links.remove(redraw_toolbar)
+
+def update_theme() -> None:
+ themes_parsed = get_theme(theme)
+ theme_colors = themes_parsed.get("colors")
+ # Apply theme on colors
+ ncolors = {}
+ # Legacy color check
+ logger.debug(dir(colors))
+ legacy = check_legacy_colors()
+ for color_name in theme_colors:
+ c = theme_colors.get(color_name)
+ ncolors[color_name] = c[color_mode]
+ if legacy:
+ colors[f"day{c[3].replace('--','-')}"] = c[1]
+ colors[f"night{c[3].replace('--','-')}"] = c[2]
+ # Potentially add fusion fixes too?
+ else:
+ logger.debug(getattr(colors, color_name, False))
+ if getattr(colors, color_name, False):
+ setattr(colors, color_name, (c[1], c[2]))
+ # Apply theme on palette
+ apply_theme(ncolors)
+ refresh_all_windows()
+
+def apply_theme(colors) -> None:
+ # Load palette
+ palette = QPalette()
+ # QT mappings
+ #logger.debug(QPalette.ColorRole.__dict__)
+ color_map = {
+ QPalette.ColorRole.Window: "WINDOW_BG",
+ QPalette.ColorRole.WindowText: "TEXT_FG",
+ QPalette.ColorRole.Base: "FRAME_BG",
+ QPalette.ColorRole.AlternateBase: "WINDOW_BG",
+ QPalette.ColorRole.ToolTipBase: "TOOLTIP_BG",
+ QPalette.ColorRole.ToolTipText: "TEXT_FG",
+ QPalette.ColorRole.Text: "TEXT_FG",
+ QPalette.ColorRole.Button: "BUTTON_BG",
+ QPalette.ColorRole.ButtonText: "TEXT_FG",
+ QPalette.ColorRole.BrightText: "HIGHLIGHT_FG",
+ QPalette.ColorRole.HighlightedText: "HIGHLIGHT_FG",
+ QPalette.ColorRole.Link: "LINK",
+ QPalette.ColorRole.NoRole: "WINDOW_BG",
+ }
+ for color_role in color_map:
+ palette.setColor(color_role, QColor(colors[color_map[color_role]]))
+
+ highlight_bg = QColor(colors["HIGHLIGHT_BG"])
+ highlight_bg.setAlpha(64)
+ palette.setColor(QPalette.ColorRole.Highlight, highlight_bg)
+
+ disabled_color = QColor(colors["DISABLED"])
+ palette.setColor(QPalette.ColorRole.PlaceholderText, disabled_color)
+ 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)
+
+ # Update palette
+ mw.app.setPalette(palette)
+ theme_manager.default_palette = palette
+ theme_manager._apply_style(mw.app)
+
+ # Update webview bg
+ AnkiWebView._getWindowColor = lambda *args: QColor(colors["WINDOW_BG"])
+ AnkiWebView.get_window_bg_color = lambda *args: QColor(colors["WINDOW_BG"])
+
+# Create menu actions
+def create_menu_action(parent: QWidget, dialog_class: QDialog, dialog_name: str) -> QAction:
+ def open_dialog():
+ dialog = dialog_class(mw)
+ return dialog.exec()
+
+ action = QAction(dialog_name, parent)
+ #qconnect(action.triggered, open_dialog)
+ action.triggered.connect(open_dialog)
+ return action
+
+# Load in the Anki-redesign menu
+if not hasattr(mw, 'anki_redesign'):
+ mw.anki_redesign = QMenu("&Anki-redesign", mw)
+ mw.form.menubar.insertMenu(mw.form.menuHelp.menuAction(), mw.anki_redesign)
+
+ mw.anki_redesign.addAction(create_menu_action(mw.anki_redesign, ConfigDialog, "&Config"))
+ mw.anki_redesign.addSeparator()
+ mw.reset()
+ update_theme()
+ mw.reset()
+
+ # Rereload view to fix theme change on startup
+ if 'Qt6' in QPalette.ColorRole.__module__:
+ logger.debug('QT6 DETECTED....')
+ config = get_config()
+ if config["theme_reload"]:
+ update_theme()
+
+def on_theme_did_change() -> None:
+ global color_mode
+ color_mode = 2 if theme_manager.get_night_mode() else 1 # 1 = light and 2 = dark
+ update_theme()
+ logger.debug("THEME CHANGEEEED")
+ refresh_all_windows()
+ mw.reset()
+ update_theme()
+
+
+if attribute_exists(gui_hooks, "theme_did_change"):
+ gui_hooks.theme_did_change.append(on_theme_did_change)
+ logger.debug("YEP") \ No newline at end of file