From 5a37b8e2af029e5a0da970a958325f65bf9a9719 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 23 Feb 2021 22:31:04 +0100 Subject: [PATCH 1/5] Add direct col reference to dyndeckconf --- qt/aqt/dyndeckconf.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 8c88df964..c05d28337 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -40,12 +40,13 @@ class DeckConf(QDialog): QDialog.__init__(self, mw) self.mw = mw + self.col = self.mw.col self.did: Optional[int] = None self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) self.initialSetup() - self.old_deck = self.mw.col.decks.current() + self.old_deck = self.col.decks.current() if deck and deck["dyn"]: # modify existing dyn deck @@ -83,7 +84,7 @@ class DeckConf(QDialog): without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) self.form.buttonBox.button(QDialogButtonBox.Ok).setText(label) - if self.mw.col.schedVer() == 1: + if self.col.schedVer() == 1: self.form.secondFilter.setVisible(False) restoreGeom(self, "dyndeckconf") @@ -100,23 +101,23 @@ class DeckConf(QDialog): def new_dyn_deck(self) -> None: suffix: int = 1 - while self.mw.col.decks.id_for_name( + while self.col.decks.id_for_name( without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=suffix)) ): suffix += 1 name: str = without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=suffix)) - self.did = self.mw.col.decks.new_filtered(name) - self.deck = self.mw.col.decks.current() + self.did = self.col.decks.new_filtered(name) + self.deck = self.col.decks.current() def set_default_searches(self, deck_name: str) -> None: self.form.search.setText( - self.mw.col.build_search_string( + self.col.build_search_string( SearchNode(deck=deck_name), SearchNode(card_state=SearchNode.CARD_STATE_DUE), ) ) self.form.search_2.setText( - self.mw.col.build_search_string( + self.col.build_search_string( SearchNode(deck=deck_name), SearchNode(card_state=SearchNode.CARD_STATE_NEW), ) @@ -152,7 +153,7 @@ class DeckConf(QDialog): def _on_search_button(self, line: QLineEdit) -> None: try: - search = self.mw.col.build_search_string(line.text()) + search = self.col.build_search_string(line.text()) except InvalidInput as err: line.setFocus() line.selectAll() @@ -162,7 +163,7 @@ class DeckConf(QDialog): def _onReschedToggled(self, _state: int) -> None: self.form.previewDelayWidget.setVisible( - not self.form.resched.isChecked() and self.mw.col.schedVer() > 1 + not self.form.resched.isChecked() and self.col.schedVer() > 1 ) def loadConf(self, deck: Optional[Deck] = None) -> None: @@ -175,7 +176,7 @@ class DeckConf(QDialog): search, limit, order = d["terms"][0] f.search.setText(search) - if self.mw.col.schedVer() == 1: + if self.col.schedVer() == 1: if d["delays"]: f.steps.setText(self.listToUser(d["delays"])) f.stepsOn.setChecked(True) @@ -205,35 +206,36 @@ class DeckConf(QDialog): d = self.deck if f.name.text() and d["name"] != f.name.text(): - self.mw.col.decks.rename(d, f.name.text()) + self.col.decks.rename(d, f.name.text()) gui_hooks.sidebar_should_refresh_decks() d["resched"] = f.resched.isChecked() d["delays"] = None - if self.mw.col.schedVer() == 1 and f.stepsOn.isChecked(): + if self.col.schedVer() == 1 and f.stepsOn.isChecked(): steps = self.userToList(f.steps) if steps: d["delays"] = steps else: d["delays"] = None - search = self.mw.col.build_search_string(f.search.text()) + search = self.col.build_search_string(f.search.text()) terms = [[search, f.limit.value(), f.order.currentIndex()]] if f.secondFilter.isChecked(): - search_2 = self.mw.col.build_search_string(f.search_2.text()) + search_2 = self.col.build_search_string(f.search_2.text()) terms.append([search_2, f.limit_2.value(), f.order_2.currentIndex()]) d["terms"] = terms d["previewDelay"] = f.previewDelay.value() - self.mw.col.decks.save(d) + self.col.decks.save(d) def reject(self) -> None: if self.did: - self.mw.col.decks.rem(self.did) - self.mw.col.decks.select(self.old_deck["id"]) + self.col.decks.rem(self.did) + self.col.decks.select(self.old_deck["id"]) + self.mw.reset() saveGeom(self, "dyndeckconf") QDialog.reject(self) aqt.dialogs.markClosed("DynDeckConfDialog") @@ -246,7 +248,7 @@ class DeckConf(QDialog): except DeckRenameError as err: showWarning(err.description) else: - if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): + if not self.col.sched.rebuild_filtered_deck(self.deck["id"]): if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): return saveGeom(self, "dyndeckconf") From ae88f7e5935b352a1081baea8077fee666ef2bf1 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 23 Feb 2021 23:12:39 +0100 Subject: [PATCH 2/5] Add clickable hint to dyndeckconf --- ftl/core/decks.ftl | 1 + qt/aqt/dyndeckconf.py | 49 ++++++++++++++++++++++++++++++++++++++++ qt/aqt/forms/dyndconf.ui | 19 ++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/ftl/core/decks.ftl b/ftl/core/decks.ftl index 222a993c2..22175d3e7 100644 --- a/ftl/core/decks.ftl +++ b/ftl/core/decks.ftl @@ -31,6 +31,7 @@ decks-reschedule-cards-based-on-my-answers = Reschedule cards based on my answer decks-study = Study decks-study-deck = Study Deck decks-the-provided-search-did-not-match = The provided search did not match any cards. Would you like to revise it? +decks-unmovable-cards = Some cards may be excluded despite matching the search. decks-it-has-card = { $count -> [one] It has { $count } card. diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index c05d28337..f64f82bd0 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -70,6 +70,7 @@ class DeckConf(QDialog): self.set_custom_searches(search, search_2) qconnect(self.form.search_button.clicked, self.on_search_button) qconnect(self.form.search_button_2.clicked, self.on_search_button_2) + qconnect(self.form.hint_button.clicked, self.on_hint_button) color = theme_manager.color(colors.LINK) self.setStyleSheet( f"""QPushButton[flat=true] {{ text-align: left; color: {color}; padding: 0; border: 0 }} @@ -161,6 +162,54 @@ class DeckConf(QDialog): else: aqt.dialogs.open("Browser", self.mw, search=(search,)) + def on_hint_button(self) -> None: + """Open the browser to show cards that match the typed-in filters but cannot be included + due to internal limitations. + """ + manual_filters = [self.form.search.text()] + if self.form.secondFilter.isChecked(): + manual_filters.append(self.form.search_2.text()) + + implicit_filters = [ + SearchNode(card_state=SearchNode.CARD_STATE_SUSPENDED), + SearchNode(card_state=SearchNode.CARD_STATE_BURIED), + ] + + if self.col.schedVer() == 1: + # v1 scheduler cannot include learning cards + if self.did is None: + # rebuild will reset learning cards from this deck so they can be included + implicit_filters.append( + self.col.group_searches( + SearchNode(card_state=SearchNode.CARD_STATE_LEARN), + SearchNode(negated=SearchNode(deck=self.deck["name"])), + ) + ) + else: + implicit_filters.append( + SearchNode(card_state=SearchNode.CARD_STATE_LEARN) + ) + + if self.did is None: + # rebuild; old filtered deck will be emptied, so cards can be included + implicit_filters.append( + self.col.group_searches( + SearchNode(deck="filtered"), + SearchNode(negated=SearchNode(deck=self.deck["name"])), + ) + ) + else: + implicit_filters.append(SearchNode(deck="filtered")) + + manual_filter = self.col.group_searches(*manual_filters, joiner="OR") + implicit_filter = self.col.group_searches(*implicit_filters, joiner="OR") + try: + search = self.col.build_search_string(manual_filter, implicit_filter) + except InvalidInput as err: + show_invalid_search_error(err) + else: + aqt.dialogs.open("Browser", self.mw, search=(search,)) + def _onReschedToggled(self, _state: int) -> None: self.form.previewDelayWidget.setVisible( not self.form.resched.isChecked() and self.col.schedVer() > 1 diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 6e9eaa441..be2925ce9 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -250,6 +250,25 @@ + + + + Qt::NoFocus + + + SEARCH_VIEW_IN_BROWSER + + + DECKS_UNMOVABLE_CARDS + + + false + + + true + + + From d6b1c0cf3a0c4e98dcff00e0cf576780ae5ccdb0 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Tue, 23 Feb 2021 23:34:05 +0100 Subject: [PATCH 3/5] Give dyndeck hint unique styling --- qt/aqt/dyndeckconf.py | 9 ++++++--- qt/aqt/forms/dyndconf.ui | 9 +++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index f64f82bd0..916187d3b 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -71,10 +71,13 @@ class DeckConf(QDialog): qconnect(self.form.search_button.clicked, self.on_search_button) qconnect(self.form.search_button_2.clicked, self.on_search_button_2) qconnect(self.form.hint_button.clicked, self.on_hint_button) - color = theme_manager.color(colors.LINK) + blue = theme_manager.color(colors.LINK) + grey = theme_manager.color(colors.DISABLED) self.setStyleSheet( - f"""QPushButton[flat=true] {{ text-align: left; color: {color}; padding: 0; border: 0 }} - QPushButton[flat=true]:hover {{ text-decoration: underline }}""" + f"""QPushButton[label] {{ padding: 0; border: 0 }} + QPushButton[label]:hover {{ text-decoration: underline }} + QPushButton[label="search"] {{ text-align: left; color: {blue} }} + QPushButton[label="hint"] {{ text-align: right; color: {grey} }}""" ) disable_help_button(self) self.setWindowModality(Qt.WindowModal) diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index be2925ce9..3e91c0a96 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -79,6 +79,9 @@ true + + search + @@ -143,6 +146,9 @@ true + + search + @@ -267,6 +273,9 @@ true + + hint + From 234ca4d4967e66704b936e28789b73a4a6a19c0c Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 24 Feb 2021 11:14:33 +0100 Subject: [PATCH 4/5] Refactor dyndeckconf/on_hint_button --- qt/aqt/dyndeckconf.py | 72 +++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 916187d3b..4565188fe 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -1,6 +1,6 @@ # Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from typing import Callable, List, Optional +from typing import Callable, List, Optional, Tuple import aqt from anki.collection import SearchNode @@ -169,41 +169,13 @@ class DeckConf(QDialog): """Open the browser to show cards that match the typed-in filters but cannot be included due to internal limitations. """ - manual_filters = [self.form.search.text()] - if self.form.secondFilter.isChecked(): - manual_filters.append(self.form.search_2.text()) - - implicit_filters = [ + manual_filters = (self.form.search.text(), *self._second_filter()) + implicit_filters = ( SearchNode(card_state=SearchNode.CARD_STATE_SUSPENDED), SearchNode(card_state=SearchNode.CARD_STATE_BURIED), - ] - - if self.col.schedVer() == 1: - # v1 scheduler cannot include learning cards - if self.did is None: - # rebuild will reset learning cards from this deck so they can be included - implicit_filters.append( - self.col.group_searches( - SearchNode(card_state=SearchNode.CARD_STATE_LEARN), - SearchNode(negated=SearchNode(deck=self.deck["name"])), - ) - ) - else: - implicit_filters.append( - SearchNode(card_state=SearchNode.CARD_STATE_LEARN) - ) - - if self.did is None: - # rebuild; old filtered deck will be emptied, so cards can be included - implicit_filters.append( - self.col.group_searches( - SearchNode(deck="filtered"), - SearchNode(negated=SearchNode(deck=self.deck["name"])), - ) - ) - else: - implicit_filters.append(SearchNode(deck="filtered")) - + *self._learning_search_node(), + *self._filtered_search_node(), + ) manual_filter = self.col.group_searches(*manual_filters, joiner="OR") implicit_filter = self.col.group_searches(*implicit_filters, joiner="OR") try: @@ -213,6 +185,38 @@ class DeckConf(QDialog): else: aqt.dialogs.open("Browser", self.mw, search=(search,)) + def _second_filter(self) -> Tuple[str]: + if self.form.secondFilter.isChecked(): + return (self.form.search_2.text(),) + return () + + def _learning_search_node(self) -> Tuple[SearchNode]: + """Return a search node that matches learning cards if the old scheduler is enabled. + If it's a rebuild, exclude cards from this filtered deck as those will be reset. + """ + if self.col.schedVer() == 1: + if self.did is None: + return ( + self.col.group_searches( + SearchNode(card_state=SearchNode.CARD_STATE_LEARN), + SearchNode(negated=SearchNode(deck=self.deck["name"])), + ), + ) + return (SearchNode(card_state=SearchNode.CARD_STATE_LEARN),) + return () + + def _filtered_search_node(self) -> Tuple[SearchNode]: + """Return a search node that matches cards in filtered decks, if applicable excluding those + in the deck being rebuild.""" + if self.did is None: + return ( + self.col.group_searches( + SearchNode(deck="filtered"), + SearchNode(negated=SearchNode(deck=self.deck["name"])), + ), + ) + return (SearchNode(deck="filtered"),) + def _onReschedToggled(self, _state: int) -> None: self.form.previewDelayWidget.setVisible( not self.form.resched.isChecked() and self.col.schedVer() > 1 From e95c2fa6cec798298d1e31b345870e4e66c210ad Mon Sep 17 00:00:00 2001 From: RumovZ Date: Wed, 24 Feb 2021 11:24:27 +0100 Subject: [PATCH 5/5] Fix type hints in dyndeckconf --- qt/aqt/dyndeckconf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 4565188fe..d1c4e8e2e 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -185,12 +185,12 @@ class DeckConf(QDialog): else: aqt.dialogs.open("Browser", self.mw, search=(search,)) - def _second_filter(self) -> Tuple[str]: + def _second_filter(self) -> Tuple[str, ...]: if self.form.secondFilter.isChecked(): return (self.form.search_2.text(),) return () - def _learning_search_node(self) -> Tuple[SearchNode]: + def _learning_search_node(self) -> Tuple[SearchNode, ...]: """Return a search node that matches learning cards if the old scheduler is enabled. If it's a rebuild, exclude cards from this filtered deck as those will be reset. """