diff --git a/proto/backend.proto b/proto/backend.proto
index 402fa1b54..3fcb56a3a 100644
--- a/proto/backend.proto
+++ b/proto/backend.proto
@@ -264,6 +264,8 @@ message DeckTreeNode {
uint32 review_count = 6;
uint32 learn_count = 7;
uint32 new_count = 8;
+
+ bool filtered = 16;
}
message RenderExistingCardIn {
diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py
index 83e908f01..5913fd217 100644
--- a/pylib/anki/schedv2.py
+++ b/pylib/anki/schedv2.py
@@ -15,7 +15,7 @@ from anki import hooks
from anki.cards import Card
from anki.consts import *
from anki.lang import _
-from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
+from anki.rsbackend import DeckTreeNode, FormatTimeSpanContext, SchedTimingToday
from anki.utils import ids2str, intTime
# card types: 0=new, 1=lrn, 2=rev, 3=relrn
@@ -232,6 +232,10 @@ order by due"""
"List of (base name, did, rev, lrn, new, children)"
return self.col.backend.legacy_deck_tree()
+ def deck_due_tree(self) -> DeckTreeNode:
+ "Returns a tree of decks with counts."
+ return self.col.backend.deck_tree(include_counts=True)
+
# Getting the next card
##########################################################################
diff --git a/qt/aqt/deckbrowser.py b/qt/aqt/deckbrowser.py
index 883e1c1d9..5470dad0c 100644
--- a/qt/aqt/deckbrowser.py
+++ b/qt/aqt/deckbrowser.py
@@ -6,12 +6,11 @@ from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
-from typing import Any
import aqt
from anki.errors import DeckRenameError
from anki.lang import _, ngettext
-from anki.rsbackend import TR
+from anki.rsbackend import TR, DeckTreeNode
from anki.utils import ids2str
from aqt import AnkiQt, gui_hooks
from aqt.qt import *
@@ -39,8 +38,13 @@ class DeckBrowserContent:
stats: str
+@dataclass
+class RenderDeckNodeContext:
+ current_deck_id: int
+
+
class DeckBrowser:
- _dueTree: Any
+ _dueTree: DeckTreeNode
def __init__(self, mw: AnkiQt) -> None:
self.mw = mw
@@ -83,7 +87,7 @@ class DeckBrowser:
draggedDeckDid, ontoDeckDid = arg.split(",")
self._dragDeckOnto(draggedDeckDid, ontoDeckDid)
elif cmd == "collapse":
- self._collapse(arg)
+ self._collapse(int(arg))
return False
def _selDeck(self, did):
@@ -106,7 +110,7 @@ class DeckBrowser:
def _renderPage(self, reuse=False):
if not reuse:
- self._dueTree = self.mw.col.sched.deckDueTree()
+ self._dueTree = self.mw.col.sched.deck_due_tree()
self.__renderPage(None)
return
self.web.evalWithCallback("window.pageYOffset", self.__renderPage)
@@ -143,53 +147,49 @@ where id > ?""",
buf = self.mw.col.backend.studied_today(cards, float(thetime))
return buf
- def _renderDeckTree(self, nodes, depth=0):
- if not nodes:
- return ""
- if depth == 0:
- buf = """
+ def _renderDeckTree(self, top: DeckTreeNode) -> str:
+ buf = """
%s | %s |
%s | |
""" % (
- _("Deck"),
- tr(TR.STATISTICS_DUE_COUNT),
- _("New"),
- )
- buf += self._topLevelDragRow()
- else:
- buf = ""
- for node in nodes:
- buf += self._deckRow(node, depth)
- if depth == 0:
- buf += self._topLevelDragRow()
+ _("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 _deckRow(self, node, depth):
- name, did, due, lrn, new, children = node
- deck = self.mw.col.decks.get(did)
- collapsed = self.mw.col.decks.get(did)["collapsed"]
- if collapsed:
+ def _render_deck_node(self, node: DeckTreeNode, ctx: RenderDeckNodeContext) -> str:
+ if node.collapsed:
prefix = "+"
else:
prefix = "-"
- due += lrn
+
+ due = node.review_count + node.learn_count
def indent():
- return " " * 6 * depth
+ return " " * 6 * (node.level - 1)
- if did == self.mw.col.conf["curDeck"]:
+ if node.deck_id == ctx.current_deck_id:
klass = "deck current"
else:
klass = "deck"
- buf = "" % (klass, did)
+
+ buf = "
" % (klass, node.deck_id)
# deck link
- if children:
+ if node.children:
collapse = (
"%s"
- % (did, prefix)
+ % (node.deck_id, prefix)
)
else:
collapse = ""
- if deck["dyn"]:
+ if node.filtered:
extraclass = "filtered"
else:
extraclass = ""
@@ -200,8 +200,8 @@ where id > ?""",
indent(),
collapse,
extraclass,
- did,
- name,
+ node.deck_id,
+ node.name,
)
# due counts
def nonzeroColour(cnt, klass):
@@ -211,16 +211,17 @@ where id > ?""",
buf += "%s | %s | " % (
nonzeroColour(due, "review-count"),
- nonzeroColour(new, "new-count"),
+ nonzeroColour(node.new_count, "new-count"),
)
# options
buf += (
""
- " |
" % did
+ "
" % node.deck_id
)
# children
- if not collapsed:
- buf += self._renderDeckTree(children, depth + 1)
+ if not node.collapsed:
+ for child in node.children:
+ buf += self._render_deck_node(child, ctx)
return buf
def _topLevelDragRow(self):
@@ -265,10 +266,21 @@ where id > ?""",
self.mw.col.decks.select(did)
self.mw.onDeckConf()
- def _collapse(self, did):
+ def _collapse(self, did: int) -> None:
self.mw.col.decks.collapse(did)
+ self._toggle_collapsed_in_node(did, self._dueTree)
self._renderPage(reuse=True)
+ def _toggle_collapsed_in_node(self, deck_id: int, node: DeckTreeNode) -> bool:
+ "Toggle collapsed on deck in tree. Returns true if found."
+ if node.deck_id == deck_id:
+ node.collapsed = not node.collapsed
+ return True
+ for child in node.children:
+ if self._toggle_collapsed_in_node(deck_id, child):
+ return True
+ return False
+
def _dragDeckOnto(self, draggedDeckDid, ontoDeckDid):
try:
self.mw.col.decks.renameForDragAndDrop(draggedDeckDid, ontoDeckDid)
diff --git a/qt/mypy.ini b/qt/mypy.ini
index 5f88a2a12..bdee644c0 100644
--- a/qt/mypy.ini
+++ b/qt/mypy.ini
@@ -62,3 +62,5 @@ check_untyped_defs=true
check_untyped_defs=true
[mypy-aqt.preferences]
check_untyped_defs=true
+[mypy-aqt.deckbrowser]
+check_untyped_defs=true
diff --git a/rslib/src/decks/tree.rs b/rslib/src/decks/tree.rs
index ff7a0386d..d5daeb7e2 100644
--- a/rslib/src/decks/tree.rs
+++ b/rslib/src/decks/tree.rs
@@ -62,16 +62,21 @@ fn add_child_nodes(
}
}
-fn add_collapsed(node: &mut DeckTreeNode, decks: &HashMap, browser: bool) {
+fn add_collapsed_and_filtered(
+ node: &mut DeckTreeNode,
+ decks: &HashMap,
+ browser: bool,
+) {
if let Some(deck) = decks.get(&DeckID(node.deck_id)) {
node.collapsed = if browser {
deck.common.browser_collapsed
} else {
deck.common.study_collapsed
};
+ node.filtered = deck.is_filtered();
}
for child in &mut node.children {
- add_collapsed(child, decks, browser);
+ add_collapsed_and_filtered(child, decks, browser);
}
}
@@ -201,7 +206,7 @@ impl Collection {
.map(|d| (d.id, d))
.collect();
- add_collapsed(&mut tree, &decks_map, !counts);
+ add_collapsed_and_filtered(&mut tree, &decks_map, !counts);
if self.default_deck_is_empty()? {
hide_default_deck(&mut tree);
}