From 5c5860eabc17da2f397fa382913d8887d964f5ea Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 28 Feb 2020 13:22:25 +0100 Subject: [PATCH 01/15] indicate that card_will_show belong to multiple windows --- qt/tools/genhooks_gui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 54b131f51..cda84a43a 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -114,6 +114,9 @@ hooks = [ legacy_hook="reviewCleanup", doc="Called before Anki transitions from the review screen to another screen.", ), + # Multiple windows + ################### + # reviewer, clayout and browser Hook( name="card_will_show", args=["text: str", "card: Card", "kind: str"], From f81134830bd45c6bc1d32b9b0c2de3412a9c6a43 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 29 Feb 2020 17:01:38 +0100 Subject: [PATCH 02/15] move deck_browser hooks apart --- qt/tools/genhooks_gui.py | 52 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 54b131f51..39b334e4c 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -45,31 +45,6 @@ hooks = [ content.table += "\n
my html
" """, ), - Hook( - name="deck_browser_did_render", - args=["deck_browser: aqt.deckbrowser.DeckBrowser"], - doc="""Allow to update the deck browser window. E.g. change its title.""", - ), - Hook( - name="deck_browser_will_render_content", - args=[ - "deck_browser: aqt.deckbrowser.DeckBrowser", - "content: aqt.deckbrowser.DeckBrowserContent", - ], - doc="""Used to modify HTML content sections in the deck browser body - - 'content' contains the sections of HTML content the deck browser body - will be updated with. - - When modifying the content of a particular section, please make sure your - changes only perform the minimum required edits to make your add-on work. - You should avoid overwriting or interfering with existing data as much - as possible, instead opting to append your own changes, e.g.: - - def on_deck_browser_will_render_content(deck_browser, content): - content.stats += "\n
my html
" - """, - ), Hook( name="reviewer_did_show_question", args=["card: Card"], @@ -121,6 +96,33 @@ hooks = [ legacy_hook="prepareQA", doc="Can modify card text before review/preview.", ), + # Deck browser + ################### + Hook( + name="deck_browser_did_render", + args=["deck_browser: aqt.deckbrowser.DeckBrowser"], + doc="""Allow to update the deck browser window. E.g. change its title.""", + ), + Hook( + name="deck_browser_will_render_content", + args=[ + "deck_browser: aqt.deckbrowser.DeckBrowser", + "content: aqt.deckbrowser.DeckBrowserContent", + ], + doc="""Used to modify HTML content sections in the deck browser body + + 'content' contains the sections of HTML content the deck browser body + will be updated with. + + When modifying the content of a particular section, please make sure your + changes only perform the minimum required edits to make your add-on work. + You should avoid overwriting or interfering with existing data as much + as possible, instead opting to append your own changes, e.g.: + + def on_deck_browser_will_render_content(deck_browser, content): + content.stats += "\n
my html
" + """, + ), # Deck options ################### Hook( From d22ad11224ed5f32cb41e0732bd0145927afc13a Mon Sep 17 00:00:00 2001 From: Glutanimate Date: Sat, 29 Feb 2020 20:15:23 +0100 Subject: [PATCH 03/15] Display add-on name in add-on configuration window title --- qt/aqt/addons.py | 6 ++++++ qt/ftl/addons.ftl | 2 ++ 2 files changed, 8 insertions(+) diff --git a/qt/aqt/addons.py b/qt/aqt/addons.py index 41c74a2c4..826fe4b0c 100644 --- a/qt/aqt/addons.py +++ b/qt/aqt/addons.py @@ -1258,6 +1258,12 @@ class ConfigEditor(QDialog): self.updateText(self.conf) restoreGeom(self, "addonconf") restoreSplitter(self.form.splitter, "addonconf") + self.setWindowTitle( + tr( + TR.ADDONS_CONFIG_WINDOW_TITLE, + name=self.mgr.addon_meta(addon).human_name(), + ) + ) self.show() def onRestoreDefaults(self): diff --git a/qt/ftl/addons.ftl b/qt/ftl/addons.ftl index d59c68354..14ed8a491 100644 --- a/qt/ftl/addons.ftl +++ b/qt/ftl/addons.ftl @@ -4,3 +4,5 @@ addons-failed-to-load = When loading '{$name}': {$traceback} +# Shown in the add-on configuration screen (Tools>Add-ons>Config), in the title bar +addons-config-window-title = Configure '{$name}' From 047e027e01982b8ba963cb2733173b91d41fa39f Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sat, 29 Feb 2020 17:02:51 +0100 Subject: [PATCH 04/15] browser_did_init --- qt/aqt/browser.py | 1 + qt/aqt/gui_hooks.py | 24 ++++++++++++++++++++++++ qt/tools/genhooks_gui.py | 1 + 3 files changed, 26 insertions(+) diff --git a/qt/aqt/browser.py b/qt/aqt/browser.py index 4a1f7b317..ecdcbe1ab 100644 --- a/qt/aqt/browser.py +++ b/qt/aqt/browser.py @@ -596,6 +596,7 @@ class Browser(QMainWindow): self.updateFont() self.onUndoState(self.mw.form.actionUndo.isEnabled()) self.setupSearch() + gui_hooks.browser_will_show(self) self.show() def setupMenus(self) -> None: diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 5a001251e..08746896e 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -302,6 +302,30 @@ class _BrowserWillBuildTreeFilter: browser_will_build_tree = _BrowserWillBuildTreeFilter() +class _BrowserWillShowHook: + _hooks: List[Callable[["aqt.browser.Browser"], None]] = [] + + def append(self, cb: Callable[["aqt.browser.Browser"], None]) -> None: + """(browser: aqt.browser.Browser)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.browser.Browser"], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, browser: aqt.browser.Browser) -> None: + for hook in self._hooks: + try: + hook(browser) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +browser_will_show = _BrowserWillShowHook() + + 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 39b334e4c..849ed1212 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -147,6 +147,7 @@ hooks = [ ), # Browser ################### + Hook(name="browser_will_show", args=["browser: aqt.browser.Browser"]), Hook( name="browser_menus_did_init", args=["browser: aqt.browser.Browser"], From 80ba217772001172f666da6f7bfee480aee8e437 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Fri, 28 Feb 2020 13:34:54 +0100 Subject: [PATCH 05/15] hook for initializing clayout --- qt/aqt/clayout.py | 1 + qt/aqt/gui_hooks.py | 27 +++++++++++++++++++++++++++ qt/tools/genhooks_gui.py | 8 ++++++++ 3 files changed, 36 insertions(+) diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index 87084cd8b..d95ef635d 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -65,6 +65,7 @@ class CardLayout(QDialog): v1.addLayout(self.buttons) v1.setContentsMargins(12, 12, 12, 12) self.setLayout(v1) + gui_hooks.card_layout_will_show(self) self.redraw() restoreGeom(self, "CardLayout") self.setWindowModality(Qt.ApplicationModal) diff --git a/qt/aqt/gui_hooks.py b/qt/aqt/gui_hooks.py index 5a001251e..a41a12d36 100644 --- a/qt/aqt/gui_hooks.py +++ b/qt/aqt/gui_hooks.py @@ -328,6 +328,33 @@ class _BrowserWillShowContextMenuHook: browser_will_show_context_menu = _BrowserWillShowContextMenuHook() +class _CardLayoutWillShowHook: + """Allow to change the display of the card layout. After most values are + set and before the window is actually shown.""" + + _hooks: List[Callable[["aqt.clayout.CardLayout"], None]] = [] + + def append(self, cb: Callable[["aqt.clayout.CardLayout"], None]) -> None: + """(clayout: aqt.clayout.CardLayout)""" + self._hooks.append(cb) + + def remove(self, cb: Callable[["aqt.clayout.CardLayout"], None]) -> None: + if cb in self._hooks: + self._hooks.remove(cb) + + def __call__(self, clayout: aqt.clayout.CardLayout) -> None: + for hook in self._hooks: + try: + hook(clayout) + except: + # if the hook fails, remove it + self._hooks.remove(hook) + raise + + +card_layout_will_show = _CardLayoutWillShowHook() + + class _CardWillShowFilter: """Can modify card text before review/preview.""" diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index cda84a43a..fd63660c0 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -114,6 +114,14 @@ hooks = [ legacy_hook="reviewCleanup", doc="Called before Anki transitions from the review screen to another screen.", ), + # Card layout + ################### + Hook( + name="card_layout_will_show", + args=["clayout: aqt.clayout.CardLayout"], + doc="""Allow to change the display of the card layout. After most values are + set and before the window is actually shown.""", + ), # Multiple windows ################### # reviewer, clayout and browser From ab9999dbc9ded74335f40fa5548cf0ebf4ad5050 Mon Sep 17 00:00:00 2001 From: Arthur Milchior Date: Sun, 1 Mar 2020 13:00:36 +0100 Subject: [PATCH 06/15] countsIdx return a queue type. I missed this constant before --- pylib/anki/sched.py | 6 +++--- pylib/anki/schedv2.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pylib/anki/sched.py b/pylib/anki/sched.py index 8e721fee0..e4afefce3 100644 --- a/pylib/anki/sched.py +++ b/pylib/anki/sched.py @@ -106,8 +106,8 @@ class Scheduler: counts = [self.newCount, self.lrnCount, self.revCount] if card: idx = self.countIdx(card) - if idx == 1: - counts[1] += card.left // 1000 + if idx == QUEUE_TYPE_LRN: + counts[QUEUE_TYPE_LRN] += card.left // 1000 else: counts[idx] += 1 @@ -139,7 +139,7 @@ order by due""" def countIdx(self, card: Card) -> int: if card.queue == QUEUE_TYPE_DAY_LEARN_RELEARN: - return 1 + return QUEUE_TYPE_LRN return card.queue def answerButtons(self, card: Card) -> int: diff --git a/pylib/anki/schedv2.py b/pylib/anki/schedv2.py index 142d2eef7..5307e8f90 100644 --- a/pylib/anki/schedv2.py +++ b/pylib/anki/schedv2.py @@ -161,7 +161,7 @@ order by due""" def countIdx(self, card: Card) -> int: if card.queue in (QUEUE_TYPE_DAY_LEARN_RELEARN, QUEUE_TYPE_PREVIEW): - return 1 + return QUEUE_TYPE_LRN return card.queue def answerButtons(self, card: Card) -> int: From cd570eef2420b62ee5956dbccc3e2bcd3144cf1e Mon Sep 17 00:00:00 2001 From: Alan Du Date: Wed, 26 Feb 2020 21:08:38 -0500 Subject: [PATCH 07/15] Monkeytype qt/aqt/webview.py --- qt/aqt/webview.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 0efc96969..675b5ec52 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -5,7 +5,7 @@ import dataclasses import json import math import sys -from typing import Any, List, Optional, Tuple +from typing import Any, Callable, List, Optional, Sequence, Tuple from anki.lang import _ from anki.utils import isLin, isMac, isWin @@ -176,7 +176,7 @@ class AnkiWebView(QWebEngineView): # type: ignore self.onBridgeCmd: Callable[[str], Any] = self.defaultOnBridgeCmd self._domDone = True - self._pendingActions: List[Tuple[str, List[Any]]] = [] + self._pendingActions: List[Tuple[str, Sequence[Any]]] = [] self.requiresCol = True self.setPage(self._page) @@ -258,13 +258,13 @@ class AnkiWebView(QWebEngineView): # type: ignore def dropEvent(self, evt): pass - def setHtml(self, html): + def setHtml(self, html: str) -> None: # discard any previous pending actions self._pendingActions = [] self._domDone = True self._queueAction("setHtml", html) - def _setHtml(self, html): + def _setHtml(self, html: str) -> None: app = QApplication.instance() oldFocus = app.focusWidget() self._domDone = False @@ -273,7 +273,7 @@ class AnkiWebView(QWebEngineView): # type: ignore if oldFocus: oldFocus.setFocus() - def zoomFactor(self): + def zoomFactor(self) -> float: # overridden scale factor? webscale = os.environ.get("ANKI_WEBSCALE") if webscale: @@ -295,7 +295,7 @@ class AnkiWebView(QWebEngineView): # type: ignore newFactor = desiredScale / qtIntScale return max(1, newFactor) - def _getQtIntScale(self, screen): + def _getQtIntScale(self, screen) -> int: # try to detect if Qt has scaled the screen # - qt will round the scale factor to a whole number, so a dpi of 125% = 1x, # and a dpi of 150% = 2x @@ -430,13 +430,13 @@ body {{ zoom: {}; background: {}; {} }} fname ) - def eval(self, js): + def eval(self, js: str) -> None: self.evalWithCallback(js, None) - def evalWithCallback(self, js, cb): + def evalWithCallback(self, js: str, cb: Callable) -> None: self._queueAction("eval", js, cb) - def _evalWithCallback(self, js, cb): + def _evalWithCallback(self, js: str, cb: Callable[[Any], Any]) -> None: if cb: def handler(val): @@ -449,11 +449,11 @@ body {{ zoom: {}; background: {}; {} }} else: self.page().runJavaScript(js) - def _queueAction(self, name, *args): + def _queueAction(self, name: str, *args: Any) -> None: self._pendingActions.append((name, args)) self._maybeRunActions() - def _maybeRunActions(self): + def _maybeRunActions(self) -> None: while self._pendingActions and self._domDone: name, args = self._pendingActions.pop(0) @@ -464,10 +464,10 @@ body {{ zoom: {}; background: {}; {} }} else: raise Exception("unknown action: {}".format(name)) - def _openLinksExternally(self, url): + def _openLinksExternally(self, url: str) -> None: openLink(url) - def _shouldIgnoreWebEvent(self): + def _shouldIgnoreWebEvent(self) -> bool: # async web events may be received after the profile has been closed # or the underlying webview has been deleted from aqt import mw @@ -499,18 +499,18 @@ body {{ zoom: {}; background: {}; {} }} else: return self.onBridgeCmd(cmd) - def defaultOnBridgeCmd(self, cmd: str) -> Any: + def defaultOnBridgeCmd(self, cmd: str) -> None: print("unhandled bridge cmd:", cmd) # legacy - def resetHandlers(self): + def resetHandlers(self) -> None: self.onBridgeCmd = self.defaultOnBridgeCmd self._bridge_context = None - def adjustHeightToFit(self): + def adjustHeightToFit(self) -> None: self.evalWithCallback("$(document.body).height()", self._onHeight) - def _onHeight(self, qvar): + def _onHeight(self, qvar: Optional[int]) -> None: from aqt import mw if qvar is None: From eebf5d2a1d9a20f0468883d4efffd1bf68e61e8a Mon Sep 17 00:00:00 2001 From: Alan Du Date: Wed, 26 Feb 2020 21:10:38 -0500 Subject: [PATCH 08/15] Monkeytype qt/aqt/toolbar.py --- qt/aqt/toolbar.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/qt/aqt/toolbar.py b/qt/aqt/toolbar.py index 17fc7dfc3..cec90d4b2 100644 --- a/qt/aqt/toolbar.py +++ b/qt/aqt/toolbar.py @@ -15,13 +15,13 @@ from aqt.webview import AnkiWebView # wrapper class for set_bridge_command() class TopToolbar: - def __init__(self, toolbar: Toolbar): + def __init__(self, toolbar: Toolbar) -> None: self.toolbar = toolbar # wrapper class for set_bridge_command() class BottomToolbar: - def __init__(self, toolbar: Toolbar): + def __init__(self, toolbar: Toolbar) -> None: self.toolbar = toolbar @@ -40,7 +40,7 @@ class Toolbar: buf: str = "", web_context: Optional[Any] = None, link_handler: Optional[Callable[[str], Any]] = 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) @@ -90,7 +90,7 @@ class Toolbar: f"""{label}""" ) - def _centerLinks(self): + def _centerLinks(self) -> str: links = [ self.create_link( "decks", @@ -149,15 +149,15 @@ class Toolbar: # Link handling ###################################################################### - def _linkHandler(self, link): + def _linkHandler(self, link: str) -> bool: if link in self.link_handlers: self.link_handlers[link]() return False - def _deckLinkHandler(self): + def _deckLinkHandler(self) -> None: self.mw.moveToState("deckBrowser") - def _studyLinkHandler(self): + def _studyLinkHandler(self) -> None: # if overview already shown, switch to review if self.mw.state == "overview": self.mw.col.startTimebox() @@ -165,16 +165,16 @@ class Toolbar: else: self.mw.onOverview() - def _addLinkHandler(self): + def _addLinkHandler(self) -> None: self.mw.onAddCard() - def _browseLinkHandler(self): + def _browseLinkHandler(self) -> None: self.mw.onBrowse() - def _statsLinkHandler(self): + def _statsLinkHandler(self) -> None: self.mw.onStats() - def _syncLinkHandler(self): + def _syncLinkHandler(self) -> None: self.mw.onSync() # HTML & CSS @@ -206,7 +206,7 @@ class BottomBar(Toolbar): buf: str = "", web_context: Optional[Any] = None, link_handler: Optional[Callable[[str], Any]] = None, - ): + ) -> None: # note: some screens may override this web_context = web_context or BottomToolbar(self) link_handler = link_handler or self._linkHandler From f8c22499cb5ae84d67a81fe64e893ab1857ba000 Mon Sep 17 00:00:00 2001 From: Alan Du Date: Wed, 26 Feb 2020 21:56:45 -0500 Subject: [PATCH 09/15] Monkeytype qt/aqt/sound.py --- qt/aqt/sound.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 16611950c..12d3e8214 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -87,7 +87,7 @@ class AVPlayer: # audio be stopped? interrupt_current_audio = True - def __init__(self): + def __init__(self) -> None: self._enqueued: List[AVTag] = [] self.current_player: Optional[Player] = None @@ -112,7 +112,7 @@ class AVPlayer: self._enqueued.insert(0, SoundOrVideoTag(filename=filename)) self._play_next_if_idle() - def toggle_pause(self): + def toggle_pause(self) -> None: if self.current_player: self.current_player.toggle_pause() @@ -179,7 +179,7 @@ av_player = AVPlayer() # return modified command array that points to bundled command, and return # required environment -def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]: +def _packagedCmd(cmd: List[str]) -> Tuple[Any, Dict[str, str]]: cmd = cmd[:] env = os.environ.copy() if "LD_LIBRARY_PATH" in env: @@ -205,7 +205,7 @@ def _packagedCmd(cmd) -> Tuple[Any, Dict[str, str]]: si = startup_info() # osx throws interrupted system call errors frequently -def retryWait(proc) -> Any: +def retryWait(proc: subprocess.Popen) -> int: while 1: try: return proc.wait() @@ -227,7 +227,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method args: List[str] = [] env: Optional[Dict[str, str]] = None - def __init__(self, taskman: TaskManager): + def __init__(self, taskman: TaskManager) -> None: self._taskman = taskman self._terminate_flag = False self._process: Optional[subprocess.Popen] = None @@ -238,7 +238,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method lambda: self._play(tag), lambda res: self._on_done(res, on_done) ) - def stop(self): + def stop(self) -> None: self._terminate_flag = True # block until stopped t = time.time() @@ -252,7 +252,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method ) self._wait_for_termination(tag) - def _wait_for_termination(self, tag: AVTag): + def _wait_for_termination(self, tag: AVTag) -> None: self._taskman.run_on_main( lambda: gui_hooks.av_player_did_begin_playing(self, tag) ) @@ -359,7 +359,7 @@ class MpvManager(MPV, SoundOrVideoPlayer): def toggle_pause(self) -> None: self.set_property("pause", not self.get_property("pause")) - def seek_relative(self, secs) -> None: + def seek_relative(self, secs: int) -> None: self.command("seek", secs, "relative") def on_idle(self) -> None: @@ -401,7 +401,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer): ) self._wait_for_termination(tag) - def command(self, *args) -> None: + def command(self, *args: Any) -> None: """Send a command over the slave interface. The trailing newline is automatically added.""" @@ -412,7 +412,7 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer): def seek_relative(self, secs: int) -> None: self.command("seek", secs, 0) - def toggle_pause(self): + def toggle_pause(self) -> None: self.command("pause") @@ -458,12 +458,12 @@ class _Recorder: class PyAudioThreadedRecorder(threading.Thread): - def __init__(self, startupDelay) -> None: + def __init__(self, startupDelay: float) -> None: threading.Thread.__init__(self) self.startupDelay = startupDelay self.finish = False - def run(self) -> Any: + def run(self) -> None: chunk = 1024 p = pyaudio.PyAudio() @@ -499,7 +499,7 @@ class PyAudioRecorder(_Recorder): # discard first 250ms which may have pops/cracks startupDelay = 0.25 - def __init__(self): + def __init__(self) -> None: for t in recFiles + [processingSrc, processingDst]: try: os.unlink(t) @@ -507,15 +507,15 @@ class PyAudioRecorder(_Recorder): pass self.encode = False - def start(self): + def start(self) -> None: self.thread = PyAudioThreadedRecorder(startupDelay=self.startupDelay) self.thread.start() - def stop(self): + def stop(self) -> None: self.thread.finish = True self.thread.join() - def file(self): + def file(self) -> str: if self.encode: tgt = "rec%d.mp3" % time.time() os.rename(processingDst, tgt) @@ -530,7 +530,7 @@ Recorder = PyAudioRecorder ########################################################################## -def getAudio(parent, encode=True): +def getAudio(parent: QWidget, encode: bool = True) -> Optional[str]: "Record and return filename" # record first r = Recorder() @@ -547,16 +547,16 @@ def getAudio(parent, encode=True): t = time.time() r.start() time.sleep(r.startupDelay) - QApplication.instance().processEvents() + QApplication.instance().processEvents() # type: ignore while not mb.clickedButton(): txt = _("Recording...
Time: %0.1f") mb.setText(txt % (time.time() - t)) mb.show() - QApplication.instance().processEvents() + QApplication.instance().processEvents() # type: ignore if mb.clickedButton() == mb.escapeButton(): r.stop() r.cleanup() - return + return None saveGeom(mb, "audioRecorder") # ensure at least a second captured while time.time() - t < 1: From 96ca469d12527ae39a5f027bd48d7cee10b166bc Mon Sep 17 00:00:00 2001 From: Alan Du Date: Wed, 26 Feb 2020 21:11:00 -0500 Subject: [PATCH 10/15] Monkeytype qt/aqt/theme.py --- qt/aqt/theme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index 6a25f9d6d..492538534 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -197,7 +197,7 @@ QTabWidget { background-color: %s; } app.setPalette(palette) - def _update_stat_colors(self): + def _update_stat_colors(self) -> None: import anki.stats as s s.colLearn = self.str_color("new-count") From 6c2dda6c9c338ebb6fe42e04506a37385f610cfe Mon Sep 17 00:00:00 2001 From: Alan Du Date: Wed, 26 Feb 2020 22:11:21 -0500 Subject: [PATCH 11/15] Monkeytype qt/aqt/reviewer.py --- pylib/anki/collection.py | 1 + qt/aqt/reviewer.py | 145 +++++++++++++++++++++------------------ 2 files changed, 78 insertions(+), 68 deletions(-) diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index f06bd038d..bd5795024 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -657,6 +657,7 @@ where c.nid = n.id and c.id in %s group by nid""" self._startTime = time.time() self._startReps = self.sched.reps + # FIXME: Use Literal[False] when on Python 3.8 def timeboxReached(self) -> Union[bool, Tuple[Any, int]]: "Return (elapsedTime, reps) if timebox reached, or False." if not self.conf["timeLim"]: diff --git a/qt/aqt/reviewer.py b/qt/aqt/reviewer.py index 316c33803..b02b96ab5 100644 --- a/qt/aqt/reviewer.py +++ b/qt/aqt/reviewer.py @@ -6,11 +6,12 @@ from __future__ import annotations import difflib import html -import html.parser import json import re import unicodedata as ucd -from typing import List, Optional +from typing import Callable, List, Optional, Sequence, Tuple, Union + +from PyQt5.QtCore import Qt from anki import hooks from anki.cards import Card @@ -25,7 +26,7 @@ from aqt.utils import askUserDialog, downArrow, qtMenuShortcutWorkaround, toolti class ReviewerBottomBar: - def __init__(self, reviewer: Reviewer): + def __init__(self, reviewer: Reviewer) -> None: self.reviewer = reviewer @@ -39,28 +40,29 @@ class Reviewer: self.cardQueue: List[Card] = [] self.hadCardQueue = False self._answeredIds: List[int] = [] - self._recordedAudio = None - self.typeCorrect = None # web init happens before this is set + self._recordedAudio: Optional[str] = None + self.typeCorrect: str = None # web init happens before this is set self.state: Optional[str] = None self.bottom = BottomBar(mw, mw.bottomWeb) hooks.card_did_leech.append(self.onLeech) - def show(self): + def show(self) -> None: self.mw.col.reset() - self.mw.setStateShortcuts(self._shortcutKeys()) + self.mw.setStateShortcuts(self._shortcutKeys()) # type: ignore self.web.set_bridge_command(self._linkHandler, self) self.bottom.web.set_bridge_command(self._linkHandler, ReviewerBottomBar(self)) - self._reps = None + self._reps: int = None self.nextCard() - def lastCard(self): + def lastCard(self) -> Optional[Card]: if self._answeredIds: if not self.card or self._answeredIds[-1] != self.card.id: try: return self.mw.col.getCard(self._answeredIds[-1]) except TypeError: # id was deleted - return + return None + return None def cleanup(self) -> None: gui_hooks.reviewer_will_end() @@ -68,9 +70,10 @@ class Reviewer: # Fetching a card ########################################################################## - def nextCard(self): + def nextCard(self) -> None: elapsed = self.mw.col.timeboxReached() if elapsed: + assert not isinstance(elapsed, bool) part1 = ( ngettext("%d card studied in", "%d cards studied in", elapsed[1]) % elapsed[1] @@ -125,7 +128,7 @@ class Reviewer: # Initializing the webview ########################################################################## - def revHtml(self): + def revHtml(self) -> str: extra = self.mw.col.conf.get("reviewExtra", "") fade = "" if self.mw.pm.glMode() == "software": @@ -140,7 +143,7 @@ class Reviewer: fade, extra ) - def _initWeb(self): + def _initWeb(self) -> None: self._reps = 0 # main window self.web.stdHtml( @@ -167,13 +170,13 @@ class Reviewer: # Showing the question ########################################################################## - def _mungeQA(self, buf): + def _mungeQA(self, buf: str) -> str: return self.typeAnsFilter(self.mw.prepare_card_text_for_display(buf)) def _showQuestion(self) -> None: self._reps += 1 self.state = "question" - self.typedAnswer = None + self.typedAnswer: str = None c = self.card # grab the question and play audio if c.isEmpty(): @@ -206,17 +209,17 @@ The front of this card is empty. Please run Tools>Empty Cards.""" # user hook gui_hooks.reviewer_did_show_question(c) - def autoplay(self, card): + def autoplay(self, card: Card) -> bool: return self.mw.col.decks.confForDid(card.odid or card.did)["autoplay"] def _replayq(self, card, previewer=None): s = previewer if previewer else self return s.mw.col.decks.confForDid(s.card.odid or s.card.did).get("replayq", True) - def _drawFlag(self): + def _drawFlag(self) -> None: self.web.eval("_drawFlag(%s);" % self.card.userFlag()) - def _drawMark(self): + def _drawMark(self) -> None: self.web.eval("_drawMark(%s);" % json.dumps(self.card.note().hasTag("marked"))) # Showing the answer @@ -246,7 +249,7 @@ The front of this card is empty. Please run Tools>Empty Cards.""" # Answering a card ############################################################ - def _answerCard(self, ease): + def _answerCard(self, ease: int) -> None: "Reschedule card and show next." if self.mw.state != "review": # showing resetRequired screen; ignore key @@ -269,7 +272,9 @@ The front of this card is empty. Please run Tools>Empty Cards.""" # Handlers ############################################################ - def _shortcutKeys(self): + def _shortcutKeys( + self, + ) -> List[Union[Tuple[str, Callable], Tuple[Qt.Key, Callable]]]: return [ ("e", self.mw.onEditCurrent), (" ", self.onEnterKey), @@ -299,18 +304,18 @@ The front of this card is empty. Please run Tools>Empty Cards.""" ("7", self.on_seek_forward), ] - def on_pause_audio(self): + def on_pause_audio(self) -> None: av_player.toggle_pause() seek_secs = 5 - def on_seek_backward(self): + def on_seek_backward(self) -> None: av_player.seek_relative(-self.seek_secs) - def on_seek_forward(self): + def on_seek_forward(self) -> None: av_player.seek_relative(self.seek_secs) - def onEnterKey(self): + def onEnterKey(self) -> None: if self.state == "question": self._getTypedAnswer() elif self.state == "answer": @@ -318,14 +323,14 @@ The front of this card is empty. Please run Tools>Empty Cards.""" "selectedAnswerButton()", self._onAnswerButton ) - def _onAnswerButton(self, val): + def _onAnswerButton(self, val: str) -> None: # button selected? if val and val in "1234": self._answerCard(int(val)) else: self._answerCard(self._defaultEase()) - def _linkHandler(self, url): + def _linkHandler(self, url: str) -> None: if url == "ans": self._getTypedAnswer() elif url.startswith("ease"): @@ -344,13 +349,13 @@ The front of this card is empty. Please run Tools>Empty Cards.""" typeAnsPat = r"\[\[type:(.+?)\]\]" - def typeAnsFilter(self, buf): + def typeAnsFilter(self, buf: str) -> str: if self.state == "question": return self.typeAnsQuestionFilter(buf) else: return self.typeAnsAnswerFilter(buf) - def typeAnsQuestionFilter(self, buf): + def typeAnsQuestionFilter(self, buf: str) -> str: self.typeCorrect = None clozeIdx = None m = re.search(self.typeAnsPat, buf) @@ -397,20 +402,19 @@ Please run Tools>Empty Cards""" buf, ) - def typeAnsAnswerFilter(self, buf): + def typeAnsAnswerFilter(self, buf: str) -> str: if not self.typeCorrect: return re.sub(self.typeAnsPat, "", buf) origSize = len(buf) buf = buf.replace("
", "") hadHR = len(buf) != origSize # munge correct value - parser = html.parser.HTMLParser() cor = self.mw.col.media.strip(self.typeCorrect) cor = re.sub("(\n|
|)+", " ", cor) cor = stripHTML(cor) # ensure we don't chomp multiple whitespace cor = cor.replace(" ", " ") - cor = parser.unescape(cor) + cor = html.unescape(cor) cor = cor.replace("\xa0", " ") cor = cor.strip() given = self.typedAnswer @@ -434,7 +438,7 @@ Please run Tools>Empty Cards""" return re.sub(self.typeAnsPat, repl, buf) - def _contentForCloze(self, txt, idx): + def _contentForCloze(self, txt: str, idx) -> str: matches = re.findall(r"\{\{c%s::(.+?)\}\}" % idx, txt, re.DOTALL) if not matches: return None @@ -452,24 +456,28 @@ Please run Tools>Empty Cards""" txt = ", ".join(matches) return txt - def tokenizeComparison(self, given, correct): + def tokenizeComparison( + self, given: str, correct: str + ) -> Tuple[List[Tuple[bool, str]], List[Tuple[bool, str]]]: # compare in NFC form so accents appear correct given = ucd.normalize("NFC", given) correct = ucd.normalize("NFC", correct) s = difflib.SequenceMatcher(None, given, correct, autojunk=False) - givenElems = [] - correctElems = [] + givenElems: List[Tuple[bool, str]] = [] + correctElems: List[Tuple[bool, str]] = [] givenPoint = 0 correctPoint = 0 offby = 0 - def logBad(old, new, str, array): + def logBad(old: int, new: int, s: str, array: List[Tuple[bool, str]]) -> None: if old != new: - array.append((False, str[old:new])) + array.append((False, s[old:new])) - def logGood(start, cnt, str, array): + def logGood( + start: int, cnt: int, s: str, array: List[Tuple[bool, str]] + ) -> None: if cnt: - array.append((True, str[start : start + cnt])) + array.append((True, s[start : start + cnt])) for x, y, cnt in s.get_matching_blocks(): # if anything was missed in correct, pad given @@ -486,17 +494,17 @@ Please run Tools>Empty Cards""" logGood(y, cnt, correct, correctElems) return givenElems, correctElems - def correct(self, given, correct, showBad=True): + def correct(self, given: str, correct: str, showBad: bool = True) -> str: "Diff-corrects the typed-in answer." givenElems, correctElems = self.tokenizeComparison(given, correct) - def good(s): + def good(s: str) -> str: return "" + html.escape(s) + "" - def bad(s): + def bad(s: str) -> str: return "" + html.escape(s) + "" - def missed(s): + def missed(s: str) -> str: return "" + html.escape(s) + "" if given == correct: @@ -519,24 +527,24 @@ Please run Tools>Empty Cards""" res = "
" + res + "
" return res - def _noLoneMarks(self, s): + def _noLoneMarks(self, s: str) -> str: # ensure a combining character at the start does not join to # previous text if s and ucd.category(s[0]).startswith("M"): return "\xa0" + s return s - def _getTypedAnswer(self): + def _getTypedAnswer(self) -> None: self.web.evalWithCallback("typeans ? typeans.value : null", self._onTypedAnswer) - def _onTypedAnswer(self, val): + def _onTypedAnswer(self, val: None) -> None: self.typedAnswer = val or "" self._showAnswer() # Bottom bar ########################################################################## - def _bottomHTML(self): + def _bottomHTML(self) -> str: return """
@@ -565,7 +573,7 @@ time = %(time)d; time=self.card.timeTaken() // 1000, ) - def _showAnswerButton(self): + def _showAnswerButton(self) -> None: if not self.typeCorrect: self.bottom.web.setFocus() middle = """ @@ -587,17 +595,17 @@ time = %(time)d; self.bottom.web.eval("showQuestion(%s,%d);" % (json.dumps(middle), maxTime)) self.bottom.web.adjustHeightToFit() - def _showEaseButtons(self): + def _showEaseButtons(self) -> None: self.bottom.web.setFocus() middle = self._answerButtons() self.bottom.web.eval("showAnswer(%s);" % json.dumps(middle)) - def _remaining(self): + def _remaining(self) -> str: if not self.mw.col.conf["dueCounts"]: return "" if self.hadCardQueue: # if it's come from the undo queue, don't count it separately - counts = list(self.mw.col.sched.counts()) + counts: List[Union[int, str]] = list(self.mw.col.sched.counts()) else: counts = list(self.mw.col.sched.counts(self.card)) idx = self.mw.col.sched.countIdx(self.card) @@ -608,13 +616,13 @@ time = %(time)d; ctxt += space + "%s" % counts[2] return ctxt - def _defaultEase(self): + def _defaultEase(self) -> int: if self.mw.col.sched.answerButtons(self.card) == 4: return 3 else: return 2 - def _answerButtonList(self): + def _answerButtonList(self) -> Sequence[Tuple[int, str]]: l = ((1, _("Again")),) cnt = self.mw.col.sched.answerButtons(self.card) if cnt == 2: @@ -624,7 +632,7 @@ time = %(time)d; else: return l + ((2, _("Hard")), (3, _("Good")), (4, _("Easy"))) - def _answerButtons(self): + def _answerButtons(self) -> str: default = self._defaultEase() def but(i, label): @@ -652,7 +660,7 @@ time = %(time)d; """ return buf + script - def _buttonTime(self, i): + def _buttonTime(self, i: int) -> str: if not self.mw.col.conf["estTimes"]: return "
" txt = self.mw.col.sched.nextIvlStr(self.card, i, True) or " " @@ -661,7 +669,7 @@ time = %(time)d; # Leeches ########################################################################## - def onLeech(self, card): + def onLeech(self, card: Card) -> None: # for now s = _("Card was a leech.") if card.queue < 0: @@ -730,7 +738,7 @@ time = %(time)d; qtMenuShortcutWorkaround(m) m.exec_(QCursor.pos()) - def _addMenuItems(self, m, rows): + def _addMenuItems(self, m, rows) -> None: for row in rows: if not row: m.addSeparator() @@ -753,10 +761,10 @@ time = %(time)d; a.setChecked(True) a.triggered.connect(func) - def onOptions(self): + def onOptions(self) -> None: self.mw.onDeckConf(self.mw.col.decks.get(self.card.odid or self.card.did)) - def setFlag(self, flag): + def setFlag(self, flag: int) -> None: # need to toggle off? if self.card.userFlag() == flag: flag = 0 @@ -764,7 +772,7 @@ time = %(time)d; self.card.flush() self._drawFlag() - def onMark(self): + def onMark(self) -> None: f = self.card.note() if f.hasTag("marked"): f.delTag("marked") @@ -773,19 +781,19 @@ time = %(time)d; f.flush() self._drawMark() - def onSuspend(self): + def onSuspend(self) -> None: self.mw.checkpoint(_("Suspend")) self.mw.col.sched.suspendCards([c.id for c in self.card.note().cards()]) tooltip(_("Note suspended.")) self.mw.reset() - def onSuspendCard(self): + def onSuspendCard(self) -> None: self.mw.checkpoint(_("Suspend")) self.mw.col.sched.suspendCards([self.card.id]) tooltip(_("Card suspended.")) self.mw.reset() - def onDelete(self): + def onDelete(self) -> None: # need to check state because the shortcut is global to the main # window if self.mw.state != "review" or not self.card: @@ -801,23 +809,24 @@ time = %(time)d; % cnt ) - def onBuryCard(self): + def onBuryCard(self) -> None: self.mw.checkpoint(_("Bury")) self.mw.col.sched.buryCards([self.card.id]) self.mw.reset() tooltip(_("Card buried.")) - def onBuryNote(self): + def onBuryNote(self) -> None: self.mw.checkpoint(_("Bury")) self.mw.col.sched.buryNote(self.card.nid) self.mw.reset() tooltip(_("Note buried.")) - def onRecordVoice(self): + def onRecordVoice(self) -> None: self._recordedAudio = getAudio(self.mw, encode=False) self.onReplayRecorded() - def onReplayRecorded(self): + def onReplayRecorded(self) -> None: if not self._recordedAudio: - return tooltip(_("You haven't recorded your voice yet.")) + tooltip(_("You haven't recorded your voice yet.")) + return av_player.play_file(self._recordedAudio) From 8bbb3bb2ee5260d6c6004998ab11e8a6b7ae5669 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 1 Mar 2020 13:18:30 -0300 Subject: [PATCH 12/15] Fixed rspy/Makefile trying to use python3 on Windows --- rspy/Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rspy/Makefile b/rspy/Makefile index 76285d577..78341c7c1 100644 --- a/rspy/Makefile +++ b/rspy/Makefile @@ -1,6 +1,12 @@ SHELL := /bin/bash FIND := $(if $(wildcard /bin/find),/bin/find,/usr/bin/find) +ifeq ($(OS),Windows_NT) + PYTHON_BIN := python +else + PYTHON_BIN := python3 +endif + .SHELLFLAGS := -eu -o pipefail -c .DELETE_ON_ERROR: MAKEFLAGS += --warn-undefined-variables @@ -37,7 +43,7 @@ build: $(DEPS) rm -rf $(OUTDIR)/ankirspy* touch ../proto/backend.proto FTL_TEMPLATE_DIRS="$(QT_FTL_TEMPLATES)" FTL_LOCALE_DIRS="$(QT_FTL_LOCALES)" \ - maturin build -i $(shell which python3) -o $(OUTDIR) $(BUILDFLAGS) + maturin build -i $(shell which ${PYTHON_BIN}) -o $(OUTDIR) $(BUILDFLAGS) check: .build/check From 9451d1f705c35462aa8b0ce12f773cd49a81d1c2 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 1 Mar 2020 22:37:57 -0300 Subject: [PATCH 13/15] Fixed rspy/Makefile not rebuilding when rspy/src files are changed --- rspy/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rspy/Makefile b/rspy/Makefile index 76285d577..488a21e0f 100644 --- a/rspy/Makefile +++ b/rspy/Makefile @@ -25,7 +25,8 @@ DEPS := .build/tools .build/vernum ../meta/buildhash \ $(wildcard $(QT_FTL_TEMPLATES)/*.ftl) \ $(wildcard $(QT_FTL_LOCALES)/*/*.ftl) \ $(shell ${FIND} ../rslib/src -name '*.rs') $(wildcard ../proto/*) \ - $(shell ${FIND} ../rslib/ftl -type f) + $(shell ${FIND} ../rslib/ftl -type f) \ + $(shell ${FIND} ./src -type f) .build/develop: $(DEPS) touch ../proto/backend.proto From 598897e82bcc6043a46a0692517926be1a79ad62 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 1 Mar 2020 22:35:25 -0300 Subject: [PATCH 14/15] Hide the Makefile buildhash rule verbosity --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d80a94f4c..19827cd68 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ pyenv: # update build hash .PHONY: buildhash buildhash: - oldhash=$$(test -f meta/buildhash && cat meta/buildhash || true); \ + @oldhash=$$(test -f meta/buildhash && cat meta/buildhash || true); \ newhash=$$(git rev-parse --short=8 HEAD || echo dev); \ if [ "$$oldhash" != "$$newhash" ]; then \ echo $$newhash > meta/buildhash; \ From 5fae10d73c6a017d00018e9f9d29846e0083dc51 Mon Sep 17 00:00:00 2001 From: evandrocoan Date: Sun, 1 Mar 2020 22:57:06 -0300 Subject: [PATCH 15/15] Created a relative badge link README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50b002461..7b145b4de 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,4 @@ For more information on building, please see README.development If you'd like to contribute code, please see README.contributing -![](https://github.com/ankitects/anki/workflows/Checks/badge.svg) +[![](../../workflows/Checks/badge.svg)](../../actions)