From 458d891d28293c44993f1fad906cfb64d7a924d6 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 25 Apr 2021 08:35:21 +0200 Subject: [PATCH 1/2] Add dec to make methods no-op if no selection --- ftl/core/browsing.ftl | 1 + qt/aqt/utils.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index ca5c72b60..5b44004f0 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -65,6 +65,7 @@ browsing-nd-names = { $num }: { $name } browsing-new = (new) browsing-new-note-type = New note type: browsing-no-flag = No Flag +browsing-no-selection = No cards or notes selected. browsing-note = Note # Exactly one character representing 'Notes'; should differ from browsing-card-initial. browsing-note-initial = N diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 2b4a0511d..83064712c 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -1009,6 +1009,19 @@ def ensure_editor_saved_on_trigger(func: Callable) -> Callable: return pyqtSlot()(ensure_editor_saved(func)) # type: ignore +def skip_if_selection_is_empty(func: Callable) -> Callable: + """Make the wrapped method a no-op and show a hint if the table selection is empty.""" + + @wraps(func) + def decorated(self: Any, *args: Any, **kwargs: Any) -> None: + if self.table.len_selection() > 0: + func(self, *args, **kwargs) + else: + tooltip(tr.browsing_no_selection()) + + return decorated + + class KeyboardModifiersPressed: "Util for type-safe checks of currently-pressed modifier keys." From b1392980f58aa0fe96dc8cc80f239953c1f997f2 Mon Sep 17 00:00:00 2001 From: RumovZ Date: Sun, 25 Apr 2021 08:47:15 +0200 Subject: [PATCH 2/2] Skip some browser methods if selection is empty This was handled inconsistently before: * Select Notes would throw a search error. * Set Due Date would throw an exception. * Dialogs with no effect would open. * No-ops would be pushed to the undo queue. --- qt/aqt/browser/browser.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 9e42e70ec..ebd8760a1 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -71,6 +71,7 @@ from aqt.utils import ( shortcut, showInfo, showWarning, + skip_if_selection_is_empty, tooltip, tr, ) @@ -573,11 +574,10 @@ where id in %s""" # Misc menu options ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def onChangeModel(self) -> None: - nids = self.oneModelNotes() - if nids: - ChangeModel(self, nids) + ChangeModel(self, self.oneModelNotes()) def createFilteredDeck(self) -> None: search = self.current_search() @@ -619,6 +619,7 @@ where id in %s""" # Card deletion ###################################################################### + @skip_if_selection_is_empty def delete_selected_notes(self) -> None: # ensure deletion is not accidentally triggered when the user is focused # in the editing screen or search bar @@ -626,11 +627,7 @@ where id in %s""" if focus != self.form.tableView: return - # nothing selected? nids = self.table.get_selected_note_ids() - if not nids: - return - # select the next card if there is one self.focusTo = self.editor.currentField self.table.to_next_row() @@ -644,14 +641,12 @@ where id in %s""" # Deck change ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def set_deck_of_selected_cards(self) -> None: from aqt.studydeck import StudyDeck cids = self.table.get_selected_card_ids() - if not cids: - return - did = self.mw.col.db.scalar("select did from cards where id = ?", cids[0]) current = self.mw.col.decks.get(did)["name"] ret = StudyDeck( @@ -675,6 +670,7 @@ where id in %s""" # Tags ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def add_tags_to_selected_notes( self, @@ -687,6 +683,7 @@ where id in %s""" parent=self, note_ids=self.selected_notes(), space_separated_tags=tags ).run_in_background(initiator=self) + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def remove_tags_from_selected_notes(self, tags: Optional[str] = None) -> None: "Shows prompt if tags not provided." @@ -721,6 +718,7 @@ where id in %s""" is_suspended = bool(self.card and self.card.queue == QUEUE_TYPE_SUSPENDED) self.form.actionToggle_Suspend.setChecked(is_suspended) + @skip_if_selection_is_empty @ensure_editor_saved def suspend_selected_cards(self, checked: bool) -> None: cids = self.selected_cards() @@ -732,14 +730,15 @@ where id in %s""" # Exporting ###################################################################### + @skip_if_selection_is_empty def _on_export_notes(self) -> None: cids = self.selectedNotesAsCards() - if cids: - ExportDialog(self.mw, cids=list(cids)) + ExportDialog(self.mw, cids=list(cids)) # Flags & Marking ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved def set_flag_of_selected_cards(self, flag: int) -> None: if not self.card: @@ -782,6 +781,7 @@ where id in %s""" # Scheduling ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def reposition(self) -> None: if self.card and self.card.queue != QUEUE_TYPE_NEW: @@ -792,6 +792,7 @@ where id in %s""" parent=self, card_ids=self.selected_cards() ).run_in_background() + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def set_due_date(self) -> None: set_due_date_dialog( @@ -800,6 +801,7 @@ where id in %s""" config_key=Config.String.SET_DUE_BROWSER, ).run_in_background() + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def forget_cards(self) -> None: forget_cards( @@ -810,6 +812,7 @@ where id in %s""" # Edit: selection ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def selectNotes(self) -> None: nids = self.selected_notes() @@ -858,13 +861,10 @@ where id in %s""" # Edit: replacing ###################################################################### + @skip_if_selection_is_empty @ensure_editor_saved_on_trigger def onFindReplace(self) -> None: - nids = self.selected_notes() - if not nids: - return - - FindAndReplaceDialog(self, mw=self.mw, note_ids=nids) + FindAndReplaceDialog(self, mw=self.mw, note_ids=self.selected_notes()) # Edit: finding dupes ######################################################################