6b1dd9ee19
- moved 'default to current deck when adding' into prefs - move some profile options into the collection config, so they're undoable and will sync. There is (currently) no automatic migration from the old profile settings, meaning users will need to set the options again if they've customized them. - tidy up preferences.py - drop the deleteMedia option that was not exposed in the UI
315 lines
11 KiB
Python
315 lines
11 KiB
Python
# Copyright: Ankitects Pty Ltd and contributors
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import anki.lang
|
|
import aqt
|
|
from anki.consts import newCardSchedulingLabels
|
|
from aqt import AnkiQt
|
|
from aqt.profiles import RecordingDriver, VideoDriver
|
|
from aqt.qt import *
|
|
from aqt.utils import (
|
|
TR,
|
|
HelpPage,
|
|
disable_help_button,
|
|
openHelp,
|
|
showInfo,
|
|
showWarning,
|
|
tr,
|
|
)
|
|
|
|
|
|
class Preferences(QDialog):
|
|
def __init__(self, mw: AnkiQt) -> None:
|
|
QDialog.__init__(self, mw, Qt.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.Help).setAutoDefault(False)
|
|
self.form.buttonBox.button(QDialogButtonBox.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
|
|
self.update_collection()
|
|
self.update_profile()
|
|
self.update_global()
|
|
self.mw.pm.save()
|
|
self.mw.reset()
|
|
self.done(0)
|
|
aqt.dialogs.markClosed("Preferences")
|
|
|
|
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
|
|
form.lrnCutoff.setValue(int(scheduling.learn_ahead_secs / 60.0))
|
|
form.newSpread.addItems(list(newCardSchedulingLabels(self.mw.col).values()))
|
|
form.newSpread.setCurrentIndex(scheduling.new_review_mix)
|
|
form.dayLearnFirst.setChecked(scheduling.day_learn_first)
|
|
form.dayOffset.setValue(scheduling.rollover)
|
|
if scheduling.scheduler_version < 2:
|
|
form.dayLearnFirst.setVisible(False)
|
|
form.legacy_timezone.setVisible(False)
|
|
else:
|
|
form.legacy_timezone.setChecked(not scheduling.new_timezone)
|
|
|
|
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)
|
|
|
|
def update_collection(self) -> None:
|
|
form = self.form
|
|
|
|
scheduling = self.prefs.scheduling
|
|
scheduling.new_review_mix = 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()
|
|
|
|
self.mw.col.set_preferences(self.prefs)
|
|
self.mw.apply_collection_options()
|
|
|
|
# Preferences stored in the profile
|
|
######################################################################
|
|
|
|
def setup_profile(self) -> None:
|
|
"Setup options stored in the user profile."
|
|
self.setup_recording_driver()
|
|
self.setup_network()
|
|
self.setup_backup()
|
|
|
|
def update_profile(self) -> None:
|
|
self.update_recording_driver()
|
|
self.update_network()
|
|
self.update_backup()
|
|
|
|
# Profile: recording driver
|
|
######################################################################
|
|
|
|
def setup_recording_driver(self) -> None:
|
|
self._recording_drivers = [
|
|
RecordingDriver.QtAudioInput,
|
|
RecordingDriver.PyAudio,
|
|
]
|
|
# The plan is to phase out PyAudio soon, so will hold off on
|
|
# making this string translatable for now.
|
|
self.form.recording_driver.addItems(
|
|
[
|
|
f"Voice recording driver: {driver.value}"
|
|
for driver in self._recording_drivers
|
|
]
|
|
)
|
|
self.form.recording_driver.setCurrentIndex(
|
|
self._recording_drivers.index(self.mw.pm.recording_driver())
|
|
)
|
|
|
|
def update_recording_driver(self) -> None:
|
|
new_audio_driver = self._recording_drivers[
|
|
self.form.recording_driver.currentIndex()
|
|
]
|
|
if self.mw.pm.recording_driver() != new_audio_driver:
|
|
self.mw.pm.set_recording_driver(new_audio_driver)
|
|
if new_audio_driver == RecordingDriver.PyAudio:
|
|
showInfo(
|
|
"""\
|
|
The PyAudio driver will likely be removed in a future update. If you find it works better \
|
|
for you than the default driver, please let us know on the Anki forums."""
|
|
)
|
|
|
|
# Profile: network
|
|
######################################################################
|
|
|
|
def setup_network(self) -> None:
|
|
self.form.media_log.setText(tr(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(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(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.modSchema(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(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.currentLang
|
|
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(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(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())
|
|
)
|
|
|
|
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(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
|
|
|
|
|
|
def video_driver_name_for_platform(driver: VideoDriver) -> str:
|
|
if driver == VideoDriver.ANGLE:
|
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_ANGLE)
|
|
elif driver == VideoDriver.Software:
|
|
if isMac:
|
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_SOFTWARE_MAC)
|
|
else:
|
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_SOFTWARE_OTHER)
|
|
else:
|
|
if isMac:
|
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_OPENGL_MAC)
|
|
else:
|
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_OPENGL_OTHER)
|