Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed a bug where the alarm wouldnt stop, even after answering the card. #22

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 76 additions & 94 deletions src/speed_focus_mode/reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,16 @@
import os

import aqt

from aqt.qt import QKeySequence

from aqt import mw
from aqt.reviewer import Reviewer
from aqt.utils import tooltip
from aqt import gui_hooks

from anki.hooks import addHook, wrap
from anki.sound import play
from anki.sound import AVPlayer

from .config import local_conf
from .consts import PATH_ADDON, PATH_USERFILES, JSPY_BRIDGE, ANKI20
Expand All @@ -62,8 +65,9 @@
else:
ALERT_PATH = default_alert

av_player = AVPlayer()

# WEB
# html and timeouts
###############################################################################

button_html = """
Expand All @@ -74,7 +78,7 @@
</td>
""" % local_conf["hotkeyMoreTime"]

script_bottom = """
script = """
var spdfAutoAlertTimeout = 0;
var spdfAutoAnswerTimeout = 0;
var spdfAutoActionTimeout = 0;
Expand Down Expand Up @@ -130,8 +134,7 @@

function spdfSetAutoAlert(ms) {
clearTimeout(spdfAutoAlertTimeout);
spdfAutoAlertTimeout = setTimeout(function () {
%(bridge)s("spdf:alert"); }, ms);
spdfAutoAlertTimeout = setTimeout(function () { %(bridge)s("spdf:alert"); }, ms);
}

function spdfSetAutoAnswer(ms) {
Expand All @@ -143,8 +146,7 @@
function spdfSetAutoAction(ms, action) {
spdfReset();
clearTimeout(spdfAutoActionTimeout);
spdfAutoActionTimeout = setTimeout(function () {
%(bridge)s("spdf:action"); }, ms);
spdfAutoActionTimeout = setTimeout(function () { %(bridge)s("spdf:action"); }, ms);
spdfSetCurrentTimer(spdfAutoActionTimeout, action, ms)
}

Expand All @@ -158,90 +160,33 @@
document.getElementById("middle").insertAdjacentHTML("afterend", '%(button)s')
""" % (dict(bridge=JSPY_BRIDGE, button=button_html.replace("\n", "")))

# Suspend timer when typing answer
script_reviewer = """
function spdfOnKeyup() {
%(bridge)s("spdf:typeans");
// fire only once (legacy anki20 implementation):
typeans = document.getElementById("typeans");
typeans.removeEventListener("keyup", spdfOnKeyup);
}
setTimeout(function() {
typeans = document.getElementById("typeans");
if (typeans != null) {
typeans.addEventListener("keyup", spdfOnKeyup)
}
}, 500)
""" % (dict(bridge=JSPY_BRIDGE))

def appendHTML(self, _old):
return _old(self) + """<script>%s</script>""" % script_bottom

def onShowQuestion():
if local_conf["stopWhenTypingAnswer"]:
mw.reviewer.web.eval(script_reviewer)

# PYTHON <-> JS COMMUNICATION
###############################################################################
return _old(self) + """<script>%s</script>""" % script

def linkHandler(self, url, _old):
if not url.startswith("spdf"):
return _old(self, url)
if not mw.col:
# collection unloaded, e.g. when called during pre-exit sync
return
cmd, action = url.split(":")
conf = mw.col.decks.confForDid(self.card.odid or self.card.did)

if action == "typeans":
suspendTimers()
elif action == "alert":
play(ALERT_PATH)
timeout = conf.get('autoAlert', 0)
tooltip("Wake up! You have been looking at <br>"
"the question for <b>{}</b> seconds!".format(timeout),
period=1000)
elif action == "action":
action = conf.get('autoAction', "again")

if action == "again":
if self.state == "question":
self._showAnswer()
self._answerCard(1)
elif action == "good":
if self.state == "question":
self._showAnswer()
self._answerCard(self._defaultEase())
elif action == "bury":
mw.reviewer.onBuryCard()

# TIMER HANDLING
###############################################################################

# set timeouts for auto-alert and auto-reveal
def setAnswerTimeouts(self):
c = mw.col.decks.confForDid(self.card.odid or self.card.did)
countdown_requested = False
active = False
if c.get('autoAnswer', 0) > 0:
self.bottom.web.eval("spdfSetAutoAnswer(%d);" % (c['autoAnswer'] * 1000))
active = True
if c.get('autoAlert', 0) > 0:
self.bottom.web.eval(
"spdfSetAutoAlert(%d);" % (c['autoAlert'] * 1000))

self.bottom.web.eval("spdfSetAutoAlert(%d);" % (c['autoAlert'] * 1000))
if c.get("autoSkip") and c.get('autoAgain', 0) > 0:
action = c.get('autoAction', "again").capitalize()
self.bottom.web.eval("spdfSetAutoAction(%d, '%s');" %
(c['autoAgain'] * 1000, action))
countdown_requested = True
elif c.get('autoAnswer', 0) > 0:
self.bottom.web.eval(
"spdfSetAutoAnswer(%d);" % (c['autoAnswer'] * 1000))
countdown_requested = True
else:
return

if countdown_requested and local_conf["enableMoreTimeButton"]:
active = True

if active and local_conf["enableMoreTimeButton"]:
self.bottom.web.eval("spdfShow();")
else:
self.bottom.web.eval("spdfHide();")

# set timeout for auto-action


def setQuestionTimeouts(self):
c = mw.col.decks.confForDid(self.card.odid or self.card.did)
Expand All @@ -256,6 +201,7 @@ def setQuestionTimeouts(self):
self.bottom.web.eval("spdfHide();")


# clear timeouts for auto-alert and auto-reveal, run on answer reveal
def clearAnswerTimeouts():
reviewer = mw.reviewer
c = mw.col.decks.confForDid(reviewer.card.odid or reviewer.card.did)
Expand All @@ -274,7 +220,12 @@ def clearAnswerTimeouts():
}
""")

