current deck change is now undoable

- make sure we set flag in changes when config var changed
- move current deck get/set into backend
- set_config() now returns a bool indicating whether a change was
made, so other operations can be gated off it
- active decks generation is deferred until sched.reset()
This commit is contained in:
Damien Elmes 2021-04-06 21:37:31 +10:00
parent 5676ad5101
commit 6e954e82a5
23 changed files with 155 additions and 63 deletions

View file

@ -447,30 +447,31 @@ class DeckManager:
# Deck selection # Deck selection
############################################################# #############################################################
def active(self) -> List[DeckId]: def get_current(self) -> Deck:
"The currrently active dids." return self.col._backend.get_current_deck()
return self.col.get_config("activeDecks", [1])
def selected(self) -> DeckId: def set_current(self, deck: DeckId) -> OpChanges:
"The currently selected did." return self.col._backend.set_current_deck(deck)
return DeckId(int(self.col.conf["curDeck"]))
def get_current_id(self) -> DeckId:
"The currently selected deck ID."
return DeckId(self.get_current().id)
# legacy
def current(self) -> DeckDict: def current(self) -> DeckDict:
return self.get(self.selected()) return self.get(self.selected())
def select(self, did: DeckId) -> None: def select(self, did: DeckId) -> None:
"Select a new branch."
# make sure arg is an int; legacy callers may be passing in a string # make sure arg is an int; legacy callers may be passing in a string
did = DeckId(did) did = DeckId(did)
current = self.selected() self.set_current(did)
active = self.deck_and_child_ids(did) self.col.reset()
if current != did or active != self.active():
self.col.conf["curDeck"] = did
self.col.conf["activeDecks"] = active
# don't use this, it will likely go away def active(self) -> List[DeckId]:
def update_active(self) -> None: return self.col.sched.active_decks
self.select(self.current()["id"])
selected = get_current_id
# Parents/children # Parents/children
############################################################# #############################################################
@ -518,7 +519,7 @@ class DeckManager:
) )
def deck_and_child_ids(self, deck_id: DeckId) -> List[DeckId]: def deck_and_child_ids(self, deck_id: DeckId) -> List[DeckId]:
parent_name = self.get_legacy(deck_id)["name"] parent_name = self.col.get_deck(deck_id).name
out = [deck_id] out = [deck_id]
out.extend(self.child_ids(parent_name)) out.extend(self.child_ids(parent_name))
return out return out

View file

@ -85,8 +85,9 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
# fixme: only used by totalRevForCurrentDeck and old deck stats; # fixme: only used by totalRevForCurrentDeck and old deck stats;
# schedv2 defines separate version # schedv2 defines separate version
def _deckLimit(self) -> str: def _deckLimit(self) -> str:
self.col.decks.update_active() return ids2str(
return ids2str(self.col.decks.active()) self.col.decks.deck_and_child_ids(self.col.decks.get_current_id())
)
# Filtered deck handling # Filtered deck handling
########################################################################## ##########################################################################

View file

@ -33,7 +33,7 @@ class Scheduler(V2):
def __init__( # pylint: disable=super-init-not-called def __init__( # pylint: disable=super-init-not-called
self, col: anki.collection.Collection self, col: anki.collection.Collection
) -> None: ) -> None:
self.col = col.weakref() super().__init__(col)
self.queueLimit = 50 self.queueLimit = 50
self.reportLimit = 1000 self.reportLimit = 1000
self.dynReportLimit = 99999 self.dynReportLimit = 99999
@ -42,7 +42,6 @@ class Scheduler(V2):
self.revCount = 0 self.revCount = 0
self.newCount = 0 self.newCount = 0
self._haveQueues = False self._haveQueues = False
self._updateCutoff()
def answerCard(self, card: Card, ease: int) -> None: def answerCard(self, card: Card, ease: int) -> None:
self.col.log() self.col.log()

View file

