From 9816227b5caa86d7edbcaec7b9697aefbfead2a7 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 18 May 2021 22:08:36 +0200 Subject: [PATCH 1/6] Make sidebar flags renamable --- qt/aqt/browser/sidebar/item.py | 1 + qt/aqt/browser/sidebar/tree.py | 61 ++++++++++++++++++---------------- qt/aqt/utils.py | 33 +++++++++++++++++- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/qt/aqt/browser/sidebar/item.py b/qt/aqt/browser/sidebar/item.py index 4c38eece2..d38e8a7c4 100644 --- a/qt/aqt/browser/sidebar/item.py +++ b/qt/aqt/browser/sidebar/item.py @@ -40,6 +40,7 @@ class SidebarItemType(Enum): def is_editable(self) -> bool: return self in ( + SidebarItemType.FLAG, SidebarItemType.SAVED_SEARCH, SidebarItemType.DECK, SidebarItemType.TAG, diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index 407faadef..573979938 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -35,7 +35,14 @@ from aqt.operations.tag import ( ) from aqt.qt import * from aqt.theme import ColoredIcon, theme_manager -from aqt.utils import KeyboardModifiersPressed, askUser, getOnlyText, showWarning, tr +from aqt.utils import ( + KeyboardModifiersPressed, + askUser, + getOnlyText, + load_flags, + showWarning, + tr, +) class SidebarStage(Enum): @@ -361,6 +368,8 @@ class SidebarTreeView(QTreeView): self.rename_saved_search(item, new_name) elif item.item_type == SidebarItemType.TAG: self.rename_tag(item, new_name) + elif item.item_type == SidebarItemType.FLAG: + self.rename_flag(item, new_name) # renaming may be asynchronous so always return False return False @@ -600,35 +609,21 @@ class SidebarTreeView(QTreeView): ) root.search_node = SearchNode(flag=SearchNode.FLAG_ANY) - type = SidebarItemType.FLAG - root.add_simple( - tr.actions_red_flag(), - icon=icon.with_color(colors.FLAG1_FG), - type=type, - search_node=SearchNode(flag=SearchNode.FLAG_RED), - ) - root.add_simple( - tr.actions_orange_flag(), - icon=icon.with_color(colors.FLAG2_FG), - type=type, - search_node=SearchNode(flag=SearchNode.FLAG_ORANGE), - ) - root.add_simple( - tr.actions_green_flag(), - icon=icon.with_color(colors.FLAG3_FG), - type=type, - search_node=SearchNode(flag=SearchNode.FLAG_GREEN), - ) - root.add_simple( - tr.actions_blue_flag(), - icon=icon.with_color(colors.FLAG4_FG), - type=type, - search_node=SearchNode(flag=SearchNode.FLAG_BLUE), - ) + for index, flag in enumerate(load_flags(self.col)): + root.add_child( + SidebarItem( + name=flag[0], + icon=flag[1], + search_node=flag[2], + item_type=SidebarItemType.FLAG, + id=index + 1, + ) + ) + root.add_simple( tr.browsing_no_flag(), - icon=icon.with_color(colors.DISABLED), - type=type, + icon=icon, + type=SidebarItemType.FLAG, search_node=SearchNode(flag=SearchNode.FLAG_NONE), ) @@ -872,6 +867,16 @@ class SidebarTreeView(QTreeView): lambda: set_children_expanded(False), ) + # Flags + ########################### + + 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.refresh() + # Decks ########################### diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 7533e4566..7a96d8c6c 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -12,6 +12,7 @@ from typing import ( TYPE_CHECKING, Any, Callable, + Dict, List, Literal, Optional, @@ -35,10 +36,12 @@ from PyQt5.QtWidgets import ( import aqt from anki import Collection +from anki.collection import SearchNode from anki.lang import TR, tr_legacyglobal # pylint: disable=unused-import from anki.utils import invalidFilename, isMac, isWin, noBundledLibs, versionWithBuild +from aqt import colors from aqt.qt import * -from aqt.theme import theme_manager +from aqt.theme import ColoredIcon, theme_manager if TYPE_CHECKING: TextFormat = Union[Literal["plain", "rich"]] @@ -1023,6 +1026,34 @@ def no_arg_trigger(func: Callable) -> Callable: return pyqtSlot()(func) # type: ignore +def load_flags(col: Collection) -> List[Tuple[str, ColoredIcon, SearchNode]]: + labels = cast(Dict[str, str], col.get_config("flagLabels", {})) + icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED) + + return [ + ( + labels["1"] if "1" in labels else tr.actions_red_flag(), + icon.with_color(colors.FLAG1_FG), + SearchNode(flag=SearchNode.FLAG_RED), + ), + ( + labels["2"] if "2" in labels else tr.actions_orange_flag(), + icon.with_color(colors.FLAG2_FG), + SearchNode(flag=SearchNode.FLAG_ORANGE), + ), + ( + labels["3"] if "3" in labels else tr.actions_green_flag(), + icon.with_color(colors.FLAG3_FG), + SearchNode(flag=SearchNode.FLAG_GREEN), + ), + ( + labels["4"] if "4" in labels else tr.actions_blue_flag(), + icon.with_color(colors.FLAG4_FG), + SearchNode(flag=SearchNode.FLAG_BLUE), + ), + ] + + class KeyboardModifiersPressed: "Util for type-safe checks of currently-pressed modifier keys." From 30736ddf7562e9c7f96c18aebcab13e6d3192c1e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 19 May 2021 10:34:36 +0200 Subject: [PATCH 2/6] Use custom flag labels for browser actions --- qt/aqt/browser/browser.py | 6 ++++++ qt/aqt/browser/sidebar/tree.py | 1 + qt/aqt/utils.py | 6 +++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 95eddfd2a..3d9449842 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -42,6 +42,7 @@ from aqt.utils import ( current_top_level_widget, ensure_editor_saved, getTag, + load_flags, no_arg_trigger, openHelp, qtMenuShortcutWorkaround, @@ -176,6 +177,7 @@ class Browser(QMainWindow): qconnect( f.actionBlue_Flag.triggered, lambda: self.set_flag_of_selected_cards(4) ) + self._update_flag_labels() qconnect(f.actionExport.triggered, self._on_export_notes) # jumps qconnect(f.actionPreviousCard.triggered, self.onPreviousCard) @@ -723,6 +725,10 @@ where id in %s""" qtMenuShortcutWorkaround(self.form.menuFlag) + def _update_flag_labels(self) -> None: + for flag in load_flags(self.col): + getattr(self.form, flag[3]).setText(flag[0]) + def toggle_mark_of_selected_notes(self, checked: bool) -> None: if checked: self.add_tags_to_selected_notes(tags=MARKED_TAG) diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index 573979938..f29e3cc5e 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -875,6 +875,7 @@ class SidebarTreeView(QTreeView): labels[str(item.id)] = new_name self.col.set_config("flagLabels", labels) item.name = new_name + self.browser._update_flag_labels() self.refresh() # Decks diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 7a96d8c6c..4e1bdfc22 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -1026,7 +1026,7 @@ def no_arg_trigger(func: Callable) -> Callable: return pyqtSlot()(func) # type: ignore -def load_flags(col: Collection) -> List[Tuple[str, ColoredIcon, SearchNode]]: +def load_flags(col: Collection) -> List[Tuple[str, ColoredIcon, SearchNode, str]]: labels = cast(Dict[str, str], col.get_config("flagLabels", {})) icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED) @@ -1035,21 +1035,25 @@ def load_flags(col: Collection) -> List[Tuple[str, ColoredIcon, SearchNode]]: labels["1"] if "1" in labels else tr.actions_red_flag(), icon.with_color(colors.FLAG1_FG), SearchNode(flag=SearchNode.FLAG_RED), + "actionRed_Flag", ), ( labels["2"] if "2" in labels else tr.actions_orange_flag(), icon.with_color(colors.FLAG2_FG), SearchNode(flag=SearchNode.FLAG_ORANGE), + "actionOrange_Flag", ), ( labels["3"] if "3" in labels else tr.actions_green_flag(), icon.with_color(colors.FLAG3_FG), SearchNode(flag=SearchNode.FLAG_GREEN), + "actionGreen_Flag", ), ( labels["4"] if "4" in labels else tr.actions_blue_flag(), icon.with_color(colors.FLAG4_FG), SearchNode(flag=SearchNode.FLAG_BLUE), + "actionBlue_Flag", ), ] From 5fa68c885a6eafaf82dbff28d430e489a2b2e355 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 19 May 2021 19:17:43 +0200 Subject: [PATCH 3/6] Use dataclass instead of tuple for loaded flags --- qt/aqt/browser/browser.py | 31 +++++++++++-------------------- qt/aqt/browser/sidebar/tree.py | 10 +++++----- qt/aqt/utils.py | 24 +++++++++++++++++++----- 3 files changed, 35 insertions(+), 30 deletions(-) diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 3d9449842..4b68ca289 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -167,16 +167,14 @@ class Browser(QMainWindow): qconnect(f.action_set_due_date.triggered, self.set_due_date) qconnect(f.action_forget.triggered, self.forget_cards) qconnect(f.actionToggle_Suspend.triggered, self.suspend_selected_cards) - qconnect(f.actionRed_Flag.triggered, lambda: self.set_flag_of_selected_cards(1)) - qconnect( - f.actionOrange_Flag.triggered, lambda: self.set_flag_of_selected_cards(2) - ) - qconnect( - f.actionGreen_Flag.triggered, lambda: self.set_flag_of_selected_cards(3) - ) - qconnect( - f.actionBlue_Flag.triggered, lambda: self.set_flag_of_selected_cards(4) - ) + + 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): + qconnect( + getattr(self.form, flag.action).triggered, set_flag_func(flag.index) + ) self._update_flag_labels() qconnect(f.actionExport.triggered, self._on_export_notes) # jumps @@ -713,21 +711,14 @@ where id in %s""" flag = self.card and self.card.user_flag() flag = flag or 0 - flagActions = [ - self.form.actionRed_Flag, - self.form.actionOrange_Flag, - self.form.actionGreen_Flag, - self.form.actionBlue_Flag, - ] - - for c, act in enumerate(flagActions): - act.setChecked(flag == c + 1) + for f in load_flags(self.col): + 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): - getattr(self.form, flag[3]).setText(flag[0]) + getattr(self.form, flag.action).setText(flag.label) def toggle_mark_of_selected_notes(self, checked: bool) -> None: if checked: diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index f29e3cc5e..a20403d2e 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -609,14 +609,14 @@ class SidebarTreeView(QTreeView): ) root.search_node = SearchNode(flag=SearchNode.FLAG_ANY) - for index, flag in enumerate(load_flags(self.col)): + for flag in load_flags(self.col): root.add_child( SidebarItem( - name=flag[0], - icon=flag[1], - search_node=flag[2], + name=flag.label, + icon=flag.icon, + search_node=flag.search_node, item_type=SidebarItemType.FLAG, - id=index + 1, + id=flag.index, ) ) diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 4e1bdfc22..3407b2733 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -6,6 +6,7 @@ import os import re import subprocess import sys +from dataclasses import dataclass from enum import Enum from functools import wraps from typing import ( @@ -1026,30 +1027,43 @@ def no_arg_trigger(func: Callable) -> Callable: return pyqtSlot()(func) # type: ignore -def load_flags(col: Collection) -> List[Tuple[str, ColoredIcon, SearchNode, str]]: +@dataclass +class Flag: + index: int + label: str + icon: ColoredIcon + search_node: SearchNode + action: str + + +def load_flags(col: Collection) -> List[Flag]: labels = cast(Dict[str, str], col.get_config("flagLabels", {})) icon = ColoredIcon(path=":/icons/flag.svg", color=colors.DISABLED) return [ - ( + Flag( + 1, labels["1"] if "1" in labels else tr.actions_red_flag(), icon.with_color(colors.FLAG1_FG), SearchNode(flag=SearchNode.FLAG_RED), "actionRed_Flag", ), - ( + Flag( + 2, labels["2"] if "2" in labels else tr.actions_orange_flag(), icon.with_color(colors.FLAG2_FG), SearchNode(flag=SearchNode.FLAG_ORANGE), "actionOrange_Flag", ), - ( + Flag( + 3, labels["3"] if "3" in labels else tr.actions_green_flag(), icon.with_color(colors.FLAG3_FG), SearchNode(flag=SearchNode.FLAG_GREEN), "actionGreen_Flag", ), - ( + Flag( + 4, labels["4"] if "4" in labels else tr.actions_blue_flag(), icon.with_color(colors.FLAG4_FG), SearchNode(flag=SearchNode.FLAG_BLUE), From b5fa7923e603809b223bc573a8b426eb74610927 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 19 May 2021 19:18:49 +0200 Subject: [PATCH 4/6] Use custom flag labels in reviewer --- qt/aqt/reviewer.py | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 4c99ff8a9..abd4fa5ec 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -51,7 +51,14 @@ from aqt.qt import * from aqt.sound import av_player, play_clicked_audio, record_audio from aqt.theme import theme_manager from aqt.toolbar import BottomBar -from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, tooltip, tr +from aqt.utils import ( + askUserDialog, + downArrow, + load_flags, + qtMenuShortcutWorkaround, + tooltip, + tr, +) from aqt.webview import AnkiWebView @@ -905,29 +912,12 @@ time = %(time)d; tr.studying_flag_card(), [ [ - tr.actions_red_flag(), - "Ctrl+1", - lambda: self.set_flag_on_current_card(1), - dict(checked=currentFlag == 1), - ], - [ - tr.actions_orange_flag(), - "Ctrl+2", - lambda: self.set_flag_on_current_card(2), - dict(checked=currentFlag == 2), - ], - [ - tr.actions_green_flag(), - "Ctrl+3", - lambda: self.set_flag_on_current_card(3), - dict(checked=currentFlag == 3), - ], - [ - tr.actions_blue_flag(), - "Ctrl+4", - lambda: self.set_flag_on_current_card(4), - dict(checked=currentFlag == 4), - ], + flag.label, + f"Ctrl+{flag.index}", + self.set_flag_func(flag.index), + dict(checked=currentFlag == flag.index), + ] + for flag in load_flags(self.mw.col) ], ], [tr.studying_mark_note(), "*", self.toggle_mark_on_current_note], @@ -998,6 +988,9 @@ time = %(time)d; redraw_flag ).run_in_background(initiator=self) + def set_flag_func(self, desired_flag: int) -> Callable: + return lambda: self.set_flag_on_current_card(desired_flag) + def toggle_mark_on_current_note(self) -> None: def redraw_mark(out: OpChangesWithCount) -> None: self.card.load() From f960299345cdb7b23560eb6c7f63a6fa22d76b18 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 19 May 2021 19:26:39 +0200 Subject: [PATCH 5/6] Prefer looping over flags over exhaustive listing --- qt/aqt/reviewer.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index abd4fa5ec..4fddcd38e 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -450,7 +450,7 @@ class Reviewer: def _shortcutKeys( self, - ) -> List[Union[Tuple[str, Callable], Tuple[Qt.Key, Callable]]]: + ) -> Sequence[Union[Tuple[str, Callable], Tuple[Qt.Key, Callable]]]: return [ ("e", self.mw.onEditCurrent), (" ", self.onEnterKey), @@ -459,10 +459,10 @@ class Reviewer: ("m", self.showContextMenu), ("r", self.replayAudio), (Qt.Key_F5, self.replayAudio), - ("Ctrl+1", lambda: self.set_flag_on_current_card(1)), - ("Ctrl+2", lambda: self.set_flag_on_current_card(2)), - ("Ctrl+3", lambda: self.set_flag_on_current_card(3)), - ("Ctrl+4", lambda: self.set_flag_on_current_card(4)), + *( + (f"Ctrl+{flag.index}", self.set_flag_func(flag.index)) + for flag in load_flags(self.mw.col) + ), ("*", self.toggle_mark_on_current_note), ("=", self.bury_current_note), ("-", self.bury_current_card), From 6fb2d30426f0c13b9a33b4597b9a9ec2103bfd5a Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 19 May 2021 19:44:49 +0200 Subject: [PATCH 6/6] Remove redundant pylint exception --- qt/aqt/browser/browser.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 4b68ca289..cbc600762 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -137,7 +137,6 @@ class Browser(QMainWindow): self.sidebar.refresh_if_needed() def setupMenus(self) -> None: - # pylint: disable=unnecessary-lambda # actions f = self.form # edit