place each sidebar section under its own collapsible parent node

- Allows for group operations like "clear unused tags"
- Allows users to hide groups they're not interested in
This commit is contained in:
Damien Elmes 2021-01-29 22:10:31 +10:00
parent 5ff7944a26
commit 6ba5ff5a01
4 changed files with 118 additions and 21 deletions

View File

@ -87,6 +87,7 @@ browsing-search-in = Search in:
browsing-search-within-formatting-slow = Search within formatting (slow)
browsing-shift-position-of-existing-cards = Shift position of existing cards
browsing-sidebar = Sidebar
browsing-sidebar-filter = Sidebar filter
browsing-sort-field = Sort Field
browsing-sorting-on-this-column-is-not = Sorting on this column is not supported. Please choose another.
browsing-start-position = Start position:
@ -119,3 +120,8 @@ browsing-note-deleted =
*[other] { $count } notes deleted.
}
browsing-window-title = Browse ({ $selected } of { $total } cards selected)
browsing-sidebar-decks = Decks
browsing-sidebar-tags = Tags
browsing-sidebar-notetypes = Note Types
browsing-sidebar-saved-searches = Saved Searches
browsing-sidebar-save-current-search = Save Current Search

View File

@ -6,9 +6,10 @@ from __future__ import annotations
from concurrent.futures import Future
from enum import Enum
from typing import Iterable, List, Optional
from typing import TYPE_CHECKING, Iterable, List, Optional
import aqt
from anki.collection import ConfigBoolKey
from anki.errors import DeckRenameError
from anki.rsbackend import DeckTreeNode, FilterToSearchIn, NamedFilter, TagTreeNode
from aqt import gui_hooks
@ -18,17 +19,21 @@ from aqt.qt import *
from aqt.theme import theme_manager
from aqt.utils import TR, getOnlyText, showInfo, showWarning, tr
if TYPE_CHECKING:
from anki.collection import ConfigBoolKeyValue, TRValue
class SidebarItemType(Enum):
ROOT = 0
COLLECTION = 1
CURRENT_DECK = 2
FILTER = 3
SAVED_SEARCH = 3
DECK = 4
NOTETYPE = 5
TAG = 6
CUSTOM = 7
TEMPLATE = 8
SAVED_SEARCH_ROOT = 9
# used by an add-on hook
@ -182,6 +187,7 @@ class FilterModel(QSortFilterProxyModel):
class SidebarSearchBar(QLineEdit):
def __init__(self, sidebar: SidebarTreeView):
QLineEdit.__init__(self, sidebar)
self.setPlaceholderText(sidebar.col.tr(TR.BROWSING_SIDEBAR_FILTER))
self.sidebar = sidebar
self.timer = QTimer(self)
self.timer.setInterval(600)
@ -224,11 +230,14 @@ class SidebarTreeView(QTreeView):
(tr(TR.ACTIONS_RENAME), self.rename_tag),
(tr(TR.ACTIONS_DELETE), self.remove_tag),
),
SidebarItemType.FILTER: (
SidebarItemType.SAVED_SEARCH: (
(tr(TR.ACTIONS_RENAME), self.rename_filter),
(tr(TR.ACTIONS_DELETE), self.remove_filter),
),
SidebarItemType.NOTETYPE: ((tr(TR.ACTIONS_MANAGE), self.manage_notetype),),
SidebarItemType.SAVED_SEARCH_ROOT: (
(tr(TR.BROWSING_SIDEBAR_SAVE_CURRENT_SEARCH), self.save_current_search),
),
}
self.setUniformRowHeights(True)
@ -350,7 +359,7 @@ class SidebarTreeView(QTreeView):
list(SidebarStage)[1:],
(
self._commonly_used_tree,
self._favorites_tree,
self._saved_searches_tree,
self._deck_tree,
self._notetype_tree,
self._tag_tree,
@ -362,6 +371,29 @@ class SidebarTreeView(QTreeView):
return root
def _section_root(
self,
*,
root: SidebarItem,
name: TRValue,
icon: str,
collapse_key: ConfigBoolKeyValue,
type: Optional[SidebarItemType] = None,
) -> SidebarItem:
def update(expanded: bool):
self.col.set_config_bool(collapse_key, not expanded)
top = SidebarItem(
self.col.tr(name),
icon,
onExpanded=update,
expanded=not self.col.get_config_bool(collapse_key),
item_type=type,
)
root.addChild(top)
return top
def _commonly_used_tree(self, root: SidebarItem) -> None:
item = SidebarItem(
tr(TR.BROWSING_WHOLE_COLLECTION),
@ -378,20 +410,34 @@ class SidebarTreeView(QTreeView):
)
root.addChild(item)
def _favorites_tree(self, root: SidebarItem) -> None:
assert self.col
def _saved_searches_tree(self, root: SidebarItem) -> None:
icon = ":/icons/heart.svg"
saved = self.col.get_config("savedFilters", {})
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_SAVED_SEARCHES,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_FAVORITES,
type=SidebarItemType.SAVED_SEARCH_ROOT,
)
def on_click():
self.show_context_menu(root)
root.onClick = on_click
for name, filt in sorted(saved.items()):
item = SidebarItem(
name,
":/icons/heart.svg",
icon,
self._saved_filter(filt),
item_type=SidebarItemType.FILTER,
item_type=SidebarItemType.SAVED_SEARCH,
)
root.addChild(item)
def _tag_tree(self, root: SidebarItem) -> None:
tree = self.col.backend.tag_tree()
icon = ":/icons/tag.svg"
def render(root: SidebarItem, nodes: Iterable[TagTreeNode], head="") -> None:
for node in nodes:
@ -404,7 +450,7 @@ class SidebarTreeView(QTreeView):
item = SidebarItem(
node.name,
":/icons/tag.svg",
icon,
self._tag_filter(head + node.name),
toggle_expand(),
not node.collapsed,
@ -415,10 +461,17 @@ class SidebarTreeView(QTreeView):
newhead = head + node.name + "::"
render(item, node.children, newhead)
tree = self.col.backend.tag_tree()
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_TAGS,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_TAGS,
)
render(root, tree.children)
def _deck_tree(self, root: SidebarItem) -> None:
tree = self.col.decks.deck_tree()
icon = ":/icons/deck.svg"
def render(root, nodes: Iterable[DeckTreeNode], head="") -> None:
for node in nodes:
@ -429,7 +482,7 @@ class SidebarTreeView(QTreeView):
item = SidebarItem(
node.name,
":/icons/deck.svg",
icon,
self._deck_filter(head + node.name),
toggle_expand(),
not node.collapsed,
@ -441,16 +494,29 @@ class SidebarTreeView(QTreeView):
newhead = head + node.name + "::"
render(item, node.children, newhead)
tree = self.col.decks.deck_tree()
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_DECKS,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_DECKS,
)
render(root, tree.children)
def _notetype_tree(self, root: SidebarItem) -> None:
assert self.col
icon = ":/icons/notetype.svg"
root = self._section_root(
root=root,
name=TR.BROWSING_SIDEBAR_NOTETYPES,
icon=icon,
collapse_key=ConfigBoolKey.COLLAPSE_NOTETYPES,
)
for nt in sorted(self.col.models.all(), key=lambda nt: nt["name"].lower()):
item = SidebarItem(
nt["name"],
":/icons/notetype.svg",
self._note_filter(nt["name"]),
icon=icon,
onClick=self._note_filter(nt["name"]),
item_type=SidebarItemType.NOTETYPE,
id=nt["id"],
)
@ -458,8 +524,8 @@ class SidebarTreeView(QTreeView):
for c, tmpl in enumerate(nt["tmpls"]):
child = SidebarItem(
tmpl["name"],
":/icons/notetype.svg",
self._template_filter(nt["name"], c),
icon,
onClick=self._template_filter(nt["name"], c),
item_type=SidebarItemType.TEMPLATE,
full_name=nt["name"] + "::" + tmpl["name"],
)
@ -504,16 +570,18 @@ class SidebarTreeView(QTreeView):
item = self.model().item_for_index(idx)
if not item:
return
item_type: SidebarItemType = item.item_type
if item_type not in self.context_menus:
self.show_context_menu(item)
def show_context_menu(self, item: SidebarItem):
if item.item_type not in self.context_menus:
return
m = QMenu()
for action in self.context_menus[item_type]:
for action in self.context_menus[item.item_type]:
act_name = action[0]
act_func = action[1]
a = m.addAction(act_name)
a.triggered.connect(lambda _, func=act_func: func(item)) # type: ignore
qconnect(a.triggered, lambda _, func=act_func: func(item))
m.exec_(QCursor.pos())
def rename_deck(self, item: "aqt.browser.SidebarItem") -> None:
@ -610,3 +678,6 @@ class SidebarTreeView(QTreeView):
Models(
self.mw, parent=self.browser, fromMain=True, selected_notetype_id=item.id
)
def save_current_search(self, _item=None) -> None:
self.browser._onSaveFilter()