@ -48,7 +48,16 @@ class Scheduler(SchedulerBaseWithLegacy):
self.reps = 0 self.reps = 0
self._haveQueues = False self._haveQueues = False
self._lrnCutoff = 0 self._lrnCutoff = 0
self._updateCutoff() self._active_decks: List[DeckId] = []
self._current_deck_id = DeckId(1)
@property
def active_decks(self) -> List[DeckId]:
"Caller must make sure to make a copy."
return self._active_decks
def _update_active_decks(self) -> None:
self._active_decks = self.col.decks.deck_and_child_ids(self._current_deck_id)
# Daily cutoff # Daily cutoff
########################################################################## ##########################################################################
@ -65,8 +74,8 @@ class Scheduler(SchedulerBaseWithLegacy):
########################################################################## ##########################################################################
def reset(self) -> None: def reset(self) -> None:
self.col.decks.update_active() self._current_deck_id = self.col.decks.selected()
self._updateCutoff() self._update_active_decks()
self._reset_counts() self._reset_counts()
self._resetLrn() self._resetLrn()
self._resetRev() self._resetRev()
@ -74,10 +83,8 @@ class Scheduler(SchedulerBaseWithLegacy):
self._haveQueues = True self._haveQueues = True
def _reset_counts(self) -> None: def _reset_counts(self) -> None:
tree = self.deck_due_tree(self.col.decks.selected()) tree = self.deck_due_tree(self._current_deck_id)
node = self.col.decks.find_deck_in_tree( node = self.col.decks.find_deck_in_tree(tree, self._current_deck_id)
tree, DeckId(int(self.col.conf["curDeck"]))
)
if not node: if not node:
# current deck points to a missing deck # current deck points to a missing deck
self.newCount = 0 self.newCount = 0

View file

@ -18,6 +18,7 @@ def test_basic():
assert col.decks.id("new deck") == parentId assert col.decks.id("new deck") == parentId
# we start with the default col selected # we start with the default col selected
assert col.decks.selected() == 1 assert col.decks.selected() == 1
col.reset()
assert col.decks.active() == [1] assert col.decks.active() == [1]
# we can select a different col # we can select a different col
col.decks.select(parentId) col.decks.select(parentId)

View file

@ -17,6 +17,7 @@ from aqt.operations.deck import (
remove_decks, remove_decks,
rename_deck, rename_deck,
reparent_decks, reparent_decks,
set_current_deck,
set_deck_collapsed, set_deck_collapsed,
) )
from aqt.qt import * from aqt.qt import *
@ -79,7 +80,7 @@ class DeckBrowser:
def op_executed( def op_executed(
self, changes: OpChanges, handler: Optional[object], focused: bool self, changes: OpChanges, handler: Optional[object], focused: bool
) -> bool: ) -> bool:
if changes.study_queues: if changes.study_queues and handler is not self:
self._refresh_needed = True self._refresh_needed = True
if focused: if focused:
@ -96,7 +97,7 @@ class DeckBrowser:
else: else:
cmd = url cmd = url
if cmd == "open": if cmd == "open":
self._selDeck(arg) self.set_current_deck(DeckId(int(arg)))
elif cmd == "opts": elif cmd == "opts":
self._showOptions(arg) self._showOptions(arg)
elif cmd == "shared": elif cmd == "shared":
@ -119,9 +120,10 @@ class DeckBrowser:
self.refresh() self.refresh()
return False return False
def _selDeck(self, did: str) -> None: def set_current_deck(self, deck_id: DeckId) -> None:
self.mw.col.decks.select(DeckId(int(did))) set_current_deck(parent=self.mw, deck_id=deck_id).success(
self.mw.onOverview() lambda _: self.mw.onOverview()
).run_in_background(initiator=self)
# HTML generation # HTML generation
########################################################################## ##########################################################################

View file

