anki/qt/aqt/deckconf.py
Damien Elmes b9251290ca run pyupgrade over codebase [python upgrade required]
This adds Python 3.9 and 3.10 typing syntax to files that import
attributions from __future___. Python 3.9 should be able to cope with
the 3.10 syntax, but Python 3.8 will no longer work.

On Windows/Mac, install the latest Python 3.9 version from python.org.
There are currently no orjson wheels for Python 3.10 on Windows/Mac,
which will break the build unless you have Rust installed separately.

On Linux, modern distros should have Python 3.9 available already. If
you're on an older distro, you'll need to build Python from source first.
2021-10-04 15:05:48 +10:00

332 lines
11 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 operator import itemgetter
from typing import Any
from PyQt5.QtWidgets import QLineEdit
import aqt
from anki.consts import NEW_CARDS_RANDOM
from anki.decks import DeckConfigDict
from anki.lang import without_unicode_isolation
from aqt import gui_hooks
from aqt.qt import *
from aqt.utils import (
HelpPage,
askUser,
disable_help_button,
getOnlyText,
openHelp,
restoreGeom,
saveGeom,
showInfo,
showWarning,
tooltip,
tr,
)
class DeckConf(QDialog):
def __init__(self, mw: aqt.AnkiQt, deck: dict) -> None:
QDialog.__init__(self, mw)
self.mw = mw
self.deck = deck
self.childDids = [d[1] for d in self.mw.col.decks.children(self.deck["id"])]
self._origNewOrder = None
self.form = aqt.forms.dconf.Ui_Dialog()
self.form.setupUi(self)
gui_hooks.deck_conf_did_setup_ui_form(self)
self.mw.checkpoint(tr.actions_options())
self.setupCombos()
self.setupConfs()
self.setWindowModality(Qt.WindowModal)
qconnect(
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.DECK_OPTIONS)
)
qconnect(self.form.confOpts.clicked, self.confOpts)
qconnect(
self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked,
self.onRestore,
)
self.setWindowTitle(
without_unicode_isolation(tr.actions_options_for(val=self.deck["name"]))
)
disable_help_button(self)
# qt doesn't size properly with altered fonts otherwise
restoreGeom(self, "deckconf", adjustSize=True)
gui_hooks.deck_conf_will_show(self)
self.show()
self.exec_()
saveGeom(self, "deckconf")
def setupCombos(self) -> None:
import anki.consts as cs
f = self.form
f.newOrder.addItems(list(cs.newCardOrderLabels(self.mw.col).values()))
qconnect(f.newOrder.currentIndexChanged, self.onNewOrderChanged)
# Conf list
######################################################################
def setupConfs(self) -> None:
qconnect(self.form.dconf.currentIndexChanged, self.onConfChange)
self.conf: DeckConfigDict | None = None
self.loadConfs()
def loadConfs(self) -> None:
current = self.deck["conf"]
self.confList = self.mw.col.decks.all_config()
self.confList.sort(key=itemgetter("name"))
startOn = 0
self.ignoreConfChange = True
self.form.dconf.clear()
for idx, conf in enumerate(self.confList):
self.form.dconf.addItem(conf["name"])
if str(conf["id"]) == str(current):
startOn = idx
self.ignoreConfChange = False
self.form.dconf.setCurrentIndex(startOn)
if self._origNewOrder is None:
self._origNewOrder = self.confList[startOn]["new"]["order"]
self.onConfChange(startOn)
def confOpts(self) -> None:
m = QMenu(self.mw)
a = m.addAction(tr.actions_add())
qconnect(a.triggered, self.addGroup)
a = m.addAction(tr.actions_delete())
qconnect(a.triggered, self.remGroup)
a = m.addAction(tr.actions_rename())
qconnect(a.triggered, self.renameGroup)
a = m.addAction(tr.scheduling_set_for_all_subdecks())
qconnect(a.triggered, self.setChildren)
if not self.childDids:
a.setEnabled(False)
m.exec_(QCursor.pos())
def onConfChange(self, idx: int) -> None:
if self.ignoreConfChange:
return
if self.conf:
self.saveConf()
conf = self.confList[idx]
self.deck["conf"] = conf["id"]
self.mw.col.decks.save(self.deck)
self.loadConf()
cnt = len(self.mw.col.decks.decks_using_config(conf))
if cnt > 1:
txt = tr.scheduling_your_changes_will_affect_multiple_decks()
else:
txt = ""
self.form.count.setText(txt)
def addGroup(self) -> None:
name = getOnlyText(tr.scheduling_new_options_group_name())
if not name:
return
# first, save currently entered data to current conf
self.saveConf()
# then clone the conf
id = self.mw.col.decks.add_config_returning_id(name, clone_from=self.conf)
gui_hooks.deck_conf_did_add_config(self, self.deck, self.conf, name, id)
# set the deck to the new conf
self.deck["conf"] = id
# then reload the conf list
self.loadConfs()
def remGroup(self) -> None:
if int(self.conf["id"]) == 1:
showInfo(tr.scheduling_the_default_configuration_cant_be_removed(), self)
else:
gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf)
self.mw.col.mod_schema(check=True)
self.mw.col.decks.remove_config(self.conf["id"])
self.conf = None
self.deck["conf"] = 1
self.loadConfs()
def renameGroup(self) -> None:
old = self.conf["name"]
name = getOnlyText(tr.actions_new_name(), default=old)
if not name or name == old:
return
gui_hooks.deck_conf_will_rename_config(self, self.deck, self.conf, name)
self.conf["name"] = name
self.saveConf()
self.loadConfs()
def setChildren(self) -> None:
if not askUser(tr.scheduling_set_all_decks_below_to(val=self.deck["name"])):
return
for did in self.childDids:
deck = self.mw.col.decks.get(did)
if deck["dyn"]:
continue
deck["conf"] = self.deck["conf"]
self.mw.col.decks.save(deck)
tooltip(tr.scheduling_deck_updated(count=len(self.childDids)))
# Loading
##################################################
def listToUser(self, l: list[Union[int, float]]) -> str:
def num_to_user(n: Union[int, float]) -> str:
if n == round(n):
return str(int(n))
else:
return str(n)
return " ".join(map(num_to_user, l))
def parentLimText(self, type: str = "new") -> str:
# top level?
if "::" not in self.deck["name"]:
return ""
lim = -1
for d in self.mw.col.decks.parents(self.deck["id"]):
c = self.mw.col.decks.config_dict_for_deck_id(d["id"])
x = c[type]["perDay"]
if lim == -1:
lim = x
else:
lim = min(x, lim)
return tr.scheduling_parent_limit(val=lim)
def loadConf(self) -> None:
self.conf = self.mw.col.decks.config_dict_for_deck_id(self.deck["id"])
# new
c = self.conf["new"]
f = self.form
f.lrnSteps.setText(self.listToUser(c["delays"]))
f.lrnGradInt.setValue(c["ints"][0])
f.lrnEasyInt.setValue(c["ints"][1])
f.lrnFactor.setValue(c["initialFactor"] / 10.0)
f.newOrder.setCurrentIndex(c["order"])
f.newPerDay.setValue(c["perDay"])
f.bury.setChecked(c.get("bury", True))
f.newplim.setText(self.parentLimText("new"))
# rev
c = self.conf["rev"]
f.revPerDay.setValue(c["perDay"])
f.easyBonus.setValue(c["ease4"] * 100)
f.fi1.setValue(c["ivlFct"] * 100)
f.maxIvl.setValue(c["maxIvl"])
f.revplim.setText(self.parentLimText("rev"))
f.buryRev.setChecked(c.get("bury", True))
f.hardFactor.setValue(int(c.get("hardFactor", 1.2) * 100))
if self.mw.col.sched_ver() == 1:
f.hardFactor.setVisible(False)
f.hardFactorLabel.setVisible(False)
# lapse
c = self.conf["lapse"]
f.lapSteps.setText(self.listToUser(c["delays"]))
f.lapMult.setValue(c["mult"] * 100)
f.lapMinInt.setValue(c["minInt"])
f.leechThreshold.setValue(c["leechFails"])
f.leechAction.setCurrentIndex(c["leechAction"])
# general
c = self.conf
f.maxTaken.setValue(c["maxTaken"])
f.showTimer.setChecked(c.get("timer", 0))
f.autoplaySounds.setChecked(c["autoplay"])
f.replayQuestion.setChecked(c.get("replayq", True))
gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf)
def onRestore(self) -> None:
self.mw.progress.start()
self.mw.col.decks.restore_to_default(self.conf)
self.mw.progress.finish()
self.loadConf()
# New order
##################################################
def onNewOrderChanged(self, new: bool) -> None:
old = self.conf["new"]["order"]
if old == new:
return
self.conf["new"]["order"] = new
self.mw.progress.start()
self.mw.col.sched.resortConf(self.conf)
self.mw.progress.finish()
# Saving
##################################################
def updateList(self, conf: Any, key: str, w: QLineEdit, minSize: int = 1) -> None:
items = str(w.text()).split(" ")
ret = []
for item in items:
if not item:
continue
try:
i = float(item)
assert i > 0
if i == int(i):
i = int(i)
ret.append(i)
except:
# invalid, don't update
showWarning(tr.scheduling_steps_must_be_numbers())
return
if len(ret) < minSize:
showWarning(tr.scheduling_at_least_one_step_is_required())
return
conf[key] = ret
def saveConf(self) -> None:
# new
c = self.conf["new"]
f = self.form
self.updateList(c, "delays", f.lrnSteps)
c["ints"][0] = f.lrnGradInt.value()
c["ints"][1] = f.lrnEasyInt.value()
c["initialFactor"] = f.lrnFactor.value() * 10
c["order"] = f.newOrder.currentIndex()
c["perDay"] = f.newPerDay.value()
c["bury"] = f.bury.isChecked()
if self._origNewOrder != c["order"]:
# order of current deck has changed, so have to resort
if c["order"] == NEW_CARDS_RANDOM:
self.mw.col.sched.randomizeCards(self.deck["id"])
else:
self.mw.col.sched.orderCards(self.deck["id"])
# rev
c = self.conf["rev"]
c["perDay"] = f.revPerDay.value()
c["ease4"] = f.easyBonus.value() / 100.0
c["ivlFct"] = f.fi1.value() / 100.0
c["maxIvl"] = f.maxIvl.value()
c["bury"] = f.buryRev.isChecked()
c["hardFactor"] = f.hardFactor.value() / 100.0
# lapse
c = self.conf["lapse"]
self.updateList(c, "delays", f.lapSteps, minSize=0)
c["mult"] = f.lapMult.value() / 100.0
c["minInt"] = f.lapMinInt.value()
c["leechFails"] = f.leechThreshold.value()
c["leechAction"] = f.leechAction.currentIndex()
# general
c = self.conf
c["maxTaken"] = f.maxTaken.value()
c["timer"] = f.showTimer.isChecked() and 1 or 0
c["autoplay"] = f.autoplaySounds.isChecked()
c["replayq"] = f.replayQuestion.isChecked()
gui_hooks.deck_conf_will_save_config(self, self.deck, self.conf)
self.mw.col.decks.save(self.deck)
self.mw.col.decks.save(self.conf)
def reject(self) -> None:
self.accept()
def accept(self) -> None:
self.saveConf()
self.mw.reset()
QDialog.accept(self)