diff --git a/TODO b/TODO index 0361923dc..825aa0624 100644 --- a/TODO +++ b/TODO @@ -50,8 +50,8 @@ hints bindings for prev/next hint more intelligent clicking (end of textfields) -filtering when typing part of name filter close hints when it's the same link +ignore keypresses shortly after link following Qt Bugs ======== diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 531004a53..c22852963 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -284,6 +284,7 @@ class QuteBrowser(QApplication): # hints kp['hint'].fire_hint.connect(tabs.cur.fire_hint) + kp['hint'].filter_hints.connect(tabs.cur.filter_hints) kp['hint'].keystring_updated.connect(tabs.cur.handle_hint_key) tabs.hint_strings_updated.connect(kp['hint'].on_hint_strings_updated) diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index f68885bc4..ccbe08da4 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -245,6 +245,11 @@ class CurCommandDispatcher(QObject): """Fire a completed hint.""" self._tabs.currentWidget().hintmanager.fire(keystr) + @pyqtSlot(str) + def filter_hints(self, filterstr): + """Filter displayed hints.""" + self._tabs.currentWidget().hintmanager.filter_hints(filterstr) + @cmdutils.register(instance='mainwindow.tabs.cur') def prevpage(self): """Open a "previous" link.""" diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index 6d304c284..7cdda94ff 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -107,7 +107,10 @@ class HintManager(QObject): Return: A list of hint strings, in the same order as the elements. """ - chars = config.get("hints", "chars") + if config.get('hints', 'mode') == 'number': + chars = '0123456789' + else: + chars = config.get('hints', 'chars') # Determine how many digits the link hints will require in the worst # case. Usually we do not need all of these digits for every link # single hint, so we can show shorter hints for a few of the links. @@ -389,6 +392,22 @@ class HintManager(QObject): for key in delete: del self._elems[key] + def filter_hints(self, filterstr): + """Filter displayed hints according to a text.""" + delete = [] + for (string, elems) in self._elems.items(): + if not elems.elem.toPlainText().lower().startswith(filterstr): + elems.label.removeFromDocument() + delete.append(string) + for key in delete: + del self._elems[key] + if not self._elems: + # Whoops, filtered all hints + modeman.leave('hint') + elif len(self._elems) == 1 and config.get('hints', 'auto-follow'): + # unpacking gets us the first (and only) key in the dict. + self.fire(*self._elems) + def fire(self, keystr, force=False): """Fire a completed hint. diff --git a/qutebrowser/config/_conftypes.py b/qutebrowser/config/_conftypes.py index 711b7e6cd..51f9e22c3 100644 --- a/qutebrowser/config/_conftypes.py +++ b/qutebrowser/config/_conftypes.py @@ -200,6 +200,16 @@ class ShellCommand(String): return shlex.split(value) +class HintMode(BaseType): + + """Base class for the hints -> mode setting.""" + + typestr = 'hint-mode' + + valid_values = ValidValues(('number', "Use numeric hints."), + ('letter', "Use the chars in hints -> chars.")) + + class Bool(BaseType): """Base class for a boolean setting. diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 326553530..798232bd6 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -410,6 +410,10 @@ DATA = OrderedDict([ SettingValue(types.Float(minval=0.0, maxval=1.0), '0.7'), "Opacity for hints."), + ('mode', + SettingValue(types.HintMode(), 'letter'), + "Mode to use for hints, 'number' or 'letter'."), + ('chars', SettingValue(types.String(minlen=2), 'asdfghjkl'), "Chars used for hint strings."), diff --git a/qutebrowser/keyinput/modeparsers.py b/qutebrowser/keyinput/modeparsers.py index 542532f1b..19deacd38 100644 --- a/qutebrowser/keyinput/modeparsers.py +++ b/qutebrowser/keyinput/modeparsers.py @@ -21,9 +21,12 @@ Module attributes: STARTCHARS: Possible chars for starting a commandline input. """ -from PyQt5.QtCore import pyqtSignal +import logging + +from PyQt5.QtCore import pyqtSignal, Qt import qutebrowser.utils.message as message +import qutebrowser.config.config as config from qutebrowser.keyinput.keyparser import CommandKeyParser @@ -61,14 +64,65 @@ class HintKeyParser(CommandKeyParser): Signals: fire_hint: When a hint keybinding was completed. Arg: the keystring/hint string pressed. + filter_hints: When the filter text changed. + Arg: the text to filter hints with. + + Attributes: + _filtertext: The text to filter with. """ fire_hint = pyqtSignal(str) + filter_hints = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent, supports_count=False, supports_chains=True) + self._filtertext = '' self.read_config('keybind.hint') + def _handle_special_key(self, e): + """Override _handle_special_key to handle string filtering. + + Return True if the keypress has been handled, and False if not. + + Args: + e: the KeyPressEvent from Qt. + + Return: + True if event has been handled, False otherwise. + + Emit: + filter_hints: Emitted when filter string has changed. + """ + logging.debug("Got special key {} text {}".format(e.key(), e.text())) + if config.get('hints', 'mode') != 'number': + return super()._handle_special_key(e) + elif e.key() == Qt.Key_Backspace: + if self._filtertext: + self._filtertext = self._filtertext[:-1] + self.filter_hints.emit(self._filtertext) + return True + elif not e.text(): + return super()._handle_special_key(e) + else: + self._filtertext += e.text() + self.filter_hints.emit(self._filtertext) + return True + + def handle(self, e): + """Handle a new keypress and call the respective handlers. + + Args: + e: the KeyPressEvent from Qt + + Emit: + keystring_updated: If a new keystring should be set. + """ + handled = self._handle_single_key(e) + if handled: + self.keystring_updated.emit(self._keystring) + return handled + return self._handle_special_key(e) + def execute(self, cmdstr, keytype, count=None): """Handle a completed keychain. @@ -88,3 +142,4 @@ class HintKeyParser(CommandKeyParser): strings: A list of hint strings. """ self.bindings = {s: s for s in strings} + self._filtertext = ''