Merge pull request #448 from glutanimate/top_toolbar_links_hook

Add a hook for adding links to the top toolbar
This commit is contained in:
Damien Elmes 2020-02-21 12:18:25 +10:00 committed by GitHub
commit b3585502cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 124 additions and 25 deletions

View file

@ -1294,6 +1294,41 @@ class _StyleDidInitFilter:
style_did_init = _StyleDidInitFilter() style_did_init = _StyleDidInitFilter()
class _TopToolbarDidInitLinksHook:
"""Used to modify or add links in the top toolbar of Anki's main window
'links' is a list of HTML link elements. Add-ons can generate their own links
by using aqt.toolbar.Toolbar.create_link. Links created in that way can then be
appended to the link list, e.g.:
def on_top_toolbar_did_init_links(links, toolbar):
my_link = toolbar.create_link(...)
links.append(my_link)
"""
_hooks: List[Callable[[List[str], "aqt.toolbar.Toolbar"], None]] = []
def append(self, cb: Callable[[List[str], "aqt.toolbar.Toolbar"], None]) -> None:
"""(links: List[str], top_toolbar: aqt.toolbar.Toolbar)"""
self._hooks.append(cb)
def remove(self, cb: Callable[[List[str], "aqt.toolbar.Toolbar"], None]) -> None:
if cb in self._hooks:
self._hooks.remove(cb)
def __call__(self, links: List[str], top_toolbar: aqt.toolbar.Toolbar) -> None:
for hook in self._hooks:
try:
hook(links, top_toolbar)
except:
# if the hook fails, remove it
self._hooks.remove(hook)
raise
top_toolbar_did_init_links = _TopToolbarDidInitLinksHook()
class _UndoStateDidChangeHook: class _UndoStateDidChangeHook:
_hooks: List[Callable[[bool], None]] = [] _hooks: List[Callable[[bool], None]] = []

View file

@ -4,10 +4,11 @@
from __future__ import annotations from __future__ import annotations
from typing import Any, Optional from typing import Any, Dict, Optional
import aqt import aqt
from anki.lang import _ from anki.lang import _
from aqt import gui_hooks
from aqt.qt import * from aqt.qt import *
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView
@ -28,13 +29,8 @@ class Toolbar:
def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None:
self.mw = mw self.mw = mw
self.web = web self.web = web
self.link_handlers = { self.link_handlers: Dict[str, Callable] = {
"decks": self._deckLinkHandler,
"study": self._studyLinkHandler, "study": self._studyLinkHandler,
"add": self._addLinkHandler,
"browse": self._browseLinkHandler,
"stats": self._statsLinkHandler,
"sync": self._syncLinkHandler,
} }
self.web.setFixedHeight(30) self.web.setFixedHeight(30)
self.web.requiresCol = False self.web.requiresCol = False
@ -58,34 +54,88 @@ class Toolbar:
# Available links # Available links
###################################################################### ######################################################################
def create_link(
self,
cmd: str,
label: str,
func: Callable,
tip: Optional[str] = None,
id: Optional[str] = None,
) -> str:
"""Generates HTML link element and registers link handler
Arguments:
cmd {str} -- Command name used for the JS Python bridge
label {str} -- Display label of the link
func {Callable} -- Callable to be called on clicking the link
Keyword Arguments:
tip {Optional[str]} -- Optional tooltip text to show on hovering
over the link (default: {None})
id: {Optional[str]} -- Optional id attribute to supply the link with
(default: {None})
Returns:
str -- HTML link element
"""
self.link_handlers[cmd] = func
title_attr = f'title="{tip}"' if tip else ""
id_attr = f'id="{id}"' if id else ""
return (
f"""<a class=hitem tabindex="-1" aria-label="{label}" """
f"""{title_attr} {id_attr} href=# onclick="return pycmd('{cmd}')">"""
f"""{label}</a>"""
)
def _centerLinks(self): def _centerLinks(self):
links = [ links = [
["decks", _("Decks"), _("Shortcut key: %s") % "D"], self.create_link(
["add", _("Add"), _("Shortcut key: %s") % "A"], "decks",
["browse", _("Browse"), _("Shortcut key: %s") % "B"], _("Decks"),
["stats", _("Stats"), _("Shortcut key: %s") % "T"], self._deckLinkHandler,
tip=_("Shortcut key: %s") % "D",
id="decks",
),
self.create_link(
"add",
_("Add"),
self._addLinkHandler,
tip=_("Shortcut key: %s") % "A",
id="add",
),
self.create_link(
"browse",
_("Browse"),
self._browseLinkHandler,
tip=_("Shortcut key: %s") % "B",
id="browse",
),
self.create_link(
"stats",
_("Stats"),
self._statsLinkHandler,
tip=_("Shortcut key: %s") % "T",
id="stats",
),
] ]
return self._linkHTML(links) + self._sync_link() links.append(self._create_sync_link())
def _linkHTML(self, links): gui_hooks.top_toolbar_did_init_links(links, self)
buf = ""
for ln, name, title in links:
buf += """
<a class=hitem tabindex="-1" aria-label="%s" title="%s" href=# onclick="return pycmd('%s')">%s</a>""" % (
name,
title,
ln,
name,
)
return buf
def _sync_link(self) -> str: return "\n".join(links)
def _create_sync_link(self) -> str:
name = _("Sync") name = _("Sync")
title = _("Shortcut key: %s") % "Y" title = _("Shortcut key: %s") % "Y"
label = "sync" label = "sync"
self.link_handlers[label] = self._syncLinkHandler
return f""" return f"""
<a class=hitem tabindex="-1" aria-label="{name}" title="{title}" href=# onclick="return pycmd('{label}')">{name} <a class=hitem tabindex="-1" aria-label="{name}" title="{title}" id="{label}" href=# onclick="return pycmd('{label}')">{name}
<img id=sync-spinner src='/_anki/imgs/refresh.svg'> <img id=sync-spinner src='/_anki/imgs/refresh.svg'>
</a>""" </a>"""

View file

@ -306,6 +306,20 @@ hooks = [
return_type="str", return_type="str",
legacy_hook="setupStyle", legacy_hook="setupStyle",
), ),
Hook(
name="top_toolbar_did_init_links",
args=["links: List[str]", "top_toolbar: aqt.toolbar.Toolbar"],
doc="""Used to modify or add links in the top toolbar of Anki's main window
'links' is a list of HTML link elements. Add-ons can generate their own links
by using aqt.toolbar.Toolbar.create_link. Links created in that way can then be
appended to the link list, e.g.:
def on_top_toolbar_did_init_links(links, toolbar):
my_link = toolbar.create_link(...)
links.append(my_link)
""",
),
Hook( Hook(
name="media_sync_did_progress", args=["entry: aqt.mediasync.LogEntryWithTime"], name="media_sync_did_progress", args=["entry: aqt.mediasync.LogEntryWithTime"],
), ),