From a8aac761f15a7615c0c4db7d0cd516dc669cbd4c Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Sat, 15 Feb 2020 21:03:15 +0100 Subject: [PATCH] Add browser_will_build_tree filter Allows add-ons to populate the browser sidebar tree with their own items, and/or take over specific construction stages in their entirety --- qt/aqt/browser.py | 36 +++++++++++++-- qt/aqt/gui_hooks.py | 99 ++++++++++++++++++++++++++++++++++++++++ qt/tools/genhooks_gui.py | 45 ++++++++++++++++++ 3 files changed, 175 insertions(+), 5 deletions(-) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index b46d723ec..997d1f854 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -11,6 +11,7 @@ import sre_constants import time import unicodedata from dataclasses import dataclass +from enum import Enum from operator import itemgetter from typing import Callable, List, Optional, Union @@ -418,6 +419,15 @@ class StatusDelegate(QItemDelegate): ###################################################################### +class SidebarStage(Enum): + ROOT = 0 + STANDARD = 1 + FAVORITES = 2 + DECKS = 3 + MODELS = 4 + TAGS = 5 + + class SidebarItem: def __init__( self, @@ -1085,11 +1095,27 @@ by clicking on one on the left.""" def buildTree(self) -> SidebarItem: root = SidebarItem("", "") - self._stdTree(root) - self._favTree(root) - self._decksTree(root) - self._modelTree(root) - self._userTagTree(root) + + handled = gui_hooks.browser_will_build_tree( + False, root, SidebarStage.ROOT, self + ) + if handled: + return root + + for stage, builder in zip( + list(SidebarStage)[1:], + ( + self._stdTree, + self._favTree, + self._decksTree, + self._modelTree, + self._userTagTree, + ), + ): + handled = gui_hooks.browser_will_build_tree(False, root, stage, self) + if not handled and builder: + builder(root) + return root def _stdTree(self, root) -> None: diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index bd2fb71ea..478d04a8c 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -203,6 +203,105 @@ class _BrowserMenusDidInitHook: browser_menus_did_init = _BrowserMenusDidInitHook() +class _BrowserWillBuildTreeFilter: + """Used to add or replace items in the browser sidebar tree + + 'tree' is the root SidebarItem that all other items are added to. + + 'stage' is an enum describing the different construction stages of + the sidebar tree at which you can interject your changes. + The different values can be inspected by looking at + aqt.browser.SidebarStage. + + If you want Anki to proceed with the construction of the tree stage + in question after your have performed your changes or additions, + return the 'handled' boolean unchanged. + + On the other hand, if you want to prevent Anki from adding its own + items at a particular construction stage (e.g. in case your add-on + implements its own version of that particular stage), return 'True'. + + If you return 'True' at SidebarStage.ROOT, the sidebar will not be + populated by any of the other construction stages. For any other stage + the tree construction will just continue as usual. + + For example, if your code wishes to replace the tag tree, you could do: + + def on_browser_will_build_tree(handled, root, stage, browser): + if stage != SidebarStage.TAGS: + # not at tag tree building stage, pass on + return handled + + # your tag tree construction code + # root.addChild(...) + + # your code handled tag tree construction, no need for Anki + # or other add-ons to build the tag tree + return True + """ + + _hooks: List[ + Callable[ + [ + bool, + "aqt.browser.SidebarItem", + "aqt.browser.SidebarStage", + "aqt.browser.Browser", + ], + bool, + ] + ] = [] + + def append( + self, + cb: Callable[ + [ + bool, + "aqt.browser.SidebarItem", + "aqt.browser.SidebarStage", + "aqt.browser.Browser", + ], + bool, + ], + ) -> None: + """(handled: bool, tree: aqt.browser.SidebarItem, stage: aqt.browser.SidebarStage, browser: aqt.browser.Browser)""" + self._hooks.append(cb) + + def remove( + self, + cb: Callable[ + [ + bool, + "aqt.browser.SidebarItem", + "aqt.browser.SidebarStage", + "aqt.browser.Browser", + ], + bool, + ], + ) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__( + self, + handled: bool, + tree: aqt.browser.SidebarItem, + stage: aqt.browser.SidebarStage, + browser: aqt.browser.Browser, + ) -> bool: + for filter in self._hooks: + try: + handled = filter(handled, tree, stage, browser) + except: + # if the hook fails, remove it + self._hooks.remove(filter) + raise + return handled + + +browser_will_build_tree = _BrowserWillBuildTreeFilter() + + class _BrowserWillShowContextMenuHook: _hooks: List[Callable[["aqt.browser.Browser", QMenu], None]] = [] diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 56268f914..9c9ab1a46 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -98,6 +98,51 @@ hooks = [ args=["browser: aqt.browser.Browser"], legacy_hook="browser.rowChanged", ), + Hook( + name="browser_will_build_tree", + args=[ + "handled: bool", + "tree: aqt.browser.SidebarItem", + "stage: aqt.browser.SidebarStage", + "browser: aqt.browser.Browser", + ], + return_type="bool", + doc="""Used to add or replace items in the browser sidebar tree + + 'tree' is the root SidebarItem that all other items are added to. + + 'stage' is an enum describing the different construction stages of + the sidebar tree at which you can interject your changes. + The different values can be inspected by looking at + aqt.browser.SidebarStage. + + If you want Anki to proceed with the construction of the tree stage + in question after your have performed your changes or additions, + return the 'handled' boolean unchanged. + + On the other hand, if you want to prevent Anki from adding its own + items at a particular construction stage (e.g. in case your add-on + implements its own version of that particular stage), return 'True'. + + If you return 'True' at SidebarStage.ROOT, the sidebar will not be + populated by any of the other construction stages. For any other stage + the tree construction will just continue as usual. + + For example, if your code wishes to replace the tag tree, you could do: + + def on_browser_will_build_tree(handled, root, stage, browser): + if stage != SidebarStage.TAGS: + # not at tag tree building stage, pass on + return handled + + # your tag tree construction code + # root.addChild(...) + + # your code handled tag tree construction, no need for Anki + # or other add-ons to build the tag tree + return True + """, + ), # States ################### Hook(