diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 8b666ab79..ab4f216d6 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -57,6 +57,7 @@ from qutebrowser.widgets.mainwindow import MainWindow from qutebrowser.widgets.crash import CrashDialog from qutebrowser.commands.keys import CommandKeyParser from qutebrowser.commands.parsers import CommandParser, SearchParser +from qutebrowser.browser.hints import HintKeyParser from qutebrowser.utils.appdirs import AppDirs from qutebrowser.utils.misc import dotted_getattr from qutebrowser.utils.debug import set_trace # pylint: disable=unused-import @@ -74,13 +75,14 @@ class QuteBrowser(QApplication): Attributes: mainwindow: The MainWindow QWidget. commandparser: The main CommandParser instance. - keyparser: The main CommandKeyParser instance. searchparser: The main SearchParser instance. + _keyparsers: A mapping from modes to keyparsers. _dirs: AppDirs instance for config/cache directories. _args: ArgumentParser instance. _timers: List of used QTimers so they don't get GCed. _shutting_down: True if we're currently shutting down. _quit_status: The current quitting status. + _mode: The mode we're currently in. """ def __init__(self): @@ -88,6 +90,7 @@ class QuteBrowser(QApplication): self._quit_status = {} self._timers = [] self._shutting_down = False + self._mode = None sys.excepthook = self._exception_hook @@ -108,40 +111,16 @@ class QuteBrowser(QApplication): self.commandparser = CommandParser() self.searchparser = SearchParser() - self.keyparser = CommandKeyParser(self) + self._keyparsers = { + "normal": CommandKeyParser(self), + "hint": HintKeyParser(self), + } self._init_cmds() self.mainwindow = MainWindow() - self.setQuitOnLastWindowClosed(False) - self.lastWindowClosed.connect(self.shutdown) - self.mainwindow.tabs.keypress.connect( - self.mainwindow.status.keypress) - self.mainwindow.tabs.keypress.connect(self.keyparser.handle) - self.keyparser.set_cmd_text.connect( - self.mainwindow.status.cmd.set_cmd_text) - self.mainwindow.tabs.set_cmd_text.connect( - self.mainwindow.status.cmd.set_cmd_text) - self.mainwindow.tabs.quit.connect(self.shutdown) - self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run) - self.mainwindow.status.cmd.got_search.connect(self.searchparser.search) - self.mainwindow.status.cmd.got_search_rev.connect( - self.searchparser.search_rev) - self.mainwindow.status.cmd.returnPressed.connect( - self.mainwindow.tabs.setFocus) - self.searchparser.do_search.connect( - self.mainwindow.tabs.cur.search) - self.keyparser.keystring_updated.connect( - self.mainwindow.status.keystring.setText) - message.bridge.error.connect(self.mainwindow.status.disp_error) - message.bridge.info.connect(self.mainwindow.status.disp_tmp_text) - self.config.style_changed.connect(style.invalidate_caches) - self.config.changed.connect(self.mainwindow.tabs.on_config_changed) - self.config.changed.connect( - self.mainwindow.completion.on_config_changed) - self.config.changed.connect(self.mainwindow.on_config_changed) - self.config.changed.connect(config.cmd_history.on_config_changed) - self.config.changed.connect(websettings.on_config_changed) - self.config.changed.connect(self.keyparser.on_config_changed) + + self._connect_signals() + self.set_mode("normal") self.mainwindow.show() self._python_hacks() @@ -241,6 +220,45 @@ class QuteBrowser(QApplication): timer.timeout.connect(lambda: None) self._timers.append(timer) + def _connect_signals(self): + """Connect all signals to their slots.""" + self.lastWindowClosed.connect(self.shutdown) + self.mainwindow.tabs.keypress.connect( + self.mainwindow.status.keypress) + self._keyparsers["normal"].set_cmd_text.connect( + self.mainwindow.status.cmd.set_cmd_text) + self.mainwindow.tabs.set_cmd_text.connect( + self.mainwindow.status.cmd.set_cmd_text) + self.mainwindow.tabs.quit.connect(self.shutdown) + self.mainwindow.status.cmd.got_cmd.connect(self.commandparser.run) + self.mainwindow.status.cmd.got_search.connect(self.searchparser.search) + self.mainwindow.status.cmd.got_search_rev.connect( + self.searchparser.search_rev) + self.mainwindow.status.cmd.returnPressed.connect( + self.mainwindow.tabs.setFocus) + self.searchparser.do_search.connect( + self.mainwindow.tabs.cur.search) + self._keyparsers["normal"].keystring_updated.connect( + self.mainwindow.status.keystring.setText) + self._keyparsers["hint"].fire_hint.connect( + self.mainwindow.tabs.cur.fire_hint) + self._keyparsers["hint"].keystring_updated.connect( + self.mainwindow.tabs.cur.handle_hint_key) + self.mainwindow.tabs.hint_strings_updated.connect( + self._keyparsers["hint"].on_hint_strings_updated) + self.mainwindow.tabs.set_mode.connect(self.set_mode) + message.bridge.error.connect(self.mainwindow.status.disp_error) + message.bridge.info.connect(self.mainwindow.status.disp_tmp_text) + self.config.style_changed.connect(style.invalidate_caches) + self.config.changed.connect(self.mainwindow.tabs.on_config_changed) + self.config.changed.connect( + self.mainwindow.completion.on_config_changed) + self.config.changed.connect(self.mainwindow.on_config_changed) + self.config.changed.connect(config.cmd_history.on_config_changed) + self.config.changed.connect(websettings.on_config_changed) + self.config.changed.connect( + self._keyparsers["normal"].on_config_changed) + def _recover_pages(self): """Try to recover all open pages. @@ -340,6 +358,20 @@ class QuteBrowser(QApplication): logging.debug("maybe_quit quitting.") self.quit() + @pyqtSlot(str) + def set_mode(self, mode): + """Set a key input mode. + + Args: + mode: The new mode to set, as an index for self._keyparsers. + """ + if self._mode is not None: + oldhandler = self._keyparsers[self._mode] + self.mainwindow.tabs.keypress.disconnect(oldhandler.handle) + handler = self._keyparsers[mode] + self.mainwindow.tabs.keypress.connect(handler.handle) + self._mode = mode + @cmdutils.register(instance='', maxsplit=0) def pyeval(self, s): """Evaluate a python string and display the results as a webpage. diff --git a/qutebrowser/browser/curcommand.py b/qutebrowser/browser/curcommand.py index bafa2a89f..16ad97d35 100644 --- a/qutebrowser/browser/curcommand.py +++ b/qutebrowser/browser/curcommand.py @@ -175,6 +175,16 @@ class CurCommandDispatcher(QObject): """ self._tabs.currentWidget().hintmanager.start(mode) + @pyqtSlot(str) + def handle_hint_key(self, keystr): + """Handle a new hint keypress.""" + self._tabs.currentWidget().hintmanager.handle_partial_key(keystr) + + @pyqtSlot(str) + def fire_hint(self, keystr): + """Fire a completed hint.""" + self._tabs.currentWidget().hintmanager.fire(keystr) + @pyqtSlot(str, int) def search(self, text, flags): """Search for text in the current page. diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py index e0975047f..acae7f519 100644 --- a/qutebrowser/browser/hints.py +++ b/qutebrowser/browser/hints.py @@ -19,10 +19,41 @@ import math +from PyQt5.QtCore import pyqtSignal, QObject + import qutebrowser.config.config as config +from qutebrowser.utils.keyparser import KeyParser -class HintManager: +class HintKeyParser(KeyParser): + + """KeyParser for hints. + + Class attributes: + supports_count: If the keyparser should support counts. + + Signals: + fire_hint: When a hint keybinding was completed. + Arg: the keystring/hint string pressed. + """ + + supports_count = False + fire_hint = pyqtSignal(str) + + def execute(self, cmdstr, count=None): + """Handle a completed keychain.""" + self.fire_hint.emit(cmdstr) + + def on_hint_strings_updated(self, strings): + """Handler for HintManager's hint_strings_updated. + + Args: + strings: A list of hint strings. + """ + self.bindings = {s: s for s in strings} + + +class HintManager(QObject): """Manage drawing hints over links or other elements. @@ -34,6 +65,10 @@ class HintManager: _frame: The QWebFrame to use. _elems: The elements we're hinting currently. _labels: The label elements. + + Signals: + hint_strings_updated: Emitted when the possible hint strings changed. + set_mode: Emitted when the input mode should be changed. """ SELECTORS = { @@ -60,12 +95,16 @@ class HintManager: top: {top}px; """ + hint_strings_updated = pyqtSignal(list) + set_mode = pyqtSignal(str) + def __init__(self, frame): """Constructor. Args: frame: The QWebFrame to use for finding elements and drawing. """ + super().__init__(frame) self._frame = frame self._elems = [] self._labels = [] @@ -196,6 +235,8 @@ class HintManager: strings = self._hint_strings(self._elems) for e, string in zip(self._elems, strings): self._draw_label(e, string) + self.hint_strings_updated.emit(strings) + self.set_mode.emit("hint") def stop(self): """Stop hinting.""" @@ -203,3 +244,12 @@ class HintManager: e.removeFromDocument() self._elems = None self._labels = [] + self.set_mode.emit("normal") + + def handle_partial_key(self, keystr): + """Handle a new partial keypress.""" + raise NotImplementedError + + def fire(self, keystr): + """Fire a completed hint.""" + raise NotImplementedError diff --git a/qutebrowser/commands/keys.py b/qutebrowser/commands/keys.py index ccad12a7b..fcac4253d 100644 --- a/qutebrowser/commands/keys.py +++ b/qutebrowser/commands/keys.py @@ -37,9 +37,11 @@ class CommandKeyParser(KeyParser): """Keyparser for command bindings. + Class attributes: + supports_count: If the keyparser should support counts. + Attributes: commandparser: Commandparser instance. - supports_count: If the keyparser should support counts. Signals: set_cmd_text: Emitted when the statusbar should set a partial command. diff --git a/qutebrowser/widgets/tabbedbrowser.py b/qutebrowser/widgets/tabbedbrowser.py index d6a21ddb4..beda72d38 100644 --- a/qutebrowser/widgets/tabbedbrowser.py +++ b/qutebrowser/widgets/tabbedbrowser.py @@ -67,6 +67,10 @@ class TabbedBrowser(TabWidget): cur_scroll_perc_changed: Scroll percentage of current tab changed. arg 1: x-position in %. arg 2: y-position in %. + hint_strings_updated: Hint strings were updated. + arg: A list of hint strings. + set_mode: The input mode should be changed. + arg: The new mode as a string. keypress: A key was pressed. arg: The QKeyEvent leading to the keypress. shutdown_complete: The shuttdown is completed. @@ -84,7 +88,9 @@ class TabbedBrowser(TabWidget): cur_url_changed = pyqtSignal('QUrl') cur_link_hovered = pyqtSignal(str, str, str) cur_scroll_perc_changed = pyqtSignal(int, int) + hint_strings_updated = pyqtSignal(list) set_cmd_text = pyqtSignal(str) + set_mode = pyqtSignal(str) keypress = pyqtSignal('QKeyEvent') shutdown_complete = pyqtSignal() quit = pyqtSignal() @@ -238,6 +244,8 @@ class TabbedBrowser(TabWidget): tab.temp_message.connect(self._filter.create(self.cur_temp_message)) tab.urlChanged.connect(self._filter.create(self.cur_url_changed)) tab.titleChanged.connect(self._titleChanged_handler) + tab.hintmanager.hint_strings_updated.connect(self.hint_strings_updated) + tab.hintmanager.set_mode.connect(self.set_mode) # FIXME sometimes this doesn't load tab.show() tab.open_tab.connect(self.tabopen)