Rewrite keymode handling to use only one mode.

Fixes #417.
Fixes #418.
See 4ab5d2df28.
This commit is contained in:
Florian Bruhin 2014-12-28 00:01:27 +01:00
parent be2c67aa19
commit 03ac8874ff
7 changed files with 68 additions and 87 deletions

View File

@ -671,7 +671,7 @@ class HintManager(QObject):
raise cmdexc.CommandError("No frame focused!")
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
if usertypes.KeyMode.hint in mode_manager.mode_stack:
if mode_manager.mode == usertypes.KeyMode.hint:
raise cmdexc.CommandError("Already hinting!")
self._check_args(target, *args)
self._context = HintContext()
@ -689,11 +689,8 @@ class HintManager(QObject):
window=self._win_id)
message_bridge.set_text(self.HINT_TEXTS[target])
self._connect_frame_signals()
try:
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
except modeman.ModeLockedError:
self._cleanup()
modeman.enter(self._win_id, usertypes.KeyMode.hint,
'HintManager.start')
def handle_partial_key(self, keystr):
"""Handle a new partial keypress."""

View File

@ -119,6 +119,10 @@ class WebView(QWebView):
hintmanager.mouse_event.connect(self.on_mouse_event)
hintmanager.set_open_target.connect(self.set_force_open_target)
objreg.register('hintmanager', hintmanager, registry=self.registry)
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
mode_manager.entered.connect(self.on_mode_entered)
mode_manager.left.connect(self.on_mode_left)
page.linkHovered.connect(self.linkHovered)
page.mainFrame().loadStarted.connect(self.on_load_started)
self.urlChanged.connect(self.on_url_changed)
@ -235,8 +239,8 @@ class WebView(QWebView):
if ((hitresult.isContentEditable() and elem.is_writable()) or
elem.is_editable()):
log.mouse.debug("Clicked editable element!")
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'click')
modeman.enter(self._win_id, usertypes.KeyMode.insert, 'click',
only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element!")
if config.get('input', 'auto-leave-insert-mode'):
@ -255,8 +259,8 @@ class WebView(QWebView):
return
if elem.is_editable():
log.mouse.debug("Clicked editable element (delayed)!")
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'click-delayed')
modeman.enter(self._win_id, usertypes.KeyMode.insert,
'click-delayed', only_if_normal=True)
else:
log.mouse.debug("Clicked non-editable element (delayed)!")
if config.get('input', 'auto-leave-insert-mode'):
@ -397,7 +401,7 @@ class WebView(QWebView):
return
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
cur_mode = mode_manager.mode()
cur_mode = mode_manager.mode
if cur_mode == usertypes.KeyMode.insert or not ok:
return
frame = self.page().currentFrame()
@ -408,8 +412,26 @@ class WebView(QWebView):
return
log.modes.debug("focus element: {}".format(repr(elem)))
if elem.is_editable():
modeman.maybe_enter(self._win_id, usertypes.KeyMode.insert,
'load finished')
modeman.enter(self._win_id, usertypes.KeyMode.insert,
'load finished', only_if_normal=True)
@pyqtSlot(usertypes.KeyMode)
def on_mode_entered(self, mode):
"""Ignore attempts to focus the widget if in any status-input mode."""
if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno):
log.webview.debug("Ignoring focus because mode {} was "
"entered.".format(mode))
self.setFocusPolicy(Qt.NoFocus)
@pyqtSlot(usertypes.KeyMode)
def on_mode_left(self, mode):
"""Restore focus policy if status-input modes were left."""
if mode in (usertypes.KeyMode.command, usertypes.KeyMode.prompt,
usertypes.KeyMode.yesno):
log.webview.debug("Restoring focus policy because mode {} was "
"left.".format(mode))
self.setFocusPolicy(Qt.WheelFocus)
@pyqtSlot(str)
def set_force_open_target(self, target):

View File

