summaryrefslogtreecommitdiff
path: root/.local/share/Anki2/addons21/advanced_review/Card_Info.py
diff options
context:
space:
mode:
Diffstat (limited to '.local/share/Anki2/addons21/advanced_review/Card_Info.py')
-rw-r--r--.local/share/Anki2/addons21/advanced_review/Card_Info.py508
1 files changed, 508 insertions, 0 deletions
diff --git a/.local/share/Anki2/addons21/advanced_review/Card_Info.py b/.local/share/Anki2/addons21/advanced_review/Card_Info.py
new file mode 100644
index 0000000..3d5031d
--- /dev/null
+++ b/.local/share/Anki2/addons21/advanced_review/Card_Info.py
@@ -0,0 +1,508 @@
+#// auth_ Mohamad Janati
+#// Copyright (c) 2019-2021 Mohamad Janati (freaking stupid, right? :|)
+
+import os
+import io
+
+from anki.hooks import addHook
+from aqt.qt import *
+from aqt.webview import AnkiWebView
+import aqt.stats
+import time
+import datetime
+from anki.lang import _
+from anki.utils import fmtTimeSpan
+from anki.stats import CardStats
+from aqt import *
+from anki.utils import htmlToTextLine
+from anki.collection import _Collection
+from aqt.reviewer import Reviewer
+
+
+#// sidebar functions
+class StatsSidebar(object):
+ def __init__(self, mw):
+ config = mw.addonManager.getConfig(__name__)
+ sidebar_autoOpen = config['Card Info sidebar_ Auto Open']
+ self.mw = mw
+ self.shown = False
+ addHook("showQuestion", self._update)
+ addHook("reviewCleanup", self._update)
+ if sidebar_autoOpen:
+ addHook("showQuestion", self.show)
+
+ def _addDockable(self, title, w):
+ class DockableWithClose(QDockWidget):
+ closed = pyqtSignal()
+ def closeEvent(self, evt):
+ self.closed.emit()
+ QDockWidget.closeEvent(self, evt)
+ dock = DockableWithClose(title, mw)
+ dock.setObjectName(title)
+ dock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
+ dock.setFeatures(QDockWidget.AllDockWidgetFeatures)
+ dock.setWidget(w)
+ if mw.width() < 600:
+ mw.resize(QSize(600, mw.height()))
+ config = mw.addonManager.getConfig(__name__)
+ sidebar_defaultPosition = config['Card Info sidebar_ Default Position']
+ if sidebar_defaultPosition == 1:
+ mw.addDockWidget(Qt.LeftDockWidgetArea, dock)
+ else:
+ mw.addDockWidget(Qt.RightDockWidgetArea, dock)
+ return dock
+
+ def _remDockable(self, dock):
+ mw.removeDockWidget(dock)
+
+ def show(self):
+ if not self.shown:
+ class ThinAnkiWebView(AnkiWebView):
+ def sizeHint(self):
+ return QSize(200, 100)
+ self.web = ThinAnkiWebView()
+ self.shown = self._addDockable("Card Info", self.web)
+ self.shown.closed.connect(self._onClosed)
+ self._update()
+
+ def hide(self):
+ if self.shown:
+ self._remDockable(self.shown)
+ self.shown = None
+
+ def toggle(self):
+ if self.shown:
+ self.hide()
+ else:
+ self.show()
+
+ def _onClosed(self):
+ # schedule removal for after evt has finished
+ self.mw.progress.timer(100, self.hide, False)
+
+ # modified _revlogData function
+ def _revlogData_mod(self, card, cs):
+ config = mw.addonManager.getConfig(__name__)
+ sidebar_font = config['Card Info sidebar_ Font']
+ reviewsToShow = config['Card Info sidebar_ number of reviews to show for a card']
+ limited_review_warning_note = config['Card Info sidebar_ warning note']
+ custom_colors = config[' Review_ Custom Colors']
+ again_color = config['Color_ Again']
+ hard_color = config['Color_ Hard']
+ good_color = config['Color_ Good']
+ easy_color = config['Color_ Easy']
+ entries = self.mw.col.db.all("select id/1000.0, ease, ivl, factor, time/1000.0, type from revlog where cid = ?", card.id)
+ if not entries:
+ return ""
+ s = "<div style='text-align: center; font-family: arial; font-weight: bold;'> Reviews </div>"
+ s += ("<style>th {font-family: %s; font-size: 13px;}</style><table width=100%% ><tr><th align=left>%s</th>") % (sidebar_font, "Date")
+ s += ("<th align=center >%s</th>" * 5) % ("Type", "Button", "Interval", "Ease", "Time")
+ cnt = 0
+ for (date, ease, ivl, factor, taken, type) in reversed(entries):
+ cnt += 1
+ s += "<tr><td>%s</td>" % time.strftime("<b>%y/%m/%d</b><br>%H:%M", time.localtime(date))
+ tstr = ["Learn", "Review", "Relearn", "Filtered", "Resched"][type]
+ import anki.stats as st
+
+ fmt = "<span style='color:%s'>%s</span>"
+ if type == 0:
+ tstr = fmt % (st.colLearn, tstr)
+ elif type == 1:
+ tstr = fmt % (st.colMature, tstr)
+ elif type == 2:
+ tstr = fmt % (st.colRelearn, tstr)
+ elif type == 3:
+ tstr = fmt % (st.colCram, tstr)
+ else:
+ tstr = fmt % ("#000", tstr)
+ if ease == 1:
+ tstr = fmt % (st.colRelearn, tstr)
+ ####################
+ int_due = "%s" % time.strftime("%y/%m/%d", time.localtime(date))
+ if ivl > 0:
+ int_due_date = time.localtime(date + (ivl * 24 * 60 * 60))
+ int_due = time.strftime("%y/%m/%d", int_due_date)
+ ####################
+ if ivl == 0:
+ ivl = "0d"
+ elif ivl > 0:
+ ivl = fmtTimeSpan(ivl * 86400, short=True)
+ else:
+ ivl = cs.time(-ivl)
+
+ if not custom_colors:
+ again_color = "#FF1111"
+ hard_color = "#FF9814"
+ good_color = "#33FF2D"
+ easy_color = "#21C0FF"
+ if self.mw.col.sched_ver() == 1 and type == 3:
+ if ease == 1:
+ button = "<div style='color: {};'>Again</div>".format(again_color)
+ elif ease == 2:
+ button = "<div style='color: {};'>Good</div>".format(good_color)
+ elif ease == 3:
+ button = "<div style='color: {};'>Good</div>".format(good_color)
+ elif ease == 4:
+ button = "<div style='color: {};'>Easy</div>".format(easy_color)
+ else:
+ button = "ease: {}".format(ease)
+ elif self.mw.col.sched_ver() == 1 and (type == 0 or type == 2):
+ if ease == 1:
+ button = "<div style='color: {};'>Again</div>".format(again_color)
+ elif ease == 2:
+ button = "<div style='color: {};'>Good</div>".format(good_color)
+ elif ease == 3:
+ button = "<div style='color: {};'>Easy</div>".format(easy_color)
+ elif ease == 4:
+ button = "<div style='color: {};'>Easy</div>".format(easy_color)
+ else:
+ button = "ease: {}".format(ease)
+ else:
+ if ease == 1:
+ button = "<div style='color: {};'>Again</div>".format(again_color)
+ elif ease == 2:
+ button = "<div style='color: {};'>Hard</div>".format(hard_color)
+ elif ease == 3:
+ button = "<div style='color: {};'>Good</div>".format(good_color)
+ elif ease == 4:
+ button = "<div style='color: {};'>Easy</div>".format(easy_color)
+ else:
+ button = "ease: {}".format(ease)
+ s += ("<td align=center>%s</td>" * 5) % (tstr, button, "%s<br>(%s)" %(ivl, int_due), "%d%%" % (factor / 10) if factor else "", cs.time(taken)) + "</tr>"
+ if reviewsToShow != 0:
+ if cnt > int(reviewsToShow) - 1:
+ break
+ else:
+ continue
+ s += "</table>"
+ warning = ""
+ if limited_review_warning_note:
+ if cnt < card.reps:
+ try:
+ a = int(reviewsToShow)
+ warning = """<div style="font-family: consolas; font-size: 12px;"><hr> You have limited previous review information number to "{}" reviews.</div>""".format(reviewsToShow)
+ except ValueError:
+ warning = """<div style="font-family: consolas; font-size: 12px;"><hr>Some of the history is missing. For more information, please see the browser documentation.</div>"""
+ return s + warning
+
+
+ # adds the modified _revlogData function to Reviewer class in aqt.browser
+ Reviewer._revlogData_mod = _revlogData_mod
+
+
+ # modified report function
+ def report_mod(self):
+ from anki import version
+ anki_version = int(version.replace('.', ''))
+ if anki_version > 2119:
+ from aqt.theme import theme_manager
+ config = mw.addonManager.getConfig(__name__)
+
+ infobar_created = config['Card Info sidebar_ Created']
+ infobar_edited = config['Card Info sidebar_ Edited']
+ infobar_firstReview = config['Card Info sidebar_ First Review']
+ infobar_latestReview = config['Card Info sidebar_ Latest Review']
+ infobar_due = config['Card Info sidebar_ Due']
+ infobar_interval = config['Card Info sidebar_ Interval']
+ infobar_ease = config['Card Info sidebar_ Ease']
+ infobar_reviews = config['Card Info sidebar_ Reviews']
+ infobar_lapses = config['Card Info sidebar_ Lapses']
+ infobar_correctPercent = config['Card Info Sidebar_ Correct Percent']
+ infobar_fastestReview = config['Card Info Sidebar_ Fastest Review']
+ infobar_slowestReview = config['Card Info Sidebar_ Slowest Review']
+ infobar_avgTime = config['Card Info sidebar_ Average Time']
+ infobar_totalTime = config['Card Info sidebar_ Total Time']
+ infobar_cardType = config['Card Info sidebar_ Card Type']
+ infobar_noteType = config['Card Info sidebar_ Note Type']
+ infobar_deck = config['Card Info sidebar_ Deck']
+ infobar_tags = config['Card Info sidebar_ Tags']
+ infobar_noteID = config['Card Info Sidebar_ Note ID']
+ infobar_cardID = config['Card Info Sidebar_ Card ID']
+ infobar_sortField = config['Card Info sidebar_ Sort Field']
+
+ c = self.card
+ fmt = lambda x, **kwargs: fmtTimeSpan(x, short=True, **kwargs)
+ self.txt = "<table width=100%>"
+ if infobar_created:
+ self.addLine("Created", time.strftime("%Y-%m-%d | %H:%M", time.localtime(c.id/1000)))
+ if infobar_edited:
+ if c.note().mod != False and time.localtime(c.id/1000) != time.localtime(c.note().mod):
+ self.addLine("Edited", time.strftime("%Y-%m-%d | %H:%M", time.localtime(c.note().mod)))
+ first = self.col.db.scalar("select min(id) from revlog where cid = ?", c.id)
+ last = self.col.db.scalar("select max(id) from revlog where cid = ?", c.id)
+ if first:
+ if infobar_firstReview:
+ self.addLine("First Review", time.strftime("%Y-%m-%d | %H:%M", time.localtime(first/1000)))
+ if infobar_latestReview:
+ self.addLine("Latest Review", time.strftime("%Y-%m-%d | %H:%M", time.localtime(last/1000)))
+ if c.type != 0:
+ if c.odid or c.queue < 0:
+ next = None
+ else:
+ if c.queue in (2,3):
+ next = time.time()+((c.due - self.col.sched.today)*86400)
+ else:
+ next = c.due
+ next = self.date(next)
+ if next:
+ if infobar_due:
+ self.addLine("Due", next)
+ if c.queue == 2:
+ if infobar_interval:
+ self.addLine("Interval", fmt(c.ivl * 86400))
+ if infobar_ease:
+ self.addLine("Ease", "%d%%" % (c.factor/10.0))
+ if infobar_lapses:
+ self.addLine("Lapses", "%d" % c.lapses)
+ if self.col.sched_ver() == 1:
+ pressed_again = mw.col.db.scalar("select sum(case when ease = 1 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_good = mw.col.db.scalar("select sum(case when ease = 2 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_easy = mw.col.db.scalar("select sum(case when ease = 3 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_all = pressed_again + pressed_good + pressed_easy
+ self.addLine("Again", "{} | {:.0f}%".format(str(pressed_again).rjust(4), float(pressed_again/pressed_all)*100))
+ self.addLine("Good", "{} | {:.0f}%".format(str(pressed_good).rjust(4), float(pressed_good/pressed_all)*100))
+ self.addLine("Easy", "{} | {:.0f}%".format(str(pressed_easy).rjust(4), float(pressed_easy/pressed_all)*100))
+ elif self.col.sched_ver() == 2:
+ pressed_again = mw.col.db.scalar("select sum(case when ease = 1 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_hard = mw.col.db.scalar("select sum(case when ease = 2 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_good = mw.col.db.scalar("select sum(case when ease = 3 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_easy = mw.col.db.scalar("select sum(case when ease = 4 then 1 else 0 end) from revlog where cid = ?", c.id)
+ pressed_all = pressed_again + pressed_hard + pressed_good + pressed_easy
+ if pressed_all < 1:
+ pressed_all = 1
+ self.addLine("Again", "{} | {:.0f}%".format(str(pressed_again).rjust(4), float(pressed_again/pressed_all)*100))
+ self.addLine("Hard", "{} | {:.0f}%".format(str(pressed_hard).rjust(4), float(pressed_hard/pressed_all)*100))
+ self.addLine("Good", "{} | {:.0f}%".format(str(pressed_good).rjust(4), float(pressed_good/pressed_all)*100))
+ self.addLine("Easy", "{} | {:.0f}%".format(str(pressed_easy).rjust(4), float(pressed_easy/pressed_all)*100))
+ if infobar_reviews:
+ self.addLine("Reviews", "%d" % c.reps)
+ (cnt, total) = self.col.db.first("select count(), sum(time)/1000 from revlog where cid = ?", c.id)
+ if infobar_correctPercent and c.reps > 0:
+ self.addLine("Correct Percentage", "{:.0f}%".format(float((c.reps-c.lapses)/c.reps)*100))
+ if infobar_fastestReview:
+ fastes_rev = mw.col.db.scalar("select time/1000.0 from revlog where cid = ? order by time asc limit 1", c.id)
+ self.addLine("Fastest Review", self.time(fastes_rev))
+ if infobar_slowestReview:
+ slowest_rev = mw.col.db.scalar("select time/1000.0 from revlog where cid = ? order by time desc limit 1", c.id)
+ self.addLine("Slowest Review", self.time(slowest_rev))
+ if cnt:
+ if infobar_avgTime:
+ self.addLine("Average Time", self.time(total / float(cnt)))
+ if infobar_totalTime:
+ self.addLine("Total Time", self.time(total))
+ elif c.queue == 0:
+ if infobar_due:
+ self.addLine("Position", c.due)
+ if infobar_cardType:
+ self.addLine("Card Type", c.template()['name'])
+ if infobar_noteType:
+ self.addLine("Note Type", c.model()['name'])
+ if infobar_noteID:
+ self.addLine("Note ID", c.nid)
+ if infobar_cardID:
+ self.addLine("Card ID", c.id)
+ if infobar_deck:
+ self.addLine("Deck", self.col.decks.name(c.did))
+ if c.note().tags:
+ if infobar_tags:
+ self.addLine("Tags", " | ".join(c.note().tags))
+ f = c.note()
+ sort_field = htmlToTextLine(f.fields[self.col.models.sortIdx(f.model())])
+ if infobar_sortField:
+ if len(sort_field) > 40:
+ self.addLine("Sort Field", "[{}<br>{}<br>{}...]".format(sort_field[:20], sort_field[20:41], sort_field[41:58]))
+ else:
+ self.addLine("Sort Field", htmlToTextLine(f.fields[self.col.models.sortIdx(f.model())]))
+ self.txt += "</table>"
+ return self.txt
+
+
+ # adds the modified report functions to CardStats class in anki.stats
+ CardStats.report_mod = report_mod
+
+
+ # modified cardStats function
+ def cardStats_mod(self, card):
+ from anki.stats import CardStats
+ return CardStats(self, card).report_mod()
+
+
+ # adds a modified cardStats function to _Collection class in anki.collection
+ _Collection.cardStats_mod = cardStats_mod
+
+
+ # functions to get more previous cards to add them to sidebard
+ def lastCard2(self):
+ if self._answeredIds:
+ if len(self._answeredIds) > 1:
+ try:
+ return self.mw.col.getCard(self._answeredIds[-2])
+ except TypeError:
+ return
+ def lastCard3(self):
+ if self._answeredIds:
+ if len(self._answeredIds) > 2:
+ try:
+ return self.mw.col.getCard(self._answeredIds[-3])
+ except TypeError:
+ return
+ def lastCard4(self):
+ if self._answeredIds:
+ if len(self._answeredIds) > 3:
+ try:
+ return self.mw.col.getCard(self._answeredIds[-4])
+ except TypeError:
+ return
+
+ # adds functions above to Reviewer class in aqt.reviewer
+ Reviewer.lastCard2 = lastCard2
+ Reviewer.lastCard3 = lastCard3
+ Reviewer.lastCard4 = lastCard4
+
+
+ def _update(self):
+ config = mw.addonManager.getConfig(__name__)
+ infobar_currentReviewCount = config['Card Info sidebar_ Current Review Count']
+ try:
+ sidebar_PreviousCards = int(config['Card Info sidebar_ Number of previous cards to show'])
+ except ValueError:
+ sidebar_PreviousCards = 2
+ if not self.shown:
+ return
+ txt = ""
+ r = self.mw.reviewer
+ d = self.mw.col
+ cs = CardStats(d, r.card)
+ current_card = r.card
+ review_count = len(self.mw.reviewer._answeredIds)
+ styles = """<style>
+ .title {
+ font-family: arial;
+ padding-bottom: 15px;
+ font-weight: bold;
+ }</style>"""
+ currentReviewCount = "<div class='title'>Current Card</div><div style='font-family: courier; font-size: 10px;'>Current Review Count: {}</div>".format(review_count)
+ if current_card:
+ txt += styles
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ else:
+ txt += "<div class='title'>Current Card</div>"
+ txt += d.cardStats_mod(current_card)
+ txt += "<p>"
+ txt += r._revlogData_mod(current_card, cs)
+ card2 = r.lastCard()
+ if card2 and sidebar_PreviousCards > 1:
+ if sidebar_PreviousCards == 2:
+ txt += "<hr><div class='title'>Last Card</div>"
+ else:
+ txt += "<hr><div class='title'>Card 2</div>"
+ txt += d.cardStats_mod(card2)
+ txt += "<p>"
+ txt += r._revlogData_mod(card2, cs)
+ if sidebar_PreviousCards < 3:
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ card3 = r.lastCard2()
+ if card3 and sidebar_PreviousCards > 2:
+ txt += "<hr><div class='title''>Card 3</div>"
+ txt += d.cardStats_mod(card3)
+ txt += "<p>"
+ txt += r._revlogData_mod(card3, cs)
+ if sidebar_PreviousCards < 4:
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ card4 = r.lastCard3()
+ if card4 and sidebar_PreviousCards > 3:
+ txt += "<hr><div class='title''>Card 4</div>"
+ txt += d.cardStats_mod(card4)
+ txt += "<p>"
+ txt += r._revlogData_mod(card4, cs)
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ if not txt:
+ styles = """<style>
+ .title {
+ font-family: arial;
+ padding-bottom: 15px;
+ font-weight: bold;
+ }</style>"""
+ txt = styles
+ card2 = r.lastCard()
+ if card2 and sidebar_PreviousCards > 1:
+ txt += "<div class='title'>Last Card</div>"
+ txt += d.cardStats_mod(card2)
+ txt += "<p>"
+ txt += r._revlogData_mod(card2, cs)
+ if sidebar_PreviousCards < 3:
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ card3 = r.lastCard2()
+ if card3 and sidebar_PreviousCards > 2:
+ txt += "<hr><div class='title''>Card 2</div>"
+ txt += d.cardStats_mod(card3)
+ txt += "<p>"
+ txt += r._revlogData_mod(card3, cs)
+ if sidebar_PreviousCards < 4:
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ card4 = r.lastCard3()
+ if card4 and sidebar_PreviousCards > 3:
+ txt += "<hr><div class='title''>Card 3</div>"
+ txt += d.cardStats_mod(card4)
+ txt += "<p>"
+ txt += r._revlogData_mod(card4, cs)
+ if infobar_currentReviewCount:
+ txt += currentReviewCount
+ style = self._style()
+ self.web.setHtml("""
+<html>
+ <head>
+ <style>%s</style>
+ </head>
+ <body>
+ <center>%s</center>
+ </body>
+</html>
+"""% (style, txt))
+
+
+ def _style(self):
+ from anki import version
+ anki_version = int(version.replace('.', ''))
+ if anki_version > 2119:
+ from aqt.theme import theme_manager
+ config = mw.addonManager.getConfig(__name__)
+ sidebar_theme = config['Card Info sidebar_ theme']
+ sidebar_font = config['Card Info sidebar_ Font']
+ from . import styles
+ dark_styles = styles.dark
+ light_styles = styles.light
+ if anki_version > 2119:
+ if sidebar_theme == 2:
+ mystyle = dark_styles
+ elif sidebar_theme == 1:
+ mystyle = light_styles
+ else:
+ if theme_manager.night_mode:
+ mystyle = dark_styles
+ else:
+ mystyle = light_styles
+ else:
+ if sidebar_theme == 2:
+ mystyle = dark_styles
+ else:
+ mystyle = light_styles
+
+
+ from anki import version
+ if version.startswith("2.0."):
+ return ""
+ return mystyle + "td { font-size: 75%; font-family:" + "{}".format(sidebar_font) + ";}"
+
+
+_cs = StatsSidebar(mw)
+
+
+def cardStats(on):
+ _cs.toggle()