diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 6d6c91b9b..e5eb6c912 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -18,7 +18,6 @@ from anki.utils import isMac from aqt import AnkiQt, gui_hooks from aqt.editor import Editor from aqt.exporting import ExportDialog -from aqt.flags import load_flags from aqt.operations.card import set_card_deck, set_card_flag from aqt.operations.collection import redo, undo from aqt.operations.note import remove_notes @@ -175,7 +174,7 @@ class Browser(QMainWindow): def set_flag_func(desired_flag: int) -> Callable: return lambda: self.set_flag_of_selected_cards(desired_flag) - for flag in load_flags(self.col): + for flag in self.mw.flags.all(): qconnect( getattr(self.form, flag.action).triggered, set_flag_func(flag.index) ) @@ -212,6 +211,7 @@ class Browser(QMainWindow): self._cleanup_preview() self.editor.cleanup() self.table.cleanup() + self.sidebar.cleanup() saveSplitter(self.form.splitter, "editor3") saveGeom(self, "editor") saveState(self, "editor") @@ -702,13 +702,13 @@ class Browser(QMainWindow): flag = self.card and self.card.user_flag() flag = flag or 0 - for f in load_flags(self.col): + for f in self.mw.flags.all(): getattr(self.form, f.action).setChecked(flag == f.index) qtMenuShortcutWorkaround(self.form.menuFlag) def _update_flag_labels(self) -> None: - for flag in load_flags(self.col): + for flag in self.mw.flags.all(): getattr(self.form, flag.action).setText(flag.label) def toggle_mark_of_selected_notes(self, checked: bool) -> None: @@ -782,6 +782,7 @@ class Browser(QMainWindow): gui_hooks.backend_did_block.append(self.table.on_backend_did_block) gui_hooks.operation_did_execute.append(self.on_operation_did_execute) gui_hooks.focus_did_change.append(self.on_focus_change) + gui_hooks.flag_label_did_change.append(self._update_flag_labels) def teardownHooks(self) -> None: gui_hooks.undo_state_did_change.remove(self.on_undo_state_change) @@ -789,6 +790,7 @@ class Browser(QMainWindow): gui_hooks.backend_did_block.remove(self.table.on_backend_will_block) gui_hooks.operation_did_execute.remove(self.on_operation_did_execute) gui_hooks.focus_did_change.remove(self.on_focus_change) + gui_hooks.flag_label_did_change.remove(self._update_flag_labels) # Undo ###################################################################### diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index 52a7c97ed..157538633 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -26,7 +26,6 @@ from aqt.browser.sidebar.searchbar import SidebarSearchBar from aqt.browser.sidebar.toolbar import SidebarTool, SidebarToolbar from aqt.clayout import CardLayout from aqt.fields import FieldDialog -from aqt.flags import load_flags from aqt.models import Models from aqt.operations import CollectionOp, QueryOp from aqt.operations.deck import ( @@ -109,6 +108,11 @@ class SidebarTreeView(QTreeView): self.toolbar = SidebarToolbar(self) self.searchBar = SidebarSearchBar(self) + gui_hooks.flag_label_did_change.append(self.refresh) + + def cleanup(self) -> None: + gui_hooks.flag_label_did_change.remove(self.refresh) + @property def tool(self) -> SidebarTool: return self._tool @@ -677,7 +681,7 @@ class SidebarTreeView(QTreeView): ) root.search_node = SearchNode(flag=SearchNode.FLAG_ANY) - for flag in load_flags(self.col): + for flag in self.mw.flags.all(): root.add_child( SidebarItem( name=flag.label, @@ -961,12 +965,8 @@ class SidebarTreeView(QTreeView): ########################### def rename_flag(self, item: SidebarItem, new_name: str) -> None: - labels = self.col.get_config("flagLabels", {}) - labels[str(item.id)] = new_name - self.col.set_config("flagLabels", labels) item.name = new_name - self.browser._update_flag_labels() - self.refresh() + self.mw.flags.rename_flag(item.id, new_name) # Decks ########################### diff --git a/qt/aqt/flags.py b/qt/aqt/flags.py index 3d1780a13..d2767912c 100644 --- a/qt/aqt/flags.py +++ b/qt/aqt/flags.py @@ -3,11 +3,11 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Dict, List, cast +from typing import Dict, List, Optional, cast -from anki import Collection +import aqt from anki.collection import SearchNode -from aqt import colors +from aqt import colors, gui_hooks from aqt.theme import ColoredIcon from aqt.utils import tr @@ -30,60 +30,82 @@ class Flag: action: str -def load_flags(col: Collection) -> List[Flag]: - """Return a list of all flags, reloading labels from the config.""" +class FlagManager: + def __init__(self, mw: aqt.main.AnkiQt) -> None: + self.mw = mw + self._flags: Optional[List[Flag]] = None - labels = cast(Dict[str, str], col.get_config("flagLabels", {})) - icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED) + def all(self) -> List[Flag]: + """Return a list of all flags.""" + if self._flags is None: + self._load_flags() + return self._flags - return [ - Flag( - 1, - labels["1"] if "1" in labels else tr.actions_flag_red(), - icon.with_color(colors.FLAG1_FG), - SearchNode(flag=SearchNode.FLAG_RED), - "actionRed_Flag", - ), - Flag( - 2, - labels["2"] if "2" in labels else tr.actions_flag_orange(), - icon.with_color(colors.FLAG2_FG), - SearchNode(flag=SearchNode.FLAG_ORANGE), - "actionOrange_Flag", - ), - Flag( - 3, - labels["3"] if "3" in labels else tr.actions_flag_green(), - icon.with_color(colors.FLAG3_FG), - SearchNode(flag=SearchNode.FLAG_GREEN), - "actionGreen_Flag", - ), - Flag( - 4, - labels["4"] if "4" in labels else tr.actions_flag_blue(), - icon.with_color(colors.FLAG4_FG), - SearchNode(flag=SearchNode.FLAG_BLUE), - "actionBlue_Flag", - ), - Flag( - 5, - labels["5"] if "5" in labels else tr.actions_flag_pink(), - icon.with_color(colors.FLAG5_FG), - SearchNode(flag=SearchNode.FLAG_PINK), - "actionPink_Flag", - ), - Flag( - 6, - labels["6"] if "6" in labels else tr.actions_flag_turquoise(), - icon.with_color(colors.FLAG6_FG), - SearchNode(flag=SearchNode.FLAG_TURQUOISE), - "actionTurquoise_Flag", - ), - Flag( - 7, - labels["7"] if "7" in labels else tr.actions_flag_purple(), - icon.with_color(colors.FLAG7_FG), - SearchNode(flag=SearchNode.FLAG_PURPLE), - "actionPurple_Flag", - ), - ] + def get_flag(self, flag_index: int) -> Flag: + if not 1 <= flag_index <= len(self.all()): + raise Exception(f"Flag index out of range (1-{len(self.all())}).") + return self.all()[flag_index - 1] + + def rename_flag(self, flag_index: int, new_name: str) -> None: + if new_name in ("", self.get_flag(flag_index).label): + return + labels = self.mw.col.get_config("flagLabels", {}) + labels[str(flag_index)] = self.get_flag(flag_index).label = new_name + self.mw.col.set_config("flagLabels", labels) + gui_hooks.flag_label_did_change() + + def _load_flags(self) -> None: + labels = cast(Dict[str, str], self.mw.col.get_config("flagLabels", {})) + icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED) + + self._flags = [ + Flag( + 1, + labels["1"] if "1" in labels else tr.actions_flag_red(), + icon.with_color(colors.FLAG1_FG), + SearchNode(flag=SearchNode.FLAG_RED), + "actionRed_Flag", + ), + Flag( + 2, + labels["2"] if "2" in labels else tr.actions_flag_orange(), + icon.with_color(colors.FLAG2_FG), + SearchNode(flag=SearchNode.FLAG_ORANGE), + "actionOrange_Flag", + ), + Flag( + 3, + labels["3"] if "3" in labels else tr.actions_flag_green(), + icon.with_color(colors.FLAG3_FG), + SearchNode(flag=SearchNode.FLAG_GREEN), + "actionGreen_Flag", + ), + Flag( + 4, + labels["4"] if "4" in labels else tr.actions_flag_blue(), + icon.with_color(colors.FLAG4_FG), + SearchNode(flag=SearchNode.FLAG_BLUE), + "actionBlue_Flag", + ), + Flag( + 5, + labels["5"] if "5" in labels else tr.actions_flag_pink(), + icon.with_color(colors.FLAG5_FG), + SearchNode(flag=SearchNode.FLAG_PINK), + "actionPink_Flag", + ), + Flag( + 6, + labels["6"] if "6" in labels else tr.actions_flag_turquoise(), + icon.with_color(colors.FLAG6_FG), + SearchNode(flag=SearchNode.FLAG_TURQUOISE), + "actionTurquoise_Flag", + ), + Flag( + 7, + labels["7"] if "7" in labels else tr.actions_flag_purple(), + icon.with_color(colors.FLAG7_FG), + SearchNode(flag=SearchNode.FLAG_PURPLE), + "actionPurple_Flag", + ), + ] diff --git a/qt/aqt/main.py b/qt/aqt/main.py index da4b9f69d..c224a18cb 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -48,6 +48,7 @@ from aqt import gui_hooks from aqt.addons import DownloadLogEntry, check_and_prompt_for_updates, show_log_to_user from aqt.dbcheck import check_db from aqt.emptycards import show_empty_cards +from aqt.flags import FlagManager from aqt.legacy import install_pylib_legacy from aqt.mediacheck import check_media_db from aqt.mediasync import MediaSyncer @@ -113,6 +114,7 @@ class AnkiQt(QMainWindow): self.col: Optional[Collection] = None self.taskman = TaskManager(self) self.media_syncer = MediaSyncer(self) + self.flags = FlagManager(self) aqt.mw = self self.app = app self.pm = profileManager diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 326019f3a..fb46ab16c 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -25,7 +25,6 @@ from anki.utils import stripHTML from aqt import AnkiQt, gui_hooks from aqt.browser.card_info import CardInfoDialog from aqt.deckoptions import confirm_deck_then_display_options -from aqt.flags import load_flags from aqt.operations.card import set_card_flag from aqt.operations.note import remove_notes from aqt.operations.scheduling import ( @@ -447,7 +446,7 @@ class Reviewer: (Qt.Key_F5, self.replayAudio), *( (f"Ctrl+{flag.index}", self.set_flag_func(flag.index)) - for flag in load_flags(self.mw.col) + for flag in self.mw.flags.all() ), ("*", self.toggle_mark_on_current_note), ("=", self.bury_current_note), @@ -904,7 +903,7 @@ time = %(time)d; self.set_flag_func(flag.index), dict(checked=currentFlag == flag.index), ] - for flag in load_flags(self.mw.col) + for flag in self.mw.flags.all() ], ], [tr.studying_bury_card(), "-", self.bury_current_card], diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 2fa4ed3a7..aebeb2587 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -946,6 +946,11 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest) args=["menu: QMenu", "deck_id: int"], legacy_hook="showDeckOptions", ), + Hook( + name="flag_label_did_change", + args=[], + doc="Used to update the GUI when a new flag label is assigned.", + ), ] suffix = ""