From d576a8d88f7141e2037137a423fc4714c4ef4e41 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 14 Jun 2022 16:01:16 +0200 Subject: Don't render messages at Qt rich text by default Closes #7246 --- qutebrowser/browser/webengine/notification.py | 2 +- qutebrowser/mainwindow/messageview.py | 21 +++++++++++-- qutebrowser/utils/message.py | 21 ++++++++++--- tests/unit/mainwindow/test_messageview.py | 43 +++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/qutebrowser/browser/webengine/notification.py b/qutebrowser/browser/webengine/notification.py index 3571bb24d..2ebe99e33 100644 --- a/qutebrowser/browser/webengine/notification.py +++ b/qutebrowser/browser/webengine/notification.py @@ -519,7 +519,7 @@ class MessagesNotificationAdapter(AbstractNotificationAdapter): markup = self._format_message(qt_notification) new_id = replaces_id if replaces_id is not None else next(self._id_gen) - message.info(markup, replace=f'notifications-{new_id}') + message.info(markup, replace=f'notifications-{new_id}', rich=True) # Faking closing, timing might not be 100% accurate QTimer.singleShot( diff --git a/qutebrowser/mainwindow/messageview.py b/qutebrowser/mainwindow/messageview.py index 482d9f5e7..752cc9db7 100644 --- a/qutebrowser/mainwindow/messageview.py +++ b/qutebrowser/mainwindow/messageview.py @@ -37,6 +37,7 @@ class Message(QLabel): level: usertypes.MessageLevel, text: str, replace: Optional[str], + text_format: Qt.TextFormat, parent: QWidget = None, ) -> None: super().__init__(text, parent) @@ -44,6 +45,7 @@ class Message(QLabel): self.level = level self.setAttribute(Qt.WA_StyledBackground, True) self.setWordWrap(True) + self.setTextFormat(text_format) qss = """ padding-top: 2px; padding-bottom: 2px; @@ -74,15 +76,31 @@ class Message(QLabel): raise ValueError("Invalid level {!r}".format(level)) stylesheet.set_register(self, qss, update=False) + @staticmethod + def _text_format(info: message.MessageInfo) -> Qt.TextFormat: + """The Qt.TextFormat to use based on the given MessageInfo.""" + return Qt.TextFormat.RichText if info.rich else Qt.TextFormat.PlainText + @classmethod def from_info(cls, info: message.MessageInfo, parent: QWidget = None) -> "Message": return cls( level=info.level, text=info.text, replace=info.replace, + text_format=cls._text_format(info), parent=parent, ) + def update_from_info(self, info: message.MessageInfo) -> None: + """Update the text from the given info. + + Both the message this gets called on and the given MessageInfo need to have + the same level. + """ + assert self.level == info.level, (self, info) + self.setTextFormat(self._text_format(info)) + self.setText(info.text) + class MessageView(QWidget): @@ -138,8 +156,7 @@ class MessageView(QWidget): existing = [msg for msg in self._messages if msg.replace == info.replace] if existing: assert len(existing) == 1, existing - assert existing[0].level == info.level, (existing, info) - existing[0].setText(info.text) + existing[0].update_from_info(info) self.update_geometry.emit() return diff --git a/qutebrowser/utils/message.py b/qutebrowser/utils/message.py index 48dcc9945..682382dc8 100644 --- a/qutebrowser/utils/message.py +++ b/qutebrowser/utils/message.py @@ -38,6 +38,7 @@ class MessageInfo: level: usertypes.MessageLevel text: str replace: Optional[str] = None + rich: bool = False def _log_stack(typ: str, stack: str) -> None: @@ -52,13 +53,19 @@ def _log_stack(typ: str, stack: str) -> None: log.message.debug("Stack for {} message:\n{}".format(typ, stack_text)) -def error(message: str, *, stack: str = None, replace: str = None) -> None: +def error( + message: str, *, + stack: str = None, + replace: str = None, + rich: bool = False, +) -> None: """Display an error message. Args: message: The message to show. stack: The stack trace to show (if any). replace: Replace existing messages which are still being shown. + rich: Show message as rich text. """ if stack is None: stack = ''.join(traceback.format_stack()) @@ -71,15 +78,17 @@ def error(message: str, *, stack: str = None, replace: str = None) -> None: level=usertypes.MessageLevel.error, text=message, replace=replace, + rich=rich, ) -def warning(message: str, *, replace: str = None) -> None: +def warning(message: str, *, replace: str = None, rich: bool = False) -> None: """Display a warning message. Args: message: The message to show. replace: Replace existing messages which are still being shown. + rich: Show message as rich text. """ _log_stack('warning', ''.join(traceback.format_stack())) log.message.warning(message) @@ -87,21 +96,24 @@ def warning(message: str, *, replace: str = None) -> None: level=usertypes.MessageLevel.warning, text=message, replace=replace, + rich=rich, ) -def info(message: str, *, replace: str = None) -> None: +def info(message: str, *, replace: str = None, rich: bool = False) -> None: """Display an info message. Args: message: The message to show. replace: Replace existing messages which are still being shown. + rich: Show message as rich text. """ log.message.info(message) global_bridge.show( level=usertypes.MessageLevel.info, text=message, replace=replace, + rich=rich, ) @@ -263,9 +275,10 @@ class GlobalMessageBridge(QObject): level: usertypes.MessageLevel, text: str, replace: str = None, + rich: bool = False, ) -> None: """Show the given message.""" - info = MessageInfo(level=level, text=text, replace=replace) + info = MessageInfo(level=level, text=text, replace=replace, rich=rich) if self._connected: self.show_message.emit(info) else: diff --git a/tests/unit/mainwindow/test_messageview.py b/tests/unit/mainwindow/test_messageview.py index 0f9dc9ea9..32bd638c7 100644 --- a/tests/unit/mainwindow/test_messageview.py +++ b/tests/unit/mainwindow/test_messageview.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . +import contextlib + import pytest from PyQt5.QtCore import Qt @@ -63,6 +65,7 @@ def test_word_wrap(view, qtbot): """A long message should be wrapped.""" with qtbot.wait_signal(view._clear_timer.timeout): view.show_message(message.MessageInfo(usertypes.MessageLevel.info, 'short')) + assert len(view._messages) == 1 height1 = view.sizeHint().height() assert height1 > 0 @@ -73,12 +76,52 @@ def test_word_wrap(view, qtbot): "island deep in the sea.") view.show_message(message.MessageInfo(usertypes.MessageLevel.info, text)) + assert len(view._messages) == 1 height2 = view.sizeHint().height() assert height2 > height1 assert view._messages[0].wordWrap() +@pytest.mark.parametrize("rich, higher, expected_format", [ + (True, True, Qt.TextFormat.RichText), + (False, False, Qt.TextFormat.PlainText), + (None, False, Qt.TextFormat.PlainText), +]) +@pytest.mark.parametrize("replace", ["test", None]) +def test_rich_text(view, qtbot, rich, higher, expected_format, replace): + """Rich text should be rendered appropriately.""" + level = usertypes.MessageLevel.info + text = 'with

markup

' + text2 = 'with

markup

2' + + info1 = message.MessageInfo(level, text, replace=replace) + info2 = message.MessageInfo(level, text2, replace=replace, rich=rich) + + ctx = ( + qtbot.wait_signal(view._clear_timer.timeout) if replace is None + else contextlib.nullcontext() + ) + with ctx: + view.show_message(info1) + assert len(view._messages) == 1 + + height1 = view.sizeHint().height() + assert height1 > 0 + + assert view._messages[0].textFormat() == Qt.TextFormat.PlainText # default + + view.show_message(info2) + height2 = view.sizeHint().height() + assert len(view._messages) == 1 + + assert view._messages[0].textFormat() == expected_format + if higher: + assert height2 > height1 + else: + assert height2 == height1 + + def test_show_message_twice(view): """Show the same message twice -> only one should be shown.""" view.show_message(message.MessageInfo(usertypes.MessageLevel.info, 'test')) -- cgit v1.2.3-54-g00ecf