82196753ec
In v3, it's more informative to show the count of child decks separately, since increasing the limit of the current deck does not increase the limits of child decks. When we rework the decks list in the future, a tooltip will hopefully provide an easier way for users to see where cards are available, and where limits are being applied. Closes #1868
193 lines
7.3 KiB
Python
193 lines
7.3 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
from __future__ import annotations
|
|
|
|
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 count_with_children(self, parent: int, children: int) -> str:
|
|
if children:
|
|
return f"{parent} {tr.custom_study_available_child_count(children)}"
|
|
else:
|
|
return str(parent)
|
|
|
|
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_2(
|
|
count_string=self.count_with_children(
|
|
self.defaults.available_new,
|
|
self.defaults.available_new_in_children,
|
|
),
|
|
)
|
|
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_2(
|
|
count_string=self.count_with_children(
|
|
self.defaults.available_review,
|
|
self.defaults.available_review_in_children,
|
|
),
|
|
)
|
|
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()
|