mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
move previewer to a different class.
This uses exactly the same code, with one exception. In the previewer `self` became `self.parent` in order to have action on the browser. And in the browser, some `self` become `self.previewer` to access the previewer. (Some function having an action on the previewer starting from the browser now are separated in two. One version in the previewer doing the same thing. One version in the browser, calling the version in the previewer if it exists.) Preview dialog now takes a QWidget in general, not necesarrily a Browser. The parameter is called parent
This commit is contained in:
parent
b5f0f459ce
commit
45ccd4aa3c
2 changed files with 269 additions and 226 deletions
|
@ -5,8 +5,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import json
|
||||
import re
|
||||
import sre_constants
|
||||
import time
|
||||
import unicodedata
|
||||
|
@ -16,6 +14,7 @@ from operator import itemgetter
|
|||
from typing import Callable, List, Optional, Sequence, Union
|
||||
|
||||
import anki
|
||||
import aqt
|
||||
import aqt.forms
|
||||
from anki import hooks
|
||||
from anki.cards import Card
|
||||
|
@ -29,8 +28,8 @@ from anki.utils import htmlToTextLine, ids2str, intTime, isMac, isWin
|
|||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.editor import Editor
|
||||
from aqt.exporting import ExportDialog
|
||||
from aqt.previewer import Previewer
|
||||
from aqt.qt import *
|
||||
from aqt.sound import av_player, play_clicked_audio
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import (
|
||||
MenuList,
|
||||
|
@ -57,12 +56,6 @@ from aqt.utils import (
|
|||
from aqt.webview import AnkiWebView
|
||||
|
||||
|
||||
@dataclass
|
||||
class PreviewDialog:
|
||||
dialog: QDialog
|
||||
browser: Browser
|
||||
|
||||
|
||||
@dataclass
|
||||
class FindDupesDialog:
|
||||
dialog: QDialog
|
||||
|
@ -583,7 +576,7 @@ class Browser(QMainWindow):
|
|||
self.col = self.mw.col
|
||||
self.lastFilter = ""
|
||||
self.focusTo = None
|
||||
self._previewWindow = None
|
||||
self._previewer = None
|
||||
self._closeEventHasCleanedUp = False
|
||||
self.form = aqt.forms.browser.Ui_Dialog()
|
||||
self.form.setupUi(self)
|
||||
|
@ -1569,229 +1562,22 @@ where id in %s"""
|
|||
# Preview
|
||||
######################################################################
|
||||
|
||||
_previewTimer = None
|
||||
_lastPreviewRender: Union[int, float] = 0
|
||||
_lastPreviewState = None
|
||||
_previewCardChanged = False
|
||||
|
||||
def onTogglePreview(self):
|
||||
if self._previewWindow:
|
||||
self._closePreview()
|
||||
if self._previewer:
|
||||
self._previewer._closePreview()
|
||||
self._previewer = None
|
||||
else:
|
||||
self._openPreview()
|
||||
|
||||
def _openPreview(self):
|
||||
self._previewState = "question"
|
||||
self._lastPreviewState = None
|
||||
self._previewWindow = QDialog(None, Qt.Window)
|
||||
self._previewWindow.setWindowTitle(_("Preview"))
|
||||
|
||||
self._previewWindow.finished.connect(self._onPreviewFinished)
|
||||
self._previewWindow.silentlyClose = True
|
||||
vbox = QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._previewWeb = AnkiWebView(title="previewer")
|
||||
vbox.addWidget(self._previewWeb)
|
||||
bbox = QDialogButtonBox()
|
||||
|
||||
self._previewReplay = bbox.addButton(
|
||||
_("Replay Audio"), QDialogButtonBox.ActionRole
|
||||
)
|
||||
self._previewReplay.setAutoDefault(False)
|
||||
self._previewReplay.setShortcut(QKeySequence("R"))
|
||||
self._previewReplay.setToolTip(_("Shortcut key: %s" % "R"))
|
||||
|
||||
self._previewPrev = bbox.addButton("<", QDialogButtonBox.ActionRole)
|
||||
self._previewPrev.setAutoDefault(False)
|
||||
self._previewPrev.setShortcut(QKeySequence("Left"))
|
||||
self._previewPrev.setToolTip(_("Shortcut key: Left arrow"))
|
||||
|
||||
self._previewNext = bbox.addButton(">", QDialogButtonBox.ActionRole)
|
||||
self._previewNext.setAutoDefault(True)
|
||||
self._previewNext.setShortcut(QKeySequence("Right"))
|
||||
self._previewNext.setToolTip(_("Shortcut key: Right arrow or Enter"))
|
||||
|
||||
self._previewPrev.clicked.connect(self._onPreviewPrev)
|
||||
self._previewNext.clicked.connect(self._onPreviewNext)
|
||||
self._previewReplay.clicked.connect(self._onReplayAudio)
|
||||
|
||||
self.previewShowBothSides = QCheckBox(_("Show Both Sides"))
|
||||
self.previewShowBothSides.setShortcut(QKeySequence("B"))
|
||||
self.previewShowBothSides.setToolTip(_("Shortcut key: %s" % "B"))
|
||||
bbox.addButton(self.previewShowBothSides, QDialogButtonBox.ActionRole)
|
||||
self._previewBothSides = self.col.conf.get("previewBothSides", False)
|
||||
self.previewShowBothSides.setChecked(self._previewBothSides)
|
||||
self.previewShowBothSides.toggled.connect(self._onPreviewShowBothSides)
|
||||
|
||||
self._setupPreviewWebview()
|
||||
|
||||
vbox.addWidget(bbox)
|
||||
self._previewWindow.setLayout(vbox)
|
||||
restoreGeom(self._previewWindow, "preview")
|
||||
self._previewWindow.show()
|
||||
self._renderPreview(True)
|
||||
|
||||
def _onPreviewFinished(self, ok):
|
||||
saveGeom(self._previewWindow, "preview")
|
||||
self.mw.progress.timer(100, self._onClosePreview, False)
|
||||
self.form.previewButton.setChecked(False)
|
||||
|
||||
def _onPreviewPrev(self):
|
||||
if self._previewState == "answer" and not self._previewBothSides:
|
||||
self._previewState = "question"
|
||||
self._renderPreview()
|
||||
else:
|
||||
self.editor.saveNow(lambda: self._moveCur(QAbstractItemView.MoveUp))
|
||||
|
||||
def _onPreviewNext(self):
|
||||
if self._previewState == "question":
|
||||
self._previewState = "answer"
|
||||
self._renderPreview()
|
||||
else:
|
||||
self.editor.saveNow(lambda: self._moveCur(QAbstractItemView.MoveDown))
|
||||
|
||||
def _onReplayAudio(self):
|
||||
self.mw.reviewer.replayAudio(self)
|
||||
|
||||
def _updatePreviewButtons(self):
|
||||
if not self._previewWindow:
|
||||
return
|
||||
current = self.currentRow()
|
||||
canBack = current > 0 or (
|
||||
current == 0
|
||||
and self._previewState == "answer"
|
||||
and not self._previewBothSides
|
||||
)
|
||||
self._previewPrev.setEnabled(bool(self.singleCard and canBack))
|
||||
canForward = (
|
||||
self.currentRow() < self.model.rowCount(None) - 1
|
||||
or self._previewState == "question"
|
||||
)
|
||||
self._previewNext.setEnabled(bool(self.singleCard and canForward))
|
||||
|
||||
def _closePreview(self):
|
||||
if self._previewWindow:
|
||||
self._previewWindow.close()
|
||||
self._onClosePreview()
|
||||
|
||||
def _onClosePreview(self):
|
||||
self._previewWindow = self._previewPrev = self._previewNext = None
|
||||
|
||||
def _setupPreviewWebview(self):
|
||||
jsinc = [
|
||||
"jquery.js",
|
||||
"browsersel.js",
|
||||
"mathjax/conf.js",
|
||||
"mathjax/MathJax.js",
|
||||
"reviewer.js",
|
||||
]
|
||||
web_context = PreviewDialog(dialog=self._previewWindow, browser=self)
|
||||
self._previewWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(),
|
||||
css=["reviewer.css"],
|
||||
js=jsinc,
|
||||
context=web_context,
|
||||
)
|
||||
self._previewWeb.set_bridge_command(
|
||||
self._on_preview_bridge_cmd, web_context,
|
||||
)
|
||||
|
||||
def _on_preview_bridge_cmd(self, cmd: str) -> Any:
|
||||
if cmd.startswith("play:"):
|
||||
play_clicked_audio(cmd, self.card)
|
||||
self._previewer = Previewer(self, self.mw)
|
||||
self._previewer._openPreview()
|
||||
|
||||
def _renderPreview(self, cardChanged=False):
|
||||
self._cancelPreviewTimer()
|
||||
# Keep track of whether _renderPreview() has ever been called
|
||||
# with cardChanged=True since the last successful render
|
||||
self._previewCardChanged |= cardChanged
|
||||
# avoid rendering in quick succession
|
||||
elapMS = int((time.time() - self._lastPreviewRender) * 1000)
|
||||
delay = 300
|
||||
if elapMS < delay:
|
||||
self._previewTimer = self.mw.progress.timer(
|
||||
delay - elapMS, self._renderScheduledPreview, False
|
||||
)
|
||||
else:
|
||||
self._renderScheduledPreview()
|
||||
if self._previewer:
|
||||
self._previewer._renderPreview(cardChanged)
|
||||
|
||||
def _cancelPreviewTimer(self):
|
||||
if self._previewTimer:
|
||||
self._previewTimer.stop()
|
||||
self._previewTimer = None
|
||||
|
||||
def _renderScheduledPreview(self) -> None:
|
||||
self._cancelPreviewTimer()
|
||||
self._lastPreviewRender = time.time()
|
||||
|
||||
if not self._previewWindow:
|
||||
return
|
||||
c = self.card
|
||||
func = "_showQuestion"
|
||||
if not c or not self.singleCard:
|
||||
txt = _("(please select 1 card)")
|
||||
bodyclass = ""
|
||||
self._lastPreviewState = None
|
||||
else:
|
||||
if self._previewBothSides:
|
||||
self._previewState = "answer"
|
||||
elif self._previewCardChanged:
|
||||
self._previewState = "question"
|
||||
|
||||
currentState = self._previewStateAndMod()
|
||||
if currentState == self._lastPreviewState:
|
||||
# nothing has changed, avoid refreshing
|
||||
return
|
||||
|
||||
# need to force reload even if answer
|
||||
txt = c.q(reload=True)
|
||||
|
||||
if self._previewState == "answer":
|
||||
func = "_showAnswer"
|
||||
txt = c.a()
|
||||
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
|
||||
|
||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||
|
||||
if self.mw.reviewer.autoplay(c):
|
||||
if self._previewBothSides:
|
||||
# if we're showing both sides at once, remove any audio
|
||||
# from the answer that's appeared on the question already
|
||||
question_audio = c.question_av_tags()
|
||||
only_on_answer_audio = [
|
||||
x for x in c.answer_av_tags() if x not in question_audio
|
||||
]
|
||||
audio = question_audio + only_on_answer_audio
|
||||
elif self._previewState == "question":
|
||||
audio = c.question_av_tags()
|
||||
else:
|
||||
audio = c.answer_av_tags()
|
||||
av_player.play_tags(audio)
|
||||
else:
|
||||
av_player.clear_queue_and_maybe_interrupt()
|
||||
|
||||
txt = self.mw.prepare_card_text_for_display(txt)
|
||||
txt = gui_hooks.card_will_show(
|
||||
txt, c, "preview" + self._previewState.capitalize()
|
||||
)
|
||||
self._lastPreviewState = self._previewStateAndMod()
|
||||
self._updatePreviewButtons()
|
||||
self._previewWeb.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass))
|
||||
self._previewCardChanged = False
|
||||
|
||||
def _onPreviewShowBothSides(self, toggle):
|
||||
self._previewBothSides = toggle
|
||||
self.col.conf["previewBothSides"] = toggle
|
||||
self.col.setMod()
|
||||
if self._previewState == "answer" and not toggle:
|
||||
self._previewState = "question"
|
||||
self._renderPreview()
|
||||
|
||||
def _previewStateAndMod(self):
|
||||
c = self.card
|
||||
n = c.note()
|
||||
n.load()
|
||||
return (self._previewState, c.id, n.mod)
|
||||
if self._previewer:
|
||||
self._previewer._cancelPreviewTimer()
|
||||
|
||||
# Card deletion
|
||||
######################################################################
|
||||
|
|
257
qt/aqt/previewer.py
Normal file
257
qt/aqt/previewer.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
import json
|
||||
import re
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Union
|
||||
|
||||
from anki.lang import _
|
||||
from aqt import AnkiQt, gui_hooks
|
||||
from aqt.qt import (
|
||||
QAbstractItemView,
|
||||
QCheckBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QKeySequence,
|
||||
Qt,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from aqt.sound import av_player, play_clicked_audio
|
||||
from aqt.theme import theme_manager
|
||||
from aqt.utils import restoreGeom, saveGeom
|
||||
from aqt.webview import AnkiWebView
|
||||
|
||||
|
||||
@dataclass
|
||||
class PreviewDialog:
|
||||
dialog: QDialog
|
||||
parent: QWidget
|
||||
|
||||
|
||||
class Previewer:
|
||||
_lastPreviewState = None
|
||||
_previewCardChanged = False
|
||||
_lastPreviewRender: Union[int, float] = 0
|
||||
_previewTimer = None
|
||||
|
||||
def __init__(self, parent: QWidget, mw: AnkiQt):
|
||||
self.parent = parent
|
||||
self.mw = mw
|
||||
|
||||
def _openPreview(self):
|
||||
self._previewState = "question"
|
||||
self._lastPreviewState = None
|
||||
self._previewWindow = QDialog(None, Qt.Window)
|
||||
self._previewWindow.setWindowTitle(_("Preview"))
|
||||
|
||||
self._previewWindow.finished.connect(self._onPreviewFinished)
|
||||
self._previewWindow.silentlyClose = True
|
||||
vbox = QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._previewWeb = AnkiWebView(title="previewer")
|
||||
vbox.addWidget(self._previewWeb)
|
||||
bbox = QDialogButtonBox()
|
||||
|
||||
self._previewReplay = bbox.addButton(
|
||||
_("Replay Audio"), QDialogButtonBox.ActionRole
|
||||
)
|
||||
self._previewReplay.setAutoDefault(False)
|
||||
self._previewReplay.setShortcut(QKeySequence("R"))
|
||||
self._previewReplay.setToolTip(_("Shortcut key: %s" % "R"))
|
||||
|
||||
self._previewPrev = bbox.addButton("<", QDialogButtonBox.ActionRole)
|
||||
self._previewPrev.setAutoDefault(False)
|
||||
self._previewPrev.setShortcut(QKeySequence("Left"))
|
||||
self._previewPrev.setToolTip(_("Shortcut key: Left arrow"))
|
||||
|
||||
self._previewNext = bbox.addButton(">", QDialogButtonBox.ActionRole)
|
||||
self._previewNext.setAutoDefault(True)
|
||||
self._previewNext.setShortcut(QKeySequence("Right"))
|
||||
self._previewNext.setToolTip(_("Shortcut key: Right arrow or Enter"))
|
||||
|
||||
self._previewPrev.clicked.connect(self._onPreviewPrev)
|
||||
self._previewNext.clicked.connect(self._onPreviewNext)
|
||||
self._previewReplay.clicked.connect(self._onReplayAudio)
|
||||
|
||||
self.previewShowBothSides = QCheckBox(_("Show Both Sides"))
|
||||
self.previewShowBothSides.setShortcut(QKeySequence("B"))
|
||||
self.previewShowBothSides.setToolTip(_("Shortcut key: %s" % "B"))
|
||||
bbox.addButton(self.previewShowBothSides, QDialogButtonBox.ActionRole)
|
||||
self._previewBothSides = self.mw.col.conf.get("previewBothSides", False)
|
||||
self.previewShowBothSides.setChecked(self._previewBothSides)
|
||||
self.previewShowBothSides.toggled.connect(self._onPreviewShowBothSides)
|
||||
|
||||
self._setupPreviewWebview()
|
||||
|
||||
vbox.addWidget(bbox)
|
||||
self._previewWindow.setLayout(vbox)
|
||||
restoreGeom(self._previewWindow, "preview")
|
||||
self._previewWindow.show()
|
||||
self._renderPreview(True)
|
||||
|
||||
def _onPreviewFinished(self, ok):
|
||||
saveGeom(self._previewWindow, "preview")
|
||||
self.mw.progress.timer(100, self._onClosePreview, False)
|
||||
self.parent.form.previewButton.setChecked(False)
|
||||
|
||||
def _onPreviewPrev(self):
|
||||
if self._previewState == "answer" and not self._previewBothSides:
|
||||
self._previewState = "question"
|
||||
self._renderPreview()
|
||||
else:
|
||||
self.parent.editor.saveNow(
|
||||
lambda: self.parent._moveCur(QAbstractItemView.MoveUp)
|
||||
)
|
||||
|
||||
def _onPreviewNext(self):
|
||||
if self._previewState == "question":
|
||||
self._previewState = "answer"
|
||||
self._renderPreview()
|
||||
else:
|
||||
self.parent.editor.saveNow(
|
||||
lambda: self.parent._moveCur(QAbstractItemView.MoveDown)
|
||||
)
|
||||
|
||||
def _onReplayAudio(self):
|
||||
self.mw.reviewer.replayAudio(self)
|
||||
|
||||
def _updatePreviewButtons(self):
|
||||
if not self._previewWindow:
|
||||
return
|
||||
current = self.parent.currentRow()
|
||||
canBack = current > 0 or (
|
||||
current == 0
|
||||
and self._previewState == "answer"
|
||||
and not self._previewBothSides
|
||||
)
|
||||
self._previewPrev.setEnabled(bool(self.parent.singleCard and canBack))
|
||||
canForward = (
|
||||
self.parent.currentRow() < self.parent.model.rowCount(None) - 1
|
||||
or self._previewState == "question"
|
||||
)
|
||||
self._previewNext.setEnabled(bool(self.parent.singleCard and canForward))
|
||||
|
||||
def _closePreview(self):
|
||||
if self._previewWindow:
|
||||
self._previewWindow.close()
|
||||
self._onClosePreview()
|
||||
|
||||
def _onClosePreview(self):
|
||||
self.parent.previewer = None
|
||||
self._previewWindow = self._previewPrev = self._previewNext = None
|
||||
|
||||
def _setupPreviewWebview(self):
|
||||
jsinc = [
|
||||
"jquery.js",
|
||||
"browsersel.js",
|
||||
"mathjax/conf.js",
|
||||
"mathjax/MathJax.js",
|
||||
"reviewer.js",
|
||||
]
|
||||
web_context = PreviewDialog(dialog=self._previewWindow, parent=self.parent)
|
||||
self._previewWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(),
|
||||
css=["reviewer.css"],
|
||||
js=jsinc,
|
||||
context=web_context,
|
||||
)
|
||||
self._previewWeb.set_bridge_command(
|
||||
self._on_preview_bridge_cmd, web_context,
|
||||
)
|
||||
|
||||
def _on_preview_bridge_cmd(self, cmd: str) -> Any:
|
||||
if cmd.startswith("play:"):
|
||||
play_clicked_audio(cmd, self.parent.card)
|
||||
|
||||
def _renderPreview(self, cardChanged=False):
|
||||
self._cancelPreviewTimer()
|
||||
# Keep track of whether _renderPreview() has ever been called
|
||||
# with cardChanged=True since the last successful render
|
||||
self._previewCardChanged |= cardChanged
|
||||
# avoid rendering in quick succession
|
||||
elapMS = int((time.time() - self._lastPreviewRender) * 1000)
|
||||
delay = 300
|
||||
if elapMS < delay:
|
||||
self._previewTimer = self.mw.progress.timer(
|
||||
delay - elapMS, self._renderScheduledPreview, False
|
||||
)
|
||||
else:
|
||||
self._renderScheduledPreview()
|
||||
|
||||
def _cancelPreviewTimer(self):
|
||||
if self._previewTimer:
|
||||
self._previewTimer.stop()
|
||||
self._previewTimer = None
|
||||
|
||||
def _renderScheduledPreview(self) -> None:
|
||||
self._cancelPreviewTimer()
|
||||
self._lastPreviewRender = time.time()
|
||||
|
||||
if not self._previewWindow:
|
||||
return
|
||||
c = self.parent.card
|
||||
func = "_showQuestion"
|
||||
if not c or not self.parent.singleCard:
|
||||
txt = _("(please select 1 card)")
|
||||
bodyclass = ""
|
||||
self._lastPreviewState = None
|
||||
else:
|
||||
if self._previewBothSides:
|
||||
self._previewState = "answer"
|
||||
elif self._previewCardChanged:
|
||||
self._previewState = "question"
|
||||
|
||||
currentState = self._previewStateAndMod()
|
||||
if currentState == self._lastPreviewState:
|
||||
# nothing has changed, avoid refreshing
|
||||
return
|
||||
|
||||
# need to force reload even if answer
|
||||
txt = c.q(reload=True)
|
||||
|
||||
if self._previewState == "answer":
|
||||
func = "_showAnswer"
|
||||
txt = c.a()
|
||||
txt = re.sub(r"\[\[type:[^]]+\]\]", "", txt)
|
||||
|
||||
bodyclass = theme_manager.body_classes_for_card_ord(c.ord)
|
||||
|
||||
if self.mw.reviewer.autoplay(c):
|
||||
if self._previewBothSides:
|
||||
# if we're showing both sides at once, remove any audio
|
||||
# from the answer that's appeared on the question already
|
||||
question_audio = c.question_av_tags()
|
||||
only_on_answer_audio = [
|
||||
x for x in c.answer_av_tags() if x not in question_audio
|
||||
]
|
||||
audio = question_audio + only_on_answer_audio
|
||||
elif self._previewState == "question":
|
||||
audio = c.question_av_tags()
|
||||
else:
|
||||
audio = c.answer_av_tags()
|
||||
av_player.play_tags(audio)
|
||||
else:
|
||||
av_player.clear_queue_and_maybe_interrupt()
|
||||
|
||||
txt = self.mw.prepare_card_text_for_display(txt)
|
||||
txt = gui_hooks.card_will_show(
|
||||
txt, c, "preview" + self._previewState.capitalize()
|
||||
)
|
||||
self._lastPreviewState = self._previewStateAndMod()
|
||||
self._updatePreviewButtons()
|
||||
self._previewWeb.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass))
|
||||
self._previewCardChanged = False
|
||||
|
||||
def _onPreviewShowBothSides(self, toggle):
|
||||
self._previewBothSides = toggle
|
||||
self.mw.col.conf["previewBothSides"] = toggle
|
||||
self.mw.col.setMod()
|
||||
if self._previewState == "answer" and not toggle:
|
||||
self._previewState = "question"
|
||||
self._renderPreview()
|
||||
|
||||
def _previewStateAndMod(self):
|
||||
c = self.parent.card
|
||||
n = c.note()
|
||||
n.load()
|
||||
return (self._previewState, c.id, n.mod)
|
Loading…
Reference in a new issue