-
Notifications
You must be signed in to change notification settings - Fork 0
/
timed_answers.py
155 lines (115 loc) · 4.17 KB
/
timed_answers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import random
from anki.hooks import addHook, wrap
from aqt import mw
from aqt.reviewer import Reviewer
from aqt.qt import *
from aqt.deckconf import DeckConf
from aqt.forms import dconf
from aqt.utils import showInfo
LIVE_TIMER = None
timerJs = """
;(function(seconds, killHook) {
var startTime = Date.now();
var timerElement = $('<span class="answer-timer">')
.prependTo('table .stat:first')
.css('margin-left', '5px');
window.killHooks = window.killHooks || {};
window.killHooks[killHook] = function() {
timerElement.remove();
delete window.killHooks[killHook];
};
function formatTime(seconds) {
var minutes = ~~(seconds / 60),
seconds = ('0' + ~~(seconds %% 60)).substr(-2);
return minutes + ':' + seconds;
}
;(function tick() {
var timeRemaining = seconds - (Date.now() - startTime) / 1000;
if (timeRemaining <= 0) {
if (killHook in window.killHooks) {
window.killHooks[killHook]();
}
return;
}
timerElement.text(formatTime(timeRemaining));
setTimeout(tick, 1000);
})();
})(%s, '%s');
"""
class AnswerTimer(object):
def __init__(self, card):
self._card = card
deckConf = mw.col.decks.confForDid(card.odid or card.did)
self.answerTime = deckConf['rev'].get('timeToAnswer', 0)
self.showTimer = deckConf['rev'].get('showTTACountdown', False)
self.killHook = str(random.randint(0, 2**32))
def isNeeded(self):
return bool(self.answerTime)
def start(self):
mw.progress.timer(self.answerTime * 1000, self._timeLimitUp, False)
if self.showTimer:
self.injectTimer()
def onQuestionAnswered(self):
mw.bottomWeb.eval('window.killHooks["%s"]()' % self.killHook);
def injectTimer(self):
mw.bottomWeb.eval(timerJs % (self.answerTime, self.killHook));
def _isRolling(self):
return mw.reviewer.state == 'question' and mw.reviewer.card == self._card
def _advanceTimer(self):
if self._isRolling():
mw.progress.timer(1000, self._advanceTimer, False)
def _timeLimitUp(self):
# Make sure we haven't answered or moved onto a different question.
if self._isRolling():
mw.reviewer._showAnswer()
def customFormSetupUi(self, *args, **kwargs):
_old = kwargs['_old']
del kwargs['_old']
ret = _old(self, *args, **kwargs)
row = self.gridLayout_3.rowCount()
spinBox = QSpinBox()
spinBox.setMinimum(0)
self.gridLayout_3.addWidget(QLabel('Time to Answer'), row, 0)
self.gridLayout_3.addWidget(spinBox, row, 1)
self.gridLayout_3.addWidget(QLabel('seconds'), row, 2)
self.timeToAnswer = spinBox
row += 1
checkbox = QCheckBox()
self.gridLayout_3.addWidget(QLabel('Show countdown'), row, 0)
self.gridLayout_3.addWidget(checkbox, row, 1)
self.showTTATimer = checkbox
return ret
def customDeckConfSaveConf(self, *args, **kwargs):
self.conf['rev']['timeToAnswer'] = self.form.timeToAnswer.value()
self.conf['rev']['showTTACountdown'] = self.form.showTTATimer.isChecked()
def customDeckConfLoadConf(self, *args, **kwargs):
self.form.timeToAnswer.setValue(self.conf['rev'].get('timeToAnswer', 0))
self.form.showTTATimer.setChecked(self.conf['rev'].get('showTTACountdown', False))
def startTimer():
global LIVE_TIMER
card = mw.reviewer.card
if (LIVE_TIMER and LIVE_TIMER._card == card):
return
LIVE_TIMER = AnswerTimer(card)
if LIVE_TIMER.isNeeded():
LIVE_TIMER.start()
def stopTimer():
global LIVE_TIMER
LIVE_TIMER.onQuestionAnswered()
DeckConf.saveConf = wrap(
DeckConf.saveConf,
customDeckConfSaveConf,
'before')
DeckConf.loadConf = wrap(
DeckConf.loadConf,
customDeckConfLoadConf,
'after')
Reviewer._showAnswerButton = wrap(
Reviewer._showAnswerButton,
lambda *args, **kwargs: startTimer(),
'after')
dconf.Ui_Dialog.setupUi = wrap(
dconf.Ui_Dialog.setupUi,
customFormSetupUi,
'around')
addHook('showAnswer', stopTimer)