commit
eae46b4a6f
@ -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 BrowserPreviewer
|
||||
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,21 @@ 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.close()
|
||||
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 = BrowserPreviewer(self, self.mw)
|
||||
self._previewer.open()
|
||||
|
||||
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.render(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.cancel_timer()
|
||||
|
||||
# Card deletion
|
||||
######################################################################
|
||||
|
371
qt/aqt/previewer.py
Normal file
371
qt/aqt/previewer.py
Normal file
@ -0,0 +1,371 @@
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
from typing import Any, List, Optional, Union
|
||||
|
||||
from anki.cards import Card
|
||||
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
|
||||
|
||||
|
||||
class Previewer(QDialog):
|
||||
_last_state = None
|
||||
_card_changed = False
|
||||
_last_render: Union[int, float] = 0
|
||||
_timer = None
|
||||
|
||||
def __init__(self, parent: QWidget, mw: AnkiQt):
|
||||
super().__init__(None, Qt.Window)
|
||||
self._open = True
|
||||
self._parent = parent
|
||||
self.mw = mw
|
||||
|
||||
def card(self) -> Optional[Card]:
|
||||
raise NotImplementedError
|
||||
|
||||
def open(self):
|
||||
self._state = "question"
|
||||
self._last_state = None
|
||||
self._create_gui()
|
||||
self._setup_web_view()
|
||||
self.render(True)
|
||||
self.show()
|
||||
|
||||
def _create_gui(self):
|
||||
self.setWindowTitle(_("Preview"))
|
||||
|
||||
self.finished.connect(self._onFinished)
|
||||
self.silentlyClose = True
|
||||
self.vbox = QVBoxLayout()
|
||||
self.vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._web = AnkiWebView(title="previewer")
|
||||
self.vbox.addWidget(self._web)
|
||||
self.bbox = QDialogButtonBox()
|
||||
|
||||
self._replay = self.bbox.addButton(
|
||||
_("Replay Audio"), QDialogButtonBox.ActionRole
|
||||
)
|
||||
self._replay.setAutoDefault(False)
|
||||
self._replay.setShortcut(QKeySequence("R"))
|
||||
self._replay.setToolTip(_("Shortcut key: %s" % "R"))
|
||||
self._replay.clicked.connect(self._onReplayAudio)
|
||||
|
||||
self.showBothSides = QCheckBox(_("Show Both Sides"))
|
||||
self.showBothSides.setShortcut(QKeySequence("B"))
|
||||
self.showBothSides.setToolTip(_("Shortcut key: %s" % "B"))
|
||||
self.bbox.addButton(self.showBothSides, QDialogButtonBox.ActionRole)
|
||||
self._bothSides = self.mw.col.conf.get("previewBothSides", False)
|
||||
self.showBothSides.setChecked(self._bothSides)
|
||||
self.showBothSides.toggled.connect(self._on_show_both_sides)
|
||||
|
||||
self.vbox.addWidget(self.bbox)
|
||||
self.setLayout(self.vbox)
|
||||
restoreGeom(self, "preview")
|
||||
|
||||
def _on_finished(self, ok):
|
||||
saveGeom(self, "preview")
|
||||
self.mw.progress.timer(100, self._on_close, False)
|
||||
|
||||
def _on_replay_audio(self):
|
||||
self.mw.reviewer.replayAudio(self)
|
||||
|
||||
def close(self):
|
||||
if self:
|
||||
self.close()
|
||||
self._on_close()
|
||||
|
||||
def _on_close(self):
|
||||
self._open = False
|
||||
|
||||
def _setup_web_view(self):
|
||||
jsinc = [
|
||||
"jquery.js",
|
||||
"browsersel.js",
|
||||
"mathjax/conf.js",
|
||||
"mathjax/MathJax.js",
|
||||
"reviewer.js",
|
||||
]
|
||||
self._previewWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self,
|
||||
)
|
||||
self._web.set_bridge_command(self._on_bridge_cmd, self)
|
||||
|
||||
def _on_bridge_cmd(self, cmd: str) -> Any:
|
||||
if cmd.startswith("play:"):
|
||||
play_clicked_audio(cmd, self.card())
|
||||
|
||||
def render(self, cardChanged=False):
|
||||
self.cancel_timer()
|
||||
# Keep track of whether render() has ever been called
|
||||
# with cardChanged=True since the last successful render
|
||||
self._card_changed |= cardChanged
|
||||
# avoid rendering in quick succession
|
||||
elapMS = int((time.time() - self._last_render) * 1000)
|
||||
delay = 300
|
||||
if elapMS < delay:
|
||||
self._timer = self.mw.progress.timer(
|
||||
delay - elapMS, self._render_scheduled, False
|
||||
)
|
||||
else:
|
||||
self._render_scheduled()
|
||||
|
||||
def cancel_timer(self):
|
||||
if self._timer:
|
||||
self._timer.stop()
|
||||
self._timer = None
|
||||
|
||||
def _render_scheduled(self) -> None:
|
||||
self.cancel_timer()
|
||||
self._last_render = time.time()
|
||||
|
||||
if not self._open:
|
||||
return
|
||||
c = self.card()
|
||||
func = "_showQuestion"
|
||||
if not c:
|
||||
txt = _("(please select 1 card)")
|
||||
bodyclass = ""
|
||||
self._last_state = None
|
||||
else:
|
||||
if self._bothSides:
|
||||
self._state = "answer"
|
||||
elif self._card_changed:
|
||||
self._state = "question"
|
||||
|
||||
currentState = self._state_and_mod()
|
||||
if currentState == self._last_state:
|
||||
# nothing has changed, avoid refreshing
|
||||
return
|
||||
|
||||
# need to force reload even if answer
|
||||
txt = c.q(reload=True)
|
||||
|
||||
if self._state == "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._bothSides:
|
||||
# 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._state == "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._state.capitalize())
|
||||
self._last_state = self._state_and_mod()
|
||||
self._web.eval("{}({},'{}');".format(func, json.dumps(txt), bodyclass))
|
||||
self._card_changed = False
|
||||
|
||||
def _on_show_both_sides(self, toggle):
|
||||
self._bothSides = toggle
|
||||
self.mw.col.conf["previewBothSides"] = toggle
|
||||
self.mw.col.setMod()
|
||||
if self._state == "answer" and not toggle:
|
||||
self._state = "question"
|
||||
self.render()
|
||||
|
||||
def _state_and_mod(self):
|
||||
c = self.card()
|
||||
n = c.note()
|
||||
n.load()
|
||||
return (self._state, c.id, n.mod)
|
||||
|
||||
|
||||
class MultipleCardsPreviewer(Previewer):
|
||||
def card(self) -> Optional[Card]:
|
||||
# need to state explicitly it's not implement to avoid W0223
|
||||
raise NotImplementedError
|
||||
|
||||
def _create_gui(self):
|
||||
super()._create_gui()
|
||||
self._prev = self.bbox.addButton("<", QDialogButtonBox.ActionRole)
|
||||
self._prev.setAutoDefault(False)
|
||||
self._prev.setShortcut(QKeySequence("Left"))
|
||||
self._prev.setToolTip(_("Shortcut key: Left arrow"))
|
||||
|
||||
self._next = self.bbox.addButton(">", QDialogButtonBox.ActionRole)
|
||||
self._next.setAutoDefault(True)
|
||||
self._next.setShortcut(QKeySequence("Right"))
|
||||
self._next.setToolTip(_("Shortcut key: Right arrow or Enter"))
|
||||
|
||||
self._prev.clicked.connect(self._on_prev)
|
||||
self._next.clicked.connect(self._on_next)
|
||||
|
||||
def _on_prev(self):
|
||||
if self._state == "answer" and not self._bothSides:
|
||||
self._state = "question"
|
||||
self.render()
|
||||
else:
|
||||
self._on_prev_card()
|
||||
|
||||
def _on_prev_card(self):
|
||||
...
|
||||
|
||||
def _on_next(self):
|
||||
if self._state == "question":
|
||||
self._state = "answer"
|
||||
self.render()
|
||||
else:
|
||||
self._on_next_card()
|
||||
|
||||
def _on_next_card(self):
|
||||
...
|
||||
|
||||
def _updateButtons(self):
|
||||
if not self._open:
|
||||
return
|
||||
self._prev.setEnabled(self._should_enable_prev())
|
||||
self._next.setEnabled(self._should_enable_next())
|
||||
|
||||
def _should_enable_prev(self):
|
||||
return self._state == "answer" and not self._bothSides
|
||||
|
||||
def _should_enable_next(self):
|
||||
return self._state == "question"
|
||||
|
||||
def _on_close(self):
|
||||
super()._on_close()
|
||||
self._prev = None
|
||||
self._next = None
|
||||
|
||||
|
||||
class BrowserPreviewer(MultipleCardsPreviewer):
|
||||
def card(self) -> Optional[Card]:
|
||||
if self._parent.singleCard:
|
||||
return self._parent.card
|
||||
else:
|
||||
return None
|
||||
|
||||
def _on_finished(self, ok):
|
||||
super()._on_finished(ok)
|
||||
self._parent.form.previewButton.setChecked(False)
|
||||
|
||||
def _on_prev_card(self):
|
||||
self._parent.editor.saveNow(
|
||||
lambda: self._parent._moveCur(QAbstractItemView.MoveUp)
|
||||
)
|
||||
|
||||
def _on_next_card(self):
|
||||
self._parent.editor.saveNow(
|
||||
lambda: self._parent._moveCur(QAbstractItemView.MoveDown)
|
||||
)
|
||||
|
||||
def _should_enable_prev(self):
|
||||
return super()._should_enable_prev() or self._parent.currentRow() > 0
|
||||
|
||||
def _should_enable_next(self):
|
||||
return (
|
||||
super()._should_enable_next()
|
||||
or self._parent.currentRow() < self._parent.model.rowCount(None) - 1
|
||||
)
|
||||
|
||||
def _on_close(self):
|
||||
super()._on_close()
|
||||
self._parent.previewer = None
|
||||
|
||||
def _render_scheduled(self) -> None:
|
||||
super()._render_scheduled()
|
||||
self._updateButtons()
|
||||
|
||||
|
||||
class ListCardsPreviewer(MultipleCardsPreviewer):
|
||||
def __init__(self, cards: List[Union[Card, int]], *args, **kwargs):
|
||||
"""A previewer displaying a list of card.
|
||||
|
||||
List can be changed by setting self.cards to a new value.
|
||||
|
||||
self.cards contains both cid and card. So that card is loaded
|
||||
only when required and is not loaded twice.
|
||||
|
||||
"""
|
||||
self.index = 0
|
||||
self.cards = cards
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def card(self):
|
||||
if not self.cards:
|
||||
return None
|
||||
if isinstance(self.cards[self.index], int):
|
||||
self.cards[self.index] = self.mw.col.getCard(self.cards[self.index])
|
||||
return self.cards[self.index]
|
||||
|
||||
def open(self):
|
||||
if not self.cards:
|
||||
return
|
||||
super().open()
|
||||
|
||||
def _on_prev_card(self):
|
||||
self.index -= 1
|
||||
self.render()
|
||||
|
||||
def _on_next_card(self):
|
||||
self.index += 1
|
||||
self.render()
|
||||
|
||||
def _should_enable_prev(self):
|
||||
return super()._should_enable_prev() or self.index > 0
|
||||
|
||||
def _should_enable_next(self):
|
||||
return super()._should_enable_next() or self.index < len(self.cards) - 1
|
||||
|
||||
def _on_other_side(self):
|
||||
if self._state == "question":
|
||||
self._state = "answer"
|
||||
else:
|
||||
self._state = "question"
|
||||
self.render()
|
||||
|
||||
|
||||
class SingleCardPreviewer(Previewer):
|
||||
def __init__(self, card: Card, *args, **kwargs):
|
||||
self._card = card
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def card(self) -> Card:
|
||||
return self._card
|
||||
|
||||
def _create_gui(self):
|
||||
super()._create_gui()
|
||||
self._other_side = self.bbox.addButton(
|
||||
"Other side", QDialogButtonBox.ActionRole
|
||||
)
|
||||
self._other_side.setAutoDefault(False)
|
||||
self._other_side.setShortcut(QKeySequence("Right"))
|
||||
self._other_side.setShortcut(QKeySequence("Left"))
|
||||
self._other_side.setToolTip(_("Shortcut key: Left or Right arrow"))
|
||||
self._other_side.clicked.connect(self._on_other_side)
|
||||
|
||||
def _on_other_side(self):
|
||||
if self._state == "question":
|
||||
self._state = "answer"
|
||||
else:
|
||||
self._state = "question"
|
||||
self.render()
|
Loading…
Reference in New Issue
Block a user