Rewrite keymode handling to use only one mode.
Fixes #417.
Fixes #418.
See 4ab5d2df28
.
This commit is contained in:
parent
be2c67aa19
commit
03ac8874ff
@ -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."""
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user