From 18ae9e612e6da9aa7ff14856f12302008286d276 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Sat, 15 Feb 2020 23:21:23 +0100 Subject: [PATCH 1/8] Add top_toolbar_did_init_links hook Allows extending the links in the top toolbar, in a similar fashion to editor_did_init_shortcuts --- qt/aqt/gui_hooks.py | 32 ++++++++++++++++++++++++++++++++ qt/aqt/toolbar.py | 12 +++++++----- qt/tools/genhooks_gui.py | 4 ++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index bd2fb71ea..6e0c957dc 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -1041,6 +1041,38 @@ class _StyleDidInitFilter: style_did_init = _StyleDidInitFilter() +class _TopToolbarDidInitLinksHook: + _hooks: List[ + Callable[[List[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] + ] = [] + + def append( + self, cb: Callable[[List[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] + ) -> None: + """(links: List[Tuple[str, str, str]], top_toolbar: aqt.toolbar.Toolbar)""" + self._hooks.append(cb) + + def remove( + self, cb: Callable[[List[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] + ) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__( + self, links: List[Tuple[str, str, 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: _hooks: List[Callable[[bool], None]] = [] diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 4d3b6b9ae..8f762e835 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -6,6 +6,7 @@ from __future__ import annotations import aqt from anki.lang import _ +from aqt import gui_hooks from aqt.qt import * from aqt.webview import AnkiWebView @@ -47,12 +48,13 @@ class Toolbar: def _centerLinks(self): links = [ - ["decks", _("Decks"), _("Shortcut key: %s") % "D"], - ["add", _("Add"), _("Shortcut key: %s") % "A"], - ["browse", _("Browse"), _("Shortcut key: %s") % "B"], - ["stats", _("Stats"), _("Shortcut key: %s") % "T"], - ["sync", _("Sync"), _("Shortcut key: %s") % "Y"], + ("decks", _("Decks"), _("Shortcut key: %s") % "D"), + ("add", _("Add"), _("Shortcut key: %s") % "A"), + ("browse", _("Browse"), _("Shortcut key: %s") % "B"), + ("stats", _("Stats"), _("Shortcut key: %s") % "T"), + ("sync", _("Sync"), _("Shortcut key: %s") % "Y"), ] + gui_hooks.top_toolbar_did_init_links(links, self) return self._linkHTML(links) def _linkHTML(self, links): diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 56268f914..fcbdabadc 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -187,6 +187,10 @@ hooks = [ return_type="str", legacy_hook="setupStyle", ), + Hook( + name="top_toolbar_did_init_links", + args=["links: List[Tuple[str, str, str]]", "top_toolbar: aqt.toolbar.Toolbar",], + ), # Adding cards ################### Hook( From 621e634bb2cd9f868047f729bd61b8492f28a331 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Sat, 15 Feb 2020 23:22:41 +0100 Subject: [PATCH 2/8] Add a convenience function for creating toolbar links Similar to aqt.editor.Editor.addButton --- qt/aqt/toolbar.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 8f762e835..ae8608820 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import Dict + import aqt from anki.lang import _ from aqt import gui_hooks @@ -27,7 +29,7 @@ class Toolbar: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: self.mw = mw self.web = web - self.link_handlers = { + self.link_handlers: Dict[str, Callable] = { "decks": self._deckLinkHandler, "study": self._studyLinkHandler, "add": self._addLinkHandler, @@ -46,6 +48,13 @@ class Toolbar: # Available links ###################################################################### + def addLink( + self, name: str, cmd: str, func: Callable, tip: str = "", + ): + self.link_handlers[cmd] = func + + return (cmd, name, tip) + def _centerLinks(self): links = [ ("decks", _("Decks"), _("Shortcut key: %s") % "D"), From e13fee5aa320a41d653d0d14a66c21b8ae8e1443 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 16:15:50 +0100 Subject: [PATCH 3/8] Refactor center link creation and link handler registration Uses a CenterLink dataclass to describe individual links, and transforms them into HTML using create_link, which may also be used by add-ons. --- qt/aqt/toolbar.py | 83 +++++++++++++++++++++++++++++++---------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index d96a9a3f0..c72faf896 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -4,6 +4,7 @@ from __future__ import annotations +from dataclasses import dataclass from typing import Any, Dict, Optional import aqt @@ -25,17 +26,21 @@ class BottomToolbar: self.toolbar = toolbar +@dataclass +class CenterLink: + cmd: str + label: str + func: Callable + tip: Optional[str] = None + id: Optional[str] = None + + class Toolbar: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: self.mw = mw self.web = web self.link_handlers: Dict[str, Callable] = { - "decks": self._deckLinkHandler, "study": self._studyLinkHandler, - "add": self._addLinkHandler, - "browse": self._browseLinkHandler, - "stats": self._statsLinkHandler, - "sync": self._syncLinkHandler, } self.web.setFixedHeight(30) self.web.requiresCol = False @@ -59,39 +64,61 @@ class Toolbar: # Available links ###################################################################### - def addLink( - self, name: str, cmd: str, func: Callable, tip: str = "", - ): - self.link_handlers[cmd] = func + def create_link(self, link: CenterLink): + self.link_handlers[link.cmd] = link.func - return (cmd, name, tip) + title_attr = f'title="{link.tip}"' if link.tip else "" + id_attr = f"id={link.id}" if link.id else "" + + return ( + f"""""" + f"""{link.label}""" + ) def _centerLinks(self): links = [ - ("decks", _("Decks"), _("Shortcut key: %s") % "D"), - ("add", _("Add"), _("Shortcut key: %s") % "A"), - ("browse", _("Browse"), _("Shortcut key: %s") % "B"), - ("stats", _("Stats"), _("Shortcut key: %s") % "T"), + self.create_link(link) + for link in [ + CenterLink( + cmd="decks", + label=_("Decks"), + tip=_("Shortcut key: %s") % "D", + func=self._deckLinkHandler, + ), + CenterLink( + cmd="add", + label=_("Add"), + tip=_("Shortcut key: %s") % "A", + func=self._addLinkHandler, + ), + CenterLink( + cmd="browse", + label=_("Browse"), + tip=_("Shortcut key: %s") % "B", + func=self._browseLinkHandler, + ), + CenterLink( + cmd="stats", + label=_("Stats"), + tip=_("Shortcut key: %s") % "T", + func=self._statsLinkHandler, + ), + ] ] - gui_hooks.top_toolbar_did_init_links(links, self) - return self._linkHTML(links) + self._sync_link() - def _linkHTML(self, links): - buf = "" - for ln, name, title in links: - buf += """ - %s""" % ( - name, - title, - ln, - name, - ) - return buf + links.append(self._create_sync_link()) - def _sync_link(self) -> str: + # gui_hooks.top_toolbar_did_init_links(links, self) + + return "\n".join(links) + + def _create_sync_link(self) -> str: name = _("Sync") title = _("Shortcut key: %s") % "Y" label = "sync" + self.link_handlers[label] = self._syncLinkHandler + return f""" {name} From dfefd67508183d0a0037675cecea2ff0c6eaa696 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 16:23:33 +0100 Subject: [PATCH 4/8] Update 'top_toolbar_did_init_links' hook --- qt/aqt/gui_hooks.py | 29 ++++++++++++++++------------- qt/aqt/toolbar.py | 2 +- qt/tools/genhooks_gui.py | 12 +++++++++++- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 5fd834eea..b5a9ca35c 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -1295,25 +1295,28 @@ style_did_init = _StyleDidInitFilter() class _TopToolbarDidInitLinksHook: - _hooks: List[ - Callable[[List[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] - ] = [] + """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 append( - self, cb: Callable[[List[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] - ) -> None: - """(links: List[Tuple[str, str, str]], top_toolbar: aqt.toolbar.Toolbar)""" + 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[Tuple[str, str, str]], "aqt.toolbar.Toolbar"], None] - ) -> None: + 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[Tuple[str, str, str]], top_toolbar: aqt.toolbar.Toolbar - ) -> None: + def __call__(self, links: List[str], top_toolbar: aqt.toolbar.Toolbar) -> None: for hook in self._hooks: try: hook(links, top_toolbar) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index c72faf896..ec5c123d7 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -109,7 +109,7 @@ class Toolbar: links.append(self._create_sync_link()) - # gui_hooks.top_toolbar_did_init_links(links, self) + gui_hooks.top_toolbar_did_init_links(links, self) return "\n".join(links) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index cc49a96f9..17e55ea25 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -308,7 +308,17 @@ hooks = [ ), Hook( name="top_toolbar_did_init_links", - args=["links: List[Tuple[str, str, str]]", "top_toolbar: aqt.toolbar.Toolbar"], + 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( name="media_sync_did_progress", args=["entry: aqt.mediasync.LogEntryWithTime"], From a7d56561ba2690c14323208697d733fe55276bd6 Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 16:30:22 +0100 Subject: [PATCH 5/8] Rename CenterLink to more generic ToolbarLink and add inline docs --- qt/aqt/toolbar.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index ec5c123d7..fe4b5e004 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -27,7 +27,18 @@ class BottomToolbar: @dataclass -class CenterLink: +class ToolbarLink: + """Bundles together the data fields used to generate a link element in + Anki's top toolbar + + Attributes: + 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 + tip {Optional[str]} -- Optional tooltip text to show on hovering over the link + id: {Optional[str]} -- Optional id attribute to supply the link with + """ + cmd: str label: str func: Callable @@ -64,7 +75,7 @@ class Toolbar: # Available links ###################################################################### - def create_link(self, link: CenterLink): + def create_link(self, link: ToolbarLink): self.link_handlers[link.cmd] = link.func title_attr = f'title="{link.tip}"' if link.tip else "" @@ -80,25 +91,25 @@ class Toolbar: links = [ self.create_link(link) for link in [ - CenterLink( + ToolbarLink( cmd="decks", label=_("Decks"), tip=_("Shortcut key: %s") % "D", func=self._deckLinkHandler, ), - CenterLink( + ToolbarLink( cmd="add", label=_("Add"), tip=_("Shortcut key: %s") % "A", func=self._addLinkHandler, ), - CenterLink( + ToolbarLink( cmd="browse", label=_("Browse"), tip=_("Shortcut key: %s") % "B", func=self._browseLinkHandler, ), - CenterLink( + ToolbarLink( cmd="stats", label=_("Stats"), tip=_("Shortcut key: %s") % "T", From de333cd50382b4e42512989e491c0c0890c62f5b Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 16:34:02 +0100 Subject: [PATCH 6/8] Add ids to all link elements --- qt/aqt/toolbar.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index fe4b5e004..393e63d65 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -95,24 +95,28 @@ class Toolbar: cmd="decks", label=_("Decks"), tip=_("Shortcut key: %s") % "D", + id="decks", func=self._deckLinkHandler, ), ToolbarLink( cmd="add", label=_("Add"), tip=_("Shortcut key: %s") % "A", + id="add", func=self._addLinkHandler, ), ToolbarLink( cmd="browse", label=_("Browse"), tip=_("Shortcut key: %s") % "B", + id="browse", func=self._browseLinkHandler, ), ToolbarLink( cmd="stats", label=_("Stats"), tip=_("Shortcut key: %s") % "T", + id="stats", func=self._statsLinkHandler, ), ] @@ -131,7 +135,7 @@ class Toolbar: self.link_handlers[label] = self._syncLinkHandler return f""" -{name} +{name} """ From 075a2792f585c813710c027f947c9d0d06ad452b Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 17:05:27 +0100 Subject: [PATCH 7/8] Restore gui_hooks changes --- qt/aqt/gui_hooks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index b5a9ca35c..bc6dd98e7 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -350,7 +350,7 @@ class _CardWillShowFilter: self._hooks.remove(filter) raise # legacy support - runFilter("prepareQA", text, card, kind) + text = runFilter("prepareQA", text, card, kind) return text @@ -668,7 +668,7 @@ class _EditorDidUnfocusFieldFilter: self._hooks.remove(filter) raise # legacy support - runFilter("editFocusLost", changed, note, current_field_idx) + changed = runFilter("editFocusLost", changed, note, current_field_idx) return changed @@ -747,7 +747,7 @@ class _EditorWillUseFontForFieldFilter: self._hooks.remove(filter) raise # legacy support - runFilter("mungeEditingFontName", font) + font = runFilter("mungeEditingFontName", font) return font @@ -1287,7 +1287,7 @@ class _StyleDidInitFilter: self._hooks.remove(filter) raise # legacy support - runFilter("setupStyle", style) + style = runFilter("setupStyle", style) return style From 496548d886f45d81a66c3156ba86d0adba0b022a Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Thu, 20 Feb 2020 18:22:31 +0100 Subject: [PATCH 8/8] Refactor ToolbarLink dataclass into create_link arguments ToolbarLink was more of a vestigial left-over from an interim implementation. This change simplifies link addition and brings it closer in line with adding buttons in the editor screen --- qt/aqt/toolbar.py | 118 +++++++++++++++++++++++----------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 393e63d65..17fc7dfc3 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -4,7 +4,6 @@ from __future__ import annotations -from dataclasses import dataclass from typing import Any, Dict, Optional import aqt @@ -26,26 +25,6 @@ class BottomToolbar: self.toolbar = toolbar -@dataclass -class ToolbarLink: - """Bundles together the data fields used to generate a link element in - Anki's top toolbar - - Attributes: - 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 - tip {Optional[str]} -- Optional tooltip text to show on hovering over the link - id: {Optional[str]} -- Optional id attribute to supply the link with - """ - - cmd: str - label: str - func: Callable - tip: Optional[str] = None - id: Optional[str] = None - - class Toolbar: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: self.mw = mw @@ -75,51 +54,72 @@ class Toolbar: # Available links ###################################################################### - def create_link(self, link: ToolbarLink): - self.link_handlers[link.cmd] = link.func + 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 + """ - title_attr = f'title="{link.tip}"' if link.tip else "" - id_attr = f"id={link.id}" if link.id else "" + self.link_handlers[cmd] = func + + title_attr = f'title="{tip}"' if tip else "" + id_attr = f'id="{id}"' if id else "" return ( - f"""""" - f"""{link.label}""" + f"""""" + f"""{label}""" ) def _centerLinks(self): links = [ - self.create_link(link) - for link in [ - ToolbarLink( - cmd="decks", - label=_("Decks"), - tip=_("Shortcut key: %s") % "D", - id="decks", - func=self._deckLinkHandler, - ), - ToolbarLink( - cmd="add", - label=_("Add"), - tip=_("Shortcut key: %s") % "A", - id="add", - func=self._addLinkHandler, - ), - ToolbarLink( - cmd="browse", - label=_("Browse"), - tip=_("Shortcut key: %s") % "B", - id="browse", - func=self._browseLinkHandler, - ), - ToolbarLink( - cmd="stats", - label=_("Stats"), - tip=_("Shortcut key: %s") % "T", - id="stats", - func=self._statsLinkHandler, - ), - ] + self.create_link( + "decks", + _("Decks"), + 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", + ), ] links.append(self._create_sync_link())