diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index 98a10ad4893..cbf4dc0de12 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -49,6 +49,8 @@ Changed Fixed ~~~~~ +- Some web pages jumping to the top when the statusbar is hidden or (with + v3.0.x) when a prompt is hidden. - Compatibility with PDF.js v4 - Added an elaborate workaround for a bug in QtWebEngine 6.6.0 causing crashes on Google Mail/Meet/Chat, and a bug in QtWebEngine 6.5.0/.1/.2 causing crashes diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index da951da1b92..e39ac4f9a11 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -483,6 +483,8 @@ def _connect_signals(self): mode_manager = modeman.instance(self.win_id) # misc + self._prompt_container.release_focus.connect( + self.tabbed_browser.on_release_focus) self.tabbed_browser.close_window.connect(self.close) mode_manager.entered.connect(hints.on_mode_entered) @@ -559,6 +561,7 @@ def _connect_signals(self): self._completion.on_clear_completion_selection) self.status.cmd.hide_completion.connect( self._completion.hide) + self.status.cmd.hide_cmd.connect(self.tabbed_browser.on_release_focus) def _set_decoration(self, hidden): """Set the visibility of the window decoration via Qt.""" diff --git a/qutebrowser/mainwindow/prompt.py b/qutebrowser/mainwindow/prompt.py index 84b6cd18fce..92d3cc2ea08 100644 --- a/qutebrowser/mainwindow/prompt.py +++ b/qutebrowser/mainwindow/prompt.py @@ -202,6 +202,7 @@ def _on_mode_left(self, mode): log.prompt.debug("Left mode {}, hiding {}".format( mode, self._question)) self.show_prompts.emit(None) + if self._question.answer is None and not self._question.is_aborted: log.prompt.debug("Cancelling {} because {} was left".format( self._question, mode)) @@ -261,6 +262,7 @@ class PromptContainer(QWidget): } """ update_geometry = pyqtSignal() + release_focus = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) @@ -287,19 +289,26 @@ def _on_show_prompts(self, question): Args: question: A Question object or None. """ - item = self._layout.takeAt(0) - if item is not None: + item = qtutils.add_optional(self._layout.takeAt(0)) + if item is None: + widget = None + else: widget = item.widget() assert widget is not None - log.prompt.debug("Deleting old prompt {}".format(widget)) - widget.hide() + log.prompt.debug(f"Deleting old prompt {widget!r}") widget.deleteLater() if question is None: log.prompt.debug("No prompts left, hiding prompt container.") self._prompt = None + self.release_focus.emit() self.hide() return + elif widget is not None: + # We have more prompts to show, just hide the old one. + # This needs to happen *after* we possibly hid the entire prompt container, + # so that keyboard focus can be reassigned properly via release_focus. + widget.hide() classes = { usertypes.PromptMode.yesno: YesNoPrompt, diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index a2652a69abb..b628a03cc8f 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -377,9 +377,11 @@ def set_text(self, text): @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" - mode_manager = modeman.instance(self._win_id) - if config.val.statusbar.show == 'in-mode': + if config.val.statusbar.show == 'in-mode' and mode != usertypes.KeyMode.command: + # Showing in command mode is handled via _show_cmd_widget() self.show() + + mode_manager = modeman.instance(self._win_id) if mode_manager.parsers[mode].passthrough: self._set_mode_text(mode.name) if mode in [usertypes.KeyMode.insert, @@ -393,9 +395,11 @@ def on_mode_entered(self, mode): @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): """Clear marked mode.""" - mode_manager = modeman.instance(self._win_id) - if config.val.statusbar.show == 'in-mode': + if config.val.statusbar.show == 'in-mode' and old_mode != usertypes.KeyMode.command: + # Hiding in command mode is handled via _hide_cmd_widget() self.hide() + + mode_manager = modeman.instance(self._win_id) if mode_manager.parsers[old_mode].passthrough: if mode_manager.parsers[new_mode].passthrough: self._set_mode_text(new_mode.name) diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 98cb67cb246..28f32c4fddc 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -858,20 +858,34 @@ def on_mode_entered(self, mode): assert isinstance(tab, browsertab.AbstractTab), tab tab.data.input_mode = mode - @pyqtSlot(usertypes.KeyMode) - def on_mode_left(self, mode): - """Give focus to current tab if command mode was left.""" + @pyqtSlot() + def on_release_focus(self): + """Give keyboard focus to the current tab when requested by statusbar/prompt. + + This gets emitted by the statusbar and prompt container before they call .hide() + on themselves, with the idea that we can explicitly reassign the focus, + instead of Qt implicitly calling its QWidget::focusNextPrevChild() method, + finding a new widget to give keyboard focus to. + """ + widget = qtutils.add_optional(self.widget.currentWidget()) + if widget is None: + return + + log.modes.debug(f"Focus released, focusing {widget!r}") + widget.setFocus() + + @pyqtSlot() + def on_mode_left(self): + """Save input mode for restoring if needed.""" + if config.val.tabs.mode_on_change != 'restore': + return + widget = qtutils.add_optional(self.widget.currentWidget()) if widget is None: return - if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES: - log.modes.debug("Left status-input mode, focusing {!r}".format( - widget)) - widget.setFocus() - if config.val.tabs.mode_on_change == 'restore': - assert isinstance(widget, browsertab.AbstractTab), widget - widget.data.input_mode = usertypes.KeyMode.normal + assert isinstance(widget, browsertab.AbstractTab), widget + widget.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) def _on_current_changed(self, idx):