qutebrowser/qutebrowser/browser/mouse.py

216 lines
7.9 KiB
Python
Raw Normal View History

2016-08-10 16:37:52 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2018-02-05 12:19:50 +01:00
# Copyright 2016-2018 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2016-08-10 16:37:52 +02:00
#
# 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 <http://www.gnu.org/licenses/>.
"""Mouse handling for a browser tab."""
2017-12-15 14:35:07 +01:00
from PyQt5.QtCore import QObject, QEvent, Qt, QTimer
2016-08-10 16:37:52 +02:00
from qutebrowser.config import config
from qutebrowser.utils import message, log, usertypes
from qutebrowser.keyinput import modeman
2016-08-10 16:37:52 +02:00
class ChildEventFilter(QObject):
"""An event filter re-adding MouseEventFilter on ChildEvent.
This is needed because QtWebEngine likes to randomly change its
focusProxy...
FIXME:qtwebengine Add a test for this happening
2016-08-11 13:38:20 +02:00
Attributes:
_filter: The event filter to install.
_widget: The widget expected to send out childEvents.
"""
def __init__(self, eventfilter, widget, parent=None):
super().__init__(parent)
self._filter = eventfilter
assert widget is not None
self._widget = widget
def eventFilter(self, obj, event):
2016-08-10 20:23:41 +02:00
"""Act on ChildAdded events."""
if event.type() == QEvent.ChildAdded:
child = event.child()
log.mouse.debug("{} got new child {}, installing filter".format(
obj, child))
assert obj is self._widget
child.installEventFilter(self._filter)
return False
2016-08-10 16:37:52 +02:00
class MouseEventFilter(QObject):
2016-08-11 13:38:20 +02:00
"""Handle mouse events on a tab.
Attributes:
_tab: The browsertab object this filter is installed on.
_handlers: A dict of handler functions for the handled events.
_ignore_wheel_event: Whether to ignore the next wheelEvent.
_check_insertmode_on_release: Whether an insertmode check should be
done when the mouse is released.
2016-08-11 13:38:20 +02:00
"""
2016-08-10 16:37:52 +02:00
def __init__(self, tab, *, parent=None):
2016-08-10 16:37:52 +02:00
super().__init__(parent)
self._tab = tab
self._handlers = {
QEvent.MouseButtonPress: self._handle_mouse_press,
QEvent.MouseButtonRelease: self._handle_mouse_release,
QEvent.Wheel: self._handle_wheel,
QEvent.ContextMenu: self._handle_context_menu,
2016-08-10 16:37:52 +02:00
}
self._ignore_wheel_event = False
self._check_insertmode_on_release = False
2016-08-10 16:37:52 +02:00
def _handle_mouse_press(self, e):
2016-08-10 16:37:52 +02:00
"""Handle pressing of a mouse button."""
is_rocker_gesture = (config.val.input.rocker_gestures and
2016-08-10 16:37:52 +02:00
e.buttons() == Qt.LeftButton | Qt.RightButton)
if e.button() in [Qt.XButton1, Qt.XButton2] or is_rocker_gesture:
self._mousepress_backforward(e)
return True
self._ignore_wheel_event = True
if e.button() != Qt.NoButton:
2017-02-04 23:30:12 +01:00
self._tab.elements.find_at_pos(e.pos(),
self._mousepress_insertmode_cb)
return False
def _handle_mouse_release(self, _e):
"""Handle releasing of a mouse button."""
# We want to make sure we check the focus element after the WebView is
# updated completely.
QTimer.singleShot(0, self._mouserelease_insertmode)
return False
def _handle_wheel(self, e):
"""Zoom on Ctrl-Mousewheel.
Args:
e: The QWheelEvent.
"""
if self._ignore_wheel_event:
2017-02-05 00:13:11 +01:00
# See https://github.com/qutebrowser/qutebrowser/issues/395
self._ignore_wheel_event = False
return True
if e.modifiers() & Qt.ControlModifier:
2017-06-14 19:33:47 +02:00
divider = config.val.zoom.mouse_divider
if divider == 0:
return False
factor = self._tab.zoom.factor() + (e.angleDelta().y() / divider)
if factor < 0:
return False
perc = int(100 * factor)
message.info("Zoom level: {}%".format(perc), replace=True)
self._tab.zoom.set_factor(factor)
elif e.modifiers() & Qt.ShiftModifier:
if e.angleDelta().y() > 0:
self._tab.scroller.left()
else:
self._tab.scroller.right()
return True
2016-08-10 16:37:52 +02:00
return False
def _handle_context_menu(self, _e):
"""Suppress context menus if rocker gestures are turned on."""
return config.val.input.rocker_gestures
def _mousepress_insertmode_cb(self, elem):
"""Check if the clicked element is editable."""
if elem is None:
# Something didn't work out, let's find the focus element after
# a mouse release.
log.mouse.debug("Got None element, scheduling check on "
"mouse release")
self._check_insertmode_on_release = True
return
if elem.is_editable():
log.mouse.debug("Clicked editable element!")
if config.val.input.insert_mode.auto_enter:
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
'click', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element!")
2017-06-13 15:21:40 +02:00
if config.val.input.insert_mode.auto_leave:
2016-11-10 07:19:45 +01:00
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
2016-11-10 08:53:44 +01:00
'click', maybe=True)
def _mouserelease_insertmode(self):
"""If we have an insertmode check scheduled, handle it."""
if not self._check_insertmode_on_release:
return
self._check_insertmode_on_release = False
def mouserelease_insertmode_cb(elem):
"""Callback which gets called from JS."""
if elem is None:
log.mouse.debug("Element vanished!")
return
if elem.is_editable():
log.mouse.debug("Clicked editable element (delayed)!")
modeman.enter(self._tab.win_id, usertypes.KeyMode.insert,
'click-delayed', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element (delayed)!")
2017-06-13 15:21:40 +02:00
if config.val.input.insert_mode.auto_leave:
2016-11-10 07:19:45 +01:00
modeman.leave(self._tab.win_id, usertypes.KeyMode.insert,
'click-delayed', maybe=True)
2016-08-18 14:01:27 +02:00
self._tab.elements.find_focused(mouserelease_insertmode_cb)
2016-08-10 16:37:52 +02:00
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:
2016-09-14 20:52:32 +02:00
message.error("At beginning of history.")
2016-08-10 16:37:52 +02:00
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:
2016-09-14 20:52:32 +02:00
message.error("At end of history.")
2016-08-10 16:37:52 +02:00
def eventFilter(self, obj, event):
2016-08-10 16:37:52 +02:00
"""Filter events going to a QWeb(Engine)View."""
evtype = event.type()
if evtype not in self._handlers:
return False
if obj is not self._tab.event_target():
2017-02-04 00:11:48 +01:00
log.mouse.debug("Ignoring {} to {}".format(
event.__class__.__name__, obj))
return False
return self._handlers[evtype](event)