# 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)