Show possible keychains from current input.

When the current keystring is a partial match for one or more bindings,
show the possible bindings in a small overlay.

The overlay is partially transparent by default, but the background
color is configurable as ui->keystring.bg.
This commit is contained in:
Ryan Roden-Corrent 2016-05-14 06:49:27 -04:00
parent d03659b3da
commit 8406746889
3 changed files with 156 additions and 1 deletions

View File

@ -355,6 +355,10 @@ def data(readonly=False):
"Hide the window decoration when using wayland "
"(requires restart)"),
('show-keyhints',
SettingValue(typ.Bool(), 'true'),
"Show possible keychains based on the current keystring"),
readonly=readonly
)),
@ -1196,6 +1200,18 @@ def data(readonly=False):
"Background color for webpages if unset (or empty to use the "
"theme's color)"),
('keyhint.fg',
SettingValue(typ.QssColor(), '#FFFFFF'),
"Text color for the keyhint widget."),
('keyhint.fg.suffix',
SettingValue(typ.QssColor(), '#FFFF00'),
"Highlight color for keys to complete the current keychain"),
('keyhint.bg',
SettingValue(typ.QssColor(), 'rgba(0, 0, 0, 80%)'),
"Background color of the keyhint widget."),
readonly=readonly
)),
@ -1277,6 +1293,10 @@ def data(readonly=False):
typ.Int(none_ok=True, minval=1, maxval=MAXVALS['int']), ''),
"The default font size for fixed-pitch text."),
('keyhint',
SettingValue(typ.Font(), DEFAULT_FONT_SIZE + ' ${_monospace}'),
"Font used in the keyhint widget."),
readonly=readonly
)),
])

View File

@ -35,7 +35,7 @@ from qutebrowser.mainwindow.statusbar import bar
from qutebrowser.completion import completionwidget
from qutebrowser.keyinput import modeman
from qutebrowser.browser import hints, downloads, downloadview, commands
from qutebrowser.misc import crashsignal
from qutebrowser.misc import crashsignal, keyhintwidget
win_id_gen = itertools.count(0)
@ -160,6 +160,8 @@ class MainWindow(QWidget):
self._commandrunner = runners.CommandRunner(self.win_id)
self._keyhint = keyhintwidget.KeyHintView(self.win_id, self)
log.init.debug("Initializing modes...")
modeman.init(self.win_id, self)
@ -178,6 +180,7 @@ class MainWindow(QWidget):
# resizing will fail. Therefore, we use singleShot QTimers to make sure
# we defer this until everything else is initialized.
QTimer.singleShot(0, self._connect_resize_completion)
QTimer.singleShot(0, self._connect_resize_keyhint)
objreg.get('config').changed.connect(self.on_config_changed)
if config.get('ui', 'hide-mouse-cursor'):
@ -252,6 +255,11 @@ class MainWindow(QWidget):
self._completion.resize_completion.connect(self.resize_completion)
self.resize_completion()
def _connect_resize_keyhint(self):
"""Connect the reposition_keyhint signal and resize it once."""
self._keyhint.reposition_keyhint.connect(self.reposition_keyhint)
self.reposition_keyhint()
def _set_default_geometry(self):
"""Set some sensible default geometry."""
self.setGeometry(QRect(50, 50, 800, 600))
@ -286,6 +294,8 @@ class MainWindow(QWidget):
# commands
keyparsers[usertypes.KeyMode.normal].keystring_updated.connect(
status.keystring.setText)
keyparsers[usertypes.KeyMode.normal].keystring_updated.connect(
self._keyhint.update_keyhint)
cmd.got_cmd.connect(self._commandrunner.run_safely)
cmd.returnPressed.connect(tabs.on_cmd_return_pressed)
tabs.got_cmd.connect(self._commandrunner.run_safely)
@ -367,6 +377,24 @@ class MainWindow(QWidget):
if rect.isValid():
self._completion.setGeometry(rect)
@pyqtSlot()
def reposition_keyhint(self):
"""Adjust keyhint according to config."""
if not self._keyhint.isVisible():
return
# Shrink the window to the shown text and place it at the bottom left
width = self._keyhint.width()
height = self._keyhint.height()
topleft_y = self.height() - self.status.height() - height
topleft_y = qtutils.check_overflow(topleft_y, 'int', fatal=False)
topleft = QPoint(0, topleft_y)
bottomright = (self.status.geometry().topLeft() +
QPoint(width, 0))
rect = QRect(topleft, bottomright)
log.misc.debug('keyhint rect: {}'.format(rect))
if rect.isValid():
self._keyhint.setGeometry(rect)
@cmdutils.register(instance='main-window', scope='window')
@pyqtSlot()
def close(self):
@ -394,6 +422,7 @@ class MainWindow(QWidget):
"""
super().resizeEvent(e)
self.resize_completion()
self.reposition_keyhint()
self._downloadview.updateGeometry()
self.tabbed_browser.tabBar().refresh()

View File

@ -0,0 +1,106 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 Ryan Roden-Corrent (rcorre) <ryan@rcorre.net>
#
# 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/>.
"""Small window that pops up to show hints for possible keystrings.
When a user inputs a key that forms a partial match, this shows a small window
with each possible completion of that keystring and the corresponding command.
It is intended to help discoverability of keybindings.
"""
from PyQt5.QtWidgets import QLabel, QSizePolicy
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from qutebrowser.config import config, style
from qutebrowser.utils import objreg, utils
class KeyHintView(QLabel):
"""The view showing hints for key bindings based on the current key string.
Attributes:
_win_id: Window ID of parent.
_enabled: If False, do not show the window at all
_suffix_color: Highlight for completions to the current keychain.
Signals:
reposition_keyhint: Emitted when this widget should be resized.
"""
STYLESHEET = """
QLabel {
font: {{ font['keyhint'] }};
color: {{ color['keyhint.fg'] }};
background-color: {{ color['keyhint.bg'] }};
}
"""
reposition_keyhint = pyqtSignal()
def __init__(self, win_id, parent=None):
super().__init__(parent)
self._win_id = win_id
self.set_enabled()
config = objreg.get('config')
config.changed.connect(self.set_enabled)
self._suffix_color = config.get('colors', 'keyhint.fg.suffix')
style.set_register_stylesheet(self)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)
self.setVisible(False)
def __repr__(self):
return utils.get_repr(self)
@config.change_filter('ui', 'show-keyhints')
def set_enabled(self):
"""Update self._enabled when the config changed."""
self._enabled = config.get('ui', 'show-keyhints')
if not self._enabled: self.setVisible(False)
def showEvent(self, e):
"""Adjust the keyhint size when it's freshly shown."""
self.reposition_keyhint.emit()
super().showEvent(e)
@pyqtSlot(str)
def update_keyhint(self, prefix):
"""Show hints for the given prefix (or hide if prefix is empty).
Args:
prefix: The current partial keystring.
"""
if len(prefix) == 0 or not self._enabled:
self.setVisible(False)
return
self.setVisible(True)
text = ''
keyconf = objreg.get('key-config')
# this is only fired in normal mode
for key, cmd in keyconf.get_bindings_for('normal').items():
if key.startswith(prefix):
suffix = "<font color={}>{}</font>".format(self._suffix_color,
key[len(prefix):])
text += '{}{}\t<b>{}</b><br>'.format(prefix, suffix, cmd)
self.setText(text)
self.adjustSize()
self.reposition_keyhint.emit()