diff --git a/aqt/addcards.py b/aqt/addcards.py index 8f82e92e1..c196c1c94 100644 --- a/aqt/addcards.py +++ b/aqt/addcards.py @@ -34,7 +34,8 @@ class AddCards(QDialog): addHook('currentModelChanged', self.onModelChange) addCloseShortcut(self) self.show() - self.setupNewNote() + n = self.mw.col.newNote() + self.setAndFocusNote(n) def setupEditor(self): self.editor = aqt.editor.Editor( @@ -79,15 +80,12 @@ class AddCards(QDialog): b.setEnabled(False) self.historyButton = b - def setupNewNote(self, set=True): - f = self.mw.col.newNote() - if set: - self.editor.setNote(f, focus=True) - return f + def setAndFocusNote(self, note): + self.editor.setNote(note, focusTo=0) def onModelChange(self): oldNote = self.editor.note - note = self.setupNewNote(set=False) + note = self.mw.col.newNote() if oldNote: oldFields = list(oldNote.keys()) newFields = list(note.keys()) @@ -107,12 +105,11 @@ class AddCards(QDialog): except IndexError: pass self.removeTempNote(oldNote) - self.editor.currentField = 0 - self.editor.setNote(note, focus=True) + self.editor.setNote(note) def onReset(self, model=None, keep=False): oldNote = self.editor.note - note = self.setupNewNote(set=False) + note = self.mw.col.newNote() flds = note.model()['flds'] # copy fields from old note if oldNote: @@ -126,8 +123,7 @@ class AddCards(QDialog): note.fields[n] = "" except IndexError: break - self.editor.currentField = 0 - self.editor.setNote(note, focus=True) + self.setAndFocusNote(note) def removeTempNote(self, note): if not note or not note.id: diff --git a/aqt/browser.py b/aqt/browser.py index 523d5dd35..f7758c94a 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -359,6 +359,7 @@ class Browser(QMainWindow): self.col = self.mw.col self.forceClose = False self.lastFilter = "" + self.focusTo = None self._previewWindow = None self._closeEventHasCleanedUp = False self.form = aqt.forms.browser.Ui_Dialog() @@ -611,7 +612,6 @@ class Browser(QMainWindow): def setupEditor(self): self.editor = aqt.editor.Editor( self.mw, self.form.fieldsArea, self) - self.editor.stealFocus = False def onRowChanged(self, current, previous): "Update current note and hide/show editor." @@ -627,7 +627,8 @@ class Browser(QMainWindow): else: self.card = self.model.getCard( self.form.tableView.selectionModel().currentIndex()) - self.editor.setNote(self.card.note(reload=True)) + self.editor.setNote(self.card.note(reload=True), focusTo=self.focusTo) + self.focusTo = None self.editor.card = self.card self.singleCard = True self._renderPreview(True) @@ -1517,34 +1518,25 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, tv = self.form.tableView if idx is None: idx = tv.moveCursor(dir, self.mw.app.keyboardModifiers()) - tv.selectionModel().clear() - tv.setCurrentIndex(idx) + tv.selectionModel().setCurrentIndex( + idx, + QItemSelectionModel.Clear| + QItemSelectionModel.Select| + QItemSelectionModel.Rows) def onPreviousCard(self): + self.focusTo = self.editor.currentField self.editor.saveNow(self._onPreviousCard) def _onPreviousCard(self): - tagfocus = self.editor.tags.hasFocus() - f = self.editor.currentField self._moveCur(QAbstractItemView.MoveUp) - if tagfocus: - self.editor.tags.setFocus() - return - self.editor.web.setFocus() - self.editor.web.eval("focusField(%d)" % f) def onNextCard(self): + self.focusTo = self.editor.currentField self.editor.saveNow(self._onNextCard) def _onNextCard(self): - tagfocus = self.editor.tags.hasFocus() - f = self.editor.currentField self._moveCur(QAbstractItemView.MoveDown) - if tagfocus: - self.editor.tags.setFocus() - return - self.editor.web.setFocus() - self.editor.web.eval("focusField(%d)" % f) def onFirstCard(self): sm = self.form.tableView.selectionModel() @@ -1574,7 +1566,6 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, self.form.searchEdit.lineEdit().selectAll() def onNote(self): - self.editor.focus() self.editor.web.setFocus() self.editor.web.eval("focusField(0);") diff --git a/aqt/editcurrent.py b/aqt/editcurrent.py index d84d68ef7..13c4ac991 100644 --- a/aqt/editcurrent.py +++ b/aqt/editcurrent.py @@ -26,7 +26,7 @@ class EditCurrent(QDialog): self.form.buttonBox.button(QDialogButtonBox.Close).setShortcut( QKeySequence("Ctrl+Return")) self.editor = aqt.editor.Editor(self.mw, self.form.fieldsArea, self) - self.editor.setNote(self.mw.reviewer.card.note()) + self.editor.setNote(self.mw.reviewer.card.note(), focusTo=0) restoreGeom(self, "editcurrent") addHook("reset", self.onReset) self.mw.requireReset() diff --git a/aqt/editor.py b/aqt/editor.py index 337428881..7323f3b29 100644 --- a/aqt/editor.py +++ b/aqt/editor.py @@ -42,10 +42,8 @@ class Editor: self.widget = widget self.parentWindow = parentWindow self.note = None - self.stealFocus = True self.addMode = addMode - self._loaded = False - self.currentField = 0 + self.currentField = None # current card, for card layout self.card = None self.setupOuter() @@ -69,7 +67,6 @@ class Editor: self.web.allowDrops = True self.web.onBridgeCmd = self.onBridgeCmd self.outerLayout.addWidget(self.web, 1) - self.web.onLoadFinished = self._loadFinished righttopbtns = list() righttopbtns.append(self._addButton('text_bold', 'bold', "Bold text (Ctrl+B)", id='bold')) @@ -158,7 +155,7 @@ class Editor: ("Ctrl+T, E", self.insertLatexEqn), ("Ctrl+T, M", self.insertLatexMathEnv), ("Ctrl+Shift+X", self.onHtmlEdit), - ("Ctrl+Shift+T", lambda: self.tags.setFocus), + ("Ctrl+Shift+T", self.onFocusTags) ] runFilter("setupEditorShortcuts", cuts) for keys, fn in cuts: @@ -194,7 +191,8 @@ class Editor: return # focus lost or key/button pressed? if cmd.startswith("blur") or cmd.startswith("key"): - (type, txt) = cmd.split(":", 1) + (type, ord, txt) = cmd.split(":", 2) + ord = int(ord) txt = urllib.parse.unquote(txt) txt = unicodedata.normalize("NFC", txt) txt = self.mungeHTML(txt) @@ -202,22 +200,18 @@ class Editor: txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = self.mw.col.media.escapeImages(txt, unescape=True) - self.note.fields[self.currentField] = txt + self.note.fields[ord] = txt if not self.addMode: self.note.flush() self.mw.requireReset() if type == "blur": + self.currentField = None # run any filters if runFilter( - "editFocusLost", False, self.note, self.currentField): - # something updated the note; schedule reload - def onUpdate(): - if not self.note: - return - self.stealFocus = True - self.loadNote() - self.checkValid() - self.mw.progress.timer(100, onUpdate, False) + "editFocusLost", False, self.note, ord): + # something updated the note; update it after a subsequent focus + # event has had time to fire + self.mw.progress.timer(100, self.loadNote, False) else: self.checkValid() else: @@ -241,58 +235,42 @@ class Editor: # Setting/unsetting the current note ###################################################################### - def _loadFinished(self): - self._loaded = True - - # setup colour button - self.setupForegroundButton() - - if self.note: - self.loadNote() - - def setNote(self, note, hide=True, focus=False): + def setNote(self, note, hide=True, focusTo=None): "Make NOTE the current note." self.note = note - self.currentField = 0 - if focus: - self.stealFocus = True + self.currentField = None if self.note: - self.loadNote() + self.loadNote(focusTo=focusTo) else: self.hideCompleters() if hide: self.widget.hide() - def loadNote(self): + def loadNote(self, focusTo=None): if not self.note: return - if self.stealFocus: - field = self.currentField - else: - field = -1 - if not self._loaded: - # will be loaded when page is ready - return + data = [] for fld, val in list(self.note.items()): data.append((fld, self.mw.col.media.escapeImages(val))) - self.web.eval("setFields(%s, %d, %s);" % ( - json.dumps(data), field, json.dumps(self.prewrapMode()))) - self.web.eval("setFonts(%s);" % ( - json.dumps(self.fonts()))) - self.checkValid() - self.updateTags() self.widget.show() - if self.stealFocus: - self.web.setFocus() - self.stealFocus = False + self.updateTags() + + def oncallback(arg): + if not self.note: + return + self.setupForegroundButton() + self.checkValid() + runHook("loadNote", self) + + self.web.evalWithCallback("setFields(%s, %s); setFonts(%s); focusField(%s)" % ( + json.dumps(data), json.dumps(self.prewrapMode()), + json.dumps(self.fonts()), json.dumps(focusTo)), + oncallback) def prewrapMode(self): return self.note.model().get('prewrap', False) - def focus(self): - self.web.setFocus() - def fonts(self): return [(f['font'], f['size'], f['rtl']) for f in self.note.model()['flds']] @@ -339,14 +317,15 @@ class Editor: ###################################################################### def onHtmlEdit(self): - self.saveNow(self._onHtmlEdit) + field = self.currentField + self.saveNow(lambda: self._onHtmlEdit(field)) - def _onHtmlEdit(self): + def _onHtmlEdit(self, field): d = QDialog(self.widget) form = aqt.forms.edithtml.Ui_Dialog() form.setupUi(d) form.buttonBox.helpRequested.connect(lambda: openHelp("editor")) - form.textEdit.setPlainText(self.note.fields[self.currentField]) + form.textEdit.setPlainText(self.note.fields[field]) form.textEdit.moveCursor(QTextCursor.End) d.exec_() html = form.textEdit.toPlainText() @@ -355,11 +334,8 @@ class Editor: with warnings.catch_warnings() as w: warnings.simplefilter('ignore', UserWarning) html = str(BeautifulSoup(html, "html.parser")) - self.note.fields[self.currentField] = html - self.loadNote() - # focus field so it's saved - self.web.setFocus() - self.web.eval("focusField(%d);" % self.currentField) + self.note.fields[field] = html + self.loadNote(focusTo=field) # Tag handling ###################################################################### @@ -408,6 +384,9 @@ class Editor: def hideCompleters(self): self.tags.hideCompleter() + def onFocusTags(self): + self.tags.setFocus() + # Format buttons ###################################################################### diff --git a/web/editor.js b/web/editor.js index 8cea6eaf2..fdecef864 100644 --- a/web/editor.js +++ b/web/editor.js @@ -33,6 +33,11 @@ function onKey() { insertNewline(); return; } + // shift+tab goes to previous field + if (window.event.which === 9 && window.event.shiftKey) { + focusPrevious(); + return; + } clearChangeTimer(); changeTimer = setTimeout(function () { updateButtonState(); @@ -120,7 +125,7 @@ function clearChangeTimer() { function onFocus(elem) { currentField = elem; - pycmd("focus:" + currentField.id.substring(1)); + pycmd("focus:" + currentFieldOrdinal()); enableButtons(); // don't adjust cursor on mouse clicks if (mouseDown) { @@ -145,9 +150,22 @@ function onFocus(elem) { } function focusField(n) { + if (n === null) { + return; + } $("#f" + n).focus(); } +function focusPrevious() { + if (!currentField) { + return; + } + var previous = currentFieldOrdinal() - 1; + if (previous >= 0) { + focusField(previous); + } +} + function onDragOver(elem) { // if we focus the target element immediately, the drag&drop turns into a // copy, so note it down for later instead @@ -175,6 +193,7 @@ function caretToEnd() { function onBlur() { if (currentField) { saveField("blur"); + currentField = null; } clearChangeTimer(); disableButtons(); @@ -186,10 +205,14 @@ function saveField(type) { return; } // type is either 'blur' or 'key' - pycmd(type + ":" + currentField.innerHTML); + pycmd(type + ":" + currentFieldOrdinal() + ":" + currentField.innerHTML); clearChangeTimer(); } +function currentFieldOrdinal() { + return currentField.id.substring(1); +} + function wrappedExceptForWhitespace(text, front, back) { var match = text.match(/^(\s*)([^]*?)(\s*)$/); return match[1] + front + match[2] + back + match[3]; @@ -234,7 +257,7 @@ function wrap(front, back) { } } -function setFields(fields, focusTo, prewrap) { +function setFields(fields, prewrap) { var txt = ""; for (var i = 0; i < fields.length; i++) { var n = fields[i][0]; @@ -250,12 +273,6 @@ function setFields(fields, focusTo, prewrap) { txt += ""; } $("#fields").html("" + txt + "
"); - if (!focusTo) { - focusTo = 0; - } - if (focusTo >= 0) { - $("#f" + focusTo).focus(); - } maybeDisableButtons(); prewrapMode = prewrap; if (prewrap) {