remember last input for 'set due'; add string config; nest config types

This commit is contained in:
Damien Elmes 2021-02-08 14:10:05 +10:00
parent b56580a8c0
commit f434cff36f
12 changed files with 124 additions and 48 deletions

View file

@ -8,7 +8,8 @@ license = "AGPL-3.0-or-later"
members = ["rslib", "pylib/rsbridge"]
[lib]
name = "dummy"
# dummy top level for tooling
name = "anki"
path = "src/lib.rs"
[package.metadata.raze]

View file

@ -50,7 +50,7 @@ MediaSyncProgress = _pb.MediaSyncProgress
FullSyncProgress = _pb.FullSyncProgress
NormalSyncProgress = _pb.NormalSyncProgress
DatabaseCheckProgress = _pb.DatabaseCheckProgress
ConfigBool = _pb.ConfigBool
Config = _pb.Config
EmptyCardsReport = _pb.EmptyCardsReport
NoteWithEmptyCards = _pb.NoteWithEmptyCards
GraphPreferences = _pb.GraphPreferences
@ -586,13 +586,20 @@ class Collection:
"This is a debugging aid. Prefer .get_config() when you know the key you need."
return from_json_bytes(self._backend.get_all_config())
def get_config_bool(self, key: ConfigBool.Key.V) -> bool:
def get_config_bool(self, key: Config.Bool.Key.V) -> bool:
return self._backend.get_config_bool(key)
def set_config_bool(self, key: ConfigBool.Key.V, value: bool) -> None:
def set_config_bool(self, key: Config.Bool.Key.V, value: bool) -> None:
self.setMod()
self._backend.set_config_bool(key=key, value=value)
def get_config_string(self, key: Config.String.Key.V) -> str:
return self._backend.get_config_string(key)
def set_config_string(self, key: Config.String.Key.V, value: str) -> None:
self.setMod()
self._backend.set_config_string(key=key, value=value)
# Stats
##########################################################################

View file

@ -1,7 +1,7 @@
# coding: utf-8
import pytest
from anki.collection import BuiltinSort, ConfigBool
from anki.collection import BuiltinSort, Config
from anki.consts import *
from tests.shared import getEmptyCol, isNearCutoff
@ -121,7 +121,7 @@ def test_findCards():
col.flush()
assert col.findCards("", order=True)[-1] in latestCardIds
assert col.findCards("", order=True)[0] == firstCardId
col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, True)
col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, True)
col.flush()
assert col.findCards("", order=True)[0] in latestCardIds
assert (

View file

@ -5,7 +5,9 @@ ignore = forms,hooks_gen.py
[TYPECHECK]
ignored-modules=win32file,pywintypes,socket,win32pipe,winrt,pyaudio
ignored-classes=SearchTerm,ConfigBool
ignored-classes=
SearchTerm,
Config,
[REPORTS]
output-format=colorized

View file

@ -13,7 +13,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union,
import aqt
import aqt.forms
from anki.cards import Card
from anki.collection import Collection, ConfigBool, SearchTerm
from anki.collection import Collection, Config, SearchTerm
from anki.consts import *
from anki.errors import InvalidInput
from anki.lang import without_unicode_isolation
@ -852,13 +852,13 @@ QTableView {{ gridline-color: {grid} }}
# default to descending for non-text fields
if type == "noteFld":
ord = not ord
self.col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, ord)
self.col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, ord)
self.col.setMod()
self.col.save()
self.search()
else:
if self.col.get_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS) != ord:
self.col.set_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS, ord)
if self.col.get_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS) != ord:
self.col.set_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS, ord)
self.col.setMod()
self.col.save()
self.model.reverse()
@ -871,7 +871,7 @@ QTableView {{ gridline-color: {grid} }}
hh.setSortIndicatorShown(False)
return
idx = self.model.activeCols.index(type)
if self.col.get_config_bool(ConfigBool.BROWSER_SORT_BACKWARDS):
if self.col.get_config_bool(Config.Bool.BROWSER_SORT_BACKWARDS):
ord = Qt.DescendingOrder
else:
ord = Qt.AscendingOrder
@ -1399,7 +1399,7 @@ where id in %s"""
mw=self.mw,
parent=self,
card_ids=self.selectedCards(),
default="0",
default_key=Config.String.SET_DUE_BROWSER,
on_done=self._after_schedule,
)
)

View file

@ -7,7 +7,7 @@ import time
from typing import Any, Callable, Optional, Tuple, Union
from anki.cards import Card
from anki.collection import ConfigBool
from anki.collection import Config
from aqt import AnkiQt, gui_hooks
from aqt.qt import (
QAbstractItemView,
@ -94,7 +94,7 @@ class Previewer(QDialog):
both_sides_button.setToolTip(tr(TR.ACTIONS_SHORTCUT_KEY, val="B"))
self.bbox.addButton(both_sides_button, QDialogButtonBox.ActionRole)
self._show_both_sides = self.mw.col.get_config_bool(
ConfigBool.PREVIEW_BOTH_SIDES
Config.Bool.PREVIEW_BOTH_SIDES
)
both_sides_button.setChecked(self._show_both_sides)
qconnect(both_sides_button.toggled, self._on_show_both_sides)
@ -221,7 +221,7 @@ class Previewer(QDialog):
def _on_show_both_sides(self, toggle: bool) -> None:
self._show_both_sides = toggle
self.mw.col.set_config_bool(ConfigBool.PREVIEW_BOTH_SIDES, toggle)
self.mw.col.set_config_bool(Config.Bool.PREVIEW_BOTH_SIDES, toggle)
self.mw.col.setMod()
if self._state == "answer" and not toggle:
self._state = "question"

View file

@ -14,6 +14,7 @@ from PyQt5.QtCore import Qt
from anki import hooks
from anki.cards import Card
from anki.collection import Config
from anki.utils import stripHTML
from aqt import AnkiQt, gui_hooks
from aqt.profiles import VideoDriver
@ -809,7 +810,7 @@ time = %(time)d;
mw=self.mw,
parent=self.mw,
card_ids=[self.card.id],
default="1",
default_key=Config.String.SET_DUE_REVIEWER,
on_done=self.mw.reset,
)

View file

@ -7,6 +7,7 @@ from concurrent.futures import Future
from typing import List
import aqt
from anki.collection import Config
from anki.errors import InvalidInput
from anki.lang import TR
from aqt.qt import *
@ -18,12 +19,14 @@ def set_due_date_dialog(
mw: aqt.AnkiQt,
parent: QDialog,
card_ids: List[int],
default: str,
default_key: Config.String.Key.V,
on_done: Callable[[], None],
) -> None:
if not card_ids:
return
default = mw.col.get_config_string(default_key)
(days, success) = getText(
prompt=tr(TR.SCHEDULING_SET_DUE_DATE_PROMPT, cards=len(card_ids)),
parent=parent,
@ -33,7 +36,12 @@ def set_due_date_dialog(
if not success or not days.strip():
return
def on_done_wrapper(fut: Future) -> None:
def set_due() -> None:
mw.col.sched.set_due_date(card_ids, days)
if days != default:
mw.col.set_config_string(default_key, days)
def after_set(fut: Future) -> None:
try:
fut.result()
except Exception as e:
@ -53,9 +61,7 @@ def set_due_date_dialog(
on_done()
mw.checkpoint(tr(TR.ACTIONS_SET_DUE_DATE))
mw.taskman.with_progress(
lambda: mw.col.sched.set_due_date(card_ids, days), on_done_wrapper
)
mw.taskman.with_progress(set_due, after_set)
def forget_cards(

View file

@ -9,7 +9,7 @@ from enum import Enum, auto
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, cast
import aqt
from anki.collection import ConfigBool, SearchTerm
from anki.collection import Config, SearchTerm
from anki.decks import DeckTreeNode
from anki.errors import DeckRenameError, InvalidInput
from anki.tags import TagTreeNode
@ -526,7 +526,7 @@ class SidebarTreeView(QTreeView):
root: SidebarItem,
name: TR.V,
icon: Union[str, ColoredIcon],
collapse_key: ConfigBool.Key.V,
collapse_key: Config.Bool.Key.V,
type: Optional[SidebarItemType] = None,
) -> SidebarItem:
def update(expanded: bool) -> None:
@ -557,7 +557,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_SAVED_SEARCHES,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_SAVED_SEARCHES,
collapse_key=Config.Bool.COLLAPSE_SAVED_SEARCHES,
type=SidebarItemType.SAVED_SEARCH_ROOT,
)
@ -584,7 +584,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_RECENT,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_RECENT,
collapse_key=Config.Bool.COLLAPSE_RECENT,
type=SidebarItemType.FLAG_ROOT,
)
type = SidebarItemType.FLAG
@ -646,7 +646,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_CARD_STATE,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_CARD_STATE,
collapse_key=Config.Bool.COLLAPSE_CARD_STATE,
type=SidebarItemType.CARD_STATE_ROOT,
)
type = SidebarItemType.CARD_STATE
@ -693,7 +693,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_FLAGS,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_FLAGS,
collapse_key=Config.Bool.COLLAPSE_FLAGS,
type=SidebarItemType.FLAG_ROOT,
)
type = SidebarItemType.FLAG
@ -771,7 +771,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_TAGS,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_TAGS,
collapse_key=Config.Bool.COLLAPSE_TAGS,
type=SidebarItemType.TAG_ROOT,
)
render(root, tree.children)
@ -810,7 +810,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_DECKS,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_DECKS,
collapse_key=Config.Bool.COLLAPSE_DECKS,
type=SidebarItemType.DECK_ROOT,
)
render(root, tree.children)
@ -824,7 +824,7 @@ class SidebarTreeView(QTreeView):
root=root,
name=TR.BROWSING_SIDEBAR_NOTETYPES,
icon=icon,
collapse_key=ConfigBool.COLLAPSE_NOTETYPES,
collapse_key=Config.Bool.COLLAPSE_NOTETYPES,
type=SidebarItemType.NOTETYPE_ROOT,
)

View file

@ -228,8 +228,10 @@ service BackendService {
rpc SetConfigJson(SetConfigJsonIn) returns (Empty);
rpc RemoveConfig(String) returns (Empty);
rpc GetAllConfig(Empty) returns (Json);
rpc GetConfigBool(ConfigBool) returns (Bool);
rpc GetConfigBool(Config.Bool) returns (Bool);
rpc SetConfigBool(SetConfigBoolIn) returns (Empty);
rpc GetConfigString(Config.String) returns (String);
rpc SetConfigString(SetConfigStringIn) returns (Empty);
// preferences
@ -1216,7 +1218,8 @@ message SetDeckIn {
int64 deck_id = 2;
}
message ConfigBool {
message Config {
message Bool {
enum Key {
BROWSER_SORT_BACKWARDS = 0;
PREVIEW_BOTH_SIDES = 1;
@ -1229,9 +1232,23 @@ message ConfigBool {
COLLAPSE_FLAGS = 8;
}
Key key = 1;
}
message String {
enum Key {
SET_DUE_BROWSER = 0;
SET_DUE_REVIEWER = 1;
}
Key key = 1;
}
}
message SetConfigBoolIn {
ConfigBool.Key key = 1;
Config.Bool.Key key = 1;
bool value = 2;
}
message SetConfigStringIn {
Config.String.Key key = 1;
string value = 2;
}

View file

@ -1493,7 +1493,7 @@ impl BackendService for Backend {
.map(Into::into)
}
fn get_config_bool(&self, input: pb::ConfigBool) -> BackendResult<pb::Bool> {
fn get_config_bool(&self, input: pb::config::Bool) -> BackendResult<pb::Bool> {
self.with_col(|col| {
Ok(pb::Bool {
val: col.get_bool(input),
@ -1505,6 +1505,19 @@ impl BackendService for Backend {
self.with_col(|col| col.transact(None, |col| col.set_bool(input)))
.map(Into::into)
}
fn get_config_string(&self, input: pb::config::String) -> BackendResult<pb::String> {
self.with_col(|col| {
Ok(pb::String {
val: col.get_string(input),
})
})
}
fn set_config_string(&self, input: pb::SetConfigStringIn) -> BackendResult<pb::Empty> {
self.with_col(|col| col.transact(None, |col| col.set_string(input)))
.map(Into::into)
}
}
impl Backend {

View file

@ -5,7 +5,8 @@ use crate::{
backend_proto as pb, collection::Collection, decks::DeckID, err::Result, notetype::NoteTypeID,
timestamp::TimestampSecs,
};
use pb::config_bool::Key as BoolKey;
use pb::config::bool::Key as BoolKey;
use pb::config::string::Key as StringKey;
use serde::{de::DeserializeOwned, Serialize};
use serde_aux::field_attributes::deserialize_bool_from_anything;
use serde_derive::Deserialize;
@ -63,6 +64,8 @@ pub(crate) enum ConfigKey {
PreviewBothSides,
Rollover,
SchedulerVersion,
SetDueBrowser,
SetDueReviewer,
ShowDayLearningCardsFirst,
ShowIntervalsAboveAnswerButtons,
ShowRemainingDueCountsInStudy,
@ -102,6 +105,8 @@ impl From<ConfigKey> for &'static str {
ConfigKey::PreviewBothSides => "previewBothSides",
ConfigKey::Rollover => "rollover",
ConfigKey::SchedulerVersion => "schedVer",
ConfigKey::SetDueBrowser => "setDueBrowser",
ConfigKey::SetDueReviewer => "setDueReviewer",
ConfigKey::ShowDayLearningCardsFirst => "dayLearnFirst",
ConfigKey::ShowIntervalsAboveAnswerButtons => "estTimes",
ConfigKey::ShowRemainingDueCountsInStudy => "dueCounts",
@ -125,6 +130,15 @@ impl From<BoolKey> for ConfigKey {
}
}
impl From<StringKey> for ConfigKey {
fn from(key: StringKey) -> Self {
match key {
StringKey::SetDueBrowser => ConfigKey::SetDueBrowser,
StringKey::SetDueReviewer => ConfigKey::SetDueReviewer,
}
}
}
/// This is a workaround for old clients that used ints to represent boolean
/// values. For new config items, prefer using a bool directly.
#[derive(Deserialize, Default)]
@ -345,7 +359,7 @@ impl Collection {
}
#[allow(clippy::match_single_binding)]
pub(crate) fn get_bool(&self, config: pb::ConfigBool) -> bool {
pub(crate) fn get_bool(&self, config: pb::config::Bool) -> bool {
match config.key() {
// all options default to false at the moment
other => self.get_config_default(ConfigKey::from(other)),
@ -355,6 +369,21 @@ impl Collection {
pub(crate) fn set_bool(&self, input: pb::SetConfigBoolIn) -> Result<()> {
self.set_config(ConfigKey::from(input.key()), &input.value)
}
pub(crate) fn get_string(&self, config: pb::config::String) -> String {
let key = config.key();
let default = match key {
StringKey::SetDueBrowser => "0",
StringKey::SetDueReviewer => "1",
// other => "",
};
self.get_config_optional(ConfigKey::from(key))
.unwrap_or_else(|| default.to_string())
}
pub(crate) fn set_string(&self, input: pb::SetConfigStringIn) -> Result<()> {
self.set_config(ConfigKey::from(input.key()), &input.value)
}
}
#[derive(Deserialize, PartialEq, Debug, Clone, Copy)]