@ -53,6 +53,7 @@ from aqt.legacy import install_pylib_legacy
from aqt.mediacheck import check_media_db from aqt.mediacheck import check_media_db
from aqt.mediasync import MediaSyncer from aqt.mediasync import MediaSyncer
from aqt.operations.collection import undo from aqt.operations.collection import undo
from aqt.operations.deck import set_current_deck
from aqt.profiles import ProfileManager as ProfileManagerType from aqt.profiles import ProfileManager as ProfileManagerType
from aqt.qt import * from aqt.qt import *
from aqt.qt import sip from aqt.qt import sip
@ -1425,8 +1426,11 @@ title="%s" %s>%s</button>""" % (
ret = StudyDeck(self, dyn=True, current=self.col.decks.current()["name"]) ret = StudyDeck(self, dyn=True, current=self.col.decks.current()["name"])
if ret.name: if ret.name:
self.col.decks.select(self.col.decks.id(ret.name)) # fixme: this is silly, it should be returning an ID
self.moveToState("overview") deck_id = self.col.decks.id(ret.name)
set_current_deck(parent=self, deck_id=deck_id).success(
lambda out: self.moveToState("overview")
).run_in_background()
def onEmptyCards(self) -> None: def onEmptyCards(self) -> None:
show_empty_cards(self) show_empty_cards(self)

View file

@ -76,3 +76,7 @@ def set_deck_collapsed(
deck_id=deck_id, collapsed=collapsed, scope=scope deck_id=deck_id, collapsed=collapsed, scope=scope
), ),
) )
def set_current_deck(*, parent: QWidget, deck_id: DeckId) -> CollectionOp[OpChanges]:
return CollectionOp(parent, lambda col: col.decks.set_current(deck_id))

View file

@ -154,6 +154,8 @@ service DecksService {
rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate); rpc GetOrCreateFilteredDeck(DeckId) returns (FilteredDeckForUpdate);
rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) returns (OpChangesWithId); rpc AddOrUpdateFilteredDeck(FilteredDeckForUpdate) returns (OpChangesWithId);
rpc FilteredDeckOrderLabels(Empty) returns (StringList); rpc FilteredDeckOrderLabels(Empty) returns (StringList);
rpc SetCurrentDeck(DeckId) returns (OpChanges);
rpc GetCurrentDeck(Empty) returns (Deck);
} }
service NotesService { service NotesService {
@ -1504,7 +1506,7 @@ message OpChanges {
bool deck = 3; bool deck = 3;
bool tag = 4; bool tag = 4;
bool notetype = 5; bool notetype = 5;
bool preference = 6; bool config = 6;
bool browser_table = 7; bool browser_table = 7;
bool browser_sidebar = 8; bool browser_sidebar = 8;

View file

@ -67,7 +67,7 @@ impl ConfigService for Backend {
col.transact_no_undo(|col| { col.transact_no_undo(|col| {
// ensure it's a well-formed object // ensure it's a well-formed object
let val: Value = serde_json::from_slice(&input.value_json)?; let val: Value = serde_json::from_slice(&input.value_json)?;
col.set_config(input.key.as_str(), &val) col.set_config(input.key.as_str(), &val).map(|_| ())
}) })
}) })
.map(Into::into) .map(Into::into)
@ -98,7 +98,7 @@ impl ConfigService for Backend {
self.with_col(|col| { self.with_col(|col| {
col.transact_no_undo(|col| col.set_bool(input.key().into(), input.value)) col.transact_no_undo(|col| col.set_bool(input.key().into(), input.value))
}) })
.map(Into::into) .map(|_| ().into())
} }
fn get_config_string(&self, input: pb::config::String) -> Result<pb::String> { fn get_config_string(&self, input: pb::config::String) -> Result<pb::String> {
@ -113,7 +113,7 @@ impl ConfigService for Backend {
self.with_col(|col| { self.with_col(|col| {
col.transact_no_undo(|col| col.set_string(input.key().into(), &input.value)) col.transact_no_undo(|col| col.set_string(input.key().into(), &input.value))
}) })
.map(Into::into) .map(|_| ().into())
} }
fn get_preferences(&self, _input: pb::Empty) -> Result<pb::Preferences> { fn get_preferences(&self, _input: pb::Empty) -> Result<pb::Preferences> {

View file

@ -187,6 +187,16 @@ impl DecksService for Backend {
}) })
.map(Into::into) .map(Into::into)
} }
fn set_current_deck(&self, input: pb::DeckId) -> Result<pb::OpChanges> {
self.with_col(|col| col.set_current_deck(input.did.into()))
.map(Into::into)
}
fn get_current_deck(&self, _input: pb::Empty) -> Result<pb::Deck> {
self.with_col(|col| col.get_current_deck())
.map(|deck| (*deck).clone().into())
}
} }
impl From<pb::DeckId> for DeckId { impl From<pb::DeckId> for DeckId {

View file

@ -16,7 +16,7 @@ impl From<OpChanges> for pb::OpChanges {
deck: c.changes.deck, deck: c.changes.deck,
tag: c.changes.tag, tag: c.changes.tag,
notetype: c.changes.notetype, notetype: c.changes.notetype,
preference: c.changes.preference, config: c.changes.config,
browser_table: c.requires_browser_table_redraw(), browser_table: c.requires_browser_table_redraw(),
browser_sidebar: c.requires_browser_sidebar_redraw(), browser_sidebar: c.requires_browser_sidebar_redraw(),
editor: c.requires_editor_redraw(), editor: c.requires_editor_redraw(),

View file

@ -239,7 +239,7 @@ impl Collection {
self.storage.set_search_table_to_card_ids(cards, false)?; self.storage.set_search_table_to_card_ids(cards, false)?;
let sched = self.scheduler_version(); let sched = self.scheduler_version();
let usn = self.usn()?; let usn = self.usn()?;
self.transact(Op::SetDeck, |col| { self.transact(Op::SetCardDeck, |col| {
for mut card in col.storage.all_searched_cards()? { for mut card in col.storage.all_searched_cards()? {
if card.deck_id == deck_id { if card.deck_id == deck_id {
continue; continue;

View file

@ -69,7 +69,7 @@ impl Collection {
} }
} }
pub(crate) fn set_bool(&mut self, key: BoolKey, value: bool) -> Result<()> { pub(crate) fn set_bool(&mut self, key: BoolKey, value: bool) -> Result<bool> {
self.set_config(key, &value) self.set_config(key, &value)
} }
} }

View file

@ -1,7 +1,6 @@
// Copyright: Ankitects Pty Ltd and contributors // Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use super::ConfigKey;
use crate::prelude::*; use crate::prelude::*;
use strum::IntoStaticStr; use strum::IntoStaticStr;
@ -20,11 +19,6 @@ impl DeckConfigKey {
} }
impl Collection { impl Collection {
pub(crate) fn get_current_deck_id(&self) -> DeckId {
self.get_config_optional(ConfigKey::CurrentDeckId)
.unwrap_or(DeckId(1))
}
pub(crate) fn clear_aux_config_for_deck(&self, ntid: DeckId) -> Result<()> { pub(crate) fn clear_aux_config_for_deck(&self, ntid: DeckId) -> Result<()> {
self.remove_config_prefix(&build_aux_deck_key(ntid, "")) self.remove_config_prefix(&build_aux_deck_key(ntid, ""))
} }
@ -38,7 +32,7 @@ impl Collection {
&mut self, &mut self,
did: DeckId, did: DeckId,
ntid: NotetypeId, ntid: NotetypeId,
) -> Result<()> { ) -> Result<bool> {
let key = DeckConfigKey::LastNotetype.for_deck(did); let key = DeckConfigKey::LastNotetype.for_deck(did);
self.set_config(key.as_str(), &ntid) self.set_config(key.as_str(), &ntid)
} }

View file

@ -103,7 +103,8 @@ impl Collection {
self.get_config_optional(key).unwrap_or_default() self.get_config_optional(key).unwrap_or_default()
} }
pub(crate) fn set_config<'a, T: Serialize, K>(&mut self, key: K, val: &T) -> Result<()> /// True if added, or new value is different.
pub(crate) fn set_config<'a, T: Serialize, K>(&mut self, key: K, val: &T) -> Result<bool>
where where
K: Into<&'a str>, K: Into<&'a str>,
{ {
@ -148,6 +149,7 @@ impl Collection {
columns: Vec<browser_table::Column>, columns: Vec<browser_table::Column>,
) -> Result<()> { ) -> Result<()> {
self.set_config(ConfigKey::DesktopBrowserCardColumns, &columns) self.set_config(ConfigKey::DesktopBrowserCardColumns, &columns)
.map(|_| ())
} }
pub(crate) fn get_desktop_browser_note_columns(&self) -> Option<Vec<browser_table::Column>> { pub(crate) fn get_desktop_browser_note_columns(&self) -> Option<Vec<browser_table::Column>> {
@ -159,6 +161,7 @@ impl Collection {
columns: Vec<browser_table::Column>, columns: Vec<browser_table::Column>,
) -> Result<()> { ) -> Result<()> {
self.set_config(ConfigKey::DesktopBrowserNoteColumns, &columns) self.set_config(ConfigKey::DesktopBrowserNoteColumns, &columns)
.map(|_| ())
} }
pub(crate) fn get_creation_utc_offset(&self) -> Option<i32> { pub(crate) fn get_creation_utc_offset(&self) -> Option<i32> {
@ -169,6 +172,7 @@ impl Collection {
self.state.scheduler_info = None; self.state.scheduler_info = None;
if let Some(mins) = mins { if let Some(mins) = mins {
self.set_config(ConfigKey::CreationOffset, &mins) self.set_config(ConfigKey::CreationOffset, &mins)
.map(|_| ())
} else { } else {
self.remove_config(ConfigKey::CreationOffset) self.remove_config(ConfigKey::CreationOffset)
} }
@ -180,7 +184,7 @@ impl Collection {
pub(crate) fn set_configured_utc_offset(&mut self, mins: i32) -> Result<()> { pub(crate) fn set_configured_utc_offset(&mut self, mins: i32) -> Result<()> {
self.state.scheduler_info = None; self.state.scheduler_info = None;
self.set_config(ConfigKey::LocalOffset, &mins) self.set_config(ConfigKey::LocalOffset, &mins).map(|_| ())
} }
pub(crate) fn get_v2_rollover(&self) -> Option<u8> { pub(crate) fn get_v2_rollover(&self) -> Option<u8> {
@ -190,7 +194,7 @@ impl Collection {
pub(crate) fn set_v2_rollover(&mut self, hour: u32) -> Result<()> { pub(crate) fn set_v2_rollover(&mut self, hour: u32) -> Result<()> {
self.state.scheduler_info = None; self.state.scheduler_info = None;
self.set_config(ConfigKey::Rollover, &hour) self.set_config(ConfigKey::Rollover, &hour).map(|_| ())
} }
pub(crate) fn get_next_card_position(&self) -> u32 { pub(crate) fn get_next_card_position(&self) -> u32 {
@ -207,6 +211,7 @@ impl Collection {
pub(crate) fn set_next_card_position(&mut self, pos: u32) -> Result<()> { pub(crate) fn set_next_card_position(&mut self, pos: u32) -> Result<()> {
self.set_config(ConfigKey::NextNewCardPosition, &pos) self.set_config(ConfigKey::NextNewCardPosition, &pos)
.map(|_| ())
} }
pub(crate) fn scheduler_version(&self) -> SchedulerVersion { pub(crate) fn scheduler_version(&self) -> SchedulerVersion {
@ -218,6 +223,7 @@ impl Collection {
pub(crate) fn set_scheduler_version_config_key(&mut self, ver: SchedulerVersion) -> Result<()> { pub(crate) fn set_scheduler_version_config_key(&mut self, ver: SchedulerVersion) -> Result<()> {
self.state.scheduler_info = None; self.state.scheduler_info = None;
self.set_config(ConfigKey::SchedulerVersion, &ver) self.set_config(ConfigKey::SchedulerVersion, &ver)
.map(|_| ())
} }
pub(crate) fn learn_ahead_secs(&self) -> u32 { pub(crate) fn learn_ahead_secs(&self) -> u32 {
@ -227,6 +233,7 @@ impl Collection {
pub(crate) fn set_learn_ahead_secs(&mut self, secs: u32) -> Result<()> { pub(crate) fn set_learn_ahead_secs(&mut self, secs: u32) -> Result<()> {
self.set_config(ConfigKey::LearnAheadSecs, &secs) self.set_config(ConfigKey::LearnAheadSecs, &secs)
.map(|_| ())
} }
pub(crate) fn get_new_review_mix(&self) -> NewReviewMix { pub(crate) fn get_new_review_mix(&self) -> NewReviewMix {
@ -239,6 +246,7 @@ impl Collection {
pub(crate) fn set_new_review_mix(&mut self, mix: NewReviewMix) -> Result<()> { pub(crate) fn set_new_review_mix(&mut self, mix: NewReviewMix) -> Result<()> {
self.set_config(ConfigKey::NewReviewMix, &(mix as u8)) self.set_config(ConfigKey::NewReviewMix, &(mix as u8))
.map(|_| ())
} }
pub(crate) fn get_first_day_of_week(&self) -> Weekday { pub(crate) fn get_first_day_of_week(&self) -> Weekday {
@ -248,6 +256,7 @@ impl Collection {
pub(crate) fn set_first_day_of_week(&mut self, weekday: Weekday) -> Result<()> { pub(crate) fn set_first_day_of_week(&mut self, weekday: Weekday) -> Result<()> {
self.set_config(ConfigKey::FirstDayOfWeek, &weekday) self.set_config(ConfigKey::FirstDayOfWeek, &weekday)
.map(|_| ())
} }
pub(crate) fn get_answer_time_limit_secs(&self) -> u32 { pub(crate) fn get_answer_time_limit_secs(&self) -> u32 {
@ -257,6 +266,7 @@ impl Collection {
pub(crate) fn set_answer_time_limit_secs(&mut self, secs: u32) -> Result<()> { pub(crate) fn set_answer_time_limit_secs(&mut self, secs: u32) -> Result<()> {
self.set_config(ConfigKey::AnswerTimeLimitSecs, &secs) self.set_config(ConfigKey::AnswerTimeLimitSecs, &secs)
.map(|_| ())
} }
pub(crate) fn get_last_unburied_day(&self) -> u32 { pub(crate) fn get_last_unburied_day(&self) -> u32 {
@ -266,6 +276,7 @@ impl Collection {
pub(crate) fn set_last_unburied_day(&mut self, day: u32) -> Result<()> { pub(crate) fn set_last_unburied_day(&mut self, day: u32) -> Result<()> {
self.set_config(ConfigKey::LastUnburiedDay, &day) self.set_config(ConfigKey::LastUnburiedDay, &day)
.map(|_| ())
} }
} }

View file

@ -30,6 +30,7 @@ impl Collection {
pub(crate) fn set_current_notetype_id(&mut self, ntid: NotetypeId) -> Result<()> { pub(crate) fn set_current_notetype_id(&mut self, ntid: NotetypeId) -> Result<()> {
self.set_config(ConfigKey::CurrentNotetypeId, &ntid) self.set_config(ConfigKey::CurrentNotetypeId, &ntid)
.map(|_| ())
} }
pub(crate) fn clear_aux_config_for_notetype(&self, ntid: NotetypeId) -> Result<()> { pub(crate) fn clear_aux_config_for_notetype(&self, ntid: NotetypeId) -> Result<()> {
@ -43,7 +44,7 @@ impl Collection {
pub(crate) fn set_last_deck_for_notetype(&mut self, id: NotetypeId, did: DeckId) -> Result<()> { pub(crate) fn set_last_deck_for_notetype(&mut self, id: NotetypeId, did: DeckId) -> Result<()> {
let key = NotetypeConfigKey::LastDeckAddedTo.for_notetype(id); let key = NotetypeConfigKey::LastDeckAddedTo.for_notetype(id);
self.set_config(key.as_str(), &did) self.set_config(key.as_str(), &did).map(|_| ())
} }
} }

View file

@ -22,7 +22,7 @@ impl Collection {
.unwrap_or_else(|| default.to_string()) .unwrap_or_else(|| default.to_string())
} }
pub(crate) fn set_string(&mut self, key: StringKey, val: &str) -> Result<()> { pub(crate) fn set_string(&mut self, key: StringKey, val: &str) -> Result<bool> {
self.set_config(key, &val) self.set_config(key, &val)
} }
} }

View file

@ -21,16 +21,19 @@ impl Collection {
.get_config_entry(&entry.key)? .get_config_entry(&entry.key)?
.ok_or_else(|| AnkiError::invalid_input("config disappeared"))?; .ok_or_else(|| AnkiError::invalid_input("config disappeared"))?;
self.update_config_entry_undoable(entry, current) self.update_config_entry_undoable(entry, current)
.map(|_| ())
} }
UndoableConfigChange::Removed(entry) => self.add_config_entry_undoable(entry), UndoableConfigChange::Removed(entry) => self.add_config_entry_undoable(entry),
} }
} }
pub(super) fn set_config_undoable(&mut self, entry: Box<ConfigEntry>) -> Result<()> { /// True if added, or value changed.
pub(super) fn set_config_undoable(&mut self, entry: Box<ConfigEntry>) -> Result<bool> {
if let Some(original) = self.storage.get_config_entry(&entry.key)? { if let Some(original) = self.storage.get_config_entry(&entry.key)? {
self.update_config_entry_undoable(entry, original) self.update_config_entry_undoable(entry, original)
} else { } else {
self.add_config_entry_undoable(entry) self.add_config_entry_undoable(entry)?;
Ok(true)
} }
} }
@ -49,16 +52,19 @@ impl Collection {
Ok(()) Ok(())
} }
/// True if new value differed.
fn update_config_entry_undoable( fn update_config_entry_undoable(
&mut self, &mut self,
entry: Box<ConfigEntry>, entry: Box<ConfigEntry>,
original: Box<ConfigEntry>, original: Box<ConfigEntry>,
) -> Result<()> { ) -> Result<bool> {
if entry.value != original.value { if entry.value != original.value {
self.save_undo(UndoableConfigChange::Updated(original)); self.save_undo(UndoableConfigChange::Updated(original));
self.storage.set_config_entry(&entry)?; self.storage.set_config_entry(&entry)?;
Ok(true)
} else {
Ok(false)
} }
Ok(())
} }
} }

View file

@ -0,0 +1,41 @@
// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::sync::Arc;
use crate::{config::ConfigKey, prelude::*};
impl Collection {
pub fn set_current_deck(&mut self, deck: DeckId) -> Result<OpOutput<()>> {
self.transact(Op::SetCurrentDeck, |col| col.set_current_deck_inner(deck))
}
/// Fetch the current deck, falling back to the default if the previously
/// selected deck is invalid.
pub fn get_current_deck(&mut self) -> Result<Arc<Deck>> {
if let Some(deck) = self.get_deck(self.get_current_deck_id())? {
return Ok(deck);
}
self.get_deck(DeckId(1))?.ok_or(AnkiError::NotFound)
}
}
impl Collection {
/// The returned id may reference a deck that does not exist;
/// prefer using get_current_deck() instead.
pub(crate) fn get_current_deck_id(&self) -> DeckId {
self.get_config_optional(ConfigKey::CurrentDeckId)
.unwrap_or(DeckId(1))
}
fn set_current_deck_inner(&mut self, deck: DeckId) -> Result<()> {
if self.set_current_deck_id(deck)? {
self.state.card_queues = None;
}
Ok(())
}
fn set_current_deck_id(&mut self, did: DeckId) -> Result<bool> {
self.set_config(ConfigKey::CurrentDeckId, &did)
}
}

View file

@ -2,6 +2,7 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
mod counts; mod counts;
mod current;
mod filtered; mod filtered;
mod schema11; mod schema11;
mod tree; mod tree;

View file

@ -23,7 +23,7 @@ pub enum Op {
RenameTag, RenameTag,
ReparentTag, ReparentTag,
ScheduleAsNew, ScheduleAsNew,
SetDeck, SetCardDeck,
SetDueDate, SetDueDate,
SetFlag, SetFlag,
SortCards, SortCards,
@ -34,6 +34,7 @@ pub enum Op {
UpdateNote, UpdateNote,
UpdatePreferences, UpdatePreferences,
UpdateTag, UpdateTag,
SetCurrentDeck,
} }
impl Op { impl Op {
@ -55,7 +56,7 @@ impl Op {
Op::UpdateNote => tr.undo_update_note(), Op::UpdateNote => tr.undo_update_note(),
Op::UpdatePreferences => tr.preferences_preferences(), Op::UpdatePreferences => tr.preferences_preferences(),
Op::UpdateTag => tr.undo_update_tag(), Op::UpdateTag => tr.undo_update_tag(),
Op::SetDeck => tr.browsing_change_deck(), Op::SetCardDeck => tr.browsing_change_deck(),
Op::SetFlag => tr.undo_set_flag(), Op::SetFlag => tr.undo_set_flag(),
Op::FindAndReplace => tr.browsing_find_and_replace(), Op::FindAndReplace => tr.browsing_find_and_replace(),
Op::ClearUnusedTags => tr.browsing_clear_unused_tags(), Op::ClearUnusedTags => tr.browsing_clear_unused_tags(),
@ -68,6 +69,7 @@ impl Op {
Op::RebuildFilteredDeck => tr.undo_build_filtered_deck(), Op::RebuildFilteredDeck => tr.undo_build_filtered_deck(),
Op::EmptyFilteredDeck => tr.studying_empty(), Op::EmptyFilteredDeck => tr.studying_empty(),
Op::ExpandCollapse => tr.undo_expand_collapse(), Op::ExpandCollapse => tr.undo_expand_collapse(),
Op::SetCurrentDeck => tr.browsing_change_deck(),
} }
.into() .into()
} }
@ -80,7 +82,7 @@ pub struct StateChanges {
pub deck: bool, pub deck: bool,
pub tag: bool, pub tag: bool,
pub notetype: bool, pub notetype: bool,
pub preference: bool, pub config: bool,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -134,7 +136,12 @@ impl OpChanges {
pub fn requires_study_queue_rebuild(&self) -> bool { pub fn requires_study_queue_rebuild(&self) -> bool {
let c = &self.changes; let c = &self.changes;
!matches!(self.op, Op::AnswerCard | Op::ExpandCollapse) if self.op == Op::AnswerCard {
&& (c.card || c.deck || c.preference) return false;
}
c.card
|| (c.deck && self.op != Op::ExpandCollapse)
|| (c.config && matches!(self.op, Op::SetCurrentDeck))
} }
} }

View file

@ -126,7 +126,7 @@ impl UndoManager {
UndoableChange::Tag(_) => changes.tag = true, UndoableChange::Tag(_) => changes.tag = true,
UndoableChange::Revlog(_) => {} UndoableChange::Revlog(_) => {}
UndoableChange::Queue(_) => {} UndoableChange::Queue(_) => {}
UndoableChange::Config(_) => {} // fixme: preferences? UndoableChange::Config(_) => changes.config = true,
} }
} }