diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index 9be0b3e5d..d04a15ec6 100644 --- a/ftl/core/preferences.ftl +++ b/ftl/core/preferences.ftl @@ -58,6 +58,7 @@ preferences-appearance = Appearance preferences-general = General preferences-style = Style preferences-review = Review +preferences-answer-keys = Answer keys preferences-distractions = Distractions preferences-minimalist-mode = Minimalist mode preferences-editing = Editing @@ -71,6 +72,7 @@ preferences-import-export = Import/Export preferences-network-timeout = Network timeout preferences-reset-window-sizes = Reset Window Sizes preferences-reset-window-sizes-complete = Window sizes and locations have been reset. +preferences-shortcut-placeholder = Enter an unused shortcut key, or leave empty to disable. ## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future. diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index f66608d45..e8c613a6e 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -245,7 +245,7 @@ preferences_review - + diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 633a5bce6..7f643894e 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -1,6 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html +import functools import re from typing import Any, cast @@ -47,8 +48,39 @@ class Preferences(QDialog): self.setup_collection() self.setup_profile() self.setup_global() + self.setup_configurable_answer_keys() self.show() + def setup_configurable_answer_keys(self): + """ + Create a group box in Preferences with widgets that let the user edit answer keys. + """ + ease_labels = ( + (1, tr.studying_again()), + (2, tr.studying_hard()), + (3, tr.studying_good()), + (4, tr.studying_easy()), + ) + self.form.review_options_layout.addWidget( + group := QGroupBox(tr.preferences_answer_keys()) + ) + group.setLayout(layout := QFormLayout()) + for ease, label in ease_labels: + layout.addRow( + label, + line_edit := QLineEdit(self.mw.pm.get_answer_key(ease) or ""), + ) + qconnect( + line_edit.textChanged, + functools.partial(self.mw.pm.set_answer_key, ease), + ) + line_edit.setValidator( + QRegularExpressionValidator( + QRegularExpression(r"^[a-z0-9\]\[=,./;\'\\-]$") + ) + ) + line_edit.setPlaceholderText(tr.preferences_shortcut_placeholder()) + def accept(self) -> None: # avoid exception if main window is already closed if not self.mw.col: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index f757355d8..fffca16bf 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -10,7 +10,7 @@ import shutil import traceback from enum import Enum from pathlib import Path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Optional import anki.lang import aqt.forms @@ -116,6 +116,8 @@ class LoadMetaResult: class ProfileManager: + default_answer_keys = {ease_num: str(ease_num) for ease_num in range(1, 5)} + def __init__(self, base: Path) -> None: # "base should be retrieved via ProfileMangager.get_created_base_folder" ## Settings which should be forgotten each Anki restart @@ -537,6 +539,12 @@ create table if not exists profiles def set_spacebar_rates_card(self, on: bool) -> None: self.meta["spacebar_rates_card"] = on + def get_answer_key(self, ease: int) -> Optional[str]: + return self.meta.setdefault("answer_keys", self.default_answer_keys).get(ease) + + def set_answer_key(self, ease: int, key: str): + self.meta.setdefault("answer_keys", self.default_answer_keys)[ease] = key + def hide_top_bar(self) -> bool: return self.meta.get("hide_top_bar", False) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index d56402236..c8bd3d963 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -3,6 +3,7 @@ from __future__ import annotations +import functools import json import random import re @@ -507,14 +508,11 @@ class Reviewer: ("o", self.onOptions), ("i", self.on_card_info), ("Ctrl+Alt+i", self.on_previous_card_info), - ("1", lambda: self._answerCard(1)), - ("2", lambda: self._answerCard(2)), - ("3", lambda: self._answerCard(3)), - ("4", lambda: self._answerCard(4)), - ("h", lambda: self._answerCard(1)), - ("j", lambda: self._answerCard(2)), - ("k", lambda: self._answerCard(3)), - ("l", lambda: self._answerCard(4)), + *( + (key, functools.partial(self._answerCard, ease)) + for ease in aqt.mw.pm.default_answer_keys + if (key := aqt.mw.pm.get_answer_key(ease)) + ), ("u", self.mw.undo), ("5", self.on_pause_audio), ("6", self.on_seek_backward),