diff --git a/aqt/browser.py b/aqt/browser.py index f8f306b50..23642f8b2 100644 --- a/aqt/browser.py +++ b/aqt/browser.py @@ -373,6 +373,7 @@ class Browser(QMainWindow): self._closeEventHasCleanedUp = False self.form = aqt.forms.browser.Ui_Dialog() self.form.setupUi(self) + self.setupSidebar() restoreGeom(self, "editor", 0) restoreState(self, "editor") restoreSplitter(self.form.splitter, "editor3") @@ -430,6 +431,7 @@ class Browser(QMainWindow): f.actionFind.triggered.connect(self.onFind) f.actionNote.triggered.connect(self.onNote) f.actionTags.triggered.connect(self.onFilterButton) + f.actionSidebar.triggered.connect(self.focusSidebar) f.actionCardList.triggered.connect(self.onCardList) # help f.actionGuide.triggered.connect(self.onHelp) @@ -493,6 +495,8 @@ class Browser(QMainWindow): "Show answer on RET or register answer." if evt.key() == Qt.Key_Escape: self.close() + else: + super().keyPressEvent(evt) def setupColumns(self): self.columns = [ @@ -743,6 +747,109 @@ by clicking on one on the left.""")) def onColumnMoved(self, a, b, c): self.setColumnSizes() + # Sidebar + ###################################################################### + + class CallbackItem(QTreeWidgetItem): + def __init__(self, root, name, onclick, oncollapse=None, expanded=False): + QTreeWidgetItem.__init__(self, root, [name]) + self.setExpanded(expanded) + self.onclick = onclick + self.oncollapse = oncollapse + + class SidebarTreeWidget(QTreeWidget): + def __init__(self): + QTreeWidget.__init__(self) + self.itemClicked.connect(self.onTreeClick) + self.itemExpanded.connect(lambda item: self.onTreeCollapse(item)) + self.itemCollapsed.connect(lambda item: self.onTreeCollapse(item)) + + def keyPressEvent(self, evt): + if evt.key() in (Qt.Key_Return, Qt.Key_Enter): + item = self.currentItem() + self.onTreeClick(item, 0) + else: + super().keyPressEvent(evt) + + def onTreeClick(self, item, col): + if getattr(item, 'onclick', None): + item.onclick() + + def onTreeCollapse(self, item): + if getattr(item, 'oncollapse', None): + item.oncollapse() + + def setupSidebar(self): + dw = self.sidebarDockWidget = QDockWidget(_("Sidebar"), self) + dw.setFeatures(QDockWidget.DockWidgetClosable) + dw.setObjectName("Sidebar") + dw.setAllowedAreas(Qt.LeftDockWidgetArea) + self.sidebarTree = self.SidebarTreeWidget() + self.sidebarTree.mw = self.mw + self.sidebarTree.setFrameShape(QFrame.NoFrame) + self.sidebarTree.header().setVisible(False) + dw.setWidget(self.sidebarTree) + p = QPalette() + p.setColor(QPalette.Base, p.window().color()) + self.sidebarTree.setPalette(p) + self.sidebarTree.setVisible(True) + self.sidebarDockWidget.visibilityChanged.connect(self.onSidebarVisChanged) + + def onSidebarVisChanged(self, visible): + if visible: + self.buildTree() + else: + pass + + def focusSidebar(self): + self.sidebarDockWidget.setVisible(True) + self.sidebarTree.setFocus() + + def maybeRefreshSidebar(self): + if self.sidebarDockWidget.isVisible(): + self.buildTree() + + def buildTree(self): + self.sidebarTree.clear() + root = self.sidebarTree + self._favTree(root) + self._decksTree(root) + self._modelTree(root) + self._userTagTree(root) + self.sidebarTree.setIndentation(15) + + def _favTree(self, root): + saved = self.col.conf.get('savedFilters', {}) + for name, filt in sorted(saved.items()): + item = self.CallbackItem(root, name, lambda s=filt: self.setFilter(s)) + item.setIcon(0, QIcon(":/icons/heart.png")) + + def _userTagTree(self, root): + for t in sorted(self.col.tags.all(), key=lambda t: t.lower()): + item = self.CallbackItem( + root, t, lambda t=t: self.setFilter("tag", t)) + item.setIcon(0, QIcon(":/icons/tag.png")) + + def _decksTree(self, root): + grps = self.col.sched.deckDueTree() + def fillGroups(root, grps, head=""): + for g in grps: + item = self.CallbackItem( + root, g[0], + lambda g=g: self.setFilter("deck", head+g[0]), + lambda g=g: self.mw.col.decks.collapseBrowser(g[1]), + not self.mw.col.decks.get(g[1]).get('browserCollapsed', False)) + item.setIcon(0, QIcon(":/icons/deck.png")) + newhead = head + g[0]+"::" + fillGroups(item, g[5], newhead) + fillGroups(root, grps) + + def _modelTree(self, root): + for m in sorted(self.col.models.all(), key=itemgetter("name")): + mitem = self.CallbackItem( + root, m['name'], lambda m=m: self.setFilter("mid", str(m['id']))) + mitem.setIcon(0, QIcon(":/icons/notetype.png")) + # Filter tree ###################################################################### @@ -758,6 +865,10 @@ by clicking on one on the left.""")) self._addNoteTypeFilters(m) self._addTagFilters(m) + m.addSeparator() + m.addAction(self.sidebarDockWidget.toggleViewAction()) + m.addSeparator() + self._addSavedSearches(m) m.exec_(self.form.filter.mapToGlobal(QPoint(0,0))) @@ -1469,12 +1580,16 @@ update cards set usn=?, mod=?, did=? where id in """ + scids, addHook("reset", self.onReset) addHook("editTimer", self.refreshCurrentCard) addHook("editFocusLost", self.refreshCurrentCardFilter) + for t in "newTag", "newModel", "newDeck": + addHook(t, self.maybeRefreshSidebar) def teardownHooks(self): remHook("reset", self.onReset) remHook("editTimer", self.refreshCurrentCard) remHook("editFocusLost", self.refreshCurrentCardFilter) remHook("undoState", self.onUndoState) + for t in "newTag", "newModel", "newDeck": + remHook(t, self.maybeRefreshSidebar) def onUndoState(self, on): self.form.actionUndo.setEnabled(on) diff --git a/designer/browser.ui b/designer/browser.ui index 8c0af36bc..54c3417c1 100644 --- a/designer/browser.ui +++ b/designer/browser.ui @@ -250,6 +250,7 @@ + @@ -553,6 +554,14 @@ Ctrl+4 + + + Sidebar + + + Ctrl+Shift+R + + diff --git a/designer/icons.qrc b/designer/icons.qrc index c48a65e67..1c30eebd2 100644 --- a/designer/icons.qrc +++ b/designer/icons.qrc @@ -1,5 +1,9 @@ icons/anki.png + icons/tag.png + icons/deck.png + icons/notetype.png + icons/heart.png diff --git a/designer/icons/deck.png b/designer/icons/deck.png new file mode 100644 index 000000000..5dd6bced4 Binary files /dev/null and b/designer/icons/deck.png differ diff --git a/designer/icons/heart.png b/designer/icons/heart.png new file mode 100644 index 000000000..b9195b3ef Binary files /dev/null and b/designer/icons/heart.png differ diff --git a/designer/icons/notetype.png b/designer/icons/notetype.png new file mode 100644 index 000000000..e77b47c90 Binary files /dev/null and b/designer/icons/notetype.png differ diff --git a/designer/icons/tag.png b/designer/icons/tag.png new file mode 100644 index 000000000..85a7144e3 Binary files /dev/null and b/designer/icons/tag.png differ