track changes in fields dialog as well
And avoid bumping schema until user actually saves, but warn at start.
This commit is contained in:
parent
a2b7a30841
commit
46c363d4aa
@ -270,7 +270,6 @@ class ModelManager:
|
||||
self.remove(m["id"])
|
||||
|
||||
def remove_all_notetypes(self):
|
||||
self.col.modSchema(check=True)
|
||||
for nt in self.all_names_and_ids():
|
||||
self._remove_from_cache(nt.id)
|
||||
self.col.backend.remove_notetype(nt.id)
|
||||
@ -345,42 +344,25 @@ class ModelManager:
|
||||
def sortIdx(self, m: NoteType) -> Any:
|
||||
return m["sortf"]
|
||||
|
||||
def setSortIdx(self, m: NoteType, idx: int) -> None:
|
||||
assert 0 <= idx < len(m["flds"])
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
m["sortf"] = idx
|
||||
|
||||
self.save(m)
|
||||
|
||||
# Adding & changing fields
|
||||
##################################################
|
||||
|
||||
def newField(self, name: str) -> Field:
|
||||
def new_field(self, name: str) -> Field:
|
||||
assert isinstance(name, str)
|
||||
f = defaultField.copy()
|
||||
f["name"] = name
|
||||
return f
|
||||
|
||||
def addField(self, m: NoteType, field: Field, save=True) -> None:
|
||||
if m["id"]:
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
def add_field(self, m: NoteType, field: Field) -> None:
|
||||
"Modifies schema."
|
||||
m["flds"].append(field)
|
||||
|
||||
if m["id"] and save:
|
||||
self.save(m)
|
||||
|
||||
def remField(self, m: NoteType, field: Field, save=True) -> None:
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
def remove_field(self, m: NoteType, field: Field) -> None:
|
||||
"Modifies schema."
|
||||
m["flds"].remove(field)
|
||||
|
||||
if save:
|
||||
self.save(m)
|
||||
|
||||
def moveField(self, m: NoteType, field: Field, idx: int, save=True) -> None:
|
||||
self.col.modSchema(check=True)
|
||||
def reposition_field(self, m: NoteType, field: Field, idx: int) -> None:
|
||||
"Modifies schema."
|
||||
oldidx = m["flds"].index(field)
|
||||
if oldidx == idx:
|
||||
return
|
||||
@ -388,48 +370,55 @@ class ModelManager:
|
||||
m["flds"].remove(field)
|
||||
m["flds"].insert(idx, field)
|
||||
|
||||
if save:
|
||||
self.save(m)
|
||||
|
||||
def renameField(self, m: NoteType, field: Field, newName: str, save=True) -> None:
|
||||
def rename_field(self, m: NoteType, field: Field, new_name: str) -> None:
|
||||
assert field in m["flds"]
|
||||
field["name"] = new_name
|
||||
|
||||
field["name"] = newName
|
||||
def set_sort_index(self, nt: NoteType, idx: int) -> None:
|
||||
"Modifies schema."
|
||||
assert 0 <= idx < len(nt["flds"])
|
||||
nt["sortf"] = idx
|
||||
|
||||
if save:
|
||||
# legacy
|
||||
|
||||
newField = new_field
|
||||
|
||||
def addField(self, m: NoteType, field: Field) -> None:
|
||||
self.add_field(m, field)
|
||||
if m["id"]:
|
||||
self.save(m)
|
||||
|
||||
def remField(self, m: NoteType, field: Field) -> None:
|
||||
self.remove_field(m, field)
|
||||
self.save(m)
|
||||
|
||||
def moveField(self, m: NoteType, field: Field, idx: int) -> None:
|
||||
self.reposition_field(m, field, idx)
|
||||
self.save(m)
|
||||
|
||||
def renameField(self, m: NoteType, field: Field, newName: str) -> None:
|
||||
self.rename_field(m, field, newName)
|
||||
self.save(m)
|
||||
|
||||
# Adding & changing templates
|
||||
##################################################
|
||||
|
||||
def newTemplate(self, name: str) -> Template:
|
||||
def new_template(self, name: str) -> Template:
|
||||
t = defaultTemplate.copy()
|
||||
t["name"] = name
|
||||
return t
|
||||
|
||||
def addTemplate(self, m: NoteType, template: Template, save=True) -> None:
|
||||
if m["id"]:
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
def add_template(self, m: NoteType, template: Template) -> None:
|
||||
"Modifies schema."
|
||||
m["tmpls"].append(template)
|
||||
|
||||
if save and m["id"]:
|
||||
self.save(m)
|
||||
|
||||
def remTemplate(self, m: NoteType, template: Template, save=True) -> None:
|
||||
def remove_template(self, m: NoteType, template: Template) -> None:
|
||||
"Modifies schema."
|
||||
assert len(m["tmpls"]) > 1
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
m["tmpls"].remove(template)
|
||||
|
||||
if save:
|
||||
self.save(m)
|
||||
|
||||
def moveTemplate(
|
||||
self, m: NoteType, template: Template, idx: int, save=True
|
||||
) -> None:
|
||||
self.col.modSchema(check=True)
|
||||
|
||||
def reposition_template(self, m: NoteType, template: Template, idx: int) -> None:
|
||||
"Modifies schema."
|
||||
oldidx = m["tmpls"].index(template)
|
||||
if oldidx == idx:
|
||||
return
|
||||
@ -437,9 +426,23 @@ class ModelManager:
|
||||
m["tmpls"].remove(template)
|
||||
m["tmpls"].insert(idx, template)
|
||||
|
||||
if save:
|
||||
# legacy
|
||||
|
||||
newTemplate = new_template
|
||||
|
||||
def addTemplate(self, m: NoteType, template: Template) -> None:
|
||||
self.add_template(m, template)
|
||||
if m["id"]:
|
||||
self.save(m)
|
||||
|
||||
def remTemplate(self, m: NoteType, template: Template) -> None:
|
||||
self.remove_template(m, template)
|
||||
self.save(m)
|
||||
|
||||
def moveTemplate(self, m: NoteType, template: Template, idx: int) -> None:
|
||||
self.reposition_template(m, template, idx)
|
||||
self.save(m)
|
||||
|
||||
# Model changing
|
||||
##########################################################################
|
||||
# - maps are ord->ord, and there should not be duplicate targets
|
||||
|
@ -16,6 +16,7 @@ from anki.rsbackend import TemplateError
|
||||
from anki.template import TemplateRenderContext
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import *
|
||||
from aqt.schema_change_tracker import ChangeTracker
|
||||
from aqt.sound import av_player, play_clicked_audio
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
@ -36,7 +37,6 @@ from aqt.webview import AnkiWebView
|
||||
|
||||
# fixme: deck name on new cards
|
||||
# fixme: card count when removing
|
||||
# fixme: change tracking and tooltip in fields
|
||||
# fixme: replay suppression
|
||||
|
||||
|
||||
@ -61,7 +61,7 @@ class CardLayout(QDialog):
|
||||
self._want_fill_empty_on = fill_empty
|
||||
self.mm._remove_from_cache(self.model["id"])
|
||||
self.mw.checkpoint(_("Card Types"))
|
||||
self.changed = False
|
||||
self.change_tracker = ChangeTracker(self.mw)
|
||||
self.setupTopArea()
|
||||
self.setupMainArea()
|
||||
self.setupButtons()
|
||||
@ -389,7 +389,7 @@ class CardLayout(QDialog):
|
||||
if self.ignore_change_signals:
|
||||
return
|
||||
|
||||
self.changed = True
|
||||
self.change_tracker.mark_basic()
|
||||
|
||||
text = self.tform.edit_area.toPlainText()
|
||||
|
||||
@ -497,8 +497,9 @@ class CardLayout(QDialog):
|
||||
if not askUser(msg):
|
||||
return
|
||||
|
||||
self.changed = True
|
||||
self.mm.remTemplate(self.model, template, save=False)
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
self.mm.remove_template(self.model, template)
|
||||
|
||||
# ensure current ordinal is within bounds
|
||||
idx = self.ord
|
||||
@ -513,7 +514,8 @@ class CardLayout(QDialog):
|
||||
if not name.strip():
|
||||
return
|
||||
|
||||
self.changed = True
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
template["name"] = name
|
||||
self.redraw_everything()
|
||||
|
||||
@ -535,8 +537,9 @@ class CardLayout(QDialog):
|
||||
if pos == current_pos:
|
||||
return
|
||||
new_idx = pos - 1
|
||||
self.changed = True
|
||||
self.mm.moveTemplate(self.model, template, new_idx, save=False)
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
self.mm.reposition_template(self.model, template, new_idx)
|
||||
self.ord = new_idx
|
||||
self.redraw_everything()
|
||||
|
||||
@ -561,13 +564,14 @@ class CardLayout(QDialog):
|
||||
)
|
||||
if not askUser(txt):
|
||||
return
|
||||
self.changed = True
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
name = self._newCardName()
|
||||
t = self.mm.newTemplate(name)
|
||||
old = self.current_template()
|
||||
t["qfmt"] = old["qfmt"]
|
||||
t["afmt"] = old["afmt"]
|
||||
self.mm.addTemplate(self.model, t, save=False)
|
||||
self.mm.add_template(self.model, t)
|
||||
self.ord = len(self.templates) - 1
|
||||
self.redraw_everything()
|
||||
|
||||
@ -587,7 +591,7 @@ adjust the template manually to switch the question and answer."""
|
||||
)
|
||||
)
|
||||
return
|
||||
self.changed = True
|
||||
self.change_tracker.mark_basic()
|
||||
dst["afmt"] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src["qfmt"]
|
||||
dst["qfmt"] = m.group(2).strip()
|
||||
return True
|
||||
@ -639,7 +643,7 @@ adjust the template manually to switch the question and answer."""
|
||||
|
||||
def onBrowserDisplayOk(self, f):
|
||||
t = self.current_template()
|
||||
self.changed = True
|
||||
self.change_tracker.mark_basic()
|
||||
t["bqfmt"] = f.qfmt.text().strip()
|
||||
t["bafmt"] = f.afmt.text().strip()
|
||||
if f.overrideFont.isChecked():
|
||||
@ -678,7 +682,7 @@ Enter deck to place new %s cards in, or leave blank:"""
|
||||
l.addWidget(bb)
|
||||
d.setLayout(l)
|
||||
d.exec_()
|
||||
self.changed = True
|
||||
self.change_tracker.mark_basic()
|
||||
if not te.text().strip():
|
||||
t["did"] = None
|
||||
else:
|
||||
@ -709,7 +713,7 @@ Enter deck to place new %s cards in, or leave blank:"""
|
||||
field,
|
||||
)
|
||||
self.tform.edit_area.setPlainText(text)
|
||||
self.changed = True
|
||||
self.change_tracker.mark_basic()
|
||||
self.write_edits_to_template_and_redraw()
|
||||
|
||||
# Closing & Help
|
||||
@ -733,7 +737,7 @@ Enter deck to place new %s cards in, or leave blank:"""
|
||||
self.mw.taskman.with_progress(save, on_done)
|
||||
|
||||
def reject(self) -> None:
|
||||
if self.changed:
|
||||
if self.change_tracker.changed():
|
||||
if not askUser("Discard changes?"):
|
||||
return
|
||||
self.cleanup()
|
||||
|
@ -8,18 +8,20 @@ from anki.models import NoteType
|
||||
from anki.rsbackend import TemplateError
|
||||
from aqt import AnkiQt
|
||||
from aqt.qt import *
|
||||
from aqt.utils import askUser, getOnlyText, openHelp, showWarning
|
||||
from aqt.schema_change_tracker import ChangeTracker
|
||||
from aqt.utils import askUser, getOnlyText, openHelp, showWarning, tooltip
|
||||
|
||||
|
||||
class FieldDialog(QDialog):
|
||||
def __init__(self, mw: AnkiQt, nt: NoteType, parent=None):
|
||||
QDialog.__init__(self, parent or mw)
|
||||
self.mw = mw.weakref()
|
||||
self.mw = mw
|
||||
self.col = self.mw.col
|
||||
self.mm = self.mw.col.models
|
||||
self.model = nt
|
||||
self.mm._remove_from_cache(self.model["id"])
|
||||
self.mw.checkpoint(_("Fields"))
|
||||
self.change_tracker = ChangeTracker(self.mw)
|
||||
self.form = aqt.forms.fields.Ui_Dialog()
|
||||
self.form.setupUi(self)
|
||||
self.setWindowTitle(_("Fields for %s") % self.model["name"])
|
||||
@ -88,7 +90,9 @@ class FieldDialog(QDialog):
|
||||
name = self._uniqueName(_("New name:"), self.currentIdx, f["name"])
|
||||
if not name:
|
||||
return
|
||||
self.mm.renameField(self.model, f, name, save=False)
|
||||
|
||||
self.change_tracker.mark_basic()
|
||||
self.mm.rename_field(self.model, f, name)
|
||||
self.saveField()
|
||||
self.fillFields()
|
||||
self.form.fieldList.setCurrentRow(idx)
|
||||
@ -97,9 +101,11 @@ class FieldDialog(QDialog):
|
||||
name = self._uniqueName(_("Field name:"))
|
||||
if not name:
|
||||
return
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
self.saveField()
|
||||
f = self.mm.newField(name)
|
||||
self.mm.addField(self.model, f, save=False)
|
||||
self.mm.add_field(self.model, f)
|
||||
self.fillFields()
|
||||
self.form.fieldList.setCurrentRow(len(self.model["flds"]) - 1)
|
||||
|
||||
@ -110,8 +116,10 @@ class FieldDialog(QDialog):
|
||||
c = ngettext("%d note", "%d notes", c) % c
|
||||
if not askUser(_("Delete field from %s?") % c):
|
||||
return
|
||||
if not self.change_tracker.mark_schema():
|
||||
return
|
||||
f = self.model["flds"][self.form.fieldList.currentRow()]
|
||||
self.mm.remField(self.model, f, save=False)
|
||||
self.mm.remove_field(self.model, f)
|
||||
self.fillFields()
|
||||
self.form.fieldList.setCurrentRow(0)
|
||||
|
||||
@ -130,14 +138,18 @@ class FieldDialog(QDialog):
|
||||
self.moveField(pos)
|
||||
|
||||
def onSortField(self):
|
||||
if not self.change_tracker.mark_schema():
|
||||
return False
|
||||
# don't allow user to disable; it makes no sense
|
||||
self.form.sortField.setChecked(True)
|
||||
self.model["sortf"] = self.form.fieldList.currentRow()
|
||||
self.mm.set_sort_index(self.model, self.form.fieldList.currentRow())
|
||||
|
||||
def moveField(self, pos):
|
||||
if not self.change_tracker.mark_schema():
|
||||
return False
|
||||
self.saveField()
|
||||
f = self.model["flds"][self.currentIdx]
|
||||
self.mm.moveField(self.model, f, pos - 1, save=False)
|
||||
self.mm.reposition_field(self.model, f, pos - 1)
|
||||
self.fillFields()
|
||||
self.form.fieldList.setCurrentRow(pos - 1)
|
||||
|
||||
@ -158,12 +170,28 @@ class FieldDialog(QDialog):
|
||||
idx = self.currentIdx
|
||||
fld = self.model["flds"][idx]
|
||||
f = self.form
|
||||
fld["font"] = f.fontFamily.currentFont().family()
|
||||
fld["size"] = f.fontSize.value()
|
||||
fld["sticky"] = f.sticky.isChecked()
|
||||
fld["rtl"] = f.rtl.isChecked()
|
||||
font = f.fontFamily.currentFont().family()
|
||||
if fld["font"] != font:
|
||||
fld["font"] = font
|
||||
self.change_tracker.mark_basic()
|
||||
size = f.fontSize.value()
|
||||
if fld["size"] != size:
|
||||
fld["size"] = size
|
||||
self.change_tracker.mark_basic()
|
||||
sticky = f.sticky.isChecked()
|
||||
if fld["sticky"] != sticky:
|
||||
fld["sticky"] = sticky
|
||||
self.change_tracker.mark_basic()
|
||||
rtl = f.rtl.isChecked()
|
||||
if fld["rtl"] != rtl:
|
||||
fld["rtl"] = rtl
|
||||
self.change_tracker.mark_basic()
|
||||
|
||||
def reject(self):
|
||||
if self.change_tracker.changed():
|
||||
if not askUser("Discard changes?"):
|
||||
return
|
||||
|
||||
QDialog.reject(self)
|
||||
|
||||
def accept(self):
|
||||
@ -180,6 +208,7 @@ class FieldDialog(QDialog):
|
||||
showWarning("Unable to save changes: " + str(e))
|
||||
return
|
||||
self.mw.reset()
|
||||
tooltip("Changes saved.", parent=self.mw)
|
||||
QDialog.accept(self)
|
||||
|
||||
self.mw.taskman.with_progress(save, on_done, self)
|
||||
|
@ -1282,6 +1282,7 @@ and if the problem comes up again, please ask on the support site."""
|
||||
# Schema modifications
|
||||
##########################################################################
|
||||
|
||||
# this will gradually be phased out
|
||||
def onSchemaMod(self, arg):
|
||||
assert self.inMainThread()
|
||||
progress_shown = self.progress.busy()
|
||||
@ -1300,6 +1301,22 @@ will be lost. Continue?"""
|
||||
self.progress.start()
|
||||
return ret
|
||||
|
||||
# in favour of this
|
||||
def confirm_schema_modification(self) -> bool:
|
||||
"""If schema unmodified, ask user to confirm change.
|
||||
True if confirmed or already modified."""
|
||||
if self.col.schemaChanged():
|
||||
return True
|
||||
return askUser(
|
||||
_(
|
||||
"""\
|
||||
The requested change will require a full upload of the database when \
|
||||
you next synchronize your collection. If you have reviews or other changes \
|
||||
waiting on another device that haven't been synchronized here yet, they \
|
||||
will be lost. Continue?"""
|
||||
)
|
||||
)
|
||||
|
||||
# Advanced features
|
||||
##########################################################################
|
||||
|
||||
|
36
qt/aqt/schema_change_tracker.py
Normal file
36
qt/aqt/schema_change_tracker.py
Normal file
@ -0,0 +1,36 @@
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
|
||||
from aqt import AnkiQt
|
||||
|
||||
|
||||
class Change(enum.Enum):
|
||||
NO_CHANGE = 0
|
||||
BASIC_CHANGE = 1
|
||||
SCHEMA_CHANGE = 2
|
||||
|
||||
|
||||
class ChangeTracker:
|
||||
_changed = Change.NO_CHANGE
|
||||
|
||||
def __init__(self, mw: AnkiQt):
|
||||
self.mw = mw
|
||||
|
||||
def mark_basic(self):
|
||||
if self._changed == Change.NO_CHANGE:
|
||||
self._changed = Change.BASIC_CHANGE
|
||||
|
||||
def mark_schema(self) -> bool:
|
||||
"If false, processing should be aborted."
|
||||
if self._changed != Change.SCHEMA_CHANGE:
|
||||
if not self.mw.confirm_schema_modification():
|
||||
return False
|
||||
self._changed = Change.SCHEMA_CHANGE
|
||||
return True
|
||||
|
||||
def changed(self) -> bool:
|
||||
return self._changed != Change.NO_CHANGE
|
Loading…
Reference in New Issue
Block a user