View File

@ -1193,6 +1193,11 @@ message ConfigBool {
enum Key {
BROWSER_SORT_BACKWARDS = 0;
PREVIEW_BOTH_SIDES = 1;
COLLAPSE_TAGS = 2;
COLLAPSE_NOTETYPES = 3;
COLLAPSE_DECKS = 4;
COLLAPSE_FAVORITES = 5;
COLLAPSE_COMMON = 6;
}
Key key = 1;
}

View File

@ -42,6 +42,11 @@ pub(crate) enum ConfigKey {
BrowserSortKind,
BrowserSortReverse,
CardCountsSeparateInactive,
CollapseCommon,
CollapseDecks,
CollapseFavorites,
CollapseNotetypes,
CollapseTags,
CreationOffset,
CurrentDeckID,
CurrentNoteTypeID,
@ -74,6 +79,11 @@ impl From<ConfigKey> for &'static str {
ConfigKey::BrowserSortKind => "sortType",
ConfigKey::BrowserSortReverse => "sortBackwards",
ConfigKey::CardCountsSeparateInactive => "cardCountsSeparateInactive",
ConfigKey::CollapseCommon => "collapseCommon",
ConfigKey::CollapseDecks => "collapseDecks",
ConfigKey::CollapseFavorites => "collapseFavorites",
ConfigKey::CollapseNotetypes => "collapseNotetypes",
ConfigKey::CollapseTags => "collapseTags",
ConfigKey::CreationOffset => "creationOffset",
ConfigKey::CurrentDeckID => "curDeck",
ConfigKey::CurrentNoteTypeID => "curModel",
@ -100,6 +110,11 @@ impl From<BoolKey> for ConfigKey {
match key {
BoolKey::BrowserSortBackwards => ConfigKey::BrowserSortReverse,
BoolKey::PreviewBothSides => ConfigKey::PreviewBothSides,
BoolKey::CollapseTags => ConfigKey::CollapseTags,
BoolKey::CollapseNotetypes => ConfigKey::CollapseNotetypes,
BoolKey::CollapseDecks => ConfigKey::CollapseDecks,
BoolKey::CollapseFavorites => ConfigKey::CollapseFavorites,
BoolKey::CollapseCommon => ConfigKey::CollapseCommon,
}
}
}