class DeckBrowser: _dueTree: DeckTreeNode def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) def show(self): av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self._renderPage() # redraw top bar for theme change self.mw.toolbar.draw() 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 == "create": deck = getOnlyText(_("Name for deck:")) if deck: self.mw.col.decks.id(deck) gui_hooks.sidebar_should_refresh_decks() self.refresh() elif cmd == "drag": draggedDeckDid, ontoDeckDid = arg.split(",") self._dragDeckOnto(draggedDeckDid, ontoDeckDid) elif cmd == "collapse": self._collapse(int(arg)) return False def _selDeck(self, did): self.mw.col.decks.select(did) self.mw.onOverview() # HTML generation ########################################################################## _body = """ <center> <table cellspacing=0 cellpading=3> %(tree)s </table> <br> %(stats)s </center> """ def _renderPage(self, reuse=False): if not reuse: self._dueTree = self.mw.col.sched.deck_due_tree() self.__renderPage(None) return self.web.evalWithCallback("window.pageYOffset", self.__renderPage) def __renderPage(self, offset): content = DeckBrowserContent( tree=self._renderDeckTree(self._dueTree), stats=self._renderStats(), ) gui_hooks.deck_browser_will_render_content(self, content) self.web.stdHtml( self._body % content.__dict__, css=["deckbrowser.css"], js=["jquery.js", "jquery-ui.js", "deckbrowser.js"], context=self, ) self.web.key = "deckBrowser" self._drawButtons() if offset is not None: self._scrollToOffset(offset) gui_hooks.deck_browser_did_render(self) 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 buf = self.mw.col.backend.studied_today(cards=cards, seconds=float(thetime)) return buf def _renderDeckTree(self, top: DeckTreeNode) -> str: buf = """ <tr><th colspan=5 align=left>%s</th><th class=count>%s</th> <th class=count>%s</th><th class=optscol></th></tr>""" % ( _("Deck"), tr(TR.STATISTICS_DUE_COUNT), _("New"), ) buf += self._topLevelDragRow() ctx = RenderDeckNodeContext( current_deck_id=self.mw.col.conf["curDeck"]) for child in top.children: buf += self._render_deck_node(child, ctx) return buf def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str: if node.collapsed: prefix = "+" else: prefix = "-" due = node.review_count + node.learn_count def indent(): return " " * 6 * (node.level - 1) if node.deck_id == ctx.current_deck_id: klass = "deck current" else: klass = "deck" buf = "<tr class='%s' id='%d'>" % (klass, node.deck_id) # deck link if node.children: collapse = ( "<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>" % (node.deck_id, prefix)) else: collapse = "<span class=collapse></span>" if node.filtered: extraclass = "filtered" else: extraclass = "" buf += """ <td class=decktd colspan=5>%s%s<a class="deck %s" href=# onclick="return pycmd('open:%d')">%s</a></td>""" % ( indent(), collapse, extraclass, node.deck_id, node.name, ) # due counts def nonzeroColour(cnt, klass): if not cnt: klass = "zero-count" return f'<span class="{klass}">{cnt}</span>' buf += "<td align=right>%s</td><td align=right>%s</td>" % ( nonzeroColour(due, "review-count"), nonzeroColour(node.new_count, "new-count"), ) # options buf += ( "<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>" "<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % node.deck_id) # children if not node.collapsed: for child in node.children: buf += self._render_deck_node(child, ctx) return buf def _topLevelDragRow(self): return "<tr class='top-level-drag-row'><td colspan='6'> </td></tr>" # Options ########################################################################## def _showOptions(self, did: str) -> None: m = QMenu(self.mw) a = m.addAction(_("Rename")) qconnect(a.triggered, lambda b, did=did: self._rename(int(did))) a = m.addAction(_("Options")) qconnect(a.triggered, lambda b, did=did: self._options(did)) a = m.addAction(_("Export")) qconnect(a.triggered, lambda b, did=did: self._export(did)) a = m.addAction(_("Delete")) qconnect(a.triggered, lambda b, did=did: self._delete(int(did))) gui_hooks.deck_browser_will_show_options_menu(m, int(did)) m.exec_(QCursor.pos()) def _export(self, did): self.mw.onExport(did=did) def _rename(self, did: int) -> None: self.mw.checkpoint(_("Rename Deck")) deck = self.mw.col.decks.get(did) oldName = deck["name"] newName = getOnlyText(_("New deck name:"), default=oldName) newName = newName.replace('"', "") if not newName or newName == oldName: return try: self.mw.col.decks.rename(deck, newName) gui_hooks.sidebar_should_refresh_decks() except DeckRenameError as e: return showWarning(e.description) self.show() def _options(self, did): # select the deck first, because the dyn deck conf assumes the deck # we're editing is the current one self.mw.col.decks.select(did) self.mw.onDeckConf() def _collapse(self, did: int) -> None: self.mw.col.decks.collapse(did) node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did) if node: node.collapsed = not node.collapsed self._renderPage(reuse=True) def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid): try: self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid) gui_hooks.sidebar_should_refresh_decks() except DeckRenameError as e: return showWarning(e.description) self.show() def _delete(self, did): self.mw.checkpoint(_("Delete Deck")) deck = self.mw.col.decks.get(did) if not deck["dyn"]: dids = [did] + [r[1] for r in self.mw.col.decks.children(did)] cnt = self.mw.col.db.scalar( "select count() from cards where did in {0} or " "odid in {0}".format(ids2str(dids))) if cnt: extra = ngettext(" It has %d card.", " It has %d cards.", cnt) % cnt else: extra = None if (deck["dyn"] or not extra or askUser((_("Are you sure you wish to delete %s?") % deck["name"]) + extra)): self.mw.progress.start(immediate=True) self.mw.col.decks.rem(did, True) self.mw.progress.finish() self.show() # Top buttons ###################################################################### drawLinks = [ ["", "shared", _("Get Shared")], ["", "create", _("Create Deck")], ["Ctrl+I", "import", _("Import File")], # Ctrl+I works from menu ] def _drawButtons(self): buf = "" drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: b[0] = _("Shortcut key: %s") % shortcut(b[0]) buf += """ <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=DeckBrowserBottomBar(self), ) def _onShared(self): openLink(aqt.appShared + "decks/")
class DeckBrowser: _dueTree: DeckTreeNode def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) def show(self): av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self._renderPage() # redraw top bar for theme change self.mw.toolbar.redraw() 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 == "create": deck = getOnlyText(tr(TR.DECKS_NAME_FOR_DECK)) if deck: self.mw.col.decks.id(deck) gui_hooks.sidebar_should_refresh_decks() self.refresh() elif cmd == "drag": draggedDeckDid, ontoDeckDid = arg.split(",") self._dragDeckOnto(draggedDeckDid, ontoDeckDid) elif cmd == "collapse": self._collapse(int(arg)) return False def _selDeck(self, did): self.mw.col.decks.select(did) self.mw.onOverview() # HTML generation ########################################################################## _body = """ <center> <table cellspacing=0 cellpading=3> %(tree)s </table> <br> %(stats)s </center> """ def _renderPage(self, reuse=False): if not reuse: self._dueTree = self.mw.col.sched.deck_due_tree() self.__renderPage(None) return self.web.evalWithCallback("window.pageYOffset", self.__renderPage) def __renderPage(self, offset): content = DeckBrowserContent( tree=self._renderDeckTree(self._dueTree), stats=self._renderStats(), ) gui_hooks.deck_browser_will_render_content(self, content) self.web.stdHtml( self._body % content.__dict__, css=["css/deckbrowser.css"], js=[ "js/vendor/jquery.min.js", "js/vendor/jquery-ui.min.js", "js/deckbrowser.js", ], context=self, ) self.web.key = "deckBrowser" self._drawButtons() if offset is not None: self._scrollToOffset(offset) gui_hooks.deck_browser_did_render(self) def _scrollToOffset(self, offset): self.web.eval("$(function() { window.scrollTo(0, %d, 'instant'); });" % offset) def _renderStats(self): return '<div id="studiedToday"><span>{}</span></div>'.format( self.mw.col.studied_today(), ) def _renderDeckTree(self, top: DeckTreeNode) -> str: buf = """ <tr><th colspan=5 align=start>%s</th><th class=count>%s</th> <th class=count>%s</th><th class=optscol></th></tr>""" % ( tr(TR.DECKS_DECK), tr(TR.STATISTICS_DUE_COUNT), tr(TR.ACTIONS_NEW), ) buf += self._topLevelDragRow() ctx = RenderDeckNodeContext( current_deck_id=self.mw.col.conf["curDeck"]) for child in top.children: buf += self._render_deck_node(child, ctx) return buf def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str: if node.collapsed: prefix = "+" else: prefix = "-" due = node.review_count + node.learn_count def indent(): return " " * 6 * (node.level - 1) if node.deck_id == ctx.current_deck_id: klass = "deck current" else: klass = "deck" buf = "<tr class='%s' id='%d'>" % (klass, node.deck_id) # deck link if node.children: collapse = ( "<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>" % (node.deck_id, prefix)) else: collapse = "<span class=collapse></span>" if node.filtered: extraclass = "filtered" else: extraclass = "" buf += """ <td class=decktd colspan=5>%s%s<a class="deck %s" href=# onclick="return pycmd('open:%d')">%s</a></td>""" % ( indent(), collapse, extraclass, node.deck_id, node.name, ) # due counts def nonzeroColour(cnt, klass): if not cnt: klass = "zero-count" return f'<span class="{klass}">{cnt}</span>' buf += "<td align=right>%s</td><td align=right>%s</td>" % ( nonzeroColour(due, "review-count"), nonzeroColour(node.new_count, "new-count"), ) # options buf += ( "<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>" "<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % node.deck_id) # children if not node.collapsed: for child in node.children: buf += self._render_deck_node(child, ctx) return buf def _topLevelDragRow(self): return "<tr class='top-level-drag-row'><td colspan='6'> </td></tr>" # Options ########################################################################## def _showOptions(self, did: str) -> None: m = QMenu(self.mw) a = m.addAction(tr(TR.ACTIONS_RENAME)) qconnect(a.triggered, lambda b, did=did: self._rename(int(did))) a = m.addAction(tr(TR.ACTIONS_OPTIONS)) qconnect(a.triggered, lambda b, did=did: self._options(did)) a = m.addAction(tr(TR.ACTIONS_EXPORT)) qconnect(a.triggered, lambda b, did=did: self._export(did)) a = m.addAction(tr(TR.ACTIONS_DELETE)) qconnect(a.triggered, lambda b, did=did: self._delete(int(did))) gui_hooks.deck_browser_will_show_options_menu(m, int(did)) m.exec_(QCursor.pos()) def _export(self, did): self.mw.onExport(did=did) def _rename(self, did: int) -> None: self.mw.checkpoint(tr(TR.ACTIONS_RENAME_DECK)) deck = self.mw.col.decks.get(did) oldName = deck["name"] newName = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=oldName) newName = newName.replace('"', "") if not newName or newName == oldName: return try: self.mw.col.decks.rename(deck, newName) gui_hooks.sidebar_should_refresh_decks() except DeckRenameError as e: return showWarning(e.description) self.show() def _options(self, did): # select the deck first, because the dyn deck conf assumes the deck # we're editing is the current one self.mw.col.decks.select(did) self.mw.onDeckConf() def _collapse(self, did: int) -> None: self.mw.col.decks.collapse(did) node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did) if node: node.collapsed = not node.collapsed self._renderPage(reuse=True) def _dragDeckOnto(self, draggedDeckDid: int, ontoDeckDid: int): try: self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid) gui_hooks.sidebar_should_refresh_decks() except DeckRenameError as e: return showWarning(e.description) self.show() def ask_delete_deck(self, did: int) -> bool: deck = self.mw.col.decks.get(did) if deck["dyn"]: return True count = self.mw.col.decks.card_count(did, include_subdecks=True) if not count: return True extra = tr(TR.DECKS_IT_HAS_CARD, count=count) if askUser( tr(TR.DECKS_ARE_YOU_SURE_YOU_WISH_TO, val=deck["name"]) + " " + extra): return True return False def _delete(self, did: int) -> None: if self.ask_delete_deck(did): def do_delete(): return self.mw.col.decks.rem(did, True) def on_done(fut: Future): self.show() res = fut.result() # Required to check for errors self.mw.checkpoint(tr(TR.DECKS_DELETE_DECK)) self.mw.taskman.with_progress(do_delete, on_done) # Top buttons ###################################################################### drawLinks = [ ["", "shared", tr(TR.DECKS_GET_SHARED)], ["", "create", tr(TR.DECKS_CREATE_DECK)], ["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)], ] def _drawButtons(self): buf = "" drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val=shortcut(b[0])) buf += """ <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=DeckBrowserBottomBar(self), ) def _onShared(self): openLink(aqt.appShared + "decks/")
def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0)
class DeckBrowser: _dueTree: DeckTreeNode def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) self._v1_message_dismissed_at = 0 self._refresh_needed = False def show(self) -> None: av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self._renderPage() # redraw top bar for theme change self.mw.toolbar.redraw() self.refresh() def refresh(self) -> None: self._renderPage() self._refresh_needed = False def refresh_if_needed(self) -> None: if self._refresh_needed: self.refresh() def op_executed(self, changes: OpChanges, focused: bool) -> bool: if self.mw.col.op_affects_study_queue(changes): self._refresh_needed = True if focused: self.refresh_if_needed() return self._refresh_needed # Event handlers ########################################################################## def _linkHandler(self, url: str) -> Any: if ":" in url: (cmd, arg) = url.split(":", 1) 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 == "create": self._on_create() elif cmd == "drag": source, target = arg.split(",") self._handle_drag_and_drop(DeckID(int(source)), DeckID(int(target or 0))) elif cmd == "collapse": self._collapse(DeckID(int(arg))) elif cmd == "v2upgrade": self._confirm_upgrade() elif cmd == "v2upgradeinfo": openLink("https://faqs.ankiweb.net/the-anki-2.1-scheduler.html") elif cmd == "v2upgradelater": self._v1_message_dismissed_at = intTime() self.refresh() return False def _selDeck(self, did: str) -> None: self.mw.col.decks.select(DeckID(int(did))) self.mw.onOverview() # HTML generation ########################################################################## _body = """ <center> <table cellspacing=0 cellpading=3> %(tree)s </table> <br> %(stats)s </center> """ def _renderPage(self, reuse: bool = False) -> None: if not reuse: self._dueTree = self.mw.col.sched.deck_due_tree() self.__renderPage(None) return self.web.evalWithCallback("window.pageYOffset", self.__renderPage) def __renderPage(self, offset: int) -> None: content = DeckBrowserContent( tree=self._renderDeckTree(self._dueTree), stats=self._renderStats(), ) gui_hooks.deck_browser_will_render_content(self, content) self.web.stdHtml( self._v1_upgrade_message() + self._body % content.__dict__, css=["css/deckbrowser.css"], js=[ "js/vendor/jquery.min.js", "js/vendor/jquery-ui.min.js", "js/deckbrowser.js", ], context=self, ) self._drawButtons() if offset is not None: self._scrollToOffset(offset) gui_hooks.deck_browser_did_render(self) def _scrollToOffset(self, offset: int) -> None: self.web.eval("$(function() { window.scrollTo(0, %d, 'instant'); });" % offset) def _renderStats(self) -> str: return '<div id="studiedToday"><span>{}</span></div>'.format( self.mw.col.studied_today(), ) def _renderDeckTree(self, top: DeckTreeNode) -> str: buf = """ <tr><th colspan=5 align=start>%s</th><th class=count>%s</th> <th class=count>%s</th><th class=optscol></th></tr>""" % ( tr(TR.DECKS_DECK), tr(TR.STATISTICS_DUE_COUNT), tr(TR.ACTIONS_NEW), ) buf += self._topLevelDragRow() ctx = RenderDeckNodeContext( current_deck_id=self.mw.col.conf["curDeck"]) for child in top.children: buf += self._render_deck_node(child, ctx) return buf def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str: if node.collapsed: prefix = "+" else: prefix = "-" due = node.review_count + node.learn_count def indent() -> str: return " " * 6 * (node.level - 1) if node.deck_id == ctx.current_deck_id: klass = "deck current" else: klass = "deck" buf = "<tr class='%s' id='%d'>" % (klass, node.deck_id) # deck link if node.children: collapse = ( "<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>" % (node.deck_id, prefix)) else: collapse = "<span class=collapse></span>" if node.filtered: extraclass = "filtered" else: extraclass = "" buf += """ <td class=decktd colspan=5>%s%s<a class="deck %s" href=# onclick="return pycmd('open:%d')">%s</a></td>""" % ( indent(), collapse, extraclass, node.deck_id, node.name, ) # due counts def nonzeroColour(cnt: int, klass: str) -> str: if not cnt: klass = "zero-count" return f'<span class="{klass}">{cnt}</span>' buf += "<td align=right>%s</td><td align=right>%s</td>" % ( nonzeroColour(due, "review-count"), nonzeroColour(node.new_count, "new-count"), ) # options buf += ( "<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>" "<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % node.deck_id) # children if not node.collapsed: for child in node.children: buf += self._render_deck_node(child, ctx) return buf def _topLevelDragRow(self) -> str: return "<tr class='top-level-drag-row'><td colspan='6'> </td></tr>" # Options ########################################################################## def _showOptions(self, did: str) -> None: m = QMenu(self.mw) a = m.addAction(tr(TR.ACTIONS_RENAME)) qconnect(a.triggered, lambda b, did=did: self._rename(DeckID(int(did)))) a = m.addAction(tr(TR.ACTIONS_OPTIONS)) qconnect(a.triggered, lambda b, did=did: self._options(DeckID(int(did)))) a = m.addAction(tr(TR.ACTIONS_EXPORT)) qconnect(a.triggered, lambda b, did=did: self._export(DeckID(int(did)))) a = m.addAction(tr(TR.ACTIONS_DELETE)) qconnect(a.triggered, lambda b, did=did: self._delete(DeckID(int(did)))) gui_hooks.deck_browser_will_show_options_menu(m, int(did)) m.exec_(QCursor.pos()) def _export(self, did: DeckID) -> None: self.mw.onExport(did=did) def _rename(self, did: DeckID) -> None: deck = self.mw.col.decks.get(did) current_name = deck["name"] new_name = getOnlyText(tr(TR.DECKS_NEW_DECK_NAME), default=current_name) if not new_name or new_name == current_name: return rename_deck(mw=self.mw, deck_id=did, new_name=new_name) def _options(self, did: DeckID) -> None: # select the deck first, because the dyn deck conf assumes the deck # we're editing is the current one self.mw.col.decks.select(did) self.mw.onDeckConf() def _collapse(self, did: DeckID) -> None: self.mw.col.decks.collapse(did) node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did) if node: node.collapsed = not node.collapsed self._renderPage(reuse=True) def _handle_drag_and_drop(self, source: DeckID, target: DeckID) -> None: reparent_decks(mw=self.mw, parent=self.mw, deck_ids=[source], new_parent=target) def _delete(self, did: DeckID) -> None: remove_decks(mw=self.mw, parent=self.mw, deck_ids=[did]) # Top buttons ###################################################################### drawLinks = [ ["", "shared", tr(TR.DECKS_GET_SHARED)], ["", "create", tr(TR.DECKS_CREATE_DECK)], ["Ctrl+Shift+I", "import", tr(TR.DECKS_IMPORT_FILE)], ] def _drawButtons(self) -> None: buf = "" drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val=shortcut(b[0])) buf += """ <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=DeckBrowserBottomBar(self), ) def _onShared(self) -> None: openLink(f"{aqt.appShared}decks/") def _on_create(self) -> None: add_deck_dialog(mw=self.mw, parent=self.mw) ###################################################################### def _v1_upgrade_message(self) -> str: if self.mw.col.schedVer() == 2: return "" if (intTime() - self._v1_message_dismissed_at) < 86_400: return "" return f""" <center> <div class=callout> <div> {tr(TR.SCHEDULING_UPDATE_SOON)} </div> <div> <button onclick='pycmd("v2upgrade")'> {tr(TR.SCHEDULING_UPDATE_BUTTON)} </button> <button onclick='pycmd("v2upgradeinfo")'> {tr(TR.SCHEDULING_UPDATE_MORE_INFO_BUTTON)} </button> <button onclick='pycmd("v2upgradelater")'> {tr(TR.SCHEDULING_UPDATE_LATER_BUTTON)} </button> </div> </div> </center> """ def _confirm_upgrade(self) -> None: self.mw.col.modSchema(check=True) self.mw.col.upgrade_to_v2_scheduler() # not translated, as 2.15 should not be too far off if askUser( "Do you use AnkiDroid <= 2.14, or plan to use it in the near future? If unsure, choose No. You can adjust the setting later in the preferences screen.", defaultno=True, ): prefs = self.mw.col.get_preferences() prefs.scheduling.new_timezone = False self.mw.col.set_preferences(prefs) showInfo(tr(TR.SCHEDULING_UPDATE_DONE)) self.refresh()
class DeckBrowser: _dueTree: Any def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) def show(self): av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self._renderPage() # redraw top bar for theme change self.mw.toolbar.draw() 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 = """ <center> <table cellspacing=0 cellpading=3> %(tree)s </table> <br> %(stats)s %(countwarn)s </center> """ 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) gui_hooks.deck_browser_did_render(self) def __renderPage(self, offset): content = DeckBrowserContent( tree=self._renderDeckTree(self._dueTree), stats=self._renderStats(), countwarn=self._countWarn(), ) gui_hooks.deck_browser_will_render_content(self, content) self.web.stdHtml( self._body % content.__dict__, css=["deckbrowser.css"], js=["jquery.js", "jquery-ui.js", "deckbrowser.js"], context=self, ) 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("<!--studied-->%d card", "<!--studied-->%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 "<br><div style='width:50%;border: 1px solid #000;padding:5px;'>" + ( _("You have a lot of decks. Please see %(a)s. %(b)s") % dict( a=("<a href=# onclick=\"return pycmd('lots')\">%s</a>" % _("this page")), b=("<br><small><a href=# onclick='return pycmd(\"hidelots\")'>(" "%s)</a></small>" % (_("hide")) + "</div>"), )) def _renderDeckTree(self, nodes, depth=0): if not nodes: return "" if depth == 0: buf = """ <tr><th colspan=5 align=left>%s</th><th class=count>%s</th> <th class=count>%s</th><th class=optscol></th></tr>""" % ( _("Deck"), tr(StringsGroup.STATISTICS, "due-count"), _("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 = "<tr class='%s' id='%d'>" % (klass, did) # deck link if children: collapse = ( "<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>" % (did, prefix)) else: collapse = "<span class=collapse></span>" if deck["dyn"]: extraclass = "filtered" else: extraclass = "" buf += """ <td class=decktd colspan=5>%s%s<a class="deck %s" href=# onclick="return pycmd('open:%d')">%s</a></td>""" % ( indent(), collapse, extraclass, did, name, ) # due counts def nonzeroColour(cnt, klass): if not cnt: klass = "zero-count" if cnt >= 1000: cnt = "1000+" return f'<span class="{klass}">{cnt}</span>' buf += "<td align=right>%s</td><td align=right>%s</td>" % ( nonzeroColour(due, "review-count"), nonzeroColour(new, "new-count"), ) # options buf += ( "<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>" "<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % did) # children buf += self._renderDeckTree(children, depth + 1) return buf def _topLevelDragRow(self): return "<tr class='top-level-drag-row'><td colspan='6'> </td></tr>" # Options ########################################################################## def _showOptions(self, did) -> None: m = QMenu(self.mw) a = m.addAction(_("Rename")) qconnect(a.triggered, lambda b, did=did: self._rename(did)) a = m.addAction(_("Options")) qconnect(a.triggered, lambda b, did=did: self._options(did)) a = m.addAction(_("Export")) qconnect(a.triggered, lambda b, did=did: self._export(did)) a = m.addAction(_("Delete")) qconnect(a.triggered, lambda b, did=did: self._delete(did)) gui_hooks.deck_browser_will_show_options_menu(m, did) m.exec_(QCursor.pos()) def _export(self, did): self.mw.onExport(did=did) def _rename(self, did): self.mw.checkpoint(_("Rename Deck")) deck = self.mw.col.decks.get(did) oldName = deck["name"] newName = getOnlyText(_("New deck name:"), default=oldName) newName = newName.replace('"', "") if not newName or newName == oldName: return try: self.mw.col.decks.rename(deck, newName) except DeckRenameError as e: return showWarning(e.description) self.show() def _options(self, did): # select the deck first, because the dyn deck conf assumes the deck # we're editing is the current one self.mw.col.decks.select(did) self.mw.onDeckConf() def _collapse(self, did): self.mw.col.decks.collapse(did) self._renderPage(reuse=True) def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid): try: self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid) except DeckRenameError as e: return showWarning(e.description) self.show() def _delete(self, did): if str(did) == "1": return showWarning(_("The default deck can't be deleted.")) self.mw.checkpoint(_("Delete Deck")) deck = self.mw.col.decks.get(did) if not deck["dyn"]: dids = [did] + [r[1] for r in self.mw.col.decks.children(did)] cnt = self.mw.col.db.scalar( "select count() from cards where did in {0} or " "odid in {0}".format(ids2str(dids))) if cnt: extra = ngettext(" It has %d card.", " It has %d cards.", cnt) % cnt else: extra = None if (deck["dyn"] or not extra or askUser((_("Are you sure you wish to delete %s?") % deck["name"]) + extra)): self.mw.progress.start(immediate=True) self.mw.col.decks.rem(did, True) self.mw.progress.finish() self.show() # Top buttons ###################################################################### drawLinks = [ ["", "shared", _("Get Shared")], ["", "create", _("Create Deck")], ["Ctrl+I", "import", _("Import File")], # Ctrl+I works from menu ] def _drawButtons(self): buf = "" drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: b[0] = _("Shortcut key: %s") % shortcut(b[0]) buf += """ <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=DeckBrowserBottomBar(self), ) def _onShared(self): openLink(aqt.appShared + "decks/")
def __init__(self, mw: aqt.AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self._refresh_needed = False
class Overview: "Deck overview." def __init__(self, mw: aqt.AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self._refresh_needed = False def show(self) -> None: av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self.mw.setStateShortcuts(self._shortcutKeys()) self.refresh() def refresh(self) -> None: self._refresh_needed = False self.mw.col.reset() self._renderPage() self._renderBottom() self.mw.web.setFocus() gui_hooks.overview_did_refresh(self) def refresh_if_needed(self) -> None: if self._refresh_needed: self.refresh() def op_executed( self, changes: OpChanges, handler: Optional[object], focused: bool ) -> bool: if changes.study_queues: self._refresh_needed = True if focused: self.refresh_if_needed() return self._refresh_needed # Handlers ############################################################ def _linkHandler(self, url: str) -> bool: if url == "study": self.mw.col.startTimebox() self.mw.moveToState("review") if self.mw.state == "overview": tooltip(tr.studying_no_cards_are_due_yet()) elif url == "anki": print("anki menu") elif url == "opts": self.mw.onDeckConf() elif url == "cram": aqt.dialogs.open("FilteredDeckConfigDialog", self.mw) elif url == "refresh": self.rebuild_current_filtered_deck() elif url == "empty": self.empty_current_filtered_deck() elif url == "decks": self.mw.moveToState("deckBrowser") elif url == "review": openLink(f"{aqt.appShared}info/{self.sid}?v={self.sidVer}") elif url == "studymore" or url == "customStudy": self.onStudyMore() elif url == "unbury": self.onUnbury() elif url.lower().startswith("http"): openLink(url) return False def _shortcutKeys(self) -> List[Tuple[str, Callable]]: return [ ("o", self.mw.onDeckConf), ("r", self.rebuild_current_filtered_deck), ("e", self.empty_current_filtered_deck), ("c", self.onCustomStudyKey), ("u", self.onUnbury), ] def _current_deck_is_filtered(self) -> int: return self.mw.col.decks.current()["dyn"] def rebuild_current_filtered_deck(self) -> None: rebuild_filtered_deck( parent=self.mw, deck_id=self.mw.col.decks.selected() ).run_in_background() def empty_current_filtered_deck(self) -> None: empty_filtered_deck( parent=self.mw, deck_id=self.mw.col.decks.selected() ).run_in_background() def onCustomStudyKey(self) -> None: if not self._current_deck_is_filtered(): self.onStudyMore() def onUnbury(self) -> None: if self.mw.col.schedVer() == 1: self.mw.col.sched.unburyCardsForDeck() self.mw.reset() return info = self.mw.col.sched.congratulations_info() if info.have_sched_buried and info.have_user_buried: opts = [ tr.studying_manually_buried_cards(), tr.studying_buried_siblings(), tr.studying_all_buried_cards(), tr.actions_cancel(), ] diag = askUserDialog(tr.studying_what_would_you_like_to_unbury(), opts) diag.setDefault(0) ret = diag.run() if ret == opts[0]: self.mw.col.sched.unburyCardsForDeck(type="manual") elif ret == opts[1]: self.mw.col.sched.unburyCardsForDeck(type="siblings") elif ret == opts[2]: self.mw.col.sched.unburyCardsForDeck(type="all") else: self.mw.col.sched.unburyCardsForDeck(type="all") self.mw.reset() # HTML ############################################################ def _renderPage(self) -> None: but = self.mw.button deck = self.mw.col.decks.current() self.sid = deck.get("sharedFrom") if self.sid: self.sidVer = deck.get("ver", None) shareLink = '<a class=smallLink href="review">Reviews and Updates</a>' else: shareLink = "" if self.mw.col.sched._is_finished(): self._show_finished_screen() return table_text = self._table() content = OverviewContent( deck=deck["name"], shareLink=shareLink, desc=self._desc(deck), table=self._table(), ) gui_hooks.overview_will_render_content(self, content) self.web.stdHtml( self._body % content.__dict__, css=["css/overview.css"], js=["js/vendor/jquery.min.js", "js/overview.js"], context=self, ) def _show_finished_screen(self) -> None: self.web.load_ts_page("congrats") def _desc(self, deck: Dict[str, Any]) -> str: if deck["dyn"]: desc = tr.studying_this_is_a_special_deck_for() desc += f" {tr.studying_cards_will_be_automatically_returned_to()}" desc += f" {tr.studying_deleting_this_deck_from_the_deck()}" else: desc = deck.get("desc", "") if deck.get("md", False): desc = self.mw.col.render_markdown(desc) if not desc: return "<p>" if deck["dyn"]: dyn = "dyn" else: dyn = "" return f'<div class="descfont descmid description {dyn}">{desc}</div>' def _table(self) -> Optional[str]: counts = list(self.mw.col.sched.counts()) but = self.mw.button return """ <table width=400 cellpadding=5> <tr><td align=center valign=top> <table cellspacing=5> <tr><td>%s:</td><td><b><span class=new-count>%s</span></b></td></tr> <tr><td>%s:</td><td><b><span class=learn-count>%s</span></b></td></tr> <tr><td>%s:</td><td><b><span class=review-count>%s</span></b></td></tr> </table> </td><td align=center> %s</td></tr></table>""" % ( tr.actions_new(), counts[0], tr.scheduling_learning(), counts[1], tr.studying_to_review(), counts[2], but("study", tr.studying_study_now(), id="study", extra=" autofocus"), ) _body = """ <center> <h3>%(deck)s</h3> %(shareLink)s %(desc)s %(table)s </center> """ # Bottom area ###################################################################### def _renderBottom(self) -> None: links = [ ["O", "opts", tr.actions_options()], ] if self.mw.col.decks.current()["dyn"]: links.append(["R", "refresh", tr.actions_rebuild()]) links.append(["E", "empty", tr.studying_empty()]) else: links.append(["C", "studymore", tr.actions_custom_study()]) # links.append(["F", "cram", _("Filter/Cram")]) if self.mw.col.sched.haveBuried(): links.append(["U", "unbury", tr.studying_unbury()]) buf = "" for b in links: if b[0]: b[0] = tr.actions_shortcut_key(val=shortcut(b[0])) buf += """ <button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple( b ) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=OverviewBottomBar(self) ) # Studying more ###################################################################### def onStudyMore(self) -> None: import aqt.customstudy aqt.customstudy.CustomStudy(self.mw)
class DeckBrowser: _dueTree: DeckTreeNode def __init__(self, mw: AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) self.scrollPos = QPoint(0, 0) self._v1_message_dismissed_at = 0 self._refresh_needed = False def show(self) -> None: av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self._renderPage() # redraw top bar for theme change self.mw.toolbar.redraw() self.refresh() def refresh(self) -> None: self._renderPage() self._refresh_needed = False def refresh_if_needed(self) -> None: if self._refresh_needed: self.refresh() def op_executed(self, changes: OpChanges, handler: object | None, focused: bool) -> bool: if changes.study_queues and handler is not self: self._refresh_needed = True if focused: self.refresh_if_needed() return self._refresh_needed # Event handlers ########################################################################## def _linkHandler(self, url: str) -> Any: if ":" in url: (cmd, arg) = url.split(":", 1) else: cmd = url if cmd == "open": self.set_current_deck(DeckId(int(arg))) elif cmd == "opts": self._showOptions(arg) elif cmd == "shared": self._onShared() elif cmd == "import": self.mw.onImport() elif cmd == "create": self._on_create() elif cmd == "drag": source, target = arg.split(",") self._handle_drag_and_drop(DeckId(int(source)), DeckId(int(target or 0))) elif cmd == "collapse": self._collapse(DeckId(int(arg))) elif cmd == "v2upgrade": self._confirm_upgrade() elif cmd == "v2upgradeinfo": openLink("https://faqs.ankiweb.net/the-anki-2.1-scheduler.html") return False def set_current_deck(self, deck_id: DeckId) -> None: set_current_deck(parent=self.mw, deck_id=deck_id).success( lambda _: self.mw.onOverview()).run_in_background(initiator=self) # HTML generation ########################################################################## _body = """ <center> <table cellspacing=0 cellpading=3> %(tree)s </table> <br> %(stats)s </center> """ def _renderPage(self, reuse: bool = False) -> None: if not reuse: self._dueTree = self.mw.col.sched.deck_due_tree() self.__renderPage(None) return self.web.evalWithCallback("window.pageYOffset", self.__renderPage) def __renderPage(self, offset: int) -> None: content = DeckBrowserContent( tree=self._renderDeckTree(self._dueTree), stats=self._renderStats(), ) gui_hooks.deck_browser_will_render_content(self, content) self.web.stdHtml( self._v1_upgrade_message() + self._body % content.__dict__, css=["css/deckbrowser.css"], js=[ "js/vendor/jquery.min.js", "js/vendor/jquery-ui.min.js", "js/deckbrowser.js", ], context=self, ) self._drawButtons() if offset is not None: self._scrollToOffset(offset) gui_hooks.deck_browser_did_render(self) def _scrollToOffset(self, offset: int) -> None: self.web.eval("window.scrollTo(0, %d, 'instant');" % offset) def _renderStats(self) -> str: return '<div id="studiedToday"><span>{}</span></div>'.format( self.mw.col.studied_today(), ) def _renderDeckTree(self, top: DeckTreeNode) -> str: buf = """ <tr><th colspan=5 align=start>{}</th> <th class=count>{}</th> <th class=count>{}</th> <th class=count>{}</th> <th class=optscol></th></tr>""".format( tr.decks_deck(), tr.actions_new(), tr.card_stats_review_log_type_learn(), tr.statistics_due_count(), ) buf += self._topLevelDragRow() ctx = RenderDeckNodeContext( current_deck_id=self.mw.col.conf["curDeck"]) for child in top.children: buf += self._render_deck_node(child, ctx) return buf def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str: if node.collapsed: prefix = "+" else: prefix = "-" def indent() -> str: return " " * 6 * (node.level - 1) if node.deck_id == ctx.current_deck_id: klass = "deck current" else: klass = "deck" buf = "<tr class='%s' id='%d'>" % (klass, node.deck_id) # deck link if node.children: collapse = ( "<a class=collapse href=# onclick='return pycmd(\"collapse:%d\")'>%s</a>" % (node.deck_id, prefix)) else: collapse = "<span class=collapse></span>" if node.filtered: extraclass = "filtered" else: extraclass = "" buf += """ <td class=decktd colspan=5>%s%s<a class="deck %s" href=# onclick="return pycmd('open:%d')">%s</a></td>""" % ( indent(), collapse, extraclass, node.deck_id, node.name, ) # due counts def nonzeroColour(cnt: int, klass: str) -> str: if not cnt: klass = "zero-count" return f'<span class="{klass}">{cnt}</span>' review = nonzeroColour(node.review_count, "review-count") learn = nonzeroColour(node.learn_count, "learn-count") buf += ("<td align=right>%s</td>" * 3) % ( nonzeroColour(node.new_count, "new-count"), learn, review, ) # options buf += ( "<td align=center class=opts><a onclick='return pycmd(\"opts:%d\");'>" "<img src='/_anki/imgs/gears.svg' class=gears></a></td></tr>" % node.deck_id) # children if not node.collapsed: for child in node.children: buf += self._render_deck_node(child, ctx) return buf def _topLevelDragRow(self) -> str: return "<tr class='top-level-drag-row'><td colspan='6'> </td></tr>" # Options ########################################################################## def _showOptions(self, did: str) -> None: m = QMenu(self.mw) a = m.addAction(tr.actions_rename()) qconnect(a.triggered, lambda b, did=did: self._rename(DeckId(int(did)))) a = m.addAction(tr.actions_options()) qconnect(a.triggered, lambda b, did=did: self._options(DeckId(int(did)))) a = m.addAction(tr.actions_export()) qconnect(a.triggered, lambda b, did=did: self._export(DeckId(int(did)))) a = m.addAction(tr.actions_delete()) qconnect(a.triggered, lambda b, did=did: self._delete(DeckId(int(did)))) gui_hooks.deck_browser_will_show_options_menu(m, int(did)) m.popup(QCursor.pos()) def _export(self, did: DeckId) -> None: self.mw.onExport(did=did) def _rename(self, did: DeckId) -> None: def prompt(name: str) -> None: new_name = getOnlyText(tr.decks_new_deck_name(), default=name) if not new_name or new_name == name: return else: rename_deck(parent=self.mw, deck_id=did, new_name=new_name).run_in_background() QueryOp(parent=self.mw, op=lambda col: col.decks.name(did), success=prompt).run_in_background() def _options(self, did: DeckId) -> None: display_options_for_deck_id(did) def _collapse(self, did: DeckId) -> None: node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did) if node: node.collapsed = not node.collapsed set_deck_collapsed( parent=self.mw, deck_id=did, collapsed=node.collapsed, scope=DeckCollapseScope.REVIEWER, ).run_in_background() self._renderPage(reuse=True) def _handle_drag_and_drop(self, source: DeckId, target: DeckId) -> None: reparent_decks(parent=self.mw, deck_ids=[source], new_parent=target).run_in_background() def _delete(self, did: DeckId) -> None: remove_decks(parent=self.mw, deck_ids=[did]).run_in_background() # Top buttons ###################################################################### drawLinks = [ ["", "shared", tr.decks_get_shared()], ["", "create", tr.decks_create_deck()], ["Ctrl+Shift+I", "import", tr.decks_import_file()], ] def _drawButtons(self) -> None: buf = "" drawLinks = deepcopy(self.drawLinks) for b in drawLinks: if b[0]: b[0] = tr.actions_shortcut_key(val=shortcut(b[0])) buf += """ <button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(b) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=DeckBrowserBottomBar(self), ) def _onShared(self) -> None: openLink(f"{aqt.appShared}decks/") def _on_create(self) -> None: if op := add_deck_dialog(parent=self.mw): op.run_in_background()
def __init__(self, mw: aqt.AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb)
class Overview: "Deck overview." def __init__(self, mw: aqt.AnkiQt) -> None: self.mw = mw self.web = mw.web self.bottom = BottomBar(mw, mw.bottomWeb) def show(self): av_player.stop_and_clear_queue() self.web.set_bridge_command(self._linkHandler, self) self.mw.setStateShortcuts(self._shortcutKeys()) self.refresh() def refresh(self): self.mw.col.reset() self._renderPage() self._renderBottom() self.mw.web.setFocus() gui_hooks.overview_did_refresh(self) # Handlers ############################################################ def _linkHandler(self, url): if url == "study": self.mw.col.startTimebox() self.mw.moveToState("review") if self.mw.state == "overview": tooltip(tr(TR.STUDYING_NO_CARDS_ARE_DUE_YET)) elif url == "anki": print("anki menu") elif url == "opts": self.mw.onDeckConf() elif url == "cram": deck = self.mw.col.decks.current()["name"] self.mw.onCram(self.mw.col.build_search_string(SearchTerm(deck=deck))) elif url == "refresh": self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() elif url == "empty": self.mw.col.sched.empty_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() elif url == "decks": self.mw.moveToState("deckBrowser") elif url == "review": openLink(aqt.appShared + "info/%s?v=%s" % (self.sid, self.sidVer)) elif url == "studymore" or url == "customStudy": self.onStudyMore() elif url == "unbury": self.onUnbury() elif url.lower().startswith("http"): openLink(url) return False def _shortcutKeys(self): return [ ("o", self.mw.onDeckConf), ("r", self.onRebuildKey), ("e", self.onEmptyKey), ("c", self.onCustomStudyKey), ("u", self.onUnbury), ] def _filteredDeck(self): return self.mw.col.decks.current()["dyn"] def onRebuildKey(self): if self._filteredDeck(): self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() def onEmptyKey(self): if self._filteredDeck(): self.mw.col.sched.empty_filtered_deck(self.mw.col.decks.selected()) self.mw.reset() def onCustomStudyKey(self): if not self._filteredDeck(): self.onStudyMore() def onUnbury(self): if self.mw.col.schedVer() == 1: self.mw.col.sched.unburyCardsForDeck() self.mw.reset() return info = self.mw.col.sched.congratulations_info() if info.have_sched_buried and info.have_user_buried: opts = [ tr(TR.STUDYING_MANUALLY_BURIED_CARDS), tr(TR.STUDYING_BURIED_SIBLINGS), tr(TR.STUDYING_ALL_BURIED_CARDS), tr(TR.ACTIONS_CANCEL), ] diag = askUserDialog(tr(TR.STUDYING_WHAT_WOULD_YOU_LIKE_TO_UNBURY), opts) diag.setDefault(0) ret = diag.run() if ret == opts[0]: self.mw.col.sched.unburyCardsForDeck(type="manual") elif ret == opts[1]: self.mw.col.sched.unburyCardsForDeck(type="siblings") elif ret == opts[2]: self.mw.col.sched.unburyCardsForDeck(type="all") else: self.mw.col.sched.unburyCardsForDeck(type="all") self.mw.reset() # HTML ############################################################ def _renderPage(self): but = self.mw.button deck = self.mw.col.decks.current() self.sid = deck.get("sharedFrom") if self.sid: self.sidVer = deck.get("ver", None) shareLink = '<a class=smallLink href="review">Reviews and Updates</a>' else: shareLink = "" if self.mw.col.sched._is_finished(): self._show_finished_screen() return table_text = self._table() content = OverviewContent( deck=deck["name"], shareLink=shareLink, desc=self._desc(deck), table=self._table(), ) gui_hooks.overview_will_render_content(self, content) self.web.stdHtml( self._body % content.__dict__, css=["css/overview.css"], js=["js/vendor/jquery.min.js", "js/overview.js"], context=self, ) def _show_finished_screen(self): self.web.load_ts_page("congrats") def _desc(self, deck): if deck["dyn"]: desc = tr(TR.STUDYING_THIS_IS_A_SPECIAL_DECK_FOR) desc += " " + tr(TR.STUDYING_CARDS_WILL_BE_AUTOMATICALLY_RETURNED_TO) desc += " " + tr(TR.STUDYING_DELETING_THIS_DECK_FROM_THE_DECK) else: desc = deck.get("desc", "") if not desc: return "<p>" if deck["dyn"]: dyn = "dyn" else: dyn = "" return '<div class="descfont descmid description %s">%s</div>' % (dyn, desc) def _table(self) -> Optional[str]: counts = list(self.mw.col.sched.counts()) but = self.mw.button return """ <table width=400 cellpadding=5> <tr><td align=center valign=top> <table cellspacing=5> <tr><td>%s:</td><td><b><span class=new-count>%s</span></b></td></tr> <tr><td>%s:</td><td><b><span class=learn-count>%s</span></b></td></tr> <tr><td>%s:</td><td><b><span class=review-count>%s</span></b></td></tr> </table> </td><td align=center> %s</td></tr></table>""" % ( tr(TR.ACTIONS_NEW), counts[0], tr(TR.SCHEDULING_LEARNING), counts[1], tr(TR.STUDYING_TO_REVIEW), counts[2], but("study", tr(TR.STUDYING_STUDY_NOW), id="study", extra=" autofocus"), ) _body = """ <center> <h3>%(deck)s</h3> %(shareLink)s %(desc)s %(table)s </center> """ # Bottom area ###################################################################### def _renderBottom(self): links = [ ["O", "opts", tr(TR.ACTIONS_OPTIONS)], ] if self.mw.col.decks.current()["dyn"]: links.append(["R", "refresh", tr(TR.ACTIONS_REBUILD)]) links.append(["E", "empty", tr(TR.STUDYING_EMPTY)]) else: links.append(["C", "studymore", tr(TR.ACTIONS_CUSTOM_STUDY)]) # links.append(["F", "cram", _("Filter/Cram")]) if self.mw.col.sched.haveBuried(): links.append(["U", "unbury", tr(TR.STUDYING_UNBURY)]) buf = "" for b in links: if b[0]: b[0] = tr(TR.ACTIONS_SHORTCUT_KEY, val=shortcut(b[0])) buf += """ <button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple( b ) self.bottom.draw( buf=buf, link_handler=self._linkHandler, web_context=OverviewBottomBar(self) ) # Studying more ###################################################################### def onStudyMore(self): import aqt.customstudy aqt.customstudy.CustomStudy(self.mw)