# clear timeout for auto-action, run on next card


def clearQuestionTimeouts():
av_player.stop_and_clear_queue()

reviewer = mw.reviewer
c = mw.col.decks.confForDid(reviewer.card.odid or reviewer.card.did)
if not c.get("autoSkip"):
Expand All @@ -285,6 +236,42 @@ def clearQuestionTimeouts():
""")


# action handler
###############################################################################

def linkHandler(self, url, _old):
if not url.startswith("spdf"):
return _old(self, url)
if not mw.col:
# collection unloaded, e.g. when called during pre-exit sync
return
cmd, action = url.split(":")
conf = mw.col.decks.confForDid(self.card.odid or self.card.did)

if action == "alert":
av_player.play_file(ALERT_PATH)

timeout = conf.get('autoAlert', 0)
tooltip("Wake up! You have been looking at <br>"
"the question for <b>{}</b> seconds!".format(timeout),
period=1000)
elif action == "action":
action = conf.get('autoAction', "again")

if action == "again":
if self.state == "question":
self._showAnswer()
self._answerCard(1)
elif action == "good":
if self.state == "question":
self._showAnswer()
self._answerCard(self._defaultEase())
elif action == "bury":
mw.reviewer.onBuryCard()

# Hotkeys
###############################################################################

def suspendTimers():
if mw.state in ("review", "resetRequired"):
mw.reviewer.bottom.web.eval("""
Expand All @@ -293,46 +280,41 @@ def suspendTimers():
};
""")

def onMoreTime():
suspendTimers()
tooltip("Timer stopped.")

def onDialogOpened(self, name, *args):
"""Suspend timers when opening dialogs"""
suspendTimers()

# HOTKEYS
###############################################################################

def onReviewerStateShortcuts(shortcuts):
"""Add hint hotkey on Anki 2.1.x"""
shortcuts.append((local_conf["hotkeyMoreTime"], onMoreTime))
shortcuts.append((local_conf["hotkeyMoreTime"], suspendTimers))

def reviewerKeyHandler20(self, evt, _old):
if evt.key() == QKeySequence(local_conf["hotkeyMoreTime"])[0]:
onMoreTime()
suspendTimers()
return
return _old(self, evt)


# HOOKS
# suspend timers on specific events
###############################################################################

# would prefer to use Qt focusOutEvent, but that appears to be non-trivial
def onDialogOpened(self, name, *args):
suspendTimers()

def stopSound(*args):
av_player.stop_and_clear_queue()

def initializeReviewer():
Reviewer._linkHandler = wrap(Reviewer._linkHandler, linkHandler, "around")
Reviewer._bottomHTML = wrap(Reviewer._bottomHTML, appendHTML, 'around')
addHook("showQuestion", onShowQuestion)

Reviewer._showAnswerButton = wrap(
Reviewer._showAnswerButton, setAnswerTimeouts)
Reviewer._showEaseButtons = wrap(Reviewer._showEaseButtons,
setQuestionTimeouts)
addHook("showAnswer", clearAnswerTimeouts)
addHook("showQuestion", clearQuestionTimeouts)

aqt.DialogManager.open = wrap(aqt.DialogManager.open,
onDialogOpened, "after")

# stops audio when you return to the decks list, etc
gui_hooks.state_did_change.append(stopSound)

if ANKI20:
Reviewer._keyHandler = wrap(
Reviewer._keyHandler, reviewerKeyHandler20, "around")
Expand Down