diff --git a/__init__.py b/__init__.py index ba1ec87..7a5239c 100644 --- a/__init__.py +++ b/__init__.py @@ -1,37 +1,30 @@ # Anki Zoom -# v1.0 3/1/2020 +# v1.1 3/2/2020 # Copyright © 2020 Quip13 (random.emailforcrap@gmail.com) -# Based in part on code by Damien Elmes , Roland Sieker -# Forked from https://github.com/krassowski/Anki-Zoom +# Based in part on code by Damien Elmes , Roland Sieker and github.com/krassowski +# Big thanks to u/Glutanimate and u/yumenogotoshi for code suggestions # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html from aqt import * from aqt import mw from aqt.webview import AnkiWebView, QWebEngineView +from aqt.qt import QEvent, Qt from anki.hooks import addHook, runHook, wrap from anki.hooks import * from anki.lang import _ -zoom_step = 1.1 - -# After changing states, the reported mw.zoomFactor() is inaccurate -# When changing states, the zoom value (Z) is applied twice, essential applying a Z^2 zoom -# This value tracks the correct zoom displayed -zoomActual = 1 - -# Reflects the desired actual zoom level (what the user expects to see). -# Will deviate from zoomActual during state changes, and used to apply zoom correction -userZoomActual = 1 +config = mw.addonManager.getConfig(__name__) +zoom_step = config[ 'zoom_step' ] def zoom_in(step=None): if not step: step = zoom_step - change_zoom(zoomActual * step) + change_zoom_by(step) def zoom_out(step=None): if not step: step = zoom_step - change_zoom(zoomActual / step) + change_zoom_by(1 / step) def reset_zoom(state=None, *args): config = mw.addonManager.getConfig(__name__) @@ -44,33 +37,29 @@ def reset_zoom(state=None, *args): elif state == 'review': change_zoom( config[ 'review_zoom_default' ] ) -def change_zoom(desired_zoomActual, fromHook=False): - global zoomActual - global userZoomActual - pZoom = mw.web.zoomFactor() - i = desired_zoomActual/zoomActual - zoomFactor = pZoom*i - if zoomFactor < 0.2: #documented qt bug where zoomFactor cannot exceed 5 or 1/5 - zoomFactor = 0.2 - elif zoomFactor > 5: - zoomFactor = 5 - mw.web.setZoomFactor(zoomFactor) - zoomActual = zoomActual * (mw.web.zoomFactor()/pZoom) - if not fromHook: - userZoomActual = zoomActual - debugPrint('change_zoom, ' + str(fromHook)) +def change_zoom_by(interval): + currZoom = QWebEngineView.zoomFactor(mw.web) + change_zoom(currZoom * interval) + +def change_zoom(zoom_lvl): + mw.web.setZoomFactor(zoom_lvl) def set_save_zoom(new_state, old_state, *args): + def unpause(): + mw.setUpdatesEnabled(True) + + mw.setUpdatesEnabled(False) + old_state_zoom = QWebEngineView.zoomFactor(mw.web) if old_state == new_state: #skips if reset return config = mw.addonManager.getConfig(__name__) if old_state == 'deckBrowser': - config[ 'deckBrowser_zoom' ] = userZoomActual + config[ 'deckBrowser_zoom' ] = old_state_zoom elif old_state == 'overview': - config[ 'overview_zoom' ] = userZoomActual + config[ 'overview_zoom' ] = old_state_zoom elif old_state == 'review': - config[ 'review_zoom' ] = userZoomActual + config[ 'review_zoom' ] = old_state_zoom mw.addonManager.writeConfig(__name__, config) config = mw.addonManager.getConfig(__name__) @@ -81,81 +70,30 @@ def set_save_zoom(new_state, old_state, *args): elif new_state == 'review': change_zoom( config[ 'review_zoom' ] ) -resetSentByZoom = False -def zoomStateChange(new_state, old_state, *args): - global resetSentByZoom - global zoomActual - global userZoomActual + QTimer.singleShot(150, unpause) #prevents flickering - def unpause(): - mw.setUpdatesEnabled(True) +numDeg = 0 +def AnkiWebView_eventFilter_wrapper(self, obj, event): + global numDeg + global zoom_step - if old_state == 'startup': - return + config = mw.addonManager.getConfig(__name__) #can be moved out of func for performance but config will not update automatically + scrl_threshold = config[ 'scroll_sensitivity' ] + zoom_step = config[ 'zoom_step' ] - if resetSentByZoom: - resetSentByZoom = False - QTimer.singleShot(150, unpause) #prevents flickering - return - elif abs(userZoomActual-zoomActual)>0.001: #does not refresh if close enough - mw.setUpdatesEnabled(False) - mw.web.setZoomFactor(userZoomActual**0.5) - resetSentByZoom = True - mw.reset() #recursion - -prevZoom = mw.web.zoomFactor() -def calcActualZoom(new_state, old_state, *args): - global zoomActual - global prevZoom - global currZoom - if old_state == 'startup': - return - currZoom = mw.web.zoomFactor() - if currZoom != prevZoom: - zoomActual = currZoom**2 - prevZoom = currZoom - debugPrint('After Statechange') - -def debugPrint(debug=""): - #print(debug + ': ZoomValue: ' + f'{mw.web.zoomFactor():.3f}' + ' Actual: ' + f'{zoomActual:.3f}' + ' UserActual: ' + f'{userZoomActual:.3f}') - return - -# Uses QtEventListener to track wheel -class wheelListener(QObject): - def __init__(self): - QObject.__init__(self) - self.ctrl_Pushed = False - self.numDeg = 0 - def eventFilter(self, obj, event): - if event.type() == QEvent.Wheel and self.ctrl_Pushed: - self.numDeg = self.numDeg + event.angleDelta().y() - if self.numDeg > 110: - zoom_in() - self.numDeg = 0 - elif self.numDeg < -110: - zoom_out() - self.numDeg = 0 - elif event.type() == QEvent.KeyPress and event.key() == Qt.Key_Control: - self.ctrl_Pushed = True - elif event.type() == QEvent.KeyRelease and event.key() == Qt.Key_Control: - self.ctrl_Pushed = False - return False -wheelListener = wheelListener() - -#enables scrolling regardless of what has focus (listener installed on bottom, toolbar and main screen) -def setup_event_filters(new_state, old_state, *args): #if called before startup will throw error on linux machines - if old_state == 'startup': - reviewer_QWidget = mw.reviewer.web.findChildren(QWidget)[0] #undocumented method to get underlying event-handling widget of QWebView *may break in future Qt releases - reviewer_QWidget.installEventFilter(wheelListener) - - bottom_QWidget = mw.reviewer.bottom.web.findChildren(QWidget)[0] #undocumented method to get underlying event-handling widget of QWebView *may break in future Qt releases - bottom_QWidget.installEventFilter(wheelListener) - - toolbar_QWidget = mw.toolbar.web.findChildren(QWidget)[0] #undocumented method to get underlying event-handling widget of QWebView *may break in future Qt releases - toolbar_QWidget.installEventFilter(wheelListener) + if (mw.app.keyboardModifiers() == Qt.ControlModifier and + event.type() == QEvent.Wheel): + numDeg = numDeg + event.angleDelta().y() + if numDeg > scrl_threshold: + zoom_in() + numDeg = 0 + elif numDeg < -scrl_threshold : + zoom_out() + numDeg = 0 + +AnkiWebView.eventFilter = wrap(AnkiWebView.eventFilter, AnkiWebView_eventFilter_wrapper, 'before') def add_action(submenu, label, callback, shortcut=None): - """Add action to menu""" action = QAction(_(label), mw) action.triggered.connect(callback) if shortcut: @@ -167,7 +105,6 @@ def add_action(submenu, label, callback, shortcut=None): return action def setup_menu(): - """Set up the zoom menu.""" try: mw.addon_view_menu except AttributeError: @@ -183,21 +120,8 @@ def setup_menu(): add_action(mw.zoom_submenu, 'Zoom &In', zoom_in, ['Ctrl++','Ctrl+=']) add_action(mw.zoom_submenu, 'Zoom &Out', zoom_out, ['Ctrl+-']) mw.zoom_submenu.addSeparator() - add_action(mw.zoom_submenu, '&Reset', reset_zoom, ['Ctrl+0']) -def real_zoom_factor(self): - """Use the default zoomFactor. - - Overwrites Anki's effort to support hiDPI screens. - """ - return QWebEngineView.zoomFactor(self) - -AnkiWebView.zoomFactor = real_zoom_factor - -addHook("afterStateChange", calcActualZoom) -addHook("afterStateChange", zoomStateChange) addHook("afterStateChange", set_save_zoom) -addHook("afterStateChange", setup_event_filters) -setup_menu() +setup_menu() \ No newline at end of file diff --git a/config.json b/config.json index 9b997e6..7b6eccc 100644 --- a/config.json +++ b/config.json @@ -4,5 +4,7 @@ "review_zoom": 1.0, "review_zoom_default": 1.0, "deckBrowser_zoom": 1.0, - "deckBrowser_zoom_default": 1.0 + "deckBrowser_zoom_default": 1.0, + "scroll_sensitivity": 120, + "zoom_step": 1.1 } \ No newline at end of file diff --git a/config.md b/config.md index 7c768ba..bb72658 100644 --- a/config.md +++ b/config.md @@ -1,7 +1,15 @@ -### Anki Zoom v1.0 Config: +### Anki Zoom v1.1 Config: +All config is effective immediately + +- `deckBrowser_zoom_default`: Zoom level that the reset button applies for the deck browser - `overview_zoom_default`: Zoom level that the reset button applies for the overview screen (screen after selecting a deck) - `review_zoom_default`: Zoom level that the reset button applies for card reviews -- `deckBrowser_zoom_default`: Zoom level that the reset button applies for the deck browser \ No newline at end of file +- `zoom_step`: How much is zoom is applied per step. + Values are a percentage: e.g. `1.1` = 110% scaling on zoom in, (1/1.1)≈91% scaling on zoom out + To invert zoom, enter values <1 + +- `scroll_sensitivity`: Defines how much scrolling triggers a zoom step (in arbitrary units). Lower values correlate to more sensitive scrolling. + For reference, mousewheels typically emit 120 units per click. High resolution trackpads (Mac + Windows Precision Touchpads) produce a range of values depending on your movement. \ No newline at end of file diff --git a/manifest.json b/manifest.json index 2495dc1..00f478f 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "package": "538879081", "ankiweb_id": "538879081", "author": "Quip13", - "human_version": "v1.0", + "human_version": "v1.1", "homepage": "https://ankiweb.net/shared/info/538879081", "conflicts": [ "1846592880" ] } \ No newline at end of file diff --git a/zoom.ankiaddon b/zoom.ankiaddon index 134201c..1f9fa74 100644 Binary files a/zoom.ankiaddon and b/zoom.ankiaddon differ