expand backend Preferences and make undoable
- 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
This commit is contained in:
parent
24ad7c1f35
commit
6b1dd9ee19
@ -20,7 +20,7 @@ from bs4 import BeautifulSoup
|
||||
import aqt
|
||||
import aqt.sound
|
||||
from anki.cards import Card
|
||||
from anki.collection import SearchNode
|
||||
from anki.collection import Config, SearchNode
|
||||
from anki.consts import MODEL_CLOZE
|
||||
from anki.hooks import runFilter
|
||||
from anki.httpclient import HttpClient
|
||||
@ -781,7 +781,7 @@ class Editor:
|
||||
filter = f"{tr(TR.EDITING_MEDIA)} ({extension_filter})"
|
||||
|
||||
def accept(file: str) -> None:
|
||||
self.addMedia(file, canDelete=True)
|
||||
self.addMedia(file)
|
||||
|
||||
file = getFile(
|
||||
parent=self.widget,
|
||||
@ -793,24 +793,18 @@ class Editor:
|
||||
self.parentWindow.activateWindow()
|
||||
|
||||
def addMedia(self, path: str, canDelete: bool = False) -> None:
|
||||
"""canDelete is a legacy arg and is ignored."""
|
||||
try:
|
||||
html = self._addMedia(path, canDelete)
|
||||
html = self._addMedia(path)
|
||||
except Exception as e:
|
||||
showWarning(str(e))
|
||||
return
|
||||
self.web.eval(f"setFormat('inserthtml', {json.dumps(html)});")
|
||||
|
||||
def _addMedia(self, path: str, canDelete: bool = False) -> str:
|
||||
"Add to media folder and return local img or sound tag."
|
||||
"""Add to media folder and return local img or sound tag."""
|
||||
# copy to media folder
|
||||
fname = self.mw.col.media.addFile(path)
|
||||
# remove original?
|
||||
if canDelete and self.mw.pm.profile["deleteMedia"]:
|
||||
if os.path.abspath(fname) != os.path.abspath(path):
|
||||
try:
|
||||
os.unlink(path)
|
||||
except:
|
||||
pass
|
||||
# return a local html link
|
||||
return self.fnameToLink(fname)
|
||||
|
||||
@ -1091,7 +1085,6 @@ class EditorWebView(AnkiWebView):
|
||||
def __init__(self, parent: QWidget, editor: Editor) -> None:
|
||||
AnkiWebView.__init__(self, title="editor")
|
||||
self.editor = editor
|
||||
self.strip = self.editor.mw.pm.profile["stripHTML"]
|
||||
self.setAcceptDrops(True)
|
||||
self._markInternal = False
|
||||
clip = self.editor.mw.app.clipboard()
|
||||
@ -1110,10 +1103,12 @@ class EditorWebView(AnkiWebView):
|
||||
self.triggerPageAction(QWebEnginePage.Copy)
|
||||
|
||||
def _wantsExtendedPaste(self) -> bool:
|
||||
extended = not (self.editor.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier)
|
||||
if self.editor.mw.pm.profile.get("pasteInvert", False):
|
||||
extended = not extended
|
||||
return extended
|
||||
strip_html = self.editor.mw.col.get_config_bool(
|
||||
Config.Bool.PASTE_STRIPS_FORMATTING
|
||||
)
|
||||
if self.editor.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier:
|
||||
strip_html = not strip_html
|
||||
return strip_html
|
||||
|
||||
def _onPaste(self, mode: QClipboard.Mode) -> None:
|
||||
extended = self._wantsExtendedPaste()
|
||||
@ -1240,7 +1235,7 @@ class EditorWebView(AnkiWebView):
|
||||
return None
|
||||
im = QImage(mime.imageData())
|
||||
uname = namedtmp("paste")
|
||||
if self.editor.mw.pm.profile.get("pastePNG", False):
|
||||
if self.editor.mw.col.get_config_bool(Config.Bool.PASTE_IMAGES_AS_PNG):
|
||||
ext = ".png"
|
||||
im.save(uname + ext, None, 50)
|
||||
else:
|
||||
|
@ -80,7 +80,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="pasteInvert">
|
||||
<widget class="QCheckBox" name="paste_strips_formatting">
|
||||
<property name="text">
|
||||
<string>PREFERENCES_PASTE_WITHOUT_SHIFT_KEY_STRIPS_FORMATTING</string>
|
||||
</property>
|
||||
@ -589,7 +589,7 @@
|
||||
<tabstop>showPlayButtons</tabstop>
|
||||
<tabstop>interrupt_audio</tabstop>
|
||||
<tabstop>pastePNG</tabstop>
|
||||
<tabstop>pasteInvert</tabstop>
|
||||
<tabstop>paste_strips_formatting</tabstop>
|
||||
<tabstop>nightMode</tabstop>
|
||||
<tabstop>useCurrent</tabstop>
|
||||
<tabstop>recording_driver</tabstop>
|
||||
|
@ -27,7 +27,7 @@ import aqt.toolbar
|
||||
import aqt.webview
|
||||
from anki import hooks
|
||||
from anki._backend import RustBackend as _RustBackend
|
||||
from anki.collection import BackendUndo, Checkpoint, Collection, ReviewUndo
|
||||
from anki.collection import BackendUndo, Checkpoint, Collection, Config, ReviewUndo
|
||||
from anki.decks import Deck
|
||||
from anki.hooks import runHook
|
||||
from anki.sound import AVTag, SoundOrVideoTag
|
||||
@ -391,8 +391,6 @@ class AnkiQt(QMainWindow):
|
||||
if not self.loadCollection():
|
||||
return
|
||||
|
||||
self.pm.apply_profile_options()
|
||||
|
||||
# show main window
|
||||
if self.pm.profile["mainWindowState"]:
|
||||
restoreGeom(self, "mainWindow")
|
||||
@ -467,10 +465,10 @@ class AnkiQt(QMainWindow):
|
||||
|
||||
def _add_play_buttons(self, text: str) -> str:
|
||||
"Return card text with play buttons added, or stripped."
|
||||
if self.pm.profile.get("showPlayButtons", True):
|
||||
return aqt.sound.av_refs_to_play_icons(text)
|
||||
else:
|
||||
if self.col.get_config_bool(Config.Bool.HIDE_AUDIO_PLAY_BUTTONS):
|
||||
return anki.sound.strip_av_refs(text)
|
||||
else:
|
||||
return aqt.sound.av_refs_to_play_icons(text)
|
||||
|
||||
def prepare_card_text_for_display(self, text: str) -> str:
|
||||
text = self.col.media.escape_media_filenames(text)
|
||||
@ -508,6 +506,7 @@ class AnkiQt(QMainWindow):
|
||||
try:
|
||||
self.update_undo_actions()
|
||||
gui_hooks.collection_did_load(self.col)
|
||||
self.apply_collection_options()
|
||||
self.moveToState("deckBrowser")
|
||||
except Exception as e:
|
||||
# dump error to stderr so it gets picked up by errors.py
|
||||
@ -572,6 +571,12 @@ class AnkiQt(QMainWindow):
|
||||
self.col.reopen(after_full_sync=False)
|
||||
self.col.close_for_full_sync()
|
||||
|
||||
def apply_collection_options(self) -> None:
|
||||
"Setup audio after collection loaded."
|
||||
aqt.sound.av_player.interrupt_current_audio = self.col.get_config_bool(
|
||||
Config.Bool.INTERRUPT_AUDIO_WHEN_ANSWERING
|
||||
)
|
||||
|
||||
# Backup and auto-optimize
|
||||
##########################################################################
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
# 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 *
|
||||
@ -16,21 +18,6 @@ from aqt.utils import (
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Preferences(QDialog):
|
||||
def __init__(self, mw: AnkiQt) -> None:
|
||||
QDialog.__init__(self, mw, Qt.Window)
|
||||
@ -45,22 +32,18 @@ class Preferences(QDialog):
|
||||
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.PREFERENCES)
|
||||
)
|
||||
self.silentlyClose = True
|
||||
self.prefs = self.mw.col.get_preferences()
|
||||
self.setupLang()
|
||||
self.setupCollection()
|
||||
self.setupNetwork()
|
||||
self.setupBackup()
|
||||
self.setupOptions()
|
||||
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.updateCollection()
|
||||
self.updateNetwork()
|
||||
self.updateBackup()
|
||||
self.updateOptions()
|
||||
self.update_collection()
|
||||
self.update_profile()
|
||||
self.update_global()
|
||||
self.mw.pm.save()
|
||||
self.mw.reset()
|
||||
self.done(0)
|
||||
@ -69,16 +52,214 @@ class Preferences(QDialog):
|
||||
def reject(self) -> None:
|
||||
self.accept()
|
||||
|
||||
# Language
|
||||
# Preferences stored in the collection
|
||||
######################################################################
|
||||
|
||||
def setupLang(self) -> None:
|
||||
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.langIdx())
|
||||
qconnect(f.lang.currentIndexChanged, self.onLangIdxChanged)
|
||||
f.lang.setCurrentIndex(self.current_lang_index())
|
||||
qconnect(f.lang.currentIndexChanged, self.on_language_index_changed)
|
||||
|
||||
def langIdx(self) -> int:
|
||||
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:
|
||||
@ -90,43 +271,16 @@ class Preferences(QDialog):
|
||||
except:
|
||||
return codes.index("en_US")
|
||||
|
||||
def onLangIdxChanged(self, idx: int) -> None:
|
||||
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
|
||||
)
|
||||
|
||||
# Collection options
|
||||
# Global: video driver
|
||||
######################################################################
|
||||
|
||||
def setupCollection(self) -> None:
|
||||
import anki.consts as c
|
||||
|
||||
f = self.form
|
||||
qc = self.mw.col.conf
|
||||
|
||||
self.setup_video_driver()
|
||||
|
||||
f.newSpread.addItems(list(c.newCardSchedulingLabels(self.mw.col).values()))
|
||||
|
||||
f.useCurrent.setCurrentIndex(int(not qc.get("addToCur", True)))
|
||||
|
||||
s = self.prefs.scheduling
|
||||
f.lrnCutoff.setValue(int(s.learn_ahead_secs / 60.0))
|
||||
f.timeLimit.setValue(int(s.time_limit_secs / 60.0))
|
||||
f.showEstimates.setChecked(s.show_intervals_on_buttons)
|
||||
f.showProgress.setChecked(s.show_remaining_due_counts)
|
||||
f.newSpread.setCurrentIndex(s.new_review_mix)
|
||||
f.dayLearnFirst.setChecked(s.day_learn_first)
|
||||
f.dayOffset.setValue(s.rollover)
|
||||
|
||||
if s.scheduler_version < 2:
|
||||
f.dayLearnFirst.setVisible(False)
|
||||
f.legacy_timezone.setVisible(False)
|
||||
else:
|
||||
f.legacy_timezone.setChecked(not s.new_timezone)
|
||||
|
||||
def setup_video_driver(self) -> None:
|
||||
self.video_drivers = VideoDriver.all_for_platform()
|
||||
names = [
|
||||
@ -144,133 +298,17 @@ class Preferences(QDialog):
|
||||
self.mw.pm.set_video_driver(new_driver)
|
||||
showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
|
||||
|
||||
def updateCollection(self) -> None:
|
||||
f = self.form
|
||||
d = self.mw.col
|
||||
|
||||
self.update_video_driver()
|
||||
|
||||
qc = d.conf
|
||||
qc["addToCur"] = not f.useCurrent.currentIndex()
|
||||
|
||||
s = self.prefs.scheduling
|
||||
s.show_remaining_due_counts = f.showProgress.isChecked()
|
||||
s.show_intervals_on_buttons = f.showEstimates.isChecked()
|
||||
s.new_review_mix = f.newSpread.currentIndex()
|
||||
s.time_limit_secs = f.timeLimit.value() * 60
|
||||
s.learn_ahead_secs = f.lrnCutoff.value() * 60
|
||||
s.day_learn_first = f.dayLearnFirst.isChecked()
|
||||
s.rollover = f.dayOffset.value()
|
||||
s.new_timezone = not f.legacy_timezone.isChecked()
|
||||
|
||||
self.mw.col.set_preferences(self.prefs)
|
||||
|
||||
# Network
|
||||
######################################################################
|
||||
|
||||
def setupNetwork(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._hideAuth()
|
||||
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:
|
||||
self.form.syncUser.setText(self.prof.get("syncUser", ""))
|
||||
qconnect(self.form.syncDeauth.clicked, self.onSyncDeauth)
|
||||
self.form.syncDeauth.setText(tr(TR.SYNC_LOG_OUT_BUTTON))
|
||||
|
||||
def on_media_log(self) -> None:
|
||||
self.mw.media_syncer.show_sync_log()
|
||||
|
||||
def _hideAuth(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 onSyncDeauth(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._hideAuth()
|
||||
|
||||
def updateNetwork(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)
|
||||
|
||||
# Backup
|
||||
######################################################################
|
||||
|
||||
def setupBackup(self) -> None:
|
||||
self.form.numBackups.setValue(self.prof["numBackups"])
|
||||
|
||||
def updateBackup(self) -> None:
|
||||
self.prof["numBackups"] = self.form.numBackups.value()
|
||||
|
||||
# Basic & Advanced Options
|
||||
######################################################################
|
||||
|
||||
def setupOptions(self) -> None:
|
||||
self.form.pastePNG.setChecked(self.prof.get("pastePNG", False))
|
||||
self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100))
|
||||
self.form.pasteInvert.setChecked(self.prof.get("pasteInvert", False))
|
||||
self.form.showPlayButtons.setChecked(self.prof.get("showPlayButtons", True))
|
||||
self.form.nightMode.setChecked(self.mw.pm.night_mode())
|
||||
self.form.interrupt_audio.setChecked(self.mw.pm.interrupt_audio())
|
||||
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 updateOptions(self) -> None:
|
||||
restart_required = False
|
||||
|
||||
self.prof["pastePNG"] = self.form.pastePNG.isChecked()
|
||||
self.prof["pasteInvert"] = self.form.pasteInvert.isChecked()
|
||||
newScale = self.form.uiScale.value() / 100
|
||||
if newScale != self.mw.pm.uiScale():
|
||||
self.mw.pm.setUiScale(newScale)
|
||||
restart_required = True
|
||||
self.prof["showPlayButtons"] = self.form.showPlayButtons.isChecked()
|
||||
|
||||
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
|
||||
|
||||
self.mw.pm.set_interrupt_audio(self.form.interrupt_audio.isChecked())
|
||||
|
||||
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."""
|
||||
)
|
||||
|
||||
if restart_required:
|
||||
showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
|
||||
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)
|
||||
|
@ -89,14 +89,8 @@ profileConf: Dict[str, Any] = dict(
|
||||
numBackups=50,
|
||||
lastOptimize=intTime(),
|
||||
# editing
|
||||
fullSearch=False,
|
||||
searchHistory=[],
|
||||
lastColour="#00f",
|
||||
stripHTML=True,
|
||||
pastePNG=False,
|
||||
# not exposed in gui
|
||||
deleteMedia=False,
|
||||
preserveKeyboard=True,
|
||||
# syncing
|
||||
syncKey=None,
|
||||
syncMedia=True,
|
||||
@ -104,6 +98,10 @@ profileConf: Dict[str, Any] = dict(
|
||||
# importing
|
||||
allowHTML=False,
|
||||
importMode=1,
|
||||
# these are not used, but Anki 2.1.42 and below
|
||||
# expect these keys to exist
|
||||
stripHTML=True,
|
||||
deleteMedia=False,
|
||||
)
|
||||
|
||||
|
||||
@ -617,13 +615,6 @@ create table if not exists profiles
|
||||
# Profile-specific
|
||||
######################################################################
|
||||
|
||||
def interrupt_audio(self) -> bool:
|
||||
return self.profile.get("interrupt_audio", True)
|
||||
|
||||
def set_interrupt_audio(self, val: bool) -> None:
|
||||
self.profile["interrupt_audio"] = val
|
||||
aqt.sound.av_player.interrupt_current_audio = val
|
||||
|
||||
def set_sync_key(self, val: Optional[str]) -> None:
|
||||
self.profile["syncKey"] = val
|
||||
|
||||
@ -667,8 +658,3 @@ create table if not exists profiles
|
||||
|
||||
def set_recording_driver(self, driver: RecordingDriver) -> None:
|
||||
self.profile["recordingDriver"] = driver.value
|
||||
|
||||
######################################################################
|
||||
|
||||
def apply_profile_options(self) -> None:
|
||||
aqt.sound.av_player.interrupt_current_audio = self.interrupt_audio()
|
||||
|
@ -1036,16 +1036,27 @@ message Preferences {
|
||||
uint32 rollover = 2;
|
||||
uint32 learn_ahead_secs = 3;
|
||||
NewReviewMix new_review_mix = 4;
|
||||
bool show_remaining_due_counts = 5;
|
||||
bool show_intervals_on_buttons = 6;
|
||||
uint32 time_limit_secs = 7;
|
||||
|
||||
// v2 only
|
||||
bool new_timezone = 8;
|
||||
bool day_learn_first = 9;
|
||||
bool new_timezone = 5;
|
||||
bool day_learn_first = 6;
|
||||
}
|
||||
message Reviewing {
|
||||
bool hide_audio_play_buttons = 1;
|
||||
bool interrupt_audio_when_answering = 2;
|
||||
bool show_remaining_due_counts = 3;
|
||||
bool show_intervals_on_buttons = 4;
|
||||
uint32 time_limit_secs = 5;
|
||||
}
|
||||
message Editing {
|
||||
bool adding_defaults_to_current_deck = 1;
|
||||
bool paste_images_as_png = 2;
|
||||
bool paste_strips_formatting = 3;
|
||||
}
|
||||
|
||||
Scheduling scheduling = 1;
|
||||
Reviewing reviewing = 2;
|
||||
Editing editing = 3;
|
||||
}
|
||||
|
||||
message ClozeNumbersInNoteOut {
|
||||
@ -1275,6 +1286,10 @@ message Config {
|
||||
COLLAPSE_FLAGS = 8;
|
||||
SCHED_2021 = 9;
|
||||
ADDING_DEFAULTS_TO_CURRENT_DECK = 10;
|
||||
HIDE_AUDIO_PLAY_BUTTONS = 11;
|
||||
INTERRUPT_AUDIO_WHEN_ANSWERING = 12;
|
||||
PASTE_IMAGES_AS_PNG = 13;
|
||||
PASTE_STRIPS_FORMATTING = 14;
|
||||
}
|
||||
Key key = 1;
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ impl From<BoolKeyProto> for BoolKey {
|
||||
BoolKeyProto::CollapseFlags => BoolKey::CollapseFlags,
|
||||
BoolKeyProto::Sched2021 => BoolKey::Sched2021,
|
||||
BoolKeyProto::AddingDefaultsToCurrentDeck => BoolKey::AddingDefaultsToCurrentDeck,
|
||||
BoolKeyProto::HideAudioPlayButtons => BoolKey::HideAudioPlayButtons,
|
||||
BoolKeyProto::InterruptAudioWhenAnswering => BoolKey::InterruptAudioWhenAnswering,
|
||||
BoolKeyProto::PasteImagesAsPng => BoolKey::PasteImagesAsPng,
|
||||
BoolKeyProto::PasteStripsFormatting => BoolKey::PasteStripsFormatting,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1319,7 +1319,7 @@ impl BackendService for Backend {
|
||||
}
|
||||
|
||||
fn set_preferences(&self, input: pb::Preferences) -> BackendResult<Empty> {
|
||||
self.with_col(|col| col.transact(None, |col| col.set_preferences(input)))
|
||||
self.with_col(|col| col.set_preferences(input))
|
||||
.map(Into::into)
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,10 @@ pub enum BoolKey {
|
||||
CollapseTags,
|
||||
CollapseToday,
|
||||
FutureDueShowBacklog,
|
||||
HideAudioPlayButtons,
|
||||
InterruptAudioWhenAnswering,
|
||||
PasteImagesAsPng,
|
||||
PasteStripsFormatting,
|
||||
PreviewBothSides,
|
||||
Sched2021,
|
||||
|
||||
@ -50,7 +54,9 @@ impl Collection {
|
||||
}
|
||||
|
||||
// some keys default to true
|
||||
BoolKey::AddingDefaultsToCurrentDeck
|
||||
BoolKey::InterruptAudioWhenAnswering
|
||||
| BoolKey::ShowIntervalsAboveAnswerButtons
|
||||
| BoolKey::AddingDefaultsToCurrentDeck
|
||||
| BoolKey::FutureDueShowBacklog
|
||||
| BoolKey::ShowRemainingDueCountsInStudy
|
||||
| BoolKey::CardCountsSeparateInactive
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
use crate::{
|
||||
backend_proto::{
|
||||
preferences::scheduling::NewReviewMix as NewRevMixPB, preferences::Scheduling, Preferences,
|
||||
preferences::scheduling::NewReviewMix as NewRevMixPB,
|
||||
preferences::{Editing, Reviewing, Scheduling},
|
||||
Preferences,
|
||||
},
|
||||
collection::Collection,
|
||||
config::BoolKey,
|
||||
@ -14,19 +16,36 @@ use crate::{
|
||||
impl Collection {
|
||||
pub fn get_preferences(&self) -> Result<Preferences> {
|
||||
Ok(Preferences {
|
||||
scheduling: Some(self.get_collection_scheduling_settings()?),
|
||||
scheduling: Some(self.get_scheduling_preferences()?),
|
||||
reviewing: Some(self.get_reviewing_preferences()?),
|
||||
editing: Some(self.get_editing_preferences()?),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> {
|
||||
if let Some(sched) = prefs.scheduling {
|
||||
self.set_collection_scheduling_settings(sched)?;
|
||||
self.transact(
|
||||
Some(crate::undo::UndoableOpKind::UpdatePreferences),
|
||||
|col| col.set_preferences_inner(prefs),
|
||||
)
|
||||
}
|
||||
|
||||
fn set_preferences_inner(
|
||||
&mut self,
|
||||
prefs: Preferences,
|
||||
) -> Result<(), crate::prelude::AnkiError> {
|
||||
if let Some(sched) = prefs.scheduling {
|
||||
self.set_scheduling_preferences(sched)?;
|
||||
}
|
||||
if let Some(reviewing) = prefs.reviewing {
|
||||
self.set_reviewing_preferences(reviewing)?;
|
||||
}
|
||||
if let Some(editing) = prefs.editing {
|
||||
self.set_editing_preferences(editing)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_collection_scheduling_settings(&self) -> Result<Scheduling> {
|
||||
pub fn get_scheduling_preferences(&self) -> Result<Scheduling> {
|
||||
Ok(Scheduling {
|
||||
scheduler_version: match self.scheduler_version() {
|
||||
crate::config::SchedulerVersion::V1 => 1,
|
||||
@ -39,30 +58,15 @@ impl Collection {
|
||||
crate::config::NewReviewMix::ReviewsFirst => NewRevMixPB::ReviewsFirst,
|
||||
crate::config::NewReviewMix::NewFirst => NewRevMixPB::NewFirst,
|
||||
} as i32,
|
||||
show_remaining_due_counts: self.get_bool(BoolKey::ShowRemainingDueCountsInStudy),
|
||||
show_intervals_on_buttons: self.get_bool(BoolKey::ShowIntervalsAboveAnswerButtons),
|
||||
time_limit_secs: self.get_answer_time_limit_secs(),
|
||||
new_timezone: self.get_creation_utc_offset().is_some(),
|
||||
day_learn_first: self.get_bool(BoolKey::ShowDayLearningCardsFirst),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_collection_scheduling_settings(
|
||||
&mut self,
|
||||
settings: Scheduling,
|
||||
) -> Result<()> {
|
||||
pub(crate) fn set_scheduling_preferences(&mut self, settings: Scheduling) -> Result<()> {
|
||||
let s = settings;
|
||||
|
||||
self.set_bool(BoolKey::ShowDayLearningCardsFirst, s.day_learn_first)?;
|
||||
self.set_bool(
|
||||
BoolKey::ShowRemainingDueCountsInStudy,
|
||||
s.show_remaining_due_counts,
|
||||
)?;
|
||||
self.set_bool(
|
||||
BoolKey::ShowIntervalsAboveAnswerButtons,
|
||||
s.show_intervals_on_buttons,
|
||||
)?;
|
||||
self.set_answer_time_limit_secs(s.time_limit_secs)?;
|
||||
self.set_learn_ahead_secs(s.learn_ahead_secs)?;
|
||||
|
||||
self.set_new_review_mix(match s.new_review_mix() {
|
||||
@ -87,4 +91,52 @@ impl Collection {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_reviewing_preferences(&self) -> Result<Reviewing> {
|
||||
Ok(Reviewing {
|
||||
hide_audio_play_buttons: self.get_bool(BoolKey::HideAudioPlayButtons),
|
||||
interrupt_audio_when_answering: self.get_bool(BoolKey::InterruptAudioWhenAnswering),
|
||||
show_remaining_due_counts: self.get_bool(BoolKey::ShowRemainingDueCountsInStudy),
|
||||
show_intervals_on_buttons: self.get_bool(BoolKey::ShowIntervalsAboveAnswerButtons),
|
||||
time_limit_secs: self.get_answer_time_limit_secs(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_reviewing_preferences(&mut self, settings: Reviewing) -> Result<()> {
|
||||
let s = settings;
|
||||
self.set_bool(BoolKey::HideAudioPlayButtons, s.hide_audio_play_buttons)?;
|
||||
self.set_bool(
|
||||
BoolKey::InterruptAudioWhenAnswering,
|
||||
s.interrupt_audio_when_answering,
|
||||
)?;
|
||||
self.set_bool(
|
||||
BoolKey::ShowRemainingDueCountsInStudy,
|
||||
s.show_remaining_due_counts,
|
||||
)?;
|
||||
self.set_bool(
|
||||
BoolKey::ShowIntervalsAboveAnswerButtons,
|
||||
s.show_intervals_on_buttons,
|
||||
)?;
|
||||
self.set_answer_time_limit_secs(s.time_limit_secs)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_editing_preferences(&self) -> Result<Editing> {
|
||||
Ok(Editing {
|
||||
adding_defaults_to_current_deck: self.get_bool(BoolKey::AddingDefaultsToCurrentDeck),
|
||||
paste_images_as_png: self.get_bool(BoolKey::PasteImagesAsPng),
|
||||
paste_strips_formatting: self.get_bool(BoolKey::PasteStripsFormatting),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn set_editing_preferences(&mut self, settings: Editing) -> Result<()> {
|
||||
let s = settings;
|
||||
self.set_bool(
|
||||
BoolKey::AddingDefaultsToCurrentDeck,
|
||||
s.adding_defaults_to_current_deck,
|
||||
)?;
|
||||
self.set_bool(BoolKey::PasteImagesAsPng, s.paste_images_as_png)?;
|
||||
self.set_bool(BoolKey::PasteStripsFormatting, s.paste_strips_formatting)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ pub enum UndoableOpKind {
|
||||
RemoveNote,
|
||||
UpdateTag,
|
||||
UpdateNote,
|
||||
UpdatePreferences,
|
||||
}
|
||||
|
||||
impl UndoableOpKind {
|
||||
@ -34,6 +35,7 @@ impl Collection {
|
||||
UndoableOpKind::RemoveNote => TR::StudyingDeleteNote,
|
||||
UndoableOpKind::UpdateTag => TR::UndoUpdateTag,
|
||||
UndoableOpKind::UpdateNote => TR::UndoUpdateNote,
|
||||
UndoableOpKind::UpdatePreferences => TR::PreferencesPreferences,
|
||||
};
|
||||
|
||||
self.i18n.tr(key).to_string()
|
||||
|
Loading…
Reference in New Issue
Block a user