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
|
||||||
import aqt.sound
|
import aqt.sound
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.collection import SearchNode
|
from anki.collection import Config, SearchNode
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.hooks import runFilter
|
from anki.hooks import runFilter
|
||||||
from anki.httpclient import HttpClient
|
from anki.httpclient import HttpClient
|
||||||
@ -781,7 +781,7 @@ class Editor:
|
|||||||
filter = f"{tr(TR.EDITING_MEDIA)} ({extension_filter})"
|
filter = f"{tr(TR.EDITING_MEDIA)} ({extension_filter})"
|
||||||
|
|
||||||
def accept(file: str) -> None:
|
def accept(file: str) -> None:
|
||||||
self.addMedia(file, canDelete=True)
|
self.addMedia(file)
|
||||||
|
|
||||||
file = getFile(
|
file = getFile(
|
||||||
parent=self.widget,
|
parent=self.widget,
|
||||||
@ -793,24 +793,18 @@ class Editor:
|
|||||||
self.parentWindow.activateWindow()
|
self.parentWindow.activateWindow()
|
||||||
|
|
||||||
def addMedia(self, path: str, canDelete: bool = False) -> None:
|
def addMedia(self, path: str, canDelete: bool = False) -> None:
|
||||||
|
"""canDelete is a legacy arg and is ignored."""
|
||||||
try:
|
try:
|
||||||
html = self._addMedia(path, canDelete)
|
html = self._addMedia(path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
showWarning(str(e))
|
showWarning(str(e))
|
||||||
return
|
return
|
||||||
self.web.eval(f"setFormat('inserthtml', {json.dumps(html)});")
|
self.web.eval(f"setFormat('inserthtml', {json.dumps(html)});")
|
||||||
|
|
||||||
def _addMedia(self, path: str, canDelete: bool = False) -> str:
|
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
|
# copy to media folder
|
||||||
fname = self.mw.col.media.addFile(path)
|
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 a local html link
|
||||||
return self.fnameToLink(fname)
|
return self.fnameToLink(fname)
|
||||||
|
|
||||||
@ -1091,7 +1085,6 @@ class EditorWebView(AnkiWebView):
|
|||||||
def __init__(self, parent: QWidget, editor: Editor) -> None:
|
def __init__(self, parent: QWidget, editor: Editor) -> None:
|
||||||
AnkiWebView.__init__(self, title="editor")
|
AnkiWebView.__init__(self, title="editor")
|
||||||
self.editor = editor
|
self.editor = editor
|
||||||
self.strip = self.editor.mw.pm.profile["stripHTML"]
|
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self._markInternal = False
|
self._markInternal = False
|
||||||
clip = self.editor.mw.app.clipboard()
|
clip = self.editor.mw.app.clipboard()
|
||||||
@ -1110,10 +1103,12 @@ class EditorWebView(AnkiWebView):
|
|||||||
self.triggerPageAction(QWebEnginePage.Copy)
|
self.triggerPageAction(QWebEnginePage.Copy)
|
||||||
|
|
||||||
def _wantsExtendedPaste(self) -> bool:
|
def _wantsExtendedPaste(self) -> bool:
|
||||||
extended = not (self.editor.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier)
|
strip_html = self.editor.mw.col.get_config_bool(
|
||||||
if self.editor.mw.pm.profile.get("pasteInvert", False):
|
Config.Bool.PASTE_STRIPS_FORMATTING
|
||||||
extended = not extended
|
)
|
||||||
return extended
|
if self.editor.mw.app.queryKeyboardModifiers() & Qt.ShiftModifier:
|
||||||
|
strip_html = not strip_html
|
||||||
|
return strip_html
|
||||||
|
|
||||||
def _onPaste(self, mode: QClipboard.Mode) -> None:
|
def _onPaste(self, mode: QClipboard.Mode) -> None:
|
||||||
extended = self._wantsExtendedPaste()
|
extended = self._wantsExtendedPaste()
|
||||||
@ -1240,7 +1235,7 @@ class EditorWebView(AnkiWebView):
|
|||||||
return None
|
return None
|
||||||
im = QImage(mime.imageData())
|
im = QImage(mime.imageData())
|
||||||
uname = namedtmp("paste")
|
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"
|
ext = ".png"
|
||||||
im.save(uname + ext, None, 50)
|
im.save(uname + ext, None, 50)
|
||||||
else:
|
else:
|
||||||
|
@ -80,7 +80,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="pasteInvert">
|
<widget class="QCheckBox" name="paste_strips_formatting">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>PREFERENCES_PASTE_WITHOUT_SHIFT_KEY_STRIPS_FORMATTING</string>
|
<string>PREFERENCES_PASTE_WITHOUT_SHIFT_KEY_STRIPS_FORMATTING</string>
|
||||||
</property>
|
</property>
|
||||||
@ -589,7 +589,7 @@
|
|||||||
<tabstop>showPlayButtons</tabstop>
|
<tabstop>showPlayButtons</tabstop>
|
||||||
<tabstop>interrupt_audio</tabstop>
|
<tabstop>interrupt_audio</tabstop>
|
||||||
<tabstop>pastePNG</tabstop>
|
<tabstop>pastePNG</tabstop>
|
||||||
<tabstop>pasteInvert</tabstop>
|
<tabstop>paste_strips_formatting</tabstop>
|
||||||
<tabstop>nightMode</tabstop>
|
<tabstop>nightMode</tabstop>
|
||||||
<tabstop>useCurrent</tabstop>
|
<tabstop>useCurrent</tabstop>
|
||||||
<tabstop>recording_driver</tabstop>
|
<tabstop>recording_driver</tabstop>
|
||||||
|
@ -27,7 +27,7 @@ import aqt.toolbar
|
|||||||
import aqt.webview
|
import aqt.webview
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki._backend import RustBackend as _RustBackend
|
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.decks import Deck
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
from anki.sound import AVTag, SoundOrVideoTag
|
from anki.sound import AVTag, SoundOrVideoTag
|
||||||
@ -391,8 +391,6 @@ class AnkiQt(QMainWindow):
|
|||||||
if not self.loadCollection():
|
if not self.loadCollection():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.pm.apply_profile_options()
|
|
||||||
|
|
||||||
# show main window
|
# show main window
|
||||||
if self.pm.profile["mainWindowState"]:
|
if self.pm.profile["mainWindowState"]:
|
||||||
restoreGeom(self, "mainWindow")
|
restoreGeom(self, "mainWindow")
|
||||||
@ -467,10 +465,10 @@ class AnkiQt(QMainWindow):
|
|||||||
|
|
||||||
def _add_play_buttons(self, text: str) -> str:
|
def _add_play_buttons(self, text: str) -> str:
|
||||||
"Return card text with play buttons added, or stripped."
|
"Return card text with play buttons added, or stripped."
|
||||||
if self.pm.profile.get("showPlayButtons", True):
|
if self.col.get_config_bool(Config.Bool.HIDE_AUDIO_PLAY_BUTTONS):
|
||||||
return aqt.sound.av_refs_to_play_icons(text)
|
|
||||||
else:
|
|
||||||
return anki.sound.strip_av_refs(text)
|
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:
|
def prepare_card_text_for_display(self, text: str) -> str:
|
||||||
text = self.col.media.escape_media_filenames(text)
|
text = self.col.media.escape_media_filenames(text)
|
||||||
@ -508,6 +506,7 @@ class AnkiQt(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
self.update_undo_actions()
|
self.update_undo_actions()
|
||||||
gui_hooks.collection_did_load(self.col)
|
gui_hooks.collection_did_load(self.col)
|
||||||
|
self.apply_collection_options()
|
||||||
self.moveToState("deckBrowser")
|
self.moveToState("deckBrowser")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# dump error to stderr so it gets picked up by errors.py
|
# 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.reopen(after_full_sync=False)
|
||||||
self.col.close_for_full_sync()
|
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
|
# Backup and auto-optimize
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import anki.lang
|
import anki.lang
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.consts import newCardSchedulingLabels
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt
|
||||||
from aqt.profiles import RecordingDriver, VideoDriver
|
from aqt.profiles import RecordingDriver, VideoDriver
|
||||||
from aqt.qt import *
|
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):
|
class Preferences(QDialog):
|
||||||
def __init__(self, mw: AnkiQt) -> None:
|
def __init__(self, mw: AnkiQt) -> None:
|
||||||
QDialog.__init__(self, mw, Qt.Window)
|
QDialog.__init__(self, mw, Qt.Window)
|
||||||
@ -45,22 +32,18 @@ class Preferences(QDialog):
|
|||||||
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.PREFERENCES)
|
self.form.buttonBox.helpRequested, lambda: openHelp(HelpPage.PREFERENCES)
|
||||||
)
|
)
|
||||||
self.silentlyClose = True
|
self.silentlyClose = True
|
||||||
self.prefs = self.mw.col.get_preferences()
|
self.setup_collection()
|
||||||
self.setupLang()
|
self.setup_profile()
|
||||||
self.setupCollection()
|
self.setup_global()
|
||||||
self.setupNetwork()
|
|
||||||
self.setupBackup()
|
|
||||||
self.setupOptions()
|
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def accept(self) -> None:
|
def accept(self) -> None:
|
||||||
# avoid exception if main window is already closed
|
# avoid exception if main window is already closed
|
||||||
if not self.mw.col:
|
if not self.mw.col:
|
||||||
return
|
return
|
||||||
self.updateCollection()
|
self.update_collection()
|
||||||
self.updateNetwork()
|
self.update_profile()
|
||||||
self.updateBackup()
|
self.update_global()
|
||||||
self.updateOptions()
|
|
||||||
self.mw.pm.save()
|
self.mw.pm.save()
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
self.done(0)
|
self.done(0)
|
||||||
@ -69,16 +52,214 @@ class Preferences(QDialog):
|
|||||||
def reject(self) -> None:
|
def reject(self) -> None:
|
||||||
self.accept()
|
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 = self.form
|
||||||
f.lang.addItems([x[0] for x in anki.lang.langs])
|
f.lang.addItems([x[0] for x in anki.lang.langs])
|
||||||
f.lang.setCurrentIndex(self.langIdx())
|
f.lang.setCurrentIndex(self.current_lang_index())
|
||||||
qconnect(f.lang.currentIndexChanged, self.onLangIdxChanged)
|
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]
|
codes = [x[1] for x in anki.lang.langs]
|
||||||
lang = anki.lang.currentLang
|
lang = anki.lang.currentLang
|
||||||
if lang in anki.lang.compatMap:
|
if lang in anki.lang.compatMap:
|
||||||
@ -90,43 +271,16 @@ class Preferences(QDialog):
|
|||||||
except:
|
except:
|
||||||
return codes.index("en_US")
|
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]
|
code = anki.lang.langs[idx][1]
|
||||||
self.mw.pm.setLang(code)
|
self.mw.pm.setLang(code)
|
||||||
showInfo(
|
showInfo(
|
||||||
tr(TR.PREFERENCES_PLEASE_RESTART_ANKI_TO_COMPLETE_LANGUAGE), parent=self
|
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:
|
def setup_video_driver(self) -> None:
|
||||||
self.video_drivers = VideoDriver.all_for_platform()
|
self.video_drivers = VideoDriver.all_for_platform()
|
||||||
names = [
|
names = [
|
||||||
@ -144,133 +298,17 @@ class Preferences(QDialog):
|
|||||||
self.mw.pm.set_video_driver(new_driver)
|
self.mw.pm.set_video_driver(new_driver)
|
||||||
showInfo(tr(TR.PREFERENCES_CHANGES_WILL_TAKE_EFFECT_WHEN_YOU))
|
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()
|
def video_driver_name_for_platform(driver: VideoDriver) -> str:
|
||||||
|
if driver == VideoDriver.ANGLE:
|
||||||
qc = d.conf
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_ANGLE)
|
||||||
qc["addToCur"] = not f.useCurrent.currentIndex()
|
elif driver == VideoDriver.Software:
|
||||||
|
if isMac:
|
||||||
s = self.prefs.scheduling
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_SOFTWARE_MAC)
|
||||||
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()
|
|
||||||
else:
|
else:
|
||||||
self.form.syncUser.setText(self.prof.get("syncUser", ""))
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_SOFTWARE_OTHER)
|
||||||
qconnect(self.form.syncDeauth.clicked, self.onSyncDeauth)
|
else:
|
||||||
self.form.syncDeauth.setText(tr(TR.SYNC_LOG_OUT_BUTTON))
|
if isMac:
|
||||||
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_OPENGL_MAC)
|
||||||
def on_media_log(self) -> None:
|
else:
|
||||||
self.mw.media_syncer.show_sync_log()
|
return tr(TR.PREFERENCES_VIDEO_DRIVER_OPENGL_OTHER)
|
||||||
|
|
||||||
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))
|
|
||||||
|
@ -89,14 +89,8 @@ profileConf: Dict[str, Any] = dict(
|
|||||||
numBackups=50,
|
numBackups=50,
|
||||||
lastOptimize=intTime(),
|
lastOptimize=intTime(),
|
||||||
# editing
|
# editing
|
||||||
fullSearch=False,
|
|
||||||
searchHistory=[],
|
searchHistory=[],
|
||||||
lastColour="#00f",
|
lastColour="#00f",
|
||||||
stripHTML=True,
|
|
||||||
pastePNG=False,
|
|
||||||
# not exposed in gui
|
|
||||||
deleteMedia=False,
|
|
||||||
preserveKeyboard=True,
|
|
||||||
# syncing
|
# syncing
|
||||||
syncKey=None,
|
syncKey=None,
|
||||||
syncMedia=True,
|
syncMedia=True,
|
||||||
@ -104,6 +98,10 @@ profileConf: Dict[str, Any] = dict(
|
|||||||
# importing
|
# importing
|
||||||
allowHTML=False,
|
allowHTML=False,
|
||||||
importMode=1,
|
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
|
# 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:
|
def set_sync_key(self, val: Optional[str]) -> None:
|
||||||
self.profile["syncKey"] = val
|
self.profile["syncKey"] = val
|
||||||
|
|
||||||
@ -667,8 +658,3 @@ create table if not exists profiles
|
|||||||
|
|
||||||
def set_recording_driver(self, driver: RecordingDriver) -> None:
|
def set_recording_driver(self, driver: RecordingDriver) -> None:
|
||||||
self.profile["recordingDriver"] = driver.value
|
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 rollover = 2;
|
||||||
uint32 learn_ahead_secs = 3;
|
uint32 learn_ahead_secs = 3;
|
||||||
NewReviewMix new_review_mix = 4;
|
NewReviewMix new_review_mix = 4;
|
||||||
bool show_remaining_due_counts = 5;
|
|
||||||
bool show_intervals_on_buttons = 6;
|
|
||||||
uint32 time_limit_secs = 7;
|
|
||||||
|
|
||||||
// v2 only
|
// v2 only
|
||||||
bool new_timezone = 8;
|
bool new_timezone = 5;
|
||||||
bool day_learn_first = 9;
|
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;
|
Scheduling scheduling = 1;
|
||||||
|
Reviewing reviewing = 2;
|
||||||
|
Editing editing = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ClozeNumbersInNoteOut {
|
message ClozeNumbersInNoteOut {
|
||||||
@ -1275,6 +1286,10 @@ message Config {
|
|||||||
COLLAPSE_FLAGS = 8;
|
COLLAPSE_FLAGS = 8;
|
||||||
SCHED_2021 = 9;
|
SCHED_2021 = 9;
|
||||||
ADDING_DEFAULTS_TO_CURRENT_DECK = 10;
|
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;
|
Key key = 1;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,10 @@ impl From<BoolKeyProto> for BoolKey {
|
|||||||
BoolKeyProto::CollapseFlags => BoolKey::CollapseFlags,
|
BoolKeyProto::CollapseFlags => BoolKey::CollapseFlags,
|
||||||
BoolKeyProto::Sched2021 => BoolKey::Sched2021,
|
BoolKeyProto::Sched2021 => BoolKey::Sched2021,
|
||||||
BoolKeyProto::AddingDefaultsToCurrentDeck => BoolKey::AddingDefaultsToCurrentDeck,
|
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> {
|
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)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,10 @@ pub enum BoolKey {
|
|||||||
CollapseTags,
|
CollapseTags,
|
||||||
CollapseToday,
|
CollapseToday,
|
||||||
FutureDueShowBacklog,
|
FutureDueShowBacklog,
|
||||||
|
HideAudioPlayButtons,
|
||||||
|
InterruptAudioWhenAnswering,
|
||||||
|
PasteImagesAsPng,
|
||||||
|
PasteStripsFormatting,
|
||||||
PreviewBothSides,
|
PreviewBothSides,
|
||||||
Sched2021,
|
Sched2021,
|
||||||
|
|
||||||
@ -50,7 +54,9 @@ impl Collection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// some keys default to true
|
// some keys default to true
|
||||||
BoolKey::AddingDefaultsToCurrentDeck
|
BoolKey::InterruptAudioWhenAnswering
|
||||||
|
| BoolKey::ShowIntervalsAboveAnswerButtons
|
||||||
|
| BoolKey::AddingDefaultsToCurrentDeck
|
||||||
| BoolKey::FutureDueShowBacklog
|
| BoolKey::FutureDueShowBacklog
|
||||||
| BoolKey::ShowRemainingDueCountsInStudy
|
| BoolKey::ShowRemainingDueCountsInStudy
|
||||||
| BoolKey::CardCountsSeparateInactive
|
| BoolKey::CardCountsSeparateInactive
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::{
|
backend_proto::{
|
||||||
preferences::scheduling::NewReviewMix as NewRevMixPB, preferences::Scheduling, Preferences,
|
preferences::scheduling::NewReviewMix as NewRevMixPB,
|
||||||
|
preferences::{Editing, Reviewing, Scheduling},
|
||||||
|
Preferences,
|
||||||
},
|
},
|
||||||
collection::Collection,
|
collection::Collection,
|
||||||
config::BoolKey,
|
config::BoolKey,
|
||||||
@ -14,19 +16,36 @@ use crate::{
|
|||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn get_preferences(&self) -> Result<Preferences> {
|
pub fn get_preferences(&self) -> Result<Preferences> {
|
||||||
Ok(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<()> {
|
pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> {
|
||||||
if let Some(sched) = prefs.scheduling {
|
self.transact(
|
||||||
self.set_collection_scheduling_settings(sched)?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_collection_scheduling_settings(&self) -> Result<Scheduling> {
|
pub fn get_scheduling_preferences(&self) -> Result<Scheduling> {
|
||||||
Ok(Scheduling {
|
Ok(Scheduling {
|
||||||
scheduler_version: match self.scheduler_version() {
|
scheduler_version: match self.scheduler_version() {
|
||||||
crate::config::SchedulerVersion::V1 => 1,
|
crate::config::SchedulerVersion::V1 => 1,
|
||||||
@ -39,30 +58,15 @@ impl Collection {
|
|||||||
crate::config::NewReviewMix::ReviewsFirst => NewRevMixPB::ReviewsFirst,
|
crate::config::NewReviewMix::ReviewsFirst => NewRevMixPB::ReviewsFirst,
|
||||||
crate::config::NewReviewMix::NewFirst => NewRevMixPB::NewFirst,
|
crate::config::NewReviewMix::NewFirst => NewRevMixPB::NewFirst,
|
||||||
} as i32,
|
} 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(),
|
new_timezone: self.get_creation_utc_offset().is_some(),
|
||||||
day_learn_first: self.get_bool(BoolKey::ShowDayLearningCardsFirst),
|
day_learn_first: self.get_bool(BoolKey::ShowDayLearningCardsFirst),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_collection_scheduling_settings(
|
pub(crate) fn set_scheduling_preferences(&mut self, settings: Scheduling) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
settings: Scheduling,
|
|
||||||
) -> Result<()> {
|
|
||||||
let s = settings;
|
let s = settings;
|
||||||
|
|
||||||
self.set_bool(BoolKey::ShowDayLearningCardsFirst, s.day_learn_first)?;
|
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_learn_ahead_secs(s.learn_ahead_secs)?;
|
||||||
|
|
||||||
self.set_new_review_mix(match s.new_review_mix() {
|
self.set_new_review_mix(match s.new_review_mix() {
|
||||||
@ -87,4 +91,52 @@ impl Collection {
|
|||||||
|
|
||||||
Ok(())
|
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,
|
RemoveNote,
|
||||||
UpdateTag,
|
UpdateTag,
|
||||||
UpdateNote,
|
UpdateNote,
|
||||||
|
UpdatePreferences,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UndoableOpKind {
|
impl UndoableOpKind {
|
||||||
@ -34,6 +35,7 @@ impl Collection {
|
|||||||
UndoableOpKind::RemoveNote => TR::StudyingDeleteNote,
|
UndoableOpKind::RemoveNote => TR::StudyingDeleteNote,
|
||||||
UndoableOpKind::UpdateTag => TR::UndoUpdateTag,
|
UndoableOpKind::UpdateTag => TR::UndoUpdateTag,
|
||||||
UndoableOpKind::UpdateNote => TR::UndoUpdateNote,
|
UndoableOpKind::UpdateNote => TR::UndoUpdateNote,
|
||||||
|
UndoableOpKind::UpdatePreferences => TR::PreferencesPreferences,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.i18n.tr(key).to_string()
|
self.i18n.tr(key).to_string()
|
||||||
|
Loading…
Reference in New Issue
Block a user