diff --git a/pylib/anki/decks.py b/pylib/anki/decks.py index 47696ad8a..c683eb8d5 100644 --- a/pylib/anki/decks.py +++ b/pylib/anki/decks.py @@ -92,6 +92,16 @@ defaultConf = { "usn": 0, } +# How to select list of decks + +# difference between WITHOUT_EMPTY_DEFAULT and +# WITHOUT_EMPTY_LEAF_DEFAULT is that the first contains default when +# it has a child. This is required for deck tree. + +WITHOUT_EMPTY_DEFAULT = 0 +ALL_DECKS = 1 +WITHOUT_EMPTY_LEAF_DEFAULT = 2 + class DeckManager: decks: Dict[str, Any] @@ -219,7 +229,7 @@ class DeckManager: self.select(int(list(self.decks.keys())[0])) self.save() - def allNames(self, dyn: bool = True, forceDefault: bool = True) -> List: + def allNames(self, dyn: bool = True, forceDefault: int = ALL_DECKS) -> List: "An unsorted list of all deck names." if dyn: return [x["name"] for x in self.all(forceDefault=forceDefault)] @@ -228,14 +238,10 @@ class DeckManager: x["name"] for x in self.all(forceDefault=forceDefault) if not x["dyn"] ] - def all(self, forceDefault: bool = True) -> List: + def all(self, forceDefault: int = ALL_DECKS) -> List: "A list of all decks." decks = list(self.decks.values()) - if ( - not forceDefault - and not self.col.db.scalar("select 1 from cards where did = 1 limit 1") - and len(decks) > 1 - ): + if not forceDefault and not self.shouldDefaultBeDisplayed(forceDefault): decks = [deck for deck in decks if deck["id"] != 1] return decks @@ -513,6 +519,35 @@ class DeckManager: self._recoverOrphans() self._checkDeckTree() + def shouldDeckBeDisplayed(self, deck, forceDefault: int = ALL_DECKS) -> bool: + """Whether the deck should appear in main window, browser side list, filter, deck selection... + + True, except for empty default deck without children""" + if deck["id"] != "1": + return True + return self.shouldDefaultBeDisplayed(forceDefault) + + def shouldDefaultBeDisplayed( + self, forceDefault: int = ALL_DECKS, defaultDeck=None + ) -> bool: + """Whether the default deck should appear in main window, browser side list, filter, deck selection... + + True, except for empty default deck (without children)""" + if forceDefault == ALL_DECKS: + return True + if self.col.db.scalar("select 1 from cards where did = 1 limit 1"): + return True + if len(self.decks) == 1: + return True + if forceDefault == WITHOUT_EMPTY_LEAF_DEFAULT: + if defaultDeck is None: + defaultDeck = self.get(1) + defaultName = defaultDeck["name"] + for name in self.allNames(): + if name.startswith(f"{defaultName}::"): + return True + return False + # Deck selection ############################################################# diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 301683d95..239758e26 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -21,6 +21,7 @@ from anki import hooks from anki.cards import Card from anki.collection import _Collection from anki.consts import * +from anki.decks import WITHOUT_EMPTY_DEFAULT from anki.lang import _, ngettext from anki.models import NoteType from anki.notes import Note @@ -1156,16 +1157,25 @@ by clicking on one on the left.""" def fillGroups(root, grps, head=""): for g in grps: + baseName = g[0] + did = g[1] + children = g[5] + if str(did) == "1" and not children: + if not self.mw.col.decks.shouldDefaultBeDisplayed( + WITHOUT_EMPTY_DEFAULT + ): + # No need to test for children, we know there are not + continue item = SidebarItem( - g[0], + baseName, ":/icons/deck.svg", - lambda g=g: self.setFilter("deck", head + g[0]), - lambda expanded, g=g: self.mw.col.decks.collapseBrowser(g[1]), + lambda baseName=baseName: self.setFilter("deck", head + baseName), + lambda expanded, did=did: self.mw.col.decks.collapseBrowser(did), not self.mw.col.decks.get(g[1]).get("browserCollapsed", False), ) root.addChild(item) - newhead = head + g[0] + "::" - fillGroups(item, g[5], newhead) + newhead = head + baseName + "::" + fillGroups(item, children, newhead) fillGroups(root, grps) @@ -1312,7 +1322,12 @@ by clicking on one on the left.""" subm.addSeparator() addDecks(subm, children) else: - parent.addItem(shortname, self._filterFunc("deck", name)) + if did != 1 or self.col.decks.shouldDefaultBeDisplayed( + WITHOUT_EMPTY_DEFAULT + # no need to check for children, we know + # there are none in this else branch + ): + parent.addItem(shortname, self._filterFunc("deck", name)) # fixme: could rewrite to avoid calculating due # in the future alldecks = self.col.sched.deckDueTree() diff --git a/qt/aqt/studydeck.py b/qt/aqt/studydeck.py index 8a331f5e6..b0d56bcd9 100644 --- a/qt/aqt/studydeck.py +++ b/qt/aqt/studydeck.py @@ -3,6 +3,7 @@ # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import aqt +from anki.decks import WITHOUT_EMPTY_LEAF_DEFAULT from anki.lang import _ from aqt import gui_hooks from aqt.qt import * @@ -51,7 +52,11 @@ class StudyDeck(QDialog): if title: self.setWindowTitle(title) if not names: - names = sorted(self.mw.col.decks.allNames(dyn=dyn, forceDefault=False)) + names = sorted( + self.mw.col.decks.allNames( + dyn=dyn, forceDefault=WITHOUT_EMPTY_LEAF_DEFAULT + ) + ) self.nameFunc = None self.origNames = names else: