f3e81c8a95
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.
179 lines
6.8 KiB
Python
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()
|