# Copyright: Ankitects Pty Ltd and contributors
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from copy import deepcopy
from typing import Any
import aqt
from anki.errors import DeckRenameError
from anki.hooks import runHook
from anki.lang import _, ngettext
from anki.utils import fmtTimeSpan, ids2str
from aqt.qt import *
from aqt.sound import clearAudioQueue
from aqt.utils import askUser, getOnlyText, openHelp, openLink, shortcut, showWarning
class DeckBrowser:
_dueTree: Any
def __init__(self, mw):
self.mw = mw
self.web = mw.web
self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb)
self.scrollPos = QPoint(0, 0)
def show(self):
clearAudioQueue()
self.web.resetHandlers()
self.web.onBridgeCmd = self._linkHandler
self._renderPage()
def refresh(self):
self._renderPage()
# Event handlers
##########################################################################
def _linkHandler(self, url):
if ":" in url:
(cmd, arg) = url.split(":")
else:
cmd = url
if cmd == "open":
self._selDeck(arg)
elif cmd == "opts":
self._showOptions(arg)
elif cmd == "shared":
self._onShared()
elif cmd == "import":
self.mw.onImport()
elif cmd == "lots":
openHelp("using-decks-appropriately")
elif cmd == "hidelots":
self.mw.pm.profile["hideDeckLotsMsg"] = True
self.refresh()
elif cmd == "create":
deck = getOnlyText(_("Name for deck:"))
if deck:
self.mw.col.decks.id(deck)
self.refresh()
elif cmd == "drag":
draggedDeckDid, ontoDeckDid = arg.split(",")
self._dragDeckOnto(draggedDeckDid, ontoDeckDid)
elif cmd == "collapse":
self._collapse(arg)
return False
def _selDeck(self, did):
self.mw.col.decks.select(did)
self.mw.onOverview()
# HTML generation
##########################################################################
_body = """
%(tree)s
%(stats)s
%(countwarn)s
"""
def _renderPage(self, reuse=False):
if not reuse:
self._dueTree = self.mw.col.sched.deckDueTree()
self.__renderPage(None)
return
self.web.evalWithCallback("window.pageYOffset", self.__renderPage)
def __renderPage(self, offset):
tree = self._renderDeckTree(self._dueTree)
stats = self._renderStats()
self.web.stdHtml(
self._body % dict(tree=tree, stats=stats, countwarn=self._countWarn()),
css=["deckbrowser.css"],
js=["jquery.js", "jquery-ui.js", "deckbrowser.js"],
)
self.web.key = "deckBrowser"
self._drawButtons()
if offset is not None:
self._scrollToOffset(offset)
def _scrollToOffset(self, offset):
self.web.eval("$(function() { window.scrollTo(0, %d, 'instant'); });" % offset)
def _renderStats(self):
cards, thetime = self.mw.col.db.first(
"""
select count(), sum(time)/1000 from revlog
where id > ?""",
(self.mw.col.sched.dayCutoff - 86400) * 1000,
)
cards = cards or 0
thetime = thetime or 0
msgp1 = (
ngettext("%d card", "%d cards", cards) % cards
)
buf = _("Studied %(a)s %(b)s today.") % dict(
a=msgp1, b=fmtTimeSpan(thetime, unit=1, inTime=True)
)
return buf
def _countWarn(self):
if self.mw.col.decks.count() < 25 or self.mw.pm.profile.get("hideDeckLotsMsg"):
return ""
return "
" + (
_("You have a lot of decks. Please see %(a)s. %(b)s")
% dict(
a=(
"%s" % _("this page")
),
b=(
" ("
"%s)" % (_("hide")) + "
"
),
)
)
def _renderDeckTree(self, nodes, depth=0):
if not nodes:
return ""
if depth == 0:
buf = """
%s
%s
%s
""" % (
_("Deck"),
_("Due"),
_("New"),
)
buf += self._topLevelDragRow()
else:
buf = ""
nameMap = self.mw.col.decks.nameMap()
for node in nodes:
buf += self._deckRow(node, depth, len(nodes), nameMap)
if depth == 0:
buf += self._topLevelDragRow()
return buf
def _deckRow(self, node, depth, cnt, nameMap):
name, did, due, lrn, new, children = node
deck = self.mw.col.decks.get(did)
if did == 1 and cnt > 1 and not children:
# if the default deck is empty, hide it
if not self.mw.col.db.scalar("select 1 from cards where did = 1"):
return ""
# parent toggled for collapsing
for parent in self.mw.col.decks.parents(did, nameMap):
if parent["collapsed"]:
buff = ""
return buff
prefix = "-"
if self.mw.col.decks.get(did)["collapsed"]:
prefix = "+"
due += lrn
def indent():
return " " * 6 * depth
if did == self.mw.col.conf["curDeck"]:
klass = "deck current"
else:
klass = "deck"
buf = "