@ -106,7 +106,7 @@ class Command:
"""
mode_manager = objreg.get('mode-manager', scope='window',
window=win_id)
curmode = mode_manager.mode()
curmode = mode_manager.mode
if self._modes is not None and curmode not in self._modes:
mode_names = '/'.join(mode.name for mode in self._modes)
raise cmdexc.PrerequisitesError(

View File

@ -35,11 +35,6 @@ from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import usertypes, log, objreg, utils
class ModeLockedError(Exception):
"""Exception raised when the mode is currently locked."""
class NotInModeError(Exception):
"""Exception raised when we want to leave a mode we're not in."""
@ -82,9 +77,9 @@ def _get_modeman(win_id):
return objreg.get('mode-manager', scope='window', window=win_id)
def enter(win_id, mode, reason=None, override=False):
def enter(win_id, mode, reason=None, only_if_normal=False):
"""Enter the mode 'mode'."""
_get_modeman(win_id).enter(mode, reason, override)
_get_modeman(win_id).enter(mode, reason, only_if_normal)
def leave(win_id, mode, reason=None):
@ -92,14 +87,6 @@ def leave(win_id, mode, reason=None):
_get_modeman(win_id).leave(mode, reason)
def maybe_enter(win_id, mode, reason=None, override=False):
"""Convenience method to enter 'mode' without exceptions."""
try:
_get_modeman(win_id).enter(mode, reason, override)
except ModeLockedError:
pass
def maybe_leave(win_id, mode, reason=None):
"""Convenience method to leave 'mode' without exceptions."""
try:
@ -141,11 +128,7 @@ class ModeManager(QObject):
Attributes:
passthrough: A list of modes in which to pass through events.
locked: Whether current mode is locked. This means the current mode can
still be left (then locked will be reset), but no new mode can
be entered while the current mode is active.
mode_stack: A list of the modes we're currently in, with the active
one on the right.
mode: The mode we're currently in.
_win_id: The window ID of this ModeManager
_handlers: A dictionary of modes and their handlers.
_forward_unbound_keys: If we should forward unbound keys.
@ -169,25 +152,18 @@ class ModeManager(QObject):
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self.locked = False
self._handlers = {}
self.passthrough = []
self.mode_stack = []
self.mode = usertypes.KeyMode.none
self._releaseevents_to_pass = []
self._forward_unbound_keys = config.get(
'input', 'forward-unbound-keys')
objreg.get('config').changed.connect(self.set_forward_unbound_keys)
def __repr__(self):
return utils.get_repr(self, mode=self.mode(), locked=self.locked,
return utils.get_repr(self, mode=self.mode,
passthrough=self.passthrough)
def mode(self):
"""Get the current mode.."""
if not self.mode_stack:
return None
return self.mode_stack[-1]
def _eventFilter_keypress(self, event):
"""Handle filtering of KeyPress events.
@ -197,7 +173,7 @@ class ModeManager(QObject):
Return:
True if event should be filtered, False otherwise.
"""
curmode = self.mode()
curmode = self.mode
handler = self._handlers[curmode]
if curmode != usertypes.KeyMode.insert:
log.modes.debug("got keypress in mode {} - calling handler "
@ -245,7 +221,7 @@ class ModeManager(QObject):
filter_this = False
else:
filter_this = True
if self.mode() != usertypes.KeyMode.insert:
if self.mode != usertypes.KeyMode.insert:
log.modes.debug("filter: {}".format(filter_this))
return filter_this
@ -264,31 +240,33 @@ class ModeManager(QObject):
if passthrough:
self.passthrough.append(mode)
def enter(self, mode, reason=None, override=False):
def enter(self, mode, reason=None, only_if_normal=False):
"""Enter a new mode.
Args:
mode: The mode to enter as a KeyMode member.
reason: Why the mode was entered.
override: Override a locked mode.
only_if_normal: Only enter the new mode if we're in normal mode.
"""
if not isinstance(mode, usertypes.KeyMode):
raise TypeError("Mode {} is no KeyMode member!".format(mode))
if self.locked:
if override:
log.modes.debug("Locked to mode {}, but overriding to "
"{}.".format(self.mode(), mode))
else:
log.modes.debug("Not entering mode {} because mode is locked "
"to {}.".format(mode, self.mode()))
raise ModeLockedError("Mode is currently locked to {}".format(
self.mode()))
log.modes.debug("Entering mode {}{}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason)))
if mode not in self._handlers:
raise ValueError("No handler for mode {}".format(mode))
self.mode_stack.append(mode)
log.modes.debug("New mode stack: {}".format(self.mode_stack))
if self.mode == mode:
log.modes.debug("Ignoring request as we're in mode {} "
"already.".format(self.mode))
return
if self.mode != usertypes.KeyMode.normal:
if only_if_normal:
log.modes.debug("Ignoring request as we're in mode {} "
"and only_if_normal is set..".format(
self.mode))
return
log.modes.debug("Overriding mode {}.".format(self.mode))
self.left.emit(self.mode, mode, self._win_id)
self.mode = mode
self.entered.emit(mode, self._win_id)
@cmdutils.register(instance='mode-manager', hide=True, scope='window')
@ -311,24 +289,21 @@ class ModeManager(QObject):
mode: The name of the mode to leave.
reason: Why the mode was left.
"""
try:
self.mode_stack.remove(mode)
except ValueError:
raise NotInModeError("Mode {} not on mode stack!".format(mode))
self.locked = False
log.modes.debug("Leaving mode {}{}, new mode stack {}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason),
self.mode_stack))
self.left.emit(mode, self.mode(), self._win_id)
if self.mode != mode:
raise NotInModeError("Not in mode {}!".format(mode))
log.modes.debug("Leaving mode {}{}".format(
mode, '' if reason is None else ' (reason: {})'.format(reason)))
self.mode = usertypes.KeyMode.normal
self.left.emit(mode, self.mode, self._win_id)
@cmdutils.register(instance='mode-manager', name='leave-mode',
not_modes=[usertypes.KeyMode.normal], hide=True,
scope='window')
def leave_current_mode(self):
"""Leave the mode we're currently in."""
if self.mode() == usertypes.KeyMode.normal:
if self.mode == usertypes.KeyMode.normal:
raise ValueError("Can't leave normal mode!")
self.leave(self.mode(), 'leave current')
self.leave(self.mode, 'leave current')
@config.change_filter('input', 'forward-unbound-keys')
def set_forward_unbound_keys(self):
@ -347,7 +322,7 @@ class ModeManager(QObject):
Return:
True if event should be filtered, False otherwise.
"""
if self.mode() is None:
if self.mode is None:
# We got events before mode is set, so just pass them through.
return False
typ = event.type()

View File

@ -90,6 +90,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
"""
self.setText(text)
log.modes.debug("Setting command text, focusing {!r}".format(self))
modeman.enter(self._win_id, usertypes.KeyMode.command, 'cmd focus')
self.setFocus()
self.show_cmd.emit()
@ -183,12 +184,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
self.clear_completion_selection.emit()
self.hide_completion.emit()
def focusInEvent(self, e):
"""Extend focusInEvent to enter command mode."""
modeman.maybe_enter(self._win_id, usertypes.KeyMode.command,
'cmd focus')
super().focusInEvent(e)
def setText(self, text):
"""Extend setText to set prefix and make sure the prompt is ok."""
if not text:

View File

@ -320,15 +320,7 @@ class Prompter(QObject):
mode = self._display_question()
question.aborted.connect(
lambda: modeman.maybe_leave(self._win_id, mode, 'aborted'))
mode_manager = objreg.get('mode-manager', scope='window',
window=self._win_id)
try:
modeman.enter(self._win_id, mode, 'question asked', override=True)
except modeman.ModeLockedError:
if mode_manager.mode() != usertypes.KeyMode.prompt:
question.abort()
return None
mode_manager.locked = True
modeman.enter(self._win_id, mode, 'question asked')
if blocking:
loop = qtutils.EventLoop()
self._loops.append(loop)

View File

@ -230,8 +230,8 @@ ClickTarget = enum('ClickTarget', ['normal', 'tab', 'tab_bg', 'window'])
# Key input modes
KeyMode = enum('KeyMode', ['normal', 'hint', 'command', 'yesno', 'prompt',
'insert', 'passthrough'])
KeyMode = enum('KeyMode', ['none', 'normal', 'hint', 'command', 'yesno',
'prompt', 'insert', 'passthrough'])
# Available command completions