anki/qt/aqt/customstudy.py
Damien Elmes f3e81c8a95 Move custom study tag and limit gathering+saving into the backend
Ideally this would have been in beta 6 :-) No add-ons appear to be
using customstudy.py/taglimit.py though, so it should hopefully not be
disruptive.

In the earlier custom study changes, we didn't get around to addressing
issue #1136. Now instead of trying to determine the maximum increase
to allow (which doesn't work correctly with nested decks), we just
present the total available to the user again, and let them decide. There's
plenty of room for improvement here still, but further work here might
be better done once we look into decoupling deck limits from deck presets.

Tags and available cards are fetched prior to showing the dialog now,
and will show a progress dialog if things take a while.

Tags are stored in an aux var now, so they don't inflate the deck
object size.
2022-03-10 16:23:03 +10:00

179 lines
6.8 KiB
Python

# Copyright: Ankitects Pty Ltd and contributors
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from typing import Tuple
import aqt
import aqt.forms
import aqt.operations
from anki.collection import Collection
from anki.consts import *
from anki.decks import DeckId
from anki.scheduler import CustomStudyRequest
from anki.scheduler.base import CustomStudyDefaults
from aqt.operations import QueryOp
from aqt.operations.scheduling import custom_study
from aqt.qt import *
from aqt.taglimit import TagLimit
from aqt.utils import disable_help_button, tr
RADIO_NEW = 1
RADIO_REV = 2
RADIO_FORGOT = 3
RADIO_AHEAD = 4
RADIO_PREVIEW = 5
RADIO_CRAM = 6
TYPE_NEW = 0
TYPE_DUE = 1
TYPE_REVIEW = 2
TYPE_ALL = 3
class CustomStudy(QDialog):
@staticmethod
def fetch_data_and_show(mw: aqt.AnkiQt) -> None:
def fetch_data(
col: Collection,
) -> Tuple[DeckId, CustomStudyDefaults]:
deck_id = mw.col.decks.get_current_id()
defaults = col.sched.custom_study_defaults(deck_id)
return (deck_id, defaults)
def show_dialog(data: Tuple[DeckId, CustomStudyDefaults]) -> None:
deck_id, defaults = data
CustomStudy(mw=mw, deck_id=deck_id, defaults=defaults)
QueryOp(
parent=mw, op=fetch_data, success=show_dialog
).with_progress().run_in_background()
def __init__(
self,
mw: aqt.AnkiQt,
deck_id: DeckId,
defaults: CustomStudyDefaults,
) -> None:
"Don't call this directly; use CustomStudy.fetch_data_and_show()."
QDialog.__init__(self, mw)
self.mw = mw
self.deck_id = deck_id
self.defaults = defaults
self.form = aqt.forms.customstudy.Ui_Dialog()
self.form.setupUi(self)
disable_help_button(self)
self.setupSignals()
self.form.radioNew.click()
self.open()
def setupSignals(self) -> None:
f = self.form
qconnect(f.radioNew.clicked, lambda: self.onRadioChange(RADIO_NEW))
qconnect(f.radioRev.clicked, lambda: self.onRadioChange(RADIO_REV))
qconnect(f.radioForgot.clicked, lambda: self.onRadioChange(RADIO_FORGOT))
qconnect(f.radioAhead.clicked, lambda: self.onRadioChange(RADIO_AHEAD))
qconnect(f.radioPreview.clicked, lambda: self.onRadioChange(RADIO_PREVIEW))
qconnect(f.radioCram.clicked, lambda: self.onRadioChange(RADIO_CRAM))
def onRadioChange(self, idx: int) -> None:
form = self.form
min_spinner_value = 1
max_spinner_value = DYN_MAX_SIZE
current_spinner_value = 1
text_after_spinner = tr.custom_study_cards()
title_text = ""
show_cram_type = False
ok = tr.custom_study_ok()
if idx == RADIO_NEW:
title_text = tr.custom_study_available_new_cards(
count=self.defaults.available_new
)
text_before_spinner = tr.custom_study_increase_todays_new_card_limit_by()
current_spinner_value = self.defaults.extend_new
min_spinner_value = -DYN_MAX_SIZE
elif idx == RADIO_REV:
title_text = tr.custom_study_available_review_cards(
count=self.defaults.available_review
)
text_before_spinner = tr.custom_study_increase_todays_review_limit_by()
current_spinner_value = self.defaults.extend_review
min_spinner_value = -DYN_MAX_SIZE
elif idx == RADIO_FORGOT:
text_before_spinner = tr.custom_study_review_cards_forgotten_in_last()
text_after_spinner = tr.scheduling_days()
max_spinner_value = 30
elif idx == RADIO_AHEAD:
text_before_spinner = tr.custom_study_review_ahead_by()
text_after_spinner = tr.scheduling_days()
elif idx == RADIO_PREVIEW:
text_before_spinner = tr.custom_study_preview_new_cards_added_in_the()
text_after_spinner = tr.scheduling_days()
current_spinner_value = 1
elif idx == RADIO_CRAM:
text_before_spinner = tr.custom_study_select()
text_after_spinner = tr.custom_study_cards_from_the_deck()
ok = tr.custom_study_choose_tags()
current_spinner_value = 100
show_cram_type = True
form.spin.setVisible(True)
form.cardType.setVisible(show_cram_type)
form.title.setText(title_text)
form.title.setVisible(not not title_text)
form.spin.setMinimum(min_spinner_value)
form.spin.setMaximum(max_spinner_value)
if max_spinner_value > 0:
form.spin.setEnabled(True)
else:
form.spin.setEnabled(False)
form.spin.setValue(current_spinner_value)
form.preSpin.setText(text_before_spinner)
form.postSpin.setText(text_after_spinner)
form.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(ok)
self.radioIdx = idx
def accept(self) -> None:
request = CustomStudyRequest(deck_id=self.deck_id)
if self.radioIdx == RADIO_NEW:
request.new_limit_delta = self.form.spin.value()
elif self.radioIdx == RADIO_REV:
request.review_limit_delta = self.form.spin.value()
elif self.radioIdx == RADIO_FORGOT:
request.forgot_days = self.form.spin.value()
elif self.radioIdx == RADIO_AHEAD:
request.review_ahead_days = self.form.spin.value()
elif self.radioIdx == RADIO_PREVIEW:
request.preview_days = self.form.spin.value()
else:
request.cram.card_limit = self.form.spin.value()
cram_type = self.form.cardType.currentRow()
if cram_type == TYPE_NEW:
request.cram.kind = CustomStudyRequest.Cram.CRAM_KIND_NEW
elif cram_type == TYPE_DUE:
request.cram.kind = CustomStudyRequest.Cram.CRAM_KIND_DUE
elif cram_type == TYPE_REVIEW:
request.cram.kind = CustomStudyRequest.Cram.CRAM_KIND_REVIEW
else:
request.cram.kind = CustomStudyRequest.Cram.CRAM_KIND_ALL
def on_done(include: list[str], exclude: list[str]) -> None:
request.cram.tags_to_include.extend(include)
request.cram.tags_to_exclude.extend(exclude)
self._create_and_close(request)
# continues in background
TagLimit(self, self.defaults.tags, on_done)
return
# other cases are synchronous
self._create_and_close(request)
def _create_and_close(self, request: CustomStudyRequest) -> None:
# keep open on failure, as the cause was most likely an empty search
# result, which the user can remedy
custom_study(parent=self, request=request).success(
lambda _: QDialog.accept(self)
).run_in_background()