# Copyright: Ankitects Pty Ltd and contributors # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from __future__ import annotations import re from typing import Any, Optional import aqt from anki.sync import SyncStatus from aqt import gui_hooks from aqt.qt import * from aqt.sync import get_sync_status from aqt.utils import tr from aqt.webview import AnkiWebView # wrapper class for set_bridge_command() class TopToolbar: def __init__(self, toolbar: Toolbar) -> None: self.toolbar = toolbar # wrapper class for set_bridge_command() class BottomToolbar: def __init__(self, toolbar: Toolbar) -> None: self.toolbar = toolbar class ToolbarWebView(AnkiWebView): def __init__(self, mw: aqt.AnkiQt, title: str) -> None: AnkiWebView.__init__(self, mw, title=title) self.mw = mw self.setFocusPolicy(Qt.FocusPolicy.WheelFocus) self.disable_zoom() self.collapsed = False self.web_height = 0 # collapse timer self.hide_timer = QTimer() self.hide_timer.setSingleShot(True) self.hide_timer.setInterval(1000) qconnect(self.hide_timer.timeout, self.mw.collapse_toolbar_if_allowed) def eventFilter(self, obj, evt): if handled := super().eventFilter(obj, evt): return handled # prevent collapse if pointer inside if evt.type() == QEvent.Type.Enter: self.hide_timer.stop() self.hide_timer.setInterval(1000) return True return False def _onHeight(self, qvar: Optional[int]) -> None: super()._onHeight(qvar) self.web_height = int(qvar) def collapse(self) -> None: self.collapsed = True self.eval("""document.body.classList.add("collapsed"); """) def expand(self) -> None: self.collapsed = False self.eval("""document.body.classList.remove("collapsed"); """) def flatten(self) -> None: self.eval("document.body.classList.add('flat'); ") def elevate(self) -> None: self.eval( """ document.body.classList.remove("flat"); document.body.style.removeProperty("background"); """ ) def update_background_image(self) -> None: def set_background(val: str) -> None: # remove offset from copy background = re.sub(r"-\d+px ", "0%", val) # change computedStyle px value back to 100vw background = re.sub(r"\d+px", "100vw", background) self.eval( f"""document.body.style.setProperty("background", '{background}'); """ ) # offset reviewer background by toolbar height self.mw.web.eval( f"""document.body.style.setProperty("background-position-y", "-{self.web_height}px"); """ ) self.mw.web.evalWithCallback( """window.getComputedStyle(document.body).background; """, set_background, ) class Toolbar: def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None: self.mw = mw self.web = web self.link_handlers: dict[str, Callable] = { "study": self._studyLinkHandler, } self.web.requiresCol = False def draw( self, buf: str = "", web_context: Any | None = None, link_handler: Callable[[str], Any] | None = None, ) -> None: web_context = web_context or TopToolbar(self) link_handler = link_handler or self._linkHandler self.web.set_bridge_command(link_handler, web_context) body = self._body.format( toolbar_content=self._centerLinks(), left_tray_content=self._left_tray_content(), right_tray_content=self._right_tray_content(), ) self.web.stdHtml( body, css=["css/toolbar.css"], js=["js/vendor/jquery.min.js", "js/toolbar.js"], context=web_context, ) self.web.adjustHeightToFit() def redraw(self) -> None: self.set_sync_active(self.mw.media_syncer.is_syncing()) self.update_sync_status() gui_hooks.top_toolbar_did_redraw(self) # Available links ###################################################################### def create_link( self, cmd: str, label: str, func: Callable, tip: str | None = None, id: str | None = 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"""""" f"""{label}""" ) def _centerLinks(self) -> str: links = [ self.create_link( "decks", tr.actions_decks(), self._deckLinkHandler, tip=tr.actions_shortcut_key(val="D"), id="decks", ), self.create_link( "add", tr.actions_add(), self._addLinkHandler, tip=tr.actions_shortcut_key(val="A"), id="add", ), self.create_link( "browse", tr.qt_misc_browse(), self._browseLinkHandler, tip=tr.actions_shortcut_key(val="B"), id="browse", ), self.create_link( "stats", tr.qt_misc_stats(), self._statsLinkHandler, tip=tr.actions_shortcut_key(val="T"), id="stats", ), ] links.append(self._create_sync_link()) gui_hooks.top_toolbar_did_init_links(links, self) return "\n".join(links) # Add-ons ###################################################################### def _left_tray_content(self) -> str: left_tray_content: list[str] = [] gui_hooks.top_toolbar_will_set_left_tray_content(left_tray_content, self) return self._process_tray_content(left_tray_content) def _right_tray_content(self) -> str: right_tray_content: list[str] = [] gui_hooks.top_toolbar_will_set_right_tray_content(right_tray_content, self) return self._process_tray_content(right_tray_content) def _process_tray_content(self, content: list[str]) -> str: return "\n".join(f"""
%s |