From 0960ea6ece6b791e8d8175989c373041cde2558d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 31 Jan 2021 18:20:47 +0100 Subject: [PATCH 01/15] Rework dynndeckconf - Handle deck building inside class. New deck is built unless caller passes filtered deck. - If no deck is passed and current deck is filtered, copy settings. - Remove exec_(). --- qt/aqt/dyndeckconf.py | 88 +++++++++++++++++++++++++++++++------------ qt/aqt/main.py | 18 +-------- qt/aqt/overview.py | 4 +- 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 12b5180d4..1b4a9d241 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -5,6 +5,7 @@ from typing import List, Optional import aqt from anki.collection import SearchTerm +from anki.decks import Deck from anki.errors import InvalidInput from anki.lang import without_unicode_isolation from aqt.qt import * @@ -23,19 +24,41 @@ from aqt.utils import ( class DeckConf(QDialog): - def __init__(self, mw, first=False, search="", deck=None): + """Dialogue to modify and build a filtered deck.""" + + def __init__(self, mw, search: Optional[str] = None, deck: Optional[Deck] = None): + """If 'deck' is an existing filtered deck, load and modify its settings. + Otherwise, build a new one and derive settings from the current deck. + """ + QDialog.__init__(self, mw) self.mw = mw - self.deck = deck or self.mw.col.decks.current() - self.search = search + self.did: Optional[int] = None self.form = aqt.forms.dyndconf.Ui_Dialog() self.form.setupUi(self) - if first: - label = tr(TR.DECKS_BUILD) - else: - label = tr(TR.ACTIONS_REBUILD) - self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS)) + self.initialSetup() + self.old_deck = self.mw.col.decks.current() + + if deck and deck["dyn"]: + # modify existing dyn deck + label = tr(TR.ACTIONS_REBUILD) + self.deck = deck + self.loadConf() + elif self.old_deck["dyn"]: + # create new dyn deck from other dyn deck + label = tr(TR.DECKS_BUILD) + self.loadConf(deck=self.old_deck) + self.new_dyn_deck() + else: + # create new dyn deck from regular deck + label = tr(TR.DECKS_BUILD) + self.new_dyn_deck() + self.loadConf() + self.set_default_searches(self.old_deck["name"]) + if search is not None: + self.form.search.setText(search) + disable_help_button(self) self.setWindowModality(Qt.WindowModal) qconnect( @@ -45,25 +68,36 @@ class DeckConf(QDialog): without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) restoreGeom(self, "dyndeckconf") - self.initialSetup() - self.loadConf() - if search: - search = self.mw.col.build_search_string( - search, SearchTerm(card_state=SearchTerm.CARD_STATE_DUE) - ) - self.form.search.setText(search) - search_2 = self.mw.col.build_search_string( - search, SearchTerm(card_state=SearchTerm.CARD_STATE_NEW) - ) - self.form.search_2.setText(search_2) + self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) self.form.search.selectAll() - if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) self.show() - self.exec_() - saveGeom(self, "dyndeckconf") + + def new_dyn_deck(self): + suffix: int = 1 + while self.mw.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() + + def set_default_searches(self, deck_name): + self.form.search.setText( + self.mw.col.build_search_string( + SearchTerm(deck=deck_name), + SearchTerm(card_state=SearchTerm.CARD_STATE_DUE), + ) + ) + self.form.search_2.setText( + self.mw.col.build_search_string( + SearchTerm(deck=deck_name), + SearchTerm(card_state=SearchTerm.CARD_STATE_NEW), + ) + ) def initialSetup(self): import anki.consts as cs @@ -78,9 +112,9 @@ class DeckConf(QDialog): not self.form.resched.isChecked() and self.mw.col.schedVer() > 1 ) - def loadConf(self): + def loadConf(self, deck: Optional[Deck] = None): f = self.form - d = self.deck + d = deck or self.deck f.resched.setChecked(d["resched"]) self._onReschedToggled(0) @@ -139,7 +173,10 @@ class DeckConf(QDialog): self.mw.col.decks.save(d) def reject(self): - self.ok = False + if self.did: + self.mw.col.decks.rem(self.did) + self.mw.col.decks.select(self.old_deck["id"]) + saveGeom(self, "dyndeckconf") QDialog.reject(self) def accept(self): @@ -151,6 +188,7 @@ class DeckConf(QDialog): if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): return + saveGeom(self, "dyndeckconf") self.mw.reset() QDialog.accept(self) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 173d7a4d8..7b0680af9 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -30,7 +30,6 @@ from anki._backend import RustBackend as _RustBackend from anki.collection import Collection, SearchTerm from anki.decks import Deck from anki.hooks import runHook -from anki.lang import without_unicode_isolation from anki.sound import AVTag, SoundOrVideoTag from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields from aqt import gui_hooks @@ -1138,22 +1137,7 @@ title="%s" %s>%s""" % ( def onCram(self, search=""): import aqt.dyndeckconf - n = 1 - deck = self.col.decks.current() - if not search: - if not deck["dyn"]: - search = self.col.build_search_string(SearchTerm(deck=deck["name"])) - while self.col.decks.id_for_name( - without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n)) - ): - n += 1 - name = without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n)) - did = self.col.decks.new_filtered(name) - diag = aqt.dyndeckconf.DeckConf(self, first=True, search=search) - if not diag.ok: - # user cancelled first config - self.col.decks.rem(did) - self.col.decks.select(deck["id"]) + aqt.dyndeckconf.DeckConf(self) # Menu, title bar & status ########################################################################## diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index 088065c95..b60ed3751 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from typing import Optional import aqt -from anki.collection import SearchTerm from aqt import gui_hooks from aqt.sound import av_player from aqt.toolbar import BottomBar @@ -72,8 +71,7 @@ class Overview: elif url == "opts": self.mw.onDeckConf() elif url == "cram": - deck = self.mw.col.decks.current()["name"] - self.mw.onCram(self.mw.col.build_search_string(SearchTerm(deck=deck))) + self.mw.onCram() elif url == "refresh": self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() From 52867dfa6ca11da01f637311034e696135d0d90d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 31 Jan 2021 19:32:51 +0100 Subject: [PATCH 02/15] Make dyndeckconf a registered dialogue --- qt/aqt/__init__.py | 3 ++- qt/aqt/dyndeckconf.py | 17 ++++++++++++++--- qt/aqt/main.py | 10 ++++------ qt/aqt/overview.py | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 350a24446..0576f929d 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -69,7 +69,7 @@ except ImportError as e: # - make preferences modal? cmd+q does wrong thing -from aqt import addcards, addons, browser, editcurrent # isort:skip +from aqt import addcards, addons, browser, editcurrent, dyndeckconf # isort:skip from aqt import stats, about, preferences, mediasync # isort:skip @@ -80,6 +80,7 @@ class DialogManager: "AddonsDialog": [addons.AddonsDialog, None], "Browser": [browser.Browser, None], "EditCurrent": [editcurrent.EditCurrent, None], + "DynDeckConfDialog": [dyndeckconf.DeckConf, None], "DeckStats": [stats.DeckStats, None], "NewDeckStats": [stats.NewDeckStats, None], "About": [about.show, None], diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 1b4a9d241..50fc3937a 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -1,7 +1,7 @@ # Copyright: Ankitects Pty Ltd and contributors # -*- coding: utf-8 -*- # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from typing import List, Optional +from typing import Callable, List, Optional import aqt from anki.collection import SearchTerm @@ -67,14 +67,19 @@ class DeckConf(QDialog): self.setWindowTitle( without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) - restoreGeom(self, "dyndeckconf") - self.ok = self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) + self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) self.form.search.selectAll() if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) + restoreGeom(self, "dyndeckconf") self.show() + def reopen(self, _mw, search: Optional[str] = None, _deck: Optional[Deck] = None): + if search is not None: + self.form.search.setText(search) + self.form.search.selectAll() + def new_dyn_deck(self): suffix: int = 1 while self.mw.col.decks.id_for_name( @@ -178,6 +183,7 @@ class DeckConf(QDialog): self.mw.col.decks.select(self.old_deck["id"]) saveGeom(self, "dyndeckconf") QDialog.reject(self) + aqt.dialogs.markClosed("DynDeckConfDialog") def accept(self): try: @@ -191,6 +197,11 @@ class DeckConf(QDialog): saveGeom(self, "dyndeckconf") self.mw.reset() QDialog.accept(self) + aqt.dialogs.markClosed("DynDeckConfDialog") + + def closeWithCallback(self, callback: Callable): + self.reject() + callback() # Step load/save - fixme: share with std options screen ######################################################## diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 7b0680af9..c11f3b676 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1054,9 +1054,9 @@ title="%s" %s>%s""" % ( if not deck: deck = self.col.decks.current() if deck["dyn"]: - import aqt.dyndeckconf + import aqt - aqt.dyndeckconf.DeckConf(self, deck=deck) + aqt.dialogs.open("DynDeckConfDialog", self, None, deck) else: import aqt.deckconf @@ -1134,10 +1134,8 @@ title="%s" %s>%s""" % ( # Cramming ########################################################################## - def onCram(self, search=""): - import aqt.dyndeckconf - - aqt.dyndeckconf.DeckConf(self) + def onCram(self): + aqt.dialogs.open("DynDeckConfDialog", self) # Menu, title bar & status ########################################################################## diff --git a/qt/aqt/overview.py b/qt/aqt/overview.py index b60ed3751..7190b45fd 100644 --- a/qt/aqt/overview.py +++ b/qt/aqt/overview.py @@ -71,7 +71,7 @@ class Overview: elif url == "opts": self.mw.onDeckConf() elif url == "cram": - self.mw.onCram() + aqt.dialogs.open("DynDeckConfDialog", self.mw) elif url == "refresh": self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() From 011b562038d300c94c7252a4ebe906e2400171df Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 31 Jan 2021 23:16:49 +0100 Subject: [PATCH 03/15] Add current-filter-to-filtered-deck action --- ftl/core/browsing.ftl | 1 + qt/aqt/browser.py | 4 ++++ qt/aqt/forms/browser.ui | 10 ++++++++++ 3 files changed, 15 insertions(+) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index bf408f834..54cb44fcb 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -36,6 +36,7 @@ browsing-enter-tags-to-add = Enter tags to add: browsing-enter-tags-to-delete = Enter tags to delete: browsing-filter = Filter... browsing-filtered = (filtered) +browsing-filter-to-deck = Current Filter to Filtered Deck... browsing-find = Find: browsing-find-and-replace = Find and Replace browsing-find-duplicates = Find Duplicates diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 4554e2499..e8f421a66 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -476,6 +476,7 @@ class Browser(QMainWindow): qconnect(f.actionSelectNotes.triggered, self.selectNotes) if not isMac: f.actionClose.setVisible(False) + qconnect(f.actionFilterToDeck.triggered, self.filterToDeck) # notes qconnect(f.actionAdd.triggered, self.mw.onAddCard) qconnect(f.actionAdd_Tags.triggered, lambda: self.addTags()) @@ -1158,6 +1159,9 @@ where id in %s""" if nids: ChangeModel(self, nids) + def filterToDeck(self): + aqt.dialogs.open("DynDeckConfDialog", self.mw, self.form.searchEdit.lineEdit().text()) + # Preview ###################################################################### diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index ba6a48980..dc8a58169 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -230,6 +230,8 @@ + + @@ -583,6 +585,14 @@ Ctrl+Shift+E + + + BROWSING_FILTER_TO_DECK + + + Ctrl+Q + + From 5aac81d15fdb4283681906d92eb66797cfad810e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 08:50:19 +0100 Subject: [PATCH 04/15] Make DialogManager accept kwargs When opening a dialogue accepting multiple optional arguments, relying on position is error-prone and requires passing Nones to fill unused parameter slots. --- qt/aqt/__init__.py | 9 ++++----- qt/aqt/browser.py | 3 ++- qt/aqt/main.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qt/aqt/__init__.py b/qt/aqt/__init__.py index 0576f929d..bba10eef6 100644 --- a/qt/aqt/__init__.py +++ b/qt/aqt/__init__.py @@ -88,7 +88,7 @@ class DialogManager: "sync_log": [mediasync.MediaSyncDialog, None], } - def open(self, name: str, *args: Any) -> Any: + def open(self, name: str, *args: Any, **kwargs: Any) -> Any: (creator, instance) = self._dialogs[name] if instance: if instance.windowState() & Qt.WindowMinimized: @@ -96,12 +96,11 @@ class DialogManager: instance.activateWindow() instance.raise_() if hasattr(instance, "reopen"): - instance.reopen(*args) - return instance + instance.reopen(*args, **kwargs) else: - instance = creator(*args) + instance = creator(*args, **kwargs) self._dialogs[name][1] = instance - return instance + return instance def markClosed(self, name: str): self._dialogs[name] = [self._dialogs[name][0], None] diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index e8f421a66..29e10274e 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -1160,7 +1160,8 @@ where id in %s""" ChangeModel(self, nids) def filterToDeck(self): - aqt.dialogs.open("DynDeckConfDialog", self.mw, self.form.searchEdit.lineEdit().text()) + search = self.form.searchEdit.lineEdit().text() + aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search) # Preview ###################################################################### diff --git a/qt/aqt/main.py b/qt/aqt/main.py index c11f3b676..a3e8bac21 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1056,7 +1056,7 @@ title="%s" %s>%s""" % ( if deck["dyn"]: import aqt - aqt.dialogs.open("DynDeckConfDialog", self, None, deck) + aqt.dialogs.open("DynDeckConfDialog", self, deck=deck) else: import aqt.deckconf From d2024d1d1eea5a1782c001e70b0c26bf9c5a513d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 09:56:10 +0100 Subject: [PATCH 05/15] Fix pylints and type annotations in dyndeckconf Also fix int representation of learning steps. --- qt/aqt/dyndeckconf.py | 25 +++++++++++++++++-------- qt/aqt/main.py | 6 ++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 50fc3937a..44e955bc6 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -8,6 +8,7 @@ from anki.collection import SearchTerm from anki.decks import Deck from anki.errors import InvalidInput from anki.lang import without_unicode_isolation +from aqt import AnkiQt from aqt.qt import * from aqt.utils import ( TR, @@ -26,7 +27,9 @@ from aqt.utils import ( class DeckConf(QDialog): """Dialogue to modify and build a filtered deck.""" - def __init__(self, mw, search: Optional[str] = None, deck: Optional[Deck] = None): + def __init__( + self, mw: AnkiQt, search: Optional[str] = None, deck: Optional[Deck] = None + ): """If 'deck' is an existing filtered deck, load and modify its settings. Otherwise, build a new one and derive settings from the current deck. """ @@ -75,7 +78,9 @@ class DeckConf(QDialog): self.show() - def reopen(self, _mw, search: Optional[str] = None, _deck: Optional[Deck] = None): + def reopen( + self, _mw: AnkiQt, search: Optional[str] = None, _deck: Optional[Deck] = None + ): if search is not None: self.form.search.setText(search) self.form.search.selectAll() @@ -90,7 +95,7 @@ class DeckConf(QDialog): self.did = self.mw.col.decks.new_filtered(name) self.deck = self.mw.col.decks.current() - def set_default_searches(self, deck_name): + def set_default_searches(self, deck_name: str): self.form.search.setText( self.mw.col.build_search_string( SearchTerm(deck=deck_name), @@ -112,7 +117,7 @@ class DeckConf(QDialog): qconnect(self.form.resched.stateChanged, self._onReschedToggled) - def _onReschedToggled(self, _state): + def _onReschedToggled(self, _state: int): self.form.previewDelayWidget.setVisible( not self.form.resched.isChecked() and self.mw.col.schedVer() > 1 ) @@ -206,11 +211,15 @@ class DeckConf(QDialog): # Step load/save - fixme: share with std options screen ######################################################## - def listToUser(self, l): - return " ".join([str(x) for x in l]) + def listToUser(self, values: List[Union[float, int]]) -> str: + return " ".join( + [str(int(val)) if int(val) == val else str(val) for val in values] + ) - def userToList(self, w, minSize=1) -> Optional[List[Union[float, int]]]: - items = str(w.text()).split(" ") + def userToList( + self, line: QLineEdit, minSize: int = 1 + ) -> Optional[List[Union[float, int]]]: + items = str(line.text()).split(" ") ret = [] for item in items: if not item: diff --git a/qt/aqt/main.py b/qt/aqt/main.py index a3e8bac21..fde039084 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1051,15 +1051,13 @@ title="%s" %s>%s""" % ( aqt.dialogs.open("EditCurrent", self) def onDeckConf(self, deck=None): + import aqt.deckconf + if not deck: deck = self.col.decks.current() if deck["dyn"]: - import aqt - aqt.dialogs.open("DynDeckConfDialog", self, deck=deck) else: - import aqt.deckconf - aqt.deckconf.DeckConf(self, deck) def onOverview(self): From 6de6e5f339564828b31c014bd3b3d77e53815545 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 11:54:28 +0100 Subject: [PATCH 06/15] Make browser accept optional args and add reopen That way, the caller doesn't have to hold a reference to the browser and explicitly call it again, if it wants to search for something specific. Also, if the browser was closed and opened for a single-card-search, it now won't perform a redundant current-deck-search first. --- qt/aqt/addcards.py | 2 +- qt/aqt/browser.py | 49 ++++++++++++++++++++++++++++++++++++-------- qt/aqt/editor.py | 15 +++++++++----- qt/aqt/emptycards.py | 2 +- qt/aqt/main.py | 16 ++------------- qt/aqt/mediacheck.py | 2 +- 6 files changed, 55 insertions(+), 31 deletions(-) diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 5a8d07607..a13e2f0ec 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -162,7 +162,7 @@ class AddCards(QDialog): m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0))) def editHistory(self, nid): - self.mw.browser_search(SearchTerm(nid=nid)) + aqt.dialogs.open("Browser", self.mw, search=(SearchTerm(nid=nid),)) def addNote(self, note) -> Optional[Note]: note.model()["did"] = self.deckChooser.selectedId() diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 29e10274e..4bf3ea6a6 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -437,7 +437,12 @@ class Browser(QMainWindow): col: Collection editor: Optional[Editor] - def __init__(self, mw: AnkiQt) -> None: + def __init__( + self, + mw: AnkiQt, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ) -> None: QMainWindow.__init__(self, None, Qt.Window) self.mw = mw self.col = self.mw.col @@ -461,7 +466,7 @@ class Browser(QMainWindow): self.setupEditor() self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) - self.setupSearch() + self.setupSearch(card, search) gui_hooks.browser_will_show(self) self.show() @@ -600,17 +605,41 @@ class Browser(QMainWindow): ] self.columns.sort(key=itemgetter(1)) + def reopen( + self, + _mw: AnkiQt, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ) -> None: + if search is not None: + self.search_for_terms(*search) + self.form.searchEdit.setFocus() + elif card: + self.show_single_card(card) + self.form.searchEdit.setFocus() + # Searching ###################################################################### - def setupSearch(self): + def setupSearch( + self, + card: Optional[Card] = None, + search: Optional[Tuple[Union[str, SearchTerm]]] = None, + ): qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated) self.form.searchEdit.setCompleter(None) self.form.searchEdit.lineEdit().setPlaceholderText( tr(TR.BROWSING_SEARCH_BAR_HINT) ) self.form.searchEdit.addItems(self.mw.pm.profile["searchHistory"]) - self.search_for(self.col.build_search_string(SearchTerm(deck="current")), "") + if search is not None: + self.search_for_terms(*search) + elif card: + self.show_single_card(card) + else: + self.search_for( + self.col.build_search_string(SearchTerm(deck="current")), "" + ) self.form.searchEdit.setFocus() # search triggered by user @@ -669,15 +698,17 @@ class Browser(QMainWindow): ) return selected - def show_single_card(self, card: Optional[Card]): - """Try to search for the according note and select the given card.""" + def search_for_terms(self, *search_terms: Union[str, SearchTerm]): + search = self.col.build_search_string(*search_terms) + self.form.searchEdit.setEditText(search) + self.onSearchActivated() - nid: Optional[int] = card and card.nid or 0 - if nid: + def show_single_card(self, card: Card): + if card.nid: def on_show_single_card(): self.card = card - search = self.col.build_search_string(SearchTerm(nid=nid)) + search = self.col.build_search_string(SearchTerm(nid=card.nid)) search = gui_hooks.default_search(search, card) self.search_for(search, "") self.focusCid(card.id) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index f609089c9..c38d6c6a0 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -538,11 +538,16 @@ class Editor: self.web.eval("setBackgrounds(%s);" % json.dumps(cols)) def showDupes(self): - self.mw.browser_search( - SearchTerm( - dupe=SearchTerm.Dupe( - notetype_id=self.note.model()["id"], first_field=self.note.fields[0] - ) + aqt.dialogs.open( + "Browser", + self.mw, + search=( + SearchTerm( + dupe=SearchTerm.Dupe( + notetype_id=self.note.model()["id"], + first_field=self.note.fields[0], + ) + ), ) ) diff --git a/qt/aqt/emptycards.py b/qt/aqt/emptycards.py index b9e94d8de..d037c88bc 100644 --- a/qt/aqt/emptycards.py +++ b/qt/aqt/emptycards.py @@ -66,7 +66,7 @@ class EmptyCardsDialog(QDialog): self._delete_button.clicked.connect(self._on_delete) def _on_note_link_clicked(self, link): - self.mw.browser_search(link) + aqt.dialogs.open("Browser", self.mw, search=(link,)) def _on_delete(self): self.mw.progress.start() diff --git a/qt/aqt/main.py b/qt/aqt/main.py index fde039084..73cb7ee68 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -27,7 +27,7 @@ import aqt.toolbar import aqt.webview from anki import hooks from anki._backend import RustBackend as _RustBackend -from anki.collection import Collection, SearchTerm +from anki.collection import Collection from anki.decks import Deck from anki.hooks import runHook from anki.sound import AVTag, SoundOrVideoTag @@ -1044,8 +1044,7 @@ title="%s" %s>%s""" % ( aqt.dialogs.open("AddCards", self) def onBrowse(self) -> None: - browser = aqt.dialogs.open("Browser", self) - browser.show_single_card(self.reviewer.card) + aqt.dialogs.open("Browser", self, card=self.reviewer.card) def onEditCurrent(self): aqt.dialogs.open("EditCurrent", self) @@ -1598,14 +1597,3 @@ title="%s" %s>%s""" % ( def serverURL(self) -> str: return "http://127.0.0.1:%d/" % self.mediaServer.getPort() - - # Helpers for all windows - ########################################################################## - - def browser_search(self, *terms: Union[str, SearchTerm]) -> None: - """Wrapper for col.build_search_string() to look up the result in the browser.""" - - search = self.col.build_search_string(*terms) - browser = aqt.dialogs.open("Browser", self) - browser.form.searchEdit.lineEdit().setText(search) - browser.onSearchActivated() diff --git a/qt/aqt/mediacheck.py b/qt/aqt/mediacheck.py index 0f844a643..9ac8e30d1 100644 --- a/qt/aqt/mediacheck.py +++ b/qt/aqt/mediacheck.py @@ -148,7 +148,7 @@ class MediaChecker: if out is not None: nid, err = out - self.mw.browser_search(SearchTerm(nid=nid)) + aqt.dialogs.open("Browser", self.mw, search=(SearchTerm(nid=nid),)) showText(err, type="html") else: tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED)) From 6b1d5f14e8daf0e93be95bf5442abe42443d35e0 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 11:59:16 +0100 Subject: [PATCH 07/15] Change filtered-deck shortcut in browser to Ctrl+G --- qt/aqt/forms/browser.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index dc8a58169..89caee811 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -590,7 +590,7 @@ BROWSING_FILTER_TO_DECK - Ctrl+Q + Ctrl+G From 0949cdc73ec8ea8e70ba8fa4012181f23a23625c Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 12:09:37 +0100 Subject: [PATCH 08/15] Rename "Filtered Deck from Current Filter" --- ftl/core/browsing.ftl | 2 +- qt/aqt/browser.py | 4 ++-- qt/aqt/forms/browser.ui | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index 54cb44fcb..2b87a92dc 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -24,6 +24,7 @@ browsing-change-to = Change { $val } to: browsing-clear-unused = Clear Unused browsing-clear-unused-tags = Clear Unused Tags browsing-created = Created +browsing-create-filtered-deck = Create Filtered Deck... browsing-ctrlandshiftande = Ctrl+Shift+E browsing-current-deck = Current Deck browsing-current-note-type = Current note type: @@ -36,7 +37,6 @@ browsing-enter-tags-to-add = Enter tags to add: browsing-enter-tags-to-delete = Enter tags to delete: browsing-filter = Filter... browsing-filtered = (filtered) -browsing-filter-to-deck = Current Filter to Filtered Deck... browsing-find = Find: browsing-find-and-replace = Find and Replace browsing-find-duplicates = Find Duplicates diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 4bf3ea6a6..ef665e7fb 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -481,7 +481,7 @@ class Browser(QMainWindow): qconnect(f.actionSelectNotes.triggered, self.selectNotes) if not isMac: f.actionClose.setVisible(False) - qconnect(f.actionFilterToDeck.triggered, self.filterToDeck) + qconnect(f.actionCreateFilteredDeck.triggered, self.createFilteredDeck) # notes qconnect(f.actionAdd.triggered, self.mw.onAddCard) qconnect(f.actionAdd_Tags.triggered, lambda: self.addTags()) @@ -1190,7 +1190,7 @@ where id in %s""" if nids: ChangeModel(self, nids) - def filterToDeck(self): + def createFilteredDeck(self): search = self.form.searchEdit.lineEdit().text() aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search) diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index 89caee811..9a9d02cbe 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -231,7 +231,7 @@ - + @@ -585,9 +585,9 @@ Ctrl+Shift+E - + - BROWSING_FILTER_TO_DECK + BROWSING_CREATE_FILTERED_DECK Ctrl+G From 4d097b995da940df44cc9021652fc6362fee3682 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 13:55:03 +0100 Subject: [PATCH 09/15] Add button in dynDeckConf to search in browser --- ftl/core/search.ftl | 2 ++ qt/aqt/dyndeckconf.py | 16 ++++++++++++- qt/aqt/editor.py | 2 +- qt/aqt/forms/dyndconf.ui | 49 +++++++++++++++++++++++++++------------- 4 files changed, 51 insertions(+), 18 deletions(-) diff --git a/ftl/core/search.ftl b/ftl/core/search.ftl index 4f7fc10bc..768d77109 100644 --- a/ftl/core/search.ftl +++ b/ftl/core/search.ftl @@ -35,3 +35,5 @@ search-card-modified = Card Modified ## +# Tooltip for search lines outside browser +search-view-in-browser = View in browser diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 44e955bc6..e73fe7072 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -71,6 +71,7 @@ class DeckConf(QDialog): without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) + self.form.search.setFocus() self.form.search.selectAll() if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) @@ -83,7 +84,8 @@ class DeckConf(QDialog): ): if search is not None: self.form.search.setText(search) - self.form.search.selectAll() + self.form.search.setFocus() + self.form.search.selectAll() def new_dyn_deck(self): suffix: int = 1 @@ -110,6 +112,8 @@ class DeckConf(QDialog): ) def initialSetup(self): + qconnect(self.form.search_button.clicked, self.on_search_button) + import anki.consts as cs self.form.order.addItems(list(cs.dynOrderLabels(self.mw.col).values())) @@ -117,6 +121,16 @@ class DeckConf(QDialog): qconnect(self.form.resched.stateChanged, self._onReschedToggled) + def on_search_button(self): + try: + search = self.mw.col.build_search_string(self.form.search.text()) + except InvalidInput as err: + self.form.search.setFocus() + self.form.search.selectAll() + show_invalid_search_error(err) + else: + aqt.dialogs.open("Browser", self.mw, search=(search,)) + def _onReschedToggled(self, _state: int): self.form.previewDelayWidget.setVisible( not self.form.resched.isChecked() and self.mw.col.schedVer() > 1 diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index c38d6c6a0..d8e950eca 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -548,7 +548,7 @@ class Editor: first_field=self.note.fields[0], ) ), - ) + ), ) def fieldsAreBlank(self, previousNote=None): diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 163fd84b1..c0bdebbc8 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -6,7 +6,7 @@ 0 0 - 392 + 416 472 @@ -21,20 +21,44 @@ + + + + true + + + + SEARCH_VIEW_IN_BROWSER + + + QPushButton { text-align: left; color: blue; padding: 0; border: 0 } + + + ACTIONS_SEARCH + + + false + + + true + + + + DECKS_LIMIT_TO - - + + - ACTIONS_SEARCH + DECKS_CARDS_SELECTED_BY - + @@ -50,19 +74,12 @@ - - - - DECKS_CARDS_SELECTED_BY - - - - - - - + + + + From 0d37254238a518acb82a58332c73b9b6de6fe94e Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 18:01:57 +0100 Subject: [PATCH 10/15] Style browser-from-filtered-deck button Use theme color and add hover effect. --- qt/aqt/dyndeckconf.py | 9 +++++++-- qt/aqt/forms/dyndconf.ui | 8 -------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index e73fe7072..4e54bd0da 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -10,6 +10,7 @@ from anki.errors import InvalidInput from anki.lang import without_unicode_isolation from aqt import AnkiQt from aqt.qt import * +from aqt.theme import theme_manager from aqt.utils import ( TR, HelpPage, @@ -62,6 +63,12 @@ class DeckConf(QDialog): if search is not None: self.form.search.setText(search) + qconnect(self.form.search_button.clicked, self.on_search_button) + color = theme_manager.str_color("link") + self.setStyleSheet( + f"""QPushButton[flat=true] {{ text-align: left; color: {color}; padding: 0; border: 0 }} + QPushButton[flat=true]:hover {{ text-decoration: underline }}""" + ) disable_help_button(self) self.setWindowModality(Qt.WindowModal) qconnect( @@ -112,8 +119,6 @@ class DeckConf(QDialog): ) def initialSetup(self): - qconnect(self.form.search_button.clicked, self.on_search_button) - import anki.consts as cs self.form.order.addItems(list(cs.dynOrderLabels(self.mw.col).values())) diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index c0bdebbc8..85d9c4b30 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -22,17 +22,9 @@ - - - true - - SEARCH_VIEW_IN_BROWSER - - QPushButton { text-align: left; color: blue; padding: 0; border: 0 } - ACTIONS_SEARCH From 8065e73ef870c3b3842c44b6702f4d6999586eb9 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 19:10:05 +0100 Subject: [PATCH 11/15] Add cross links for second dyndeck filter --- ftl/core/browsing.ftl | 1 + qt/aqt/browser.py | 7 ++++ qt/aqt/dyndeckconf.py | 51 +++++++++++++++++++++-------- qt/aqt/forms/browser.ui | 9 +++++ qt/aqt/forms/dyndconf.ui | 71 ++++++++++++++++++++++------------------ 5 files changed, 94 insertions(+), 45 deletions(-) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index 2b87a92dc..8ce372e8f 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -25,6 +25,7 @@ browsing-clear-unused = Clear Unused browsing-clear-unused-tags = Clear Unused Tags browsing-created = Created browsing-create-filtered-deck = Create Filtered Deck... +browsing-create-filtered-deck_2 = Create Filtered Deck (2nd Filter)... browsing-ctrlandshiftande = Ctrl+Shift+E browsing-current-deck = Current Deck browsing-current-note-type = Current note type: diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index ef665e7fb..7454e54d6 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -482,6 +482,9 @@ class Browser(QMainWindow): if not isMac: f.actionClose.setVisible(False) qconnect(f.actionCreateFilteredDeck.triggered, self.createFilteredDeck) + qconnect(f.actionCreateFilteredDeck2.triggered, self.createFilteredDeck2) + if self.mw.col.schedVer() == 1: + f.menuEdit.removeAction(f.actionCreateFilteredDeck2) # notes qconnect(f.actionAdd.triggered, self.mw.onAddCard) qconnect(f.actionAdd_Tags.triggered, lambda: self.addTags()) @@ -1194,6 +1197,10 @@ where id in %s""" search = self.form.searchEdit.lineEdit().text() aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search) + def createFilteredDeck2(self): + search = self.form.searchEdit.lineEdit().text() + aqt.dialogs.open("DynDeckConfDialog", self.mw, search_2=search) + # Preview ###################################################################### diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 4e54bd0da..31ce27817 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -29,7 +29,11 @@ class DeckConf(QDialog): """Dialogue to modify and build a filtered deck.""" def __init__( - self, mw: AnkiQt, search: Optional[str] = None, deck: Optional[Deck] = None + self, + mw: AnkiQt, + search: Optional[str] = None, + search_2: Optional[str] = None, + deck: Optional[Deck] = None, ): """If 'deck' is an existing filtered deck, load and modify its settings. Otherwise, build a new one and derive settings from the current deck. @@ -60,10 +64,10 @@ class DeckConf(QDialog): self.new_dyn_deck() self.loadConf() self.set_default_searches(self.old_deck["name"]) - if search is not None: - self.form.search.setText(search) + 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) color = theme_manager.str_color("link") self.setStyleSheet( f"""QPushButton[flat=true] {{ text-align: left; color: {color}; padding: 0; border: 0 }} @@ -78,8 +82,6 @@ class DeckConf(QDialog): without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) - self.form.search.setFocus() - self.form.search.selectAll() if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) restoreGeom(self, "dyndeckconf") @@ -87,12 +89,13 @@ class DeckConf(QDialog): self.show() def reopen( - self, _mw: AnkiQt, search: Optional[str] = None, _deck: Optional[Deck] = None + self, + _mw: AnkiQt, + search: Optional[str] = None, + search_2: Optional[str] = None, + _deck: Optional[Deck] = None, ): - if search is not None: - self.form.search.setText(search) - self.form.search.setFocus() - self.form.search.selectAll() + self.set_custom_searches(search, search_2) def new_dyn_deck(self): suffix: int = 1 @@ -118,6 +121,20 @@ class DeckConf(QDialog): ) ) + def set_custom_searches( + self, search: Optional[str], search_2: Optional[str] + ) -> None: + if search is not None: + self.form.search.setText(search) + self.form.search.setFocus() + self.form.search.selectAll() + if search_2 is not None: + self.form.secondFilter.setChecked(True) + self.form.filter2group.setVisible(True) + self.form.search_2.setText(search_2) + self.form.search_2.setFocus() + self.form.search_2.selectAll() + def initialSetup(self): import anki.consts as cs @@ -126,12 +143,18 @@ class DeckConf(QDialog): qconnect(self.form.resched.stateChanged, self._onReschedToggled) - def on_search_button(self): + def on_search_button(self) -> None: + self._on_search_button(self.form.search) + + def on_search_button_2(self) -> None: + self._on_search_button(self.form.search_2) + + def _on_search_button(self, line: QLineEdit) -> None: try: - search = self.mw.col.build_search_string(self.form.search.text()) + search = self.mw.col.build_search_string(line.text()) except InvalidInput as err: - self.form.search.setFocus() - self.form.search.selectAll() + line.setFocus() + line.selectAll() show_invalid_search_error(err) else: aqt.dialogs.open("Browser", self.mw, search=(search,)) diff --git a/qt/aqt/forms/browser.ui b/qt/aqt/forms/browser.ui index 9a9d02cbe..72d24f97c 100644 --- a/qt/aqt/forms/browser.ui +++ b/qt/aqt/forms/browser.ui @@ -232,6 +232,7 @@ + @@ -593,6 +594,14 @@ Ctrl+G + + + BROWSING_CREATE_FILTERED_DECK_2 + + + Ctrl+Shift+G + + diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 85d9c4b30..7491d0731 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -20,22 +20,6 @@ ACTIONS_FILTER - - - - SEARCH_VIEW_IN_BROWSER - - - ACTIONS_SEARCH - - - false - - - true - - - @@ -43,12 +27,8 @@ - - - - DECKS_CARDS_SELECTED_BY - - + + @@ -69,8 +49,28 @@ - - + + + + DECKS_CARDS_SELECTED_BY + + + + + + + SEARCH_VIEW_IN_BROWSER + + + ACTIONS_SEARCH + + + false + + + true + + @@ -94,13 +94,6 @@ - - - - ACTIONS_SEARCH - - - @@ -124,6 +117,22 @@ + + + + SEARCH_VIEW_IN_BROWSER + + + ACTIONS_SEARCH + + + false + + + true + + + From 4666ba8b70e6e0780c42d06c1f8e3366410b1186 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 21:02:22 +0100 Subject: [PATCH 12/15] Enable renaming from dyndeck dialogue --- qt/aqt/dyndeckconf.py | 16 ++++-- qt/aqt/forms/dyndconf.ui | 121 +++++++++++++++++++++++++++------------ 2 files changed, 95 insertions(+), 42 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 31ce27817..955e2f29e 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -5,10 +5,10 @@ from typing import Callable, List, Optional import aqt from anki.collection import SearchTerm -from anki.decks import Deck +from anki.decks import Deck, DeckRenameError from anki.errors import InvalidInput from anki.lang import without_unicode_isolation -from aqt import AnkiQt +from aqt import AnkiQt, gui_hooks from aqt.qt import * from aqt.theme import theme_manager from aqt.utils import ( @@ -65,6 +65,8 @@ class DeckConf(QDialog): self.loadConf() self.set_default_searches(self.old_deck["name"]) + self.form.name.setText(self.deck["name"]) + self.form.name.setPlaceholderText(self.deck["name"]) 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) @@ -202,6 +204,11 @@ class DeckConf(QDialog): def saveConf(self): f = self.form d = self.deck + + if f.name.text() and d["name"] != f.name.text(): + self.mw.col.decks.rename(d, f.name.text()) + gui_hooks.sidebar_should_refresh_decks() + d["resched"] = f.resched.isChecked() d["delays"] = None @@ -236,8 +243,9 @@ class DeckConf(QDialog): try: self.saveConf() except InvalidInput as err: - show_invalid_search_error(err) - return + return show_invalid_search_error(err) + except DeckRenameError as err: + return showWarning(err.description) if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): return diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 7491d0731..2e0ddfaa6 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -6,24 +6,75 @@ 0 0 - 416 - 472 + 757 + 589 Dialog + + + + DECKS_DECK + + + + + + ACTIONS_NAME + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Minimum + + + + 40 + 20 + + + + + + + ACTIONS_FILTER - - + + + + SEARCH_VIEW_IN_BROWSER + - DECKS_LIMIT_TO + ACTIONS_SEARCH + + + false + + + true @@ -56,8 +107,24 @@ - - + + + + DECKS_LIMIT_TO + + + + + + + + + + DECKS_FILTER_2 + + + + SEARCH_VIEW_IN_BROWSER @@ -72,15 +139,9 @@ - - - - - - - DECKS_FILTER_2 - - + + + @@ -88,12 +149,6 @@ - - - - - - @@ -110,6 +165,9 @@ + + + @@ -117,22 +175,6 @@ - - - - SEARCH_VIEW_IN_BROWSER - - - ACTIONS_SEARCH - - - false - - - true - - - @@ -228,9 +270,12 @@ + name + search_button search limit order + search_button_2 search_2 limit_2 order_2 From 6c7b195da83bbdaf5d10979efe8d353fa8e3742d Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 21:17:15 +0100 Subject: [PATCH 13/15] Make browser links unfocusable Since Enter would trigger Accept anyway, there is no point in them having focus. --- qt/aqt/forms/dyndconf.ui | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index 2e0ddfaa6..daae3fc3c 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -64,6 +64,9 @@ + + Qt::NoFocus + SEARCH_VIEW_IN_BROWSER @@ -125,6 +128,9 @@ + + Qt::NoFocus + SEARCH_VIEW_IN_BROWSER From 2ddb4edbb09b7287dbdffaf5a7b8e25e23a57acf Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 23:20:57 +0100 Subject: [PATCH 14/15] Rename standard buttons to have transaltions --- qt/aqt/dyndeckconf.py | 6 +++++- qt/aqt/forms/dyndconf.ui | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 955e2f29e..8ad24e4b0 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -83,7 +83,11 @@ class DeckConf(QDialog): self.setWindowTitle( without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"])) ) - self.form.buttonBox.addButton(label, QDialogButtonBox.AcceptRole) + self.form.buttonBox.button(QDialogButtonBox.Ok).setText(label) + self.form.buttonBox.button(QDialogButtonBox.Cancel).setText( + tr(TR.ACTIONS_CANCEL) + ) + self.form.buttonBox.button(QDialogButtonBox.Help).setText(tr(TR.ACTIONS_HELP)) if self.mw.col.schedVer() == 1: self.form.secondFilter.setVisible(False) restoreGeom(self, "dyndeckconf") diff --git a/qt/aqt/forms/dyndconf.ui b/qt/aqt/forms/dyndconf.ui index daae3fc3c..6e9eaa441 100644 --- a/qt/aqt/forms/dyndconf.ui +++ b/qt/aqt/forms/dyndconf.ui @@ -269,7 +269,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Help + QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok From b450c8d45ceb7f28684f4e2a71014b02e2d0d8d4 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Mon, 1 Feb 2021 23:46:56 +0100 Subject: [PATCH 15/15] Add remaining type hints to dyndeckconf etc. --- qt/aqt/browser.py | 6 +++--- qt/aqt/dyndeckconf.py | 27 ++++++++++++++------------- qt/aqt/main.py | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index c50b4f83d..ca51dce3d 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -713,7 +713,7 @@ class Browser(QMainWindow): self.form.searchEdit.setEditText(search) self.onSearchActivated() - def show_single_card(self, card: Card): + def show_single_card(self, card: Card) -> None: if card.nid: def on_show_single_card() -> None: @@ -1197,11 +1197,11 @@ where id in %s""" if nids: ChangeModel(self, nids) - def createFilteredDeck(self): + def createFilteredDeck(self) -> None: search = self.form.searchEdit.lineEdit().text() aqt.dialogs.open("DynDeckConfDialog", self.mw, search=search) - def createFilteredDeck2(self): + def createFilteredDeck2(self) -> None: search = self.form.searchEdit.lineEdit().text() aqt.dialogs.open("DynDeckConfDialog", self.mw, search_2=search) diff --git a/qt/aqt/dyndeckconf.py b/qt/aqt/dyndeckconf.py index 60a39ea5f..c32a6e3f2 100644 --- a/qt/aqt/dyndeckconf.py +++ b/qt/aqt/dyndeckconf.py @@ -100,10 +100,10 @@ class DeckConf(QDialog): search: Optional[str] = None, search_2: Optional[str] = None, _deck: Optional[Deck] = None, - ): + ) -> None: self.set_custom_searches(search, search_2) - def new_dyn_deck(self): + def new_dyn_deck(self) -> None: suffix: int = 1 while self.mw.col.decks.id_for_name( without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=suffix)) @@ -113,7 +113,7 @@ class DeckConf(QDialog): self.did = self.mw.col.decks.new_filtered(name) self.deck = self.mw.col.decks.current() - def set_default_searches(self, deck_name: str): + def set_default_searches(self, deck_name: str) -> None: self.form.search.setText( self.mw.col.build_search_string( SearchTerm(deck=deck_name), @@ -247,18 +247,19 @@ class DeckConf(QDialog): try: self.saveConf() except InvalidInput as err: - return show_invalid_search_error(err) + show_invalid_search_error(err) except DeckRenameError as err: - return showWarning(err.description) - if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): - if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): - return - saveGeom(self, "dyndeckconf") - self.mw.reset() - QDialog.accept(self) - aqt.dialogs.markClosed("DynDeckConfDialog") + showWarning(err.description) + else: + if not self.mw.col.sched.rebuild_filtered_deck(self.deck["id"]): + if askUser(tr(TR.DECKS_THE_PROVIDED_SEARCH_DID_NOT_MATCH)): + return + saveGeom(self, "dyndeckconf") + self.mw.reset() + QDialog.accept(self) + aqt.dialogs.markClosed("DynDeckConfDialog") - def closeWithCallback(self, callback: Callable): + def closeWithCallback(self, callback: Callable) -> None: self.reject() callback() diff --git a/qt/aqt/main.py b/qt/aqt/main.py index 7152661f9..28cd61113 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -14,7 +14,7 @@ import weakref import zipfile from argparse import Namespace from threading import Thread -from typing import Any, Callable, List, Optional, Sequence, TextIO, Tuple, Union, cast +from typing import Any, Callable, List, Optional, Sequence, TextIO, Tuple, cast import anki import aqt