diff --git a/ftl/core/browsing.ftl b/ftl/core/browsing.ftl index 5b44004f0..67addb399 100644 --- a/ftl/core/browsing.ftl +++ b/ftl/core/browsing.ftl @@ -130,6 +130,11 @@ browsing-notes-updated = [one] { $count } note updated. *[other] { $count } notes updated. } +browsing-cards-updated = + { $count -> + [one] { $count } card updated. + *[other] { $count } cards updated. + } browsing-window-title = Browse ({ $selected } of { $total } cards selected) browsing-sidebar-expand = Expand browsing-sidebar-collapse = Collapse diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index e682ce1d7..aa504136c 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -470,7 +470,7 @@ class Collection: "You probably want .remove_notes_by_card() instead." self._backend.remove_cards(card_ids=card_ids) - def set_deck(self, card_ids: Sequence[CardId], deck_id: int) -> OpChanges: + def set_deck(self, card_ids: Sequence[CardId], deck_id: int) -> OpChangesWithCount: return self._backend.set_deck(card_ids=card_ids, deck_id=deck_id) def get_empty_cards(self) -> EmptyCardsReport: @@ -1137,7 +1137,9 @@ table.review-log {{ {revlog_style} }} ########################################################################## - def set_user_flag_for_cards(self, flag: int, cids: Sequence[CardId]) -> OpChanges: + def set_user_flag_for_cards( + self, flag: int, cids: Sequence[CardId] + ) -> OpChangesWithCount: return self._backend.set_flag(card_ids=cids, flag=flag) def set_wants_abort(self) -> None: diff --git a/qt/aqt/browser/browser.py b/qt/aqt/browser/browser.py index 056ff12d8..ee9e97bf1 100644 --- a/qt/aqt/browser/browser.py +++ b/qt/aqt/browser/browser.py @@ -129,6 +129,8 @@ class Browser(QMainWindow): self.editor.set_note(note) self._renderPreview() + elif changes.card: + self.card = self.table.get_current_card() def on_focus_change(self, new: Optional[QWidget], old: Optional[QWidget]) -> None: if current_top_level_widget() == self: diff --git a/qt/aqt/operations/card.py b/qt/aqt/operations/card.py index 2e7f0ac19..6071336ee 100644 --- a/qt/aqt/operations/card.py +++ b/qt/aqt/operations/card.py @@ -6,16 +6,19 @@ from __future__ import annotations from typing import Sequence from anki.cards import CardId -from anki.collection import OpChanges +from anki.collection import OpChangesWithCount from anki.decks import DeckId from aqt.operations import CollectionOp from aqt.qt import QWidget +from aqt.utils import tooltip, tr def set_card_deck( *, parent: QWidget, card_ids: Sequence[CardId], deck_id: DeckId -) -> CollectionOp[OpChanges]: - return CollectionOp(parent, lambda col: col.set_deck(card_ids, deck_id)) +) -> CollectionOp[OpChangesWithCount]: + return CollectionOp(parent, lambda col: col.set_deck(card_ids, deck_id)).success( + lambda out: tooltip(tr.browsing_cards_updated(count=out.count), parent=parent) + ) def set_card_flag( @@ -23,5 +26,9 @@ def set_card_flag( parent: QWidget, card_ids: Sequence[CardId], flag: int, -) -> CollectionOp[OpChanges]: - return CollectionOp(parent, lambda col: col.set_user_flag_for_cards(flag, card_ids)) +) -> CollectionOp[OpChangesWithCount]: + return CollectionOp( + parent, lambda col: col.set_user_flag_for_cards(flag, card_ids) + ).success( + lambda out: tooltip(tr.browsing_cards_updated(count=out.count), parent=parent) + ) diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 1f0d538dd..3fc2cd75c 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -968,7 +968,7 @@ time = %(time)d; self.mw.onDeckConf(self.mw.col.decks.get(self.card.current_deck_id())) def set_flag_on_current_card(self, desired_flag: int) -> None: - def redraw_flag(out: OpChanges) -> None: + def redraw_flag(out: OpChangesWithCount) -> None: self.card.load() self._update_flag_icon() diff --git a/rslib/backend.proto b/rslib/backend.proto index 1f3f6e725..cbdcbde7c 100644 --- a/rslib/backend.proto +++ b/rslib/backend.proto @@ -299,8 +299,8 @@ service CardsService { rpc GetCard(CardId) returns (Card); rpc UpdateCard(UpdateCardIn) returns (OpChanges); rpc RemoveCards(RemoveCardsIn) returns (Empty); - rpc SetDeck(SetDeckIn) returns (OpChanges); - rpc SetFlag(SetFlagIn) returns (OpChanges); + rpc SetDeck(SetDeckIn) returns (OpChangesWithCount); + rpc SetFlag(SetFlagIn) returns (OpChangesWithCount); } // Protobuf stored in .anki2 files diff --git a/rslib/src/backend/card.rs b/rslib/src/backend/card.rs index 03c7b59e9..8e3c5ab69 100644 --- a/rslib/src/backend/card.rs +++ b/rslib/src/backend/card.rs @@ -44,13 +44,13 @@ impl CardsService for Backend { }) } - fn set_deck(&self, input: pb::SetDeckIn) -> Result { + fn set_deck(&self, input: pb::SetDeckIn) -> Result { let cids: Vec<_> = input.card_ids.into_iter().map(CardId).collect(); let deck_id = input.deck_id.into(); self.with_col(|col| col.set_deck(&cids, deck_id).map(Into::into)) } - fn set_flag(&self, input: pb::SetFlagIn) -> Result { + fn set_flag(&self, input: pb::SetFlagIn) -> Result { self.with_col(|col| { col.set_card_flag(&to_card_ids(input.card_ids), input.flag) .map(Into::into) diff --git a/rslib/src/card/mod.rs b/rslib/src/card/mod.rs index cc82c5f85..e45f9458c 100644 --- a/rslib/src/card/mod.rs +++ b/rslib/src/card/mod.rs @@ -117,13 +117,20 @@ impl Card { self.deck_id = deck; } - fn set_flag(&mut self, flag: u8) { + /// True if flag changed. + fn set_flag(&mut self, flag: u8) -> bool { // we currently only allow 4 flags assert!(flag < 5); // but reserve space for 7, preserving the rest of // the flags (up to a byte) - self.flags = (self.flags & !0b111) | flag + let updated_flags = (self.flags & !0b111) | flag; + if self.flags != updated_flags { + self.flags = updated_flags; + true + } else { + false + } } /// Return the total number of steps left to do, ignoring the @@ -237,7 +244,7 @@ impl Collection { Ok(()) } - pub fn set_deck(&mut self, cards: &[CardId], deck_id: DeckId) -> Result> { + pub fn set_deck(&mut self, cards: &[CardId], deck_id: DeckId) -> Result> { let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?; if deck.is_filtered() { return Err(FilteredDeckError::CanNotMoveCardsInto.into()); @@ -246,19 +253,21 @@ impl Collection { let sched = self.scheduler_version(); let usn = self.usn()?; self.transact(Op::SetCardDeck, |col| { + let mut count = 0; for mut card in col.storage.all_searched_cards()? { if card.deck_id == deck_id { continue; } + count += 1; let original = card.clone(); card.set_deck(deck_id, sched); col.update_card_inner(&mut card, original, usn)?; } - Ok(()) + Ok(count) }) } - pub fn set_card_flag(&mut self, cards: &[CardId], flag: u32) -> Result> { + pub fn set_card_flag(&mut self, cards: &[CardId], flag: u32) -> Result> { if flag > 4 { return Err(AnkiError::invalid_input("invalid flag")); } @@ -267,12 +276,15 @@ impl Collection { self.storage.set_search_table_to_card_ids(cards, false)?; let usn = self.usn()?; self.transact(Op::SetFlag, |col| { + let mut count = 0; for mut card in col.storage.all_searched_cards()? { let original = card.clone(); - card.set_flag(flag); - col.update_card_inner(&mut card, original, usn)?; + if card.set_flag(flag) { + col.update_card_inner(&mut card, original, usn)?; + count += 1; + } } - Ok(()) + Ok(count) }) }