From ffd1e673b311efb126edb23d22c46578407e1d03 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 17 Apr 2015 07:49:08 +0200 Subject: [PATCH] Get rid of SearchRunner. A SearchRunner was per-mainwindow, which caused bugs when searching in a tab and in another before clearing the search. Instead we now split it between WebView/CommandDispatcher. Fixes #638. --- qutebrowser/browser/commands.py | 68 ++++++++++++++ qutebrowser/browser/webview.py | 35 +++++++- qutebrowser/commands/runners.py | 99 +-------------------- qutebrowser/mainwindow/mainwindow.py | 9 -- qutebrowser/mainwindow/statusbar/command.py | 17 ++-- qutebrowser/mainwindow/tabbedbrowser.py | 30 ------- 6 files changed, 108 insertions(+), 150 deletions(-) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 93bed2b49..7349e9a03 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -1076,3 +1076,71 @@ class CommandDispatcher: elem.evaluateJavaScript("this.value='{}'".format(text)) except webelem.IsNullError: raise cmdexc.CommandError("Element vanished while editing!") + + @cmdutils.register(instance='command-dispatcher', scope='window', + maxsplit=0) + def search(self, text="", reverse=False): + """Search for a text on the current page. With no text, clear results. + + Args: + text: The text to search for. + reverse: Reverse search direction. + """ + view = self._current_widget() + if view.search_text is not None and view.search_text != text: + # We first clear the marked text, then the highlights + view.search('', 0) + view.search('', QWebPage.HighlightAllOccurrences) + + flags = 0 + ignore_case = config.get('general', 'ignore-case') + if ignore_case == 'smart': + if not text.islower(): + flags |= QWebPage.FindCaseSensitively + elif not ignore_case: + flags |= QWebPage.FindCaseSensitively + if config.get('general', 'wrap-search'): + flags |= QWebPage.FindWrapsAroundDocument + if reverse: + flags |= QWebPage.FindBackward + # We actually search *twice* - once to highlight everything, then again + # to get a mark so we can navigate. + view.search(text, flags) + view.search(text, flags | QWebPage.HighlightAllOccurrences) + view.search_text = text + view.search_flags = flags + + @cmdutils.register(instance='command-dispatcher', hide=True, + scope='window') + def search_next(self, count: {'special': 'count'}=1): + """Continue the search to the ([count]th) next term. + + Args: + count: How many elements to ignore. + """ + view = self._current_widget() + if view.search_text is not None: + for _ in range(count): + view.search(view.search_text, view.search_flags) + + @cmdutils.register(instance='command-dispatcher', hide=True, + scope='window') + def search_prev(self, count: {'special': 'count'}=1): + """Continue the search to the ([count]th) previous term. + + Args: + count: How many elements to ignore. + """ + view = self._current_widget() + if view.search_text is None: + return + # The int() here serves as a QFlags constructor to create a copy of the + # QFlags instance rather as a reference. I don't know why it works this + # way, but it does. + flags = int(view.search_flags) + if flags & QWebPage.FindBackward: + flags &= ~QWebPage.FindBackward + else: + flags |= QWebPage.FindBackward + for _ in range(count): + view.search(view.search_text, flags) diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py index ee88e4089..20733c33e 100644 --- a/qutebrowser/browser/webview.py +++ b/qutebrowser/browser/webview.py @@ -23,7 +23,7 @@ import sys import itertools import functools -from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl +from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QTimer, QUrl, QObject from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWebKit import QWebSettings from PyQt5.QtWebKitWidgets import QWebView, QWebPage @@ -62,6 +62,8 @@ class WebView(QWebView): registry: The ObjectRegistry associated with this tab. tab_id: The tab ID of the view. win_id: The window ID of the view. + search_text: The text of the last search. + search_flags: The search flags of the last search. _cur_url: The current URL (accessed via cur_url property). _has_ssl_errors: Whether SSL errors occurred during loading. _zoom: A NeighborList with the zoom levels. @@ -102,6 +104,8 @@ class WebView(QWebView): self._zoom = None self._has_ssl_errors = False self.keep_icon = False + self.search_text = None + self.search_flags = 0 self.init_neighborlist() cfg = objreg.get('config') cfg.changed.connect(self.init_neighborlist) @@ -436,6 +440,35 @@ class WebView(QWebView): "left.".format(mode)) self.setFocusPolicy(Qt.WheelFocus) + def search(self, text, flags): + """Search for text in the current page. + + Args: + text: The text to search for. + flags: The QWebPage::FindFlags. + """ + log.webview.debug("Searching with text '{}' and flags " + "0x{:04x}.".format(text, int(flags))) + old_scroll_pos = self.scroll_pos + flags = QWebPage.FindFlags(flags) + found = self.findText(text, flags) + if not found and not flags & QWebPage.HighlightAllOccurrences and text: + message.error(self.win_id, "Text '{}' not found on " + "page!".format(text), immediately=True) + else: + backward = int(flags) & QWebPage.FindBackward + + def check_scroll_pos(): + """Check if the scroll position got smaller and show info.""" + if not backward and self.scroll_pos < old_scroll_pos: + message.info(self.win_id, "Search hit BOTTOM, continuing " + "at TOP", immediately=True) + elif backward and self.scroll_pos > old_scroll_pos: + message.info(self.win_id, "Search hit TOP, continuing at " + "BOTTOM", immediately=True) + # We first want QWebPage to refresh. + QTimer.singleShot(0, check_scroll_pos) + def createWindow(self, wintype): """Called by Qt when a page wants to create a new window. diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index 754bdb81c..f288d3742 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -21,8 +21,7 @@ import collections -from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl -from PyQt5.QtWebKitWidgets import QWebPage +from PyQt5.QtCore import pyqtSlot, QUrl, QObject from qutebrowser.config import config, configexc from qutebrowser.commands import cmdexc, cmdutils @@ -56,102 +55,6 @@ def replace_variables(win_id, arglist): return args -class SearchRunner(QObject): - - """Run searches on web pages. - - Attributes: - _text: The text from the last search. - _flags: The flags from the last search. - - Signals: - do_search: Emitted when a search should be started. - arg 1: Search string. - arg 2: Flags to use. - """ - - do_search = pyqtSignal(str, 'QWebPage::FindFlags') - - def __init__(self, parent=None): - super().__init__(parent) - self._text = None - self._flags = 0 - - def __repr__(self): - return utils.get_repr(self, text=self._text, flags=self._flags) - - @pyqtSlot(str) - @cmdutils.register(instance='search-runner', scope='window', maxsplit=0) - def search(self, text="", reverse=False): - """Search for a text on the current page. With no text, clear results. - - Args: - text: The text to search for. - reverse: Reverse search direction. - """ - if self._text is not None and self._text != text: - # We first clear the marked text, then the highlights - self.do_search.emit('', 0) - self.do_search.emit('', QWebPage.HighlightAllOccurrences) - self._text = text - self._flags = 0 - ignore_case = config.get('general', 'ignore-case') - if ignore_case == 'smart': - if not text.islower(): - self._flags |= QWebPage.FindCaseSensitively - elif not ignore_case: - self._flags |= QWebPage.FindCaseSensitively - if config.get('general', 'wrap-search'): - self._flags |= QWebPage.FindWrapsAroundDocument - if reverse: - self._flags |= QWebPage.FindBackward - # We actually search *twice* - once to highlight everything, then again - # to get a mark so we can navigate. - self.do_search.emit(self._text, self._flags) - self.do_search.emit(self._text, self._flags | - QWebPage.HighlightAllOccurrences) - - @pyqtSlot(str) - def search_rev(self, text): - """Search for a text on a website in reverse direction. - - Args: - text: The text to search for. - """ - self.search(text, reverse=True) - - @cmdutils.register(instance='search-runner', hide=True, scope='window') - def search_next(self, count: {'special': 'count'}=1): - """Continue the search to the ([count]th) next term. - - Args: - count: How many elements to ignore. - """ - if self._text is not None: - for _ in range(count): - self.do_search.emit(self._text, self._flags) - - @cmdutils.register(instance='search-runner', hide=True, scope='window') - def search_prev(self, count: {'special': 'count'}=1): - """Continue the search to the ([count]th) previous term. - - Args: - count: How many elements to ignore. - """ - if self._text is None: - return - # The int() here serves as a QFlags constructor to create a copy of the - # QFlags instance rather as a reference. I don't know why it works this - # way, but it does. - flags = int(self._flags) - if flags & QWebPage.FindBackward: - flags &= ~QWebPage.FindBackward - else: - flags |= QWebPage.FindBackward - for _ in range(count): - self.do_search.emit(self._text, flags) - - class CommandRunner(QObject): """Parse and run qutebrowser commandline commands. diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index b9e456b42..ef10dfd59 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -113,11 +113,6 @@ class MainWindow(QWidget): self._commandrunner = runners.CommandRunner(self.win_id) - log.init.debug("Initializing search...") - search_runner = runners.SearchRunner(self) - objreg.register('search-runner', search_runner, scope='window', - window=self.win_id) - log.init.debug("Initializing modes...") modeman.init(self.win_id, self) @@ -212,7 +207,6 @@ class MainWindow(QWidget): completion_obj = self._get_object('completion') tabs = self._get_object('tabbed-browser') cmd = self._get_object('status-command') - search_runner = self._get_object('search-runner') message_bridge = self._get_object('message-bridge') mode_manager = self._get_object('mode-manager') prompter = self._get_object('prompter') @@ -231,10 +225,7 @@ class MainWindow(QWidget): keyparsers[usertypes.KeyMode.normal].keystring_updated.connect( status.keystring.setText) cmd.got_cmd.connect(self._commandrunner.run_safely) - cmd.got_search.connect(search_runner.search) - cmd.got_search_rev.connect(search_runner.search_rev) cmd.returnPressed.connect(tabs.on_cmd_return_pressed) - search_runner.do_search.connect(tabs.search) tabs.got_cmd.connect(self._commandrunner.run_safely) # config diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py index 48a106793..0803cfd48 100644 --- a/qutebrowser/mainwindow/statusbar/command.py +++ b/qutebrowser/mainwindow/statusbar/command.py @@ -39,10 +39,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): Signals: got_cmd: Emitted when a command is triggered by the user. arg: The command string. - got_search: Emitted when the user started a new search. - arg: The search term. - got_rev_search: Emitted when the user started a new reverse search. - arg: The search term. clear_completion_selection: Emitted before the completion widget is hidden. hide_completion: Emitted when the completion widget should be hidden. @@ -52,8 +48,6 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): """ got_cmd = pyqtSignal(str) - got_search = pyqtSignal(str) - got_search_rev = pyqtSignal(str) clear_completion_selection = pyqtSignal() hide_completion = pyqtSignal() update_completion = pyqtSignal() @@ -167,16 +161,15 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): modes=[usertypes.KeyMode.command], scope='window') def command_accept(self): """Execute the command currently in the commandline.""" - signals = { - ':': self.got_cmd, - '/': self.got_search, - '?': self.got_search_rev, + prefixes = { + ':': '', + '/': 'search ', + '?': 'search -r ', } text = self.text() self.history.append(text) modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept') - if text[0] in signals: - signals[text[0]].emit(text[1:]) + self.got_cmd.emit(prefixes[text[0]] + text[1:]) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 4445f716c..5cef4711e 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -403,36 +403,6 @@ class TabbedBrowser(tabwidget.TabWidget): self._tab_insert_idx_right)) return idx - @pyqtSlot(str, int) - def search(self, text, flags): - """Search for text in the current page. - - Args: - text: The text to search for. - flags: The QWebPage::FindFlags. - """ - log.webview.debug("Searching with text '{}' and flags " - "0x{:04x}.".format(text, int(flags))) - widget = self.currentWidget() - old_scroll_pos = widget.scroll_pos - found = widget.findText(text, flags) - if not found and not flags & QWebPage.HighlightAllOccurrences and text: - message.error(self._win_id, "Text '{}' not found on " - "page!".format(text), immediately=True) - else: - backward = int(flags) & QWebPage.FindBackward - - def check_scroll_pos(): - """Check if the scroll position got smaller and show info.""" - if not backward and widget.scroll_pos < old_scroll_pos: - message.info(self._win_id, "Search hit BOTTOM, continuing " - "at TOP", immediately=True) - elif backward and widget.scroll_pos > old_scroll_pos: - message.info(self._win_id, "Search hit TOP, continuing at " - "BOTTOM", immediately=True) - # We first want QWebPage to refresh. - QTimer.singleShot(0, check_scroll_pos) - @config.change_filter('tabs', 'show-favicons') def update_favicons(self): """Update favicons when config was changed."""