From f908d29a5ff1af283d6f66e181203bdfbb26cce2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 10 Aug 2016 16:37:52 +0200 Subject: [PATCH] Move mouse handling to an EventFilter --- qutebrowser/browser/browsertab.py | 7 ++ qutebrowser/browser/mouse.py | 77 +++++++++++++++++++ qutebrowser/browser/webengine/webenginetab.py | 3 + qutebrowser/browser/webkit/webkittab.py | 3 + qutebrowser/browser/webkit/webview.py | 30 +------- tests/manual/mouse.html | 16 ++++ tests/unit/browser/test_tab.py | 33 +++++--- 7 files changed, 128 insertions(+), 41 deletions(-) create mode 100644 qutebrowser/browser/mouse.py create mode 100644 tests/manual/mouse.html diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 1f2ea5638..05b5673d7 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -29,6 +29,7 @@ from qutebrowser.keyinput import modeman from qutebrowser.config import config from qutebrowser.utils import utils, objreg, usertypes, message, log, qtutils from qutebrowser.misc import miscwidgets +from qutebrowser.browser import mouse tab_id_gen = itertools.count(0) @@ -481,8 +482,12 @@ class AbstractTab(QWidget): self._progress = 0 self._has_ssl_errors = False self._load_status = usertypes.LoadStatus.none + self._mouse_event_filter = mouse.MouseEventFilter(self, parent=self) self.backend = None + def _event_filter_target(self): + raise NotImplementedError + def _set_widget(self, widget): # pylint: disable=protected-access self._widget = widget @@ -494,6 +499,8 @@ class AbstractTab(QWidget): self.search._widget = widget self.printing._widget = widget widget.mouse_wheel_zoom.connect(self.zoom._on_mouse_wheel_zoom) + event_filter_target = self._event_filter_target() + event_filter_target.installEventFilter(self._mouse_event_filter) def _set_load_status(self, val): """Setter for load_status.""" diff --git a/qutebrowser/browser/mouse.py b/qutebrowser/browser/mouse.py new file mode 100644 index 000000000..3bd3bf32b --- /dev/null +++ b/qutebrowser/browser/mouse.py @@ -0,0 +1,77 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Mouse handling for a browser tab.""" + + +from qutebrowser.config import config +from qutebrowser.utils import message + + +from PyQt5.QtCore import QObject, QEvent, Qt + + +class MouseEventFilter(QObject): + + """Handle mouse events on a tab.""" + + def __init__(self, tab, parent=None): + super().__init__(parent) + self._tab = tab + self._handlers = { + QEvent.MouseButtonPress: self._handle_mouse_press, + } + + def _handle_mouse_press(self, e): + """Handle pressing of a mouse button.""" + is_rocker_gesture = (config.get('input', 'rocker-gestures') and + e.buttons() == Qt.LeftButton | Qt.RightButton) + + if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture: + self._mousepress_backforward(e) + return True + return False + + def _mousepress_backforward(self, e): + """Handle back/forward mouse button presses. + + Args: + e: The QMouseEvent. + """ + if e.button() in [Qt.XButton1, Qt.LeftButton]: + # Back button on mice which have it, or rocker gesture + if self._tab.history.can_go_back(): + self._tab.history.back() + else: + message.error(self._tab.win_id, "At beginning of history.", + immediately=True) + elif e.button() in [Qt.XButton2, Qt.RightButton]: + # Forward button on mice which have it, or rocker gesture + if self._tab.history.can_go_forward(): + self._tab.history.forward() + else: + message.error(self._tab.win_id, "At end of history.", + immediately=True) + + def eventFilter(self, _obj, event): + """Filter events going to a QWeb(Engine)View.""" + evtype = event.type() + if evtype not in self._handlers: + return False + return self._handlers[evtype](event) diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index 62148eb15..c9dd20717 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -359,6 +359,9 @@ class WebEngineTab(browsertab.AbstractTab): # FIXME:qtwebengine what about runsOnSubFrames? page.scripts().insert(script) + def _event_filter_target(self): + return self._widget.focusProxy() + def openurl(self, url): self._openurl_prepare(url) self._widget.load(url) diff --git a/qutebrowser/browser/webkit/webkittab.py b/qutebrowser/browser/webkit/webkittab.py index 5cbe19464..8d47152cd 100644 --- a/qutebrowser/browser/webkit/webkittab.py +++ b/qutebrowser/browser/webkit/webkittab.py @@ -510,6 +510,9 @@ class WebKitTab(browsertab.AbstractTab): self.zoom.set_default() self.backend = usertypes.Backend.QtWebKit + def _event_filter_target(self): + return self._widget + def openurl(self, url): self._openurl_prepare(url) self._widget.openurl(url) diff --git a/qutebrowser/browser/webkit/webview.py b/qutebrowser/browser/webkit/webview.py index d564db3b1..1d3753510 100644 --- a/qutebrowser/browser/webkit/webview.py +++ b/qutebrowser/browser/webkit/webview.py @@ -29,7 +29,7 @@ from PyQt5.QtWebKitWidgets import QWebView, QWebPage, QWebFrame from qutebrowser.config import config from qutebrowser.keyinput import modeman -from qutebrowser.utils import message, log, usertypes, utils, qtutils, objreg +from qutebrowser.utils import log, usertypes, utils, qtutils, objreg from qutebrowser.browser import hints from qutebrowser.browser.webkit import webpage, webkitelem @@ -138,27 +138,6 @@ class WebView(QWebView): elif section == 'colors' and option == 'webpage.bg': self._set_bg_color() - def _mousepress_backforward(self, e): - """Handle back/forward mouse button presses. - - Args: - e: The QMouseEvent. - """ - if e.button() in [Qt.XButton1, Qt.LeftButton]: - # Back button on mice which have it, or rocker gesture - if self.page().history().canGoBack(): - self.back() - else: - message.error(self.win_id, "At beginning of history.", - immediately=True) - elif e.button() in [Qt.XButton2, Qt.RightButton]: - # Forward button on mice which have it, or rocker gesture - if self.page().history().canGoForward(): - self.forward() - else: - message.error(self.win_id, "At end of history.", - immediately=True) - def _mousepress_insertmode(self, e): """Switch to insert mode when an editable element was clicked. @@ -407,13 +386,6 @@ class WebView(QWebView): Return: The superclass return value. """ - is_rocker_gesture = (config.get('input', 'rocker-gestures') and - e.buttons() == Qt.LeftButton | Qt.RightButton) - - if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture: - self._mousepress_backforward(e) - super().mousePressEvent(e) - return self._mousepress_insertmode(e) self._mousepress_opentarget(e) self._ignore_wheel_event = True diff --git a/tests/manual/mouse.html b/tests/manual/mouse.html new file mode 100644 index 000000000..3506b5dd5 --- /dev/null +++ b/tests/manual/mouse.html @@ -0,0 +1,16 @@ + + + + + Mouse control + + +
    +
  • Middle- or Ctrl-click on a link should open it in a new tab (fg/bg according to tabs -> background-tabs)
  • +
  • When clicking the link with shift, background-tabs should be reversed accordingly.
  • +
  • Ctrl + Mousewheel should zoom in/out
  • +
  • Back/forward keys on mouse should navigate back/forward
  • +
  • With input -> rocker-gestures set, no context menu should be shown, but pressing left+right/right+left buttons should navigate back/forward
  • +
  • When setting input -> rocker-gestures dynamically, the context menu should be hidden/shown accordingly.
  • + + diff --git a/tests/unit/browser/test_tab.py b/tests/unit/browser/test_tab.py index 0bc240b45..cd6e9b374 100644 --- a/tests/unit/browser/test_tab.py +++ b/tests/unit/browser/test_tab.py @@ -91,26 +91,35 @@ def tab(request, default_config, qtbot, tab_registry, cookiejar_and_cache): objreg.delete('mode-manager', scope='window', window=0) +class Tab(browsertab.AbstractTab): + + # pylint: disable=abstract-method + + def __init__(self, win_id, parent=None): + super().__init__(win_id, parent) + mode_manager = modeman.ModeManager(0) + self.history = browsertab.AbstractHistory(self) + self.scroller = browsertab.AbstractScroller(self, parent=self) + self.caret = browsertab.AbstractCaret(win_id=self.win_id, + mode_manager=mode_manager, + tab=self, parent=self) + self.zoom = browsertab.AbstractZoom(win_id=self.win_id) + self.search = browsertab.AbstractSearch(parent=self) + self.printing = browsertab.AbstractPrinting() + + def _event_filter_target(self): + return self._widget + + @pytest.mark.skipif(PYQT_VERSION < 0x050600, reason='Causes segfaults, see #1638') def test_tab(qtbot, view, config_stub, tab_registry): - tab_w = browsertab.AbstractTab(win_id=0) + tab_w = Tab(win_id=0) qtbot.add_widget(tab_w) assert tab_w.win_id == 0 assert tab_w._widget is None - mode_manager = modeman.ModeManager(0) - - tab_w.history = browsertab.AbstractHistory(tab_w) - tab_w.scroller = browsertab.AbstractScroller(tab_w, parent=tab_w) - tab_w.caret = browsertab.AbstractCaret(win_id=tab_w.win_id, - mode_manager=mode_manager, - tab=tab_w, parent=tab_w) - tab_w.zoom = browsertab.AbstractZoom(win_id=tab_w.win_id) - tab_w.search = browsertab.AbstractSearch(parent=tab_w) - tab_w.printing = browsertab.AbstractPrinting() - tab_w._set_widget(view) assert tab_w._widget is view assert tab_w.history._tab is tab_w