9dc3cf216a
* PEP8 dbproxy.py * PEP8 errors.py * PEP8 httpclient.py * PEP8 lang.py * PEP8 latex.py * Add decorator to deprectate key words * Make replacement for deprecated attribute optional * Use new helper `_print_replacement_warning()` * PEP8 media.py * PEP8 rsbackend.py * PEP8 sound.py * PEP8 stdmodels.py * PEP8 storage.py * PEP8 sync.py * PEP8 tags.py * PEP8 template.py * PEP8 types.py * Fix DeprecatedNamesMixinForModule The class methods need to be overridden with instance methods, so every module has its own dicts. * Use `# pylint: disable=invalid-name` instead of id * PEP8 utils.py * Only decorate `__getattr__` with `@no_type_check` * Fix mypy issue with snakecase Importing it from `anki._vendor` raises attribute errors. * Format * Remove inheritance of DeprecatedNamesMixin There's almost no shared code now and overriding classmethods with instance methods raises mypy issues. * Fix traceback frames of deprecation warnings * remove fn/TimedLog (dae) Neither Anki nor add-ons appear to have been using it * fix some issues with stringcase use (dae) - the wheel was depending on the PyPI version instead of our vendored version - _vendor:stringcase should not have been listed in the anki py_library. We already include the sources in py_srcs, and need to refer to them directly. By listing _vendor:stringcase as well, we were making a top-level stringcase library available, which would have only worked for distributing because the wheel definition was also incorrect. - mypy errors are what caused me to mistakenly add the above - they were because the type: ignore at the top of stringcase.py was causing mypy to completely ignore the file, so it was not aware of any attributes it contained.
297 lines
11 KiB
Python
297 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 typing import Any, cast
|
|
|
|
import anki.lang
|
|
import aqt
|
|
from anki.collection import OpChanges
|
|
from anki.consts import new_card_scheduling_labels
|
|
from aqt import AnkiQt
|
|
from aqt.operations.collection import set_preferences
|
|
from aqt.profiles import VideoDriver
|
|
from aqt.qt import *
|
|
from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr
|
|
|
|
|
|
class Preferences(QDialog):
|
|
def __init__(self, mw: AnkiQt) -> None:
|
|
QDialog.__init__(self, mw, Qt.WindowType.Window)
|
|
self.mw = mw
|
|
self.prof = self.mw.pm.profile
|
|
self.form = aqt.forms.preferences.Ui_Preferences()
|
|
self.form.setupUi(self)
|
|
disable_help_button(self)
|
|
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Help).setAutoDefault(
|
|
False
|
|
)
|
|
self.form.buttonBox.button(
|
|
QDialogButtonBox.StandardButton.Close
|
|
).setAutoDefault(False)
|
|
qconnect(
|
|
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.PREFERENCES)
|
|
)
|
|
self.silentlyClose = True
|
|
self.setup_collection()
|
|
self.setup_profile()
|
|
self.setup_global()
|
|
self.show()
|
|
|
|
def accept(self) -> None:
|
|
# avoid exception if main window is already closed
|
|
if not self.mw.col:
|
|
return
|
|
|
|
def after_collection_update() -> None:
|
|
self.update_profile()
|
|
self.update_global()
|
|
self.mw.pm.save()
|
|
self.done(0)
|
|
aqt.dialogs.markClosed("Preferences")
|
|
|
|
self.update_collection(after_collection_update)
|
|
|
|
def reject(self) -> None:
|
|
self.accept()
|
|
|
|
# Preferences stored in the collection
|
|
######################################################################
|
|
|
|
def setup_collection(self) -> None:
|
|
self.prefs = self.mw.col.get_preferences()
|
|
|
|
form = self.form
|
|
|
|
scheduling = self.prefs.scheduling
|
|
|
|
version = scheduling.scheduler_version
|
|
form.dayLearnFirst.setVisible(version == 2)
|
|
form.legacy_timezone.setVisible(version >= 2)
|
|
form.newSpread.setVisible(version < 3)
|
|
form.sched2021.setVisible(version >= 2)
|
|
|
|
form.lrnCutoff.setValue(int(scheduling.learn_ahead_secs / 60.0))
|
|
form.newSpread.addItems(list(new_card_scheduling_labels(self.mw.col).values()))
|
|
form.newSpread.setCurrentIndex(scheduling.new_review_mix)
|
|
form.dayLearnFirst.setChecked(scheduling.day_learn_first)
|
|
form.dayOffset.setValue(scheduling.rollover)
|
|
form.legacy_timezone.setChecked(not scheduling.new_timezone)
|
|
form.sched2021.setChecked(version == 3)
|
|
|
|
reviewing = self.prefs.reviewing
|
|
form.timeLimit.setValue(int(reviewing.time_limit_secs / 60.0))
|
|
form.showEstimates.setChecked(reviewing.show_intervals_on_buttons)
|
|
form.showProgress.setChecked(reviewing.show_remaining_due_counts)
|
|
form.showPlayButtons.setChecked(not reviewing.hide_audio_play_buttons)
|
|
form.interrupt_audio.setChecked(reviewing.interrupt_audio_when_answering)
|
|
|
|
editing = self.prefs.editing
|
|
form.useCurrent.setCurrentIndex(
|
|
0 if editing.adding_defaults_to_current_deck else 1
|
|
)
|
|
form.paste_strips_formatting.setChecked(editing.paste_strips_formatting)
|
|
form.pastePNG.setChecked(editing.paste_images_as_png)
|
|
form.default_search_text.setText(editing.default_search_text)
|
|
|
|
def update_collection(self, on_done: Callable[[], None]) -> None:
|
|
form = self.form
|
|
|
|
scheduling = self.prefs.scheduling
|
|
scheduling.new_review_mix = cast(Any, form.newSpread.currentIndex())
|
|
scheduling.learn_ahead_secs = form.lrnCutoff.value() * 60
|
|
scheduling.day_learn_first = form.dayLearnFirst.isChecked()
|
|
scheduling.rollover = form.dayOffset.value()
|
|
scheduling.new_timezone = not form.legacy_timezone.isChecked()
|
|
|
|
reviewing = self.prefs.reviewing
|
|
reviewing.show_remaining_due_counts = form.showProgress.isChecked()
|
|
reviewing.show_intervals_on_buttons = form.showEstimates.isChecked()
|
|
reviewing.time_limit_secs = form.timeLimit.value() * 60
|
|
reviewing.hide_audio_play_buttons = not self.form.showPlayButtons.isChecked()
|
|
reviewing.interrupt_audio_when_answering = self.form.interrupt_audio.isChecked()
|
|
|
|
editing = self.prefs.editing
|
|
editing.adding_defaults_to_current_deck = not form.useCurrent.currentIndex()
|
|
editing.paste_images_as_png = self.form.pastePNG.isChecked()
|
|
editing.paste_strips_formatting = self.form.paste_strips_formatting.isChecked()
|
|
editing.default_search_text = self.form.default_search_text.text()
|
|
|
|
def after_prefs_update(changes: OpChanges) -> None:
|
|
self.mw.apply_collection_options()
|
|
if scheduling.scheduler_version > 1:
|
|
want_v3 = form.sched2021.isChecked()
|
|
if self.mw.col.v3_scheduler() != want_v3:
|
|
self.mw.col.set_v3_scheduler(want_v3)
|
|
|
|
on_done()
|
|
|
|
set_preferences(parent=self, preferences=self.prefs).success(
|
|
after_prefs_update
|
|
).run_in_background()
|
|
|
|
# Preferences stored in the profile
|
|
######################################################################
|
|
|
|
def setup_profile(self) -> None:
|
|
"Setup options stored in the user profile."
|
|
self.setup_network()
|
|
self.setup_backup()
|
|
|
|
def update_profile(self) -> None:
|
|
self.update_network()
|
|
self.update_backup()
|
|
|
|
# Profile: network
|
|
######################################################################
|
|
|
|
def setup_network(self) -> None:
|
|
self.form.media_log.setText(tr.sync_media_log_button())
|
|
qconnect(self.form.media_log.clicked, self.on_media_log)
|
|
self.form.syncOnProgramOpen.setChecked(self.prof["autoSync"])
|
|
self.form.syncMedia.setChecked(self.prof["syncMedia"])
|
|
self.form.autoSyncMedia.setChecked(self.mw.pm.auto_sync_media_minutes() != 0)
|
|
if not self.prof["syncKey"]:
|
|
self._hide_sync_auth_settings()
|
|
else:
|
|
self.form.syncUser.setText(self.prof.get("syncUser", ""))
|
|
qconnect(self.form.syncDeauth.clicked, self.sync_logout)
|
|
self.form.syncDeauth.setText(tr.sync_log_out_button())
|
|
|
|
def on_media_log(self) -> None:
|
|
self.mw.media_syncer.show_sync_log()
|
|
|
|
def _hide_sync_auth_settings(self) -> None:
|
|
self.form.syncDeauth.setVisible(False)
|
|
self.form.syncUser.setText("")
|
|
self.form.syncLabel.setText(
|
|
tr.preferences_synchronizationnot_currently_enabled_click_the_sync()
|
|
)
|
|
|
|
def sync_logout(self) -> None:
|
|
if self.mw.media_syncer.is_syncing():
|
|
showWarning("Can't log out while sync in progress.")
|
|
return
|
|
self.prof["syncKey"] = None
|
|
self.mw.col.media.force_resync()
|
|
self._hide_sync_auth_settings()
|
|
|
|
def update_network(self) -> None:
|
|
self.prof["autoSync"] = self.form.syncOnProgramOpen.isChecked()
|
|
self.prof["syncMedia"] = self.form.syncMedia.isChecked()
|
|
self.mw.pm.set_auto_sync_media_minutes(
|
|
self.form.autoSyncMedia.isChecked() and 15 or 0
|
|
)
|
|
if self.form.fullSync.isChecked():
|
|
self.mw.col.mod_schema(check=False)
|
|
|
|
# Profile: backup
|
|
######################################################################
|
|
|
|
def setup_backup(self) -> None:
|
|
self.form.numBackups.setValue(self.prof["numBackups"])
|
|
|
|
def update_backup(self) -> None:
|
|
self.prof["numBackups"] = self.form.numBackups.value()
|
|
|
|
# Global preferences
|
|
######################################################################
|
|
|
|
def setup_global(self) -> None:
|
|
"Setup options global to all profiles."
|
|
self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100))
|
|
self.form.nightMode.setChecked(self.mw.pm.night_mode())
|
|
|
|
self.setup_language()
|
|
self.setup_video_driver()
|
|
|
|
self.setupOptions()
|
|
|
|
def update_global(self) -> None:
|
|
restart_required = False
|
|
|
|
self.update_video_driver()
|
|
|
|
newScale = self.form.uiScale.value() / 100
|
|
if newScale != self.mw.pm.uiScale():
|
|
self.mw.pm.setUiScale(newScale)
|
|
restart_required = True
|
|
|
|
if self.mw.pm.night_mode() != self.form.nightMode.isChecked():
|
|
self.mw.pm.set_night_mode(not self.mw.pm.night_mode())
|
|
restart_required = True
|
|
|
|
if restart_required:
|
|
showInfo(tr.preferences_changes_will_take_effect_when_you())
|
|
|
|
self.updateOptions()
|
|
|
|
# legacy - one of Henrik's add-ons is currently wrapping them
|
|
|
|
def setupOptions(self) -> None:
|
|
pass
|
|
|
|
def updateOptions(self) -> None:
|
|
pass
|
|
|
|
# Global: language
|
|
######################################################################
|
|
|
|
def setup_language(self) -> None:
|
|
f = self.form
|
|
f.lang.addItems([x[0] for x in anki.lang.langs])
|
|
f.lang.setCurrentIndex(self.current_lang_index())
|
|
qconnect(f.lang.currentIndexChanged, self.on_language_index_changed)
|
|
|
|
def current_lang_index(self) -> int:
|
|
codes = [x[1] for x in anki.lang.langs]
|
|
lang = anki.lang.current_lang
|
|
if lang in anki.lang.compatMap:
|
|
lang = anki.lang.compatMap[lang]
|
|
else:
|
|
lang = lang.replace("-", "_")
|
|
try:
|
|
return codes.index(lang)
|
|
except:
|
|
return codes.index("en_US")
|
|
|
|
def on_language_index_changed(self, idx: int) -> None:
|
|
code = anki.lang.langs[idx][1]
|
|
self.mw.pm.setLang(code)
|
|
showInfo(tr.preferences_please_restart_anki_to_complete_language(), parent=self)
|
|
|
|
# Global: video driver
|
|
######################################################################
|
|
|
|
def setup_video_driver(self) -> None:
|
|
self.video_drivers = VideoDriver.all_for_platform()
|
|
names = [
|
|
tr.preferences_video_driver(driver=video_driver_name_for_platform(d))
|
|
for d in self.video_drivers
|
|
]
|
|
self.form.video_driver.addItems(names)
|
|
self.form.video_driver.setCurrentIndex(
|
|
self.video_drivers.index(self.mw.pm.video_driver())
|
|
)
|
|
self.form.video_driver.setVisible(qtmajor == 5)
|
|
|
|
def update_video_driver(self) -> None:
|
|
new_driver = self.video_drivers[self.form.video_driver.currentIndex()]
|
|
if new_driver != self.mw.pm.video_driver():
|
|
self.mw.pm.set_video_driver(new_driver)
|
|
showInfo(tr.preferences_changes_will_take_effect_when_you())
|
|
|
|
|
|
def video_driver_name_for_platform(driver: VideoDriver) -> str:
|
|
if driver == VideoDriver.ANGLE:
|
|
return tr.preferences_video_driver_angle()
|
|
elif driver == VideoDriver.Software:
|
|
if isMac:
|
|
return tr.preferences_video_driver_software_mac()
|
|
else:
|
|
return tr.preferences_video_driver_software_other()
|
|
else:
|
|
if isMac:
|
|
return tr.preferences_video_driver_opengl_mac()
|
|
else:
|
|
return tr.preferences_video_driver_opengl_other()
|