2014-04-23 16:57:12 +02:00
|
|
|
# Copyright 2014 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
"""Mode manager singleton which handles the current keyboard mode.
|
|
|
|
|
|
|
|
Module attributes:
|
|
|
|
manager: The ModeManager instance.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
2014-04-25 06:22:01 +02:00
|
|
|
from PyQt5.QtGui import QWindow
|
2014-04-24 18:31:01 +02:00
|
|
|
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QEvent
|
2014-04-23 16:57:12 +02:00
|
|
|
|
2014-04-24 16:53:16 +02:00
|
|
|
import qutebrowser.config.config as config
|
2014-04-24 23:09:12 +02:00
|
|
|
import qutebrowser.commands.utils as cmdutils
|
2014-04-25 00:10:07 +02:00
|
|
|
import qutebrowser.utils.debug as debug
|
2014-04-24 16:53:16 +02:00
|
|
|
|
2014-04-23 16:57:12 +02:00
|
|
|
|
|
|
|
manager = None
|
|
|
|
|
|
|
|
|
2014-04-23 23:23:04 +02:00
|
|
|
def init(parent=None):
|
2014-04-23 16:57:12 +02:00
|
|
|
"""Initialize the global ModeManager.
|
|
|
|
|
|
|
|
This needs to be done by hand because the import time is before Qt is ready
|
|
|
|
for everything.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
parent: Parent to use for ModeManager.
|
|
|
|
"""
|
|
|
|
global manager
|
2014-04-23 23:23:04 +02:00
|
|
|
manager = ModeManager(parent)
|
2014-04-23 16:57:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
def enter(mode):
|
|
|
|
"""Enter the mode 'mode'."""
|
|
|
|
manager.enter(mode)
|
|
|
|
|
|
|
|
|
2014-04-24 06:44:58 +02:00
|
|
|
def leave(mode):
|
|
|
|
"""Leave the mode 'mode'."""
|
|
|
|
manager.leave(mode)
|
|
|
|
|
|
|
|
|
2014-04-24 16:03:16 +02:00
|
|
|
def maybe_leave(mode):
|
|
|
|
"""Convenience method to leave 'mode' without exceptions."""
|
|
|
|
try:
|
|
|
|
manager.leave(mode)
|
|
|
|
except ValueError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2014-04-23 16:57:12 +02:00
|
|
|
class ModeManager(QObject):
|
|
|
|
|
|
|
|
"""Manager for keyboard modes.
|
|
|
|
|
|
|
|
Attributes:
|
2014-04-24 06:59:39 +02:00
|
|
|
mode: The current mode (readonly property).
|
|
|
|
passthrough: A list of modes in which to pass through events.
|
2014-04-23 16:57:12 +02:00
|
|
|
_handlers: A dictionary of modes and their handlers.
|
2014-04-24 06:44:58 +02:00
|
|
|
_mode_stack: A list of the modes we're currently in, with the active
|
|
|
|
one on the right.
|
2014-04-23 17:56:36 +02:00
|
|
|
|
|
|
|
Signals:
|
|
|
|
entered: Emitted when a mode is entered.
|
|
|
|
arg: Name of the entered mode.
|
2014-04-24 07:01:27 +02:00
|
|
|
left: Emitted when a mode is left.
|
|
|
|
arg: Name of the left mode.
|
2014-04-24 18:31:01 +02:00
|
|
|
key_pressed: A key was pressed.
|
2014-04-23 16:57:12 +02:00
|
|
|
"""
|
|
|
|
|
2014-04-23 17:56:36 +02:00
|
|
|
entered = pyqtSignal(str)
|
2014-04-24 07:01:27 +02:00
|
|
|
left = pyqtSignal(str)
|
2014-04-24 15:47:38 +02:00
|
|
|
key_pressed = pyqtSignal('QKeyEvent')
|
2014-04-23 17:56:36 +02:00
|
|
|
|
2014-04-23 23:23:04 +02:00
|
|
|
def __init__(self, parent=None):
|
2014-04-23 16:57:12 +02:00
|
|
|
super().__init__(parent)
|
|
|
|
self._handlers = {}
|
2014-04-24 06:59:39 +02:00
|
|
|
self.passthrough = []
|
2014-04-24 06:44:58 +02:00
|
|
|
self._mode_stack = []
|
|
|
|
|
|
|
|
@property
|
|
|
|
def mode(self):
|
|
|
|
"""Read-only property for the current mode."""
|
|
|
|
if not self._mode_stack:
|
|
|
|
return None
|
|
|
|
return self._mode_stack[-1]
|
2014-04-23 16:57:12 +02:00
|
|
|
|
2014-04-23 23:22:34 +02:00
|
|
|
def register(self, mode, handler, passthrough=False):
|
2014-04-23 16:57:12 +02:00
|
|
|
"""Register a new mode.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
mode: The name of the mode.
|
|
|
|
handler: Handler for keyPressEvents.
|
2014-04-23 23:22:34 +02:00
|
|
|
passthrough: Whether to pass keybindings in this mode through to
|
|
|
|
the widgets.
|
2014-04-23 16:57:12 +02:00
|
|
|
"""
|
|
|
|
self._handlers[mode] = handler
|
2014-04-23 23:22:34 +02:00
|
|
|
if passthrough:
|
2014-04-24 06:59:39 +02:00
|
|
|
self.passthrough.append(mode)
|
2014-04-23 16:57:12 +02:00
|
|
|
|
|
|
|
def enter(self, mode):
|
2014-04-23 17:56:36 +02:00
|
|
|
"""Enter a new mode.
|
|
|
|
|
2014-04-24 06:44:58 +02:00
|
|
|
Args:
|
|
|
|
mode; The name of the mode to enter.
|
|
|
|
|
2014-04-23 17:56:36 +02:00
|
|
|
Emit:
|
|
|
|
entered: With the new mode name.
|
|
|
|
"""
|
2014-04-24 06:44:58 +02:00
|
|
|
logging.debug("Switching mode to {}".format(mode))
|
2014-04-23 23:23:30 +02:00
|
|
|
if mode not in self._handlers:
|
|
|
|
raise ValueError("No handler for mode {}".format(mode))
|
2014-04-24 07:44:54 +02:00
|
|
|
if self._mode_stack and self._mode_stack[-1] == mode:
|
|
|
|
logging.debug("Already at end of stack, doing nothing")
|
|
|
|
return
|
2014-04-24 06:44:58 +02:00
|
|
|
self._mode_stack.append(mode)
|
2014-04-24 07:44:47 +02:00
|
|
|
logging.debug("New mode stack: {}".format(self._mode_stack))
|
2014-04-23 17:56:36 +02:00
|
|
|
self.entered.emit(mode)
|
2014-04-23 22:22:58 +02:00
|
|
|
|
2014-04-24 06:44:58 +02:00
|
|
|
def leave(self, mode):
|
|
|
|
"""Leave a mode.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
mode; The name of the mode to leave.
|
|
|
|
|
|
|
|
Emit:
|
2014-04-24 07:01:27 +02:00
|
|
|
left: With the old mode name.
|
2014-04-24 06:44:58 +02:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self._mode_stack.remove(mode)
|
|
|
|
except ValueError:
|
|
|
|
raise ValueError("Mode {} not on mode stack!".format(mode))
|
2014-04-24 07:44:47 +02:00
|
|
|
logging.debug("Leaving mode {}".format(mode))
|
|
|
|
logging.debug("New mode stack: {}".format(self._mode_stack))
|
2014-04-24 07:01:27 +02:00
|
|
|
self.left.emit(mode)
|
2014-04-24 06:44:58 +02:00
|
|
|
|
2014-04-24 23:09:12 +02:00
|
|
|
# FIXME handle modes=[] and not_modes=[] params
|
|
|
|
@cmdutils.register(instance='modeman', name='leave_mode', hide=True)
|
|
|
|
def leave_current_mode(self):
|
|
|
|
if self.mode == "normal":
|
|
|
|
raise ValueError("Can't leave normal mode!")
|
|
|
|
self.leave(self.mode)
|
|
|
|
|
2014-04-25 06:22:01 +02:00
|
|
|
def eventFilter(self, obj, evt):
|
2014-04-23 23:24:46 +02:00
|
|
|
"""Filter all events based on the currently set mode.
|
|
|
|
|
|
|
|
Also calls the real keypress handler.
|
2014-04-24 15:47:38 +02:00
|
|
|
|
|
|
|
Emit:
|
|
|
|
key_pressed: When a key was actually pressed.
|
2014-04-23 23:24:46 +02:00
|
|
|
"""
|
2014-04-24 21:29:28 +02:00
|
|
|
if self.mode is None:
|
|
|
|
# We got events before mode is set, so just pass them through.
|
|
|
|
return False
|
|
|
|
typ = evt.type()
|
2014-04-23 23:24:46 +02:00
|
|
|
if typ not in [QEvent.KeyPress, QEvent.KeyRelease]:
|
|
|
|
# We're not interested in non-key-events so we pass them through.
|
2014-04-23 22:22:58 +02:00
|
|
|
return False
|
2014-04-25 06:22:01 +02:00
|
|
|
if not isinstance(obj, QWindow):
|
|
|
|
# We already handled this same event at some point earlier, so
|
|
|
|
# we're not interested in it anymore.
|
2014-04-25 06:39:17 +02:00
|
|
|
logging.debug("Got event {} for {} -> ignoring".format(
|
|
|
|
debug.EVENTS[typ], obj.__class__.__name__))
|
2014-04-25 06:22:01 +02:00
|
|
|
return False
|
|
|
|
|
2014-04-25 06:39:17 +02:00
|
|
|
logging.debug("Got event {} for {}".format(
|
|
|
|
debug.EVENTS[typ], obj.__class__.__name__))
|
|
|
|
|
2014-04-25 06:22:01 +02:00
|
|
|
handler = self._handlers[self.mode]
|
|
|
|
|
2014-04-25 00:10:07 +02:00
|
|
|
if self.mode in self.passthrough:
|
2014-04-23 23:24:46 +02:00
|
|
|
# We're currently in a passthrough mode so we pass everything
|
|
|
|
# through.*and* let the passthrough keyhandler know.
|
|
|
|
# FIXME what if we leave the passthrough mode right here?
|
2014-04-25 06:39:17 +02:00
|
|
|
logging.debug("We're in a passthrough mode -> passing through")
|
2014-04-25 06:22:01 +02:00
|
|
|
if typ == QEvent.KeyPress:
|
2014-04-25 06:39:17 +02:00
|
|
|
logging.debug("KeyPress, calling handler {}".format(handler))
|
2014-04-25 06:22:01 +02:00
|
|
|
self.key_pressed.emit(evt)
|
|
|
|
if handler is not None:
|
|
|
|
handler(evt)
|
2014-04-24 16:53:16 +02:00
|
|
|
else:
|
2014-04-25 06:39:17 +02:00
|
|
|
logging.debug("KeyRelease, not calling anything")
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
logging.debug("We're in a non-passthrough mode")
|
|
|
|
if typ == QEvent.KeyPress:
|
|
|
|
# KeyPress in a non-passthrough mode - call handler and filter
|
|
|
|
# event from widgets (unless unhandled and configured to pass
|
|
|
|
# unhandled events through)
|
2014-04-25 06:52:18 +02:00
|
|
|
logging.debug("KeyPress, calling handler {} and "
|
|
|
|
"filtering".format(handler))
|
2014-04-25 06:39:17 +02:00
|
|
|
self.key_pressed.emit(evt)
|
|
|
|
handled = handler(evt) if handler is not None else False
|
2014-04-25 06:52:18 +02:00
|
|
|
return True
|
2014-04-24 16:53:16 +02:00
|
|
|
else:
|
2014-04-25 06:39:17 +02:00
|
|
|
# KeyRelease in a non-passthrough mode - filter event and
|
|
|
|
# ignore it entirely.
|
|
|
|
logging.debug("KeyRelease, not calling anything and filtering")
|
2014-04-24 16:53:16 +02:00
|
|
|
return True
|