From 55ea24e431d6c0a36455c098b0ae707705a2f916 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 13 May 2014 07:10:50 +0200 Subject: [PATCH] Split statusbar into multiple files --- .pylintrc | 2 +- qutebrowser/widgets/_statusbar.py | 723 ------------------- qutebrowser/widgets/mainwindow.py | 2 +- qutebrowser/widgets/statusbar/__init__.py | 18 + qutebrowser/widgets/statusbar/_command.py | 217 ++++++ qutebrowser/widgets/statusbar/_keystring.py | 27 + qutebrowser/widgets/statusbar/_percentage.py | 47 ++ qutebrowser/widgets/statusbar/_progress.py | 59 ++ qutebrowser/widgets/statusbar/_text.py | 87 +++ qutebrowser/widgets/statusbar/_textbase.py | 83 +++ qutebrowser/widgets/statusbar/_url.py | 153 ++++ qutebrowser/widgets/statusbar/bar.py | 210 ++++++ 12 files changed, 903 insertions(+), 725 deletions(-) delete mode 100644 qutebrowser/widgets/_statusbar.py create mode 100644 qutebrowser/widgets/statusbar/__init__.py create mode 100644 qutebrowser/widgets/statusbar/_command.py create mode 100644 qutebrowser/widgets/statusbar/_keystring.py create mode 100644 qutebrowser/widgets/statusbar/_percentage.py create mode 100644 qutebrowser/widgets/statusbar/_progress.py create mode 100644 qutebrowser/widgets/statusbar/_text.py create mode 100644 qutebrowser/widgets/statusbar/_textbase.py create mode 100644 qutebrowser/widgets/statusbar/_url.py create mode 100644 qutebrowser/widgets/statusbar/bar.py diff --git a/.pylintrc b/.pylintrc index 08c2479a1..720f665a3 100644 --- a/.pylintrc +++ b/.pylintrc @@ -24,7 +24,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$ class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$ inlinevar-rgx=[a-z_][a-z0-9_]*$ -bad-names=foo,bar,baz,tmp +bad-names=foo,tmp [FORMAT] max-line-length=79 diff --git a/qutebrowser/widgets/_statusbar.py b/qutebrowser/widgets/_statusbar.py deleted file mode 100644 index cfb0514a0..000000000 --- a/qutebrowser/widgets/_statusbar.py +++ /dev/null @@ -1,723 +0,0 @@ -# Copyright 2014 Florian Bruhin (The Compiler) -# -# This file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with qutebrowser. If not, see . - -"""Widgets needed in the qutebrowser statusbar.""" - -import logging - -from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt -from PyQt5.QtWidgets import (QWidget, QLineEdit, QProgressBar, QLabel, - QHBoxLayout, QStackedLayout, QSizePolicy) -from PyQt5.QtGui import QPainter, QValidator - -import qutebrowser.keyinput.modeman as modeman -import qutebrowser.commands.utils as cmdutils -from qutebrowser.keyinput.modeparsers import STARTCHARS -from qutebrowser.config.style import set_register_stylesheet, get_stylesheet -from qutebrowser.utils.url import urlstring -from qutebrowser.commands.managers import split_cmdline -from qutebrowser.models.cmdhistory import (History, HistoryEmptyError, - HistoryEndReachedError) - - -class StatusBar(QWidget): - - """The statusbar at the bottom of the mainwindow. - - Class attributes: - STYLESHEET: The stylesheet template. - - Attributes: - cmd: The Command widget in the statusbar. - txt: The Text widget in the statusbar. - keystring: The KeyString widget in the statusbar. - percentage: The Percentage widget in the statusbar. - url: The Url widget in the statusbar. - prog: The Progress widget in the statusbar. - _hbox: The main QHBoxLayout. - _stack: The QStackedLayout with cmd/txt widgets. - - Class attributes: - _error: If there currently is an error, accessed through the error - property. - - For some reason we need to have this as class attribute so - pyqtProperty works correctly. - - Signals: - resized: Emitted when the statusbar has resized, so the completion - widget can adjust its size to it. - arg: The new size. - moved: Emitted when the statusbar has moved, so the completion widget - can move the the right position. - arg: The new position. - """ - - resized = pyqtSignal('QRect') - moved = pyqtSignal('QPoint') - _error = False - - STYLESHEET = """ - QWidget#StatusBar[error="false"] {{ - {color[statusbar.bg]} - }} - - QWidget#StatusBar[error="true"] {{ - {color[statusbar.bg.error]} - }} - - QWidget {{ - {color[statusbar.fg]} - {font[statusbar]} - }} - """ - - def __init__(self, parent=None): - super().__init__(parent) - self.setObjectName(self.__class__.__name__) - self.setAttribute(Qt.WA_StyledBackground) - set_register_stylesheet(self) - - self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) - - self._option = None - - self._hbox = QHBoxLayout(self) - self._hbox.setContentsMargins(0, 0, 0, 0) - self._hbox.setSpacing(5) - - self._stack = QStackedLayout() - self._stack.setContentsMargins(0, 0, 0, 0) - - self.cmd = _Command(self) - self._stack.addWidget(self.cmd) - - self.txt = _Text(self) - self._stack.addWidget(self.txt) - - self.cmd.show_cmd.connect(self._show_cmd_widget) - self.cmd.hide_cmd.connect(self._hide_cmd_widget) - self._hide_cmd_widget() - - self._hbox.addLayout(self._stack) - - self.keystring = _KeyString(self) - self._hbox.addWidget(self.keystring) - - self.url = _Url(self) - self._hbox.addWidget(self.url) - - self.percentage = _Percentage(self) - self._hbox.addWidget(self.percentage) - - self.prog = _Progress(self) - self._hbox.addWidget(self.prog) - - @pyqtProperty(bool) - def error(self): - """Getter for self.error, so it can be used as Qt property.""" - # pylint: disable=method-hidden - return self._error - - @error.setter - def error(self, val): - """Setter for self.error, so it can be used as Qt property. - - Re-set the stylesheet after setting the value, so everything gets - updated by Qt properly. - """ - self._error = val - self.setStyleSheet(get_stylesheet(self.STYLESHEET)) - - def _show_cmd_widget(self): - """Show command widget instead of temporary text.""" - self._stack.setCurrentWidget(self.cmd) - self.clear_error() - - def _hide_cmd_widget(self): - """Show temporary text instead of command widget.""" - self._stack.setCurrentWidget(self.txt) - - @pyqtSlot(str) - def disp_error(self, text): - """Display an error in the statusbar.""" - self.error = True - self.txt.errortext = text - - @pyqtSlot() - def clear_error(self): - """Clear a displayed error from the status bar.""" - self.error = False - self.txt.errortext = '' - - @pyqtSlot('QKeyEvent') - def on_key_pressed(self, e): - """Hide temporary error message if a key was pressed. - - Args: - e: The original QKeyEvent. - """ - if e.key() in [Qt.Key_Control, Qt.Key_Alt, Qt.Key_Shift, Qt.Key_Meta]: - # Only modifier pressed, don't hide yet. - return - self.txt.set_temptext('') - self.clear_error() - - @pyqtSlot(str) - def on_mode_entered(self, mode): - """Mark certain modes in the commandline.""" - if mode in modeman.instance().passthrough: - self.txt.normaltext = "-- {} MODE --".format(mode.upper()) - - @pyqtSlot(str) - def on_mode_left(self, mode): - """Clear marked mode.""" - if mode in modeman.instance().passthrough: - self.txt.normaltext = "" - - def resizeEvent(self, e): - """Extend resizeEvent of QWidget to emit a resized signal afterwards. - - Args: - e: The QResizeEvent. - - Emit: - resized: Always emitted. - """ - super().resizeEvent(e) - self.resized.emit(self.geometry()) - - def moveEvent(self, e): - """Extend moveEvent of QWidget to emit a moved signal afterwards. - - Args: - e: The QMoveEvent. - - Emit: - moved: Always emitted. - """ - super().moveEvent(e) - self.moved.emit(e.pos()) - - -class _Command(QLineEdit): - - """The commandline part of the statusbar. - - Attributes: - history: The command history object. - _statusbar: The statusbar (parent) QWidget. - _validator: The current command validator. - - 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. - show_cmd: Emitted when command input should be shown. - hide_cmd: Emitted when command input can be hidden. - """ - - got_cmd = pyqtSignal(str) - got_search = pyqtSignal(str) - got_search_rev = pyqtSignal(str) - clear_completion_selection = pyqtSignal() - hide_completion = pyqtSignal() - show_cmd = pyqtSignal() - hide_cmd = pyqtSignal() - - # FIXME won't the tab key switch to the next widget? - # See http://www.saltycrane.com/blog/2008/01/how-to-capture-tab-key-press-event-with/ - # for a possible fix. - - def __init__(self, statusbar): - super().__init__(statusbar) - self._statusbar = statusbar - self.setStyleSheet(""" - QLineEdit { - border: 0px; - padding-left: 1px; - background-color: transparent; - } - """) - self.history = History() - self._validator = _CommandValidator(self) - self.setValidator(self._validator) - self.textEdited.connect(self.history.stop) - self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) - - @pyqtSlot(str) - def set_cmd_text(self, text): - """Preset the statusbar to some text. - - Args: - text: The text to set (string). - - Emit: - textEdited: Emitted if the text changed. - """ - old_text = self.text() - self.setText(text) - if old_text != text: - # We want the completion to pop out here. - self.textEdited.emit(text) - self.setFocus() - self.show_cmd.emit() - - @pyqtSlot(str) - def on_change_completed_part(self, newtext): - """Change the part we're currently completing in the commandline. - - Args: - text: The text to set (string). - """ - # FIXME we should consider the cursor position. - text = self.text() - if text[0] in STARTCHARS: - prefix = text[0] - text = text[1:] - else: - prefix = '' - parts = split_cmdline(text) - logging.debug("Old text: '{}' - parts: '{}', changing to '{}".format( - text, parts, newtext)) - parts[-1] = newtext - self.setText(prefix + ' '.join(parts)) - self.setFocus() - self.show_cmd.emit() - - @cmdutils.register(instance='mainwindow.status.cmd', hide=True, - modes=['command']) - def command_history_prev(self): - """Handle Up presses (go back in history).""" - try: - if not self.history.browsing: - item = self.history.start(self.text().strip()) - else: - item = self.history.previtem() - except (HistoryEmptyError, HistoryEndReachedError): - return - if item: - self.set_cmd_text(item) - - @cmdutils.register(instance='mainwindow.status.cmd', hide=True, - modes=['command']) - def command_history_next(self): - """Handle Down presses (go forward in history).""" - if not self.history.browsing: - return - try: - item = self.history.nextitem() - except HistoryEndReachedError: - return - if item: - self.set_cmd_text(item) - - @cmdutils.register(instance='mainwindow.status.cmd', hide=True, - modes=['command']) - def command_accept(self): - """Handle the command in the status bar. - - Emit: - got_cmd: If a new cmd was entered. - got_search: If a new search was entered. - got_search_rev: If a new reverse search was entered. - """ - signals = { - ':': self.got_cmd, - '/': self.got_search, - '?': self.got_search_rev, - } - text = self.text() - self.history.append(text) - modeman.leave('command', 'cmd accept') - if text[0] in signals: - signals[text[0]].emit(text.lstrip(text[0])) - - def on_mode_left(self, mode): - """Clear up when ommand mode was left. - - - Clear the statusbar text if it's explicitely unfocused. - - Clear completion selection - - Hide completion - - Args: - mode: The mode which was left. - - Emit: - clear_completion_selection: Always emitted. - hide_completion: Always emitted so the completion is hidden. - """ - if mode == "command": - self.setText('') - self.history.stop() - self.hide_cmd.emit() - self.clear_completion_selection.emit() - self.hide_completion.emit() - - def focusInEvent(self, e): - """Extend focusInEvent to enter command mode.""" - modeman.enter('command', 'cmd focus') - super().focusInEvent(e) - - -class _CommandValidator(QValidator): - - """Validator to prevent the : from getting deleted.""" - - def validate(self, string, pos): - """Override QValidator::validate. - - Args: - string: The string to validate. - pos: The current curser position. - - Return: - A tuple (status, string, pos) as a QValidator should. - """ - if any(string.startswith(c) for c in STARTCHARS): - return (QValidator.Acceptable, string, pos) - else: - return (QValidator.Invalid, string, pos) - - -class _Progress(QProgressBar): - - """The progress bar part of the status bar. - - Class attributes: - STYLESHEET: The stylesheet template. - """ - - # FIXME for some reason, margin-left is not shown - STYLESHEET = """ - QProgressBar {{ - border-radius: 0px; - border: 2px solid transparent; - margin-left: 1px; - background-color: transparent; - }} - - QProgressBar::chunk {{ - {color[statusbar.progress.bg]} - }} - """ - - def __init__(self, parent): - super().__init__(parent) - set_register_stylesheet(self) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored) - self.setTextVisible(False) - self.hide() - - @pyqtSlot() - def on_load_started(self): - """Clear old error and show progress, used as slot to loadStarted.""" - self.setValue(0) - self.show() - - -class TextBase(QLabel): - - """A text in the statusbar. - - Unlike QLabel, the text will get elided. - - Eliding is loosly based on - http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html - - Attributes: - _elidemode: Where to elide the text. - _elided_text: The current elided text. - """ - - def __init__(self, bar, elidemode=Qt.ElideRight): - super().__init__(bar) - self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) - self._elidemode = elidemode - self._elided_text = '' - - def _update_elided_text(self, width): - """Update the elided text when necessary. - - Args: - width: The maximal width the text should take. - """ - self._elided_text = self.fontMetrics().elidedText( - self.text(), self._elidemode, width, Qt.TextShowMnemonic) - - def setText(self, txt): - """Extend QLabel::setText. - - This update the elided text after setting the text, and also works - around a weird QLabel redrawing bug where it doesn't redraw correctly - when the text is empty -- we explicitely need to call repaint() to - resolve this. See http://stackoverflow.com/q/21890462/2085149 - - Args: - txt: The text to set (string). - """ - super().setText(txt) - self._update_elided_text(self.geometry().width()) - if not txt: - self.repaint() - - def resizeEvent(self, e): - """Extend QLabel::resizeEvent to update the elided text afterwards.""" - super().resizeEvent(e) - self._update_elided_text(e.size().width()) - - def paintEvent(self, e): - """Override QLabel::paintEvent to draw elided text.""" - if self._elidemode == Qt.ElideNone: - super().paintEvent(e) - else: - painter = QPainter(self) - painter.drawText(0, 0, self.geometry().width(), - self.geometry().height(), self.alignment(), - self._elided_text) - - -class _Text(TextBase): - - """Text displayed in the statusbar. - - Attributes: - normaltext: The "permanent" text. Never automatically cleared. - temptext: The temporary text. Cleared on a keystroke. - errortext: The error text. Cleared on a keystroke. - _initializing: True if we're currently in __init__ and no text should - be updated yet. - - The errortext has the highest priority, i.e. it will always be shown - when it is set. The temptext is shown when there is no error, and the - (permanent) text is shown when there is neither a temporary text nor an - error. - """ - - def __init__(self, parent=None): - super().__init__(parent) - self._initializing = True - self.normaltext = '' - self.temptext = '' - self.errortext = '' - self._initializing = False - - def __setattr__(self, name, val): - """Overwrite __setattr__ to call _update_text when needed.""" - super().__setattr__(name, val) - if not name.startswith('_') and not self._initializing: - self._update_text() - - def _update_text(self): - """Update QLabel text when needed. - - Called from __setattr__ if a text property changed. - """ - for text in [self.errortext, self.temptext, self.normaltext]: - if text: - self.setText(text) - break - else: - self.setText('') - - @pyqtSlot(str) - def set_normaltext(self, val): - """Setter for normaltext, to be used as Qt slot.""" - self.normaltext = val - - @pyqtSlot(str) - def on_statusbar_message(self, val): - """Called when javascript tries to set a statusbar message. - - For some reason, this is emitted a lot with an empty string during page - load, so we currently ignore these and thus don't support clearing the - message, which is a bit unfortunate... - """ - if val: - self.temptext = val - - @pyqtSlot(str) - def set_temptext(self, val): - """Setter for temptext, to be used as Qt slot.""" - self.temptext = val - - -class _KeyString(TextBase): - - """Keychain string displayed in the statusbar.""" - - pass - - -class _Percentage(TextBase): - - """Reading percentage displayed in the statusbar.""" - - def __init__(self, parent=None): - """Constructor. Set percentage to 0%.""" - super().__init__(parent) - self.set_perc(0, 0) - - @pyqtSlot(int, int) - def set_perc(self, _, y): - """Setter to be used as a Qt slot. - - Args: - _: The x percentage (int), currently ignored. - y: The y percentage (int) - """ - if y == 0: - self.setText('[top]') - elif y == 100: - self.setText('[bot]') - else: - self.setText('[{:2}%]'.format(y)) - - -class _Url(TextBase): - - """URL displayed in the statusbar. - - Class attributes: - STYLESHEET: The stylesheet template. - - Attributes: - _old_url: The URL displayed before the hover URL. - _old_urltype: The type of the URL displayed before the hover URL. - _ssl_errors: Whether SSL errors occured while loading. - - Class attributes: - _urltype: The current URL type. One of normal/ok/error/warn/hover. - Accessed via the urltype property. - - For some reason we need to have this as class attribute so - pyqtProperty works correctly. - """ - - _urltype = None - - STYLESHEET = """ - QLabel#_Url[urltype="normal"] {{ - {color[statusbar.url.fg]} - }} - - QLabel#_Url[urltype="success"] {{ - {color[statusbar.url.fg.success]} - }} - - QLabel#_Url[urltype="error"] {{ - {color[statusbar.url.fg.error]} - }} - - QLabel#_Url[urltype="warn"] {{ - {color[statusbar.url.fg.warn]} - }} - - QLabel#_Url[urltype="hover"] {{ - {color[statusbar.url.fg.hover]} - }} - """ - - def __init__(self, bar, elidemode=Qt.ElideMiddle): - """Override TextBase::__init__ to elide in the middle by default. - - Args: - bar: The statusbar (parent) object. - elidemode: How to elide the text. - """ - super().__init__(bar, elidemode) - self.setObjectName(self.__class__.__name__) - set_register_stylesheet(self) - self._old_urltype = None - self._old_url = None - self._ssl_errors = False - - @pyqtProperty(str) - def urltype(self): - """Getter for self.urltype, so it can be used as Qt property.""" - # pylint: disable=method-hidden - return self._urltype - - @urltype.setter - def urltype(self, val): - """Setter for self.urltype, so it can be used as Qt property.""" - self._urltype = val - self.setStyleSheet(get_stylesheet(self.STYLESHEET)) - - @pyqtSlot() - def on_loading_started(self): - """Slot to clear SSL errors when loading started.""" - self._ssl_errors = False - - @pyqtSlot('QNetworkReply*', 'QList') - def on_ssl_errors(self, _reply, _errors): - self._ssl_errors = True - - @pyqtSlot(bool) - def on_loading_finished(self, ok): - """Slot for cur_loading_finished. Colors the URL according to ok. - - Args: - ok: Whether loading finished successfully (True) or not (False). - """ - if ok and not self._ssl_errors: - self.urltype = 'success' - elif ok: - self.urltype = 'warn' - else: - self.urltype = 'error' - - @pyqtSlot(str) - def set_url(self, s): - """Setter to be used as a Qt slot. - - Args: - s: The URL to set. - """ - self.setText(urlstring(s)) - self.urltype = 'normal' - - @pyqtSlot(str, str, str) - def set_hover_url(self, link, _title, _text): - """Setter to be used as a Qt slot. - - Saves old shown URL in self._old_url and restores it later if a link is - "un-hovered" when it gets called with empty parameters. - - Args: - link: The link which was hovered (string) - _title: The title of the hovered link (string) - _text: The text of the hovered link (string) - """ - if link: - if self._old_url is None: - self._old_url = self.text() - if self._old_urltype is None: - self._old_urltype = self._urltype - self.urltype = 'hover' - self.setText(link) - else: - self.setText(self._old_url) - self.urltype = self._old_urltype - self._old_url = None - self._old_urltype = None diff --git a/qutebrowser/widgets/mainwindow.py b/qutebrowser/widgets/mainwindow.py index 97bd8ba1d..892d7df0a 100644 --- a/qutebrowser/widgets/mainwindow.py +++ b/qutebrowser/widgets/mainwindow.py @@ -24,7 +24,7 @@ from PyQt5.QtCore import pyqtSlot, QRect, QPoint, QCoreApplication from PyQt5.QtWidgets import QWidget, QVBoxLayout from PyQt5.QtWebKitWidgets import QWebInspector -from qutebrowser.widgets._statusbar import StatusBar +from qutebrowser.widgets.statusbar.bar import StatusBar from qutebrowser.widgets._tabbedbrowser import TabbedBrowser from qutebrowser.widgets._completion import CompletionView import qutebrowser.commands.utils as cmdutils diff --git a/qutebrowser/widgets/statusbar/__init__.py b/qutebrowser/widgets/statusbar/__init__.py new file mode 100644 index 000000000..d89e0c70f --- /dev/null +++ b/qutebrowser/widgets/statusbar/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""The Qt widgets needed by qutebrowser.""" diff --git a/qutebrowser/widgets/statusbar/_command.py b/qutebrowser/widgets/statusbar/_command.py new file mode 100644 index 000000000..48ddfa440 --- /dev/null +++ b/qutebrowser/widgets/statusbar/_command.py @@ -0,0 +1,217 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""The commandline in the statusbar.""" + +import logging + +from PyQt5.QtCore import pyqtSignal, pyqtSlot +from PyQt5.QtWidgets import QLineEdit, QSizePolicy +from PyQt5.QtGui import QValidator + +import qutebrowser.keyinput.modeman as modeman +import qutebrowser.commands.utils as cmdutils +from qutebrowser.commands.managers import split_cmdline +from qutebrowser.keyinput.modeparsers import STARTCHARS +from qutebrowser.models.cmdhistory import (History, HistoryEmptyError, + HistoryEndReachedError) + + +class Command(QLineEdit): + + """The commandline part of the statusbar. + + Attributes: + history: The command history object. + _statusbar: The statusbar (parent) QWidget. + _validator: The current command validator. + + 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. + show_cmd: Emitted when command input should be shown. + hide_cmd: Emitted when command input can be hidden. + """ + + got_cmd = pyqtSignal(str) + got_search = pyqtSignal(str) + got_search_rev = pyqtSignal(str) + clear_completion_selection = pyqtSignal() + hide_completion = pyqtSignal() + show_cmd = pyqtSignal() + hide_cmd = pyqtSignal() + + # FIXME won't the tab key switch to the next widget? + # See http://www.saltycrane.com/blog/2008/01/how-to-capture-tab-key-press-event-with/ + # for a possible fix. + + def __init__(self, statusbar): + super().__init__(statusbar) + self._statusbar = statusbar + self.setStyleSheet(""" + QLineEdit { + border: 0px; + padding-left: 1px; + background-color: transparent; + } + """) + self.history = History() + self._validator = _CommandValidator(self) + self.setValidator(self._validator) + self.textEdited.connect(self.history.stop) + self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) + + @pyqtSlot(str) + def set_cmd_text(self, text): + """Preset the statusbar to some text. + + Args: + text: The text to set (string). + + Emit: + textEdited: Emitted if the text changed. + """ + old_text = self.text() + self.setText(text) + if old_text != text: + # We want the completion to pop out here. + self.textEdited.emit(text) + self.setFocus() + self.show_cmd.emit() + + @pyqtSlot(str) + def on_change_completed_part(self, newtext): + """Change the part we're currently completing in the commandline. + + Args: + text: The text to set (string). + """ + # FIXME we should consider the cursor position. + text = self.text() + if text[0] in STARTCHARS: + prefix = text[0] + text = text[1:] + else: + prefix = '' + parts = split_cmdline(text) + logging.debug("Old text: '{}' - parts: '{}', changing to '{}".format( + text, parts, newtext)) + parts[-1] = newtext + self.setText(prefix + ' '.join(parts)) + self.setFocus() + self.show_cmd.emit() + + @cmdutils.register(instance='mainwindow.status.cmd', hide=True, + modes=['command']) + def command_history_prev(self): + """Handle Up presses (go back in history).""" + try: + if not self.history.browsing: + item = self.history.start(self.text().strip()) + else: + item = self.history.previtem() + except (HistoryEmptyError, HistoryEndReachedError): + return + if item: + self.set_cmd_text(item) + + @cmdutils.register(instance='mainwindow.status.cmd', hide=True, + modes=['command']) + def command_history_next(self): + """Handle Down presses (go forward in history).""" + if not self.history.browsing: + return + try: + item = self.history.nextitem() + except HistoryEndReachedError: + return + if item: + self.set_cmd_text(item) + + @cmdutils.register(instance='mainwindow.status.cmd', hide=True, + modes=['command']) + def command_accept(self): + """Handle the command in the status bar. + + Emit: + got_cmd: If a new cmd was entered. + got_search: If a new search was entered. + got_search_rev: If a new reverse search was entered. + """ + signals = { + ':': self.got_cmd, + '/': self.got_search, + '?': self.got_search_rev, + } + text = self.text() + self.history.append(text) + modeman.leave('command', 'cmd accept') + if text[0] in signals: + signals[text[0]].emit(text.lstrip(text[0])) + + def on_mode_left(self, mode): + """Clear up when ommand mode was left. + + - Clear the statusbar text if it's explicitely unfocused. + - Clear completion selection + - Hide completion + + Args: + mode: The mode which was left. + + Emit: + clear_completion_selection: Always emitted. + hide_completion: Always emitted so the completion is hidden. + """ + if mode == "command": + self.setText('') + self.history.stop() + self.hide_cmd.emit() + self.clear_completion_selection.emit() + self.hide_completion.emit() + + def focusInEvent(self, e): + """Extend focusInEvent to enter command mode.""" + modeman.enter('command', 'cmd focus') + super().focusInEvent(e) + + +class _CommandValidator(QValidator): + + """Validator to prevent the : from getting deleted.""" + + def validate(self, string, pos): + """Override QValidator::validate. + + Args: + string: The string to validate. + pos: The current curser position. + + Return: + A tuple (status, string, pos) as a QValidator should. + """ + if any(string.startswith(c) for c in STARTCHARS): + return (QValidator.Acceptable, string, pos) + else: + return (QValidator.Invalid, string, pos) diff --git a/qutebrowser/widgets/statusbar/_keystring.py b/qutebrowser/widgets/statusbar/_keystring.py new file mode 100644 index 000000000..a30aec575 --- /dev/null +++ b/qutebrowser/widgets/statusbar/_keystring.py @@ -0,0 +1,27 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Keychain string displayed in the statusbar.""" + +from qutebrowser.widgets.statusbar._textbase import TextBase + + +class KeyString(TextBase): + + """Keychain string displayed in the statusbar.""" + + pass diff --git a/qutebrowser/widgets/statusbar/_percentage.py b/qutebrowser/widgets/statusbar/_percentage.py new file mode 100644 index 000000000..0beb17c52 --- /dev/null +++ b/qutebrowser/widgets/statusbar/_percentage.py @@ -0,0 +1,47 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Scroll percentage displayed in the statusbar.""" + +from PyQt5.QtCore import pyqtSlot + +from qutebrowser.widgets.statusbar._textbase import TextBase + + +class Percentage(TextBase): + + """Reading percentage displayed in the statusbar.""" + + def __init__(self, parent=None): + """Constructor. Set percentage to 0%.""" + super().__init__(parent) + self.set_perc(0, 0) + + @pyqtSlot(int, int) + def set_perc(self, _, y): + """Setter to be used as a Qt slot. + + Args: + _: The x percentage (int), currently ignored. + y: The y percentage (int) + """ + if y == 0: + self.setText('[top]') + elif y == 100: + self.setText('[bot]') + else: + self.setText('[{:2}%]'.format(y)) diff --git a/qutebrowser/widgets/statusbar/_progress.py b/qutebrowser/widgets/statusbar/_progress.py new file mode 100644 index 000000000..e13475960 --- /dev/null +++ b/qutebrowser/widgets/statusbar/_progress.py @@ -0,0 +1,59 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""The progress bar in the statusbar.""" + +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QProgressBar, QSizePolicy + +from qutebrowser.config.style import set_register_stylesheet + + +class Progress(QProgressBar): + + """The progress bar part of the status bar. + + Class attributes: + STYLESHEET: The stylesheet template. + """ + + # FIXME for some reason, margin-left is not shown + STYLESHEET = """ + QProgressBar {{ + border-radius: 0px; + border: 2px solid transparent; + margin-left: 1px; + background-color: transparent; + }} + + QProgressBar::chunk {{ + {color[statusbar.progress.bg]} + }} + """ + + def __init__(self, parent): + super().__init__(parent) + set_register_stylesheet(self) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Ignored) + self.setTextVisible(False) + self.hide() + + @pyqtSlot() + def on_load_started(self): + """Clear old error and show progress, used as slot to loadStarted.""" + self.setValue(0) + self.show() diff --git a/qutebrowser/widgets/statusbar/_text.py b/qutebrowser/widgets/statusbar/_text.py new file mode 100644 index 000000000..2d8dd83cd --- /dev/null +++ b/qutebrowser/widgets/statusbar/_text.py @@ -0,0 +1,87 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Text displayed in the statusbar.""" + +from PyQt5.QtCore import pyqtSlot + +from qutebrowser.widgets.statusbar._textbase import TextBase + + +class Text(TextBase): + + """Text displayed in the statusbar. + + Attributes: + normaltext: The "permanent" text. Never automatically cleared. + temptext: The temporary text. Cleared on a keystroke. + errortext: The error text. Cleared on a keystroke. + _initializing: True if we're currently in __init__ and no text should + be updated yet. + + The errortext has the highest priority, i.e. it will always be shown + when it is set. The temptext is shown when there is no error, and the + (permanent) text is shown when there is neither a temporary text nor an + error. + """ + + def __init__(self, parent=None): + super().__init__(parent) + self._initializing = True + self.normaltext = '' + self.temptext = '' + self.errortext = '' + self._initializing = False + + def __setattr__(self, name, val): + """Overwrite __setattr__ to call _update_text when needed.""" + super().__setattr__(name, val) + if not name.startswith('_') and not self._initializing: + self._update_text() + + def _update_text(self): + """Update QLabel text when needed. + + Called from __setattr__ if a text property changed. + """ + for text in [self.errortext, self.temptext, self.normaltext]: + if text: + self.setText(text) + break + else: + self.setText('') + + @pyqtSlot(str) + def set_normaltext(self, val): + """Setter for normaltext, to be used as Qt slot.""" + self.normaltext = val + + @pyqtSlot(str) + def on_statusbar_message(self, val): + """Called when javascript tries to set a statusbar message. + + For some reason, this is emitted a lot with an empty string during page + load, so we currently ignore these and thus don't support clearing the + message, which is a bit unfortunate... + """ + if val: + self.temptext = val + + @pyqtSlot(str) + def set_temptext(self, val): + """Setter for temptext, to be used as Qt slot.""" + self.temptext = val diff --git a/qutebrowser/widgets/statusbar/_textbase.py b/qutebrowser/widgets/statusbar/_textbase.py new file mode 100644 index 000000000..6e22f6a6b --- /dev/null +++ b/qutebrowser/widgets/statusbar/_textbase.py @@ -0,0 +1,83 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""Base text widgets for statusbar.""" + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QLabel, QSizePolicy +from PyQt5.QtGui import QPainter + + +class TextBase(QLabel): + + """A text in the statusbar. + + Unlike QLabel, the text will get elided. + + Eliding is loosly based on + http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html + + Attributes: + _elidemode: Where to elide the text. + _elided_text: The current elided text. + """ + + def __init__(self, bar, elidemode=Qt.ElideRight): + super().__init__(bar) + self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) + self._elidemode = elidemode + self._elided_text = '' + + def _update_elided_text(self, width): + """Update the elided text when necessary. + + Args: + width: The maximal width the text should take. + """ + self._elided_text = self.fontMetrics().elidedText( + self.text(), self._elidemode, width, Qt.TextShowMnemonic) + + def setText(self, txt): + """Extend QLabel::setText. + + This update the elided text after setting the text, and also works + around a weird QLabel redrawing bug where it doesn't redraw correctly + when the text is empty -- we explicitely need to call repaint() to + resolve this. See http://stackoverflow.com/q/21890462/2085149 + + Args: + txt: The text to set (string). + """ + super().setText(txt) + self._update_elided_text(self.geometry().width()) + if not txt: + self.repaint() + + def resizeEvent(self, e): + """Extend QLabel::resizeEvent to update the elided text afterwards.""" + super().resizeEvent(e) + self._update_elided_text(e.size().width()) + + def paintEvent(self, e): + """Override QLabel::paintEvent to draw elided text.""" + if self._elidemode == Qt.ElideNone: + super().paintEvent(e) + else: + painter = QPainter(self) + painter.drawText(0, 0, self.geometry().width(), + self.geometry().height(), self.alignment(), + self._elided_text) diff --git a/qutebrowser/widgets/statusbar/_url.py b/qutebrowser/widgets/statusbar/_url.py new file mode 100644 index 000000000..1f107a7e8 --- /dev/null +++ b/qutebrowser/widgets/statusbar/_url.py @@ -0,0 +1,153 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""URL displayed in the statusbar.""" + +from PyQt5.QtCore import pyqtSlot, pyqtProperty, Qt + +from qutebrowser.widgets.statusbar._textbase import TextBase +from qutebrowser.config.style import set_register_stylesheet, get_stylesheet +from qutebrowser.utils.url import urlstring + + +class Url(TextBase): + + """URL displayed in the statusbar. + + Class attributes: + STYLESHEET: The stylesheet template. + + Attributes: + _old_url: The URL displayed before the hover URL. + _old_urltype: The type of the URL displayed before the hover URL. + _ssl_errors: Whether SSL errors occured while loading. + + Class attributes: + _urltype: The current URL type. One of normal/ok/error/warn/hover. + Accessed via the urltype property. + + For some reason we need to have this as class attribute so + pyqtProperty works correctly. + """ + + _urltype = None + + STYLESHEET = """ + QLabel#_Url[urltype="normal"] {{ + {color[statusbar.url.fg]} + }} + + QLabel#_Url[urltype="success"] {{ + {color[statusbar.url.fg.success]} + }} + + QLabel#_Url[urltype="error"] {{ + {color[statusbar.url.fg.error]} + }} + + QLabel#_Url[urltype="warn"] {{ + {color[statusbar.url.fg.warn]} + }} + + QLabel#_Url[urltype="hover"] {{ + {color[statusbar.url.fg.hover]} + }} + """ + + def __init__(self, bar, elidemode=Qt.ElideMiddle): + """Override TextBase::__init__ to elide in the middle by default. + + Args: + bar: The statusbar (parent) object. + elidemode: How to elide the text. + """ + super().__init__(bar, elidemode) + self.setObjectName(self.__class__.__name__) + set_register_stylesheet(self) + self._old_urltype = None + self._old_url = None + self._ssl_errors = False + + @pyqtProperty(str) + def urltype(self): + """Getter for self.urltype, so it can be used as Qt property.""" + # pylint: disable=method-hidden + return self._urltype + + @urltype.setter + def urltype(self, val): + """Setter for self.urltype, so it can be used as Qt property.""" + self._urltype = val + self.setStyleSheet(get_stylesheet(self.STYLESHEET)) + + @pyqtSlot() + def on_loading_started(self): + """Slot to clear SSL errors when loading started.""" + self._ssl_errors = False + + @pyqtSlot('QNetworkReply*', 'QList') + def on_ssl_errors(self, _reply, _errors): + self._ssl_errors = True + + @pyqtSlot(bool) + def on_loading_finished(self, ok): + """Slot for cur_loading_finished. Colors the URL according to ok. + + Args: + ok: Whether loading finished successfully (True) or not (False). + """ + if ok and not self._ssl_errors: + self.urltype = 'success' + elif ok: + self.urltype = 'warn' + else: + self.urltype = 'error' + + @pyqtSlot(str) + def set_url(self, s): + """Setter to be used as a Qt slot. + + Args: + s: The URL to set. + """ + self.setText(urlstring(s)) + self.urltype = 'normal' + + @pyqtSlot(str, str, str) + def set_hover_url(self, link, _title, _text): + """Setter to be used as a Qt slot. + + Saves old shown URL in self._old_url and restores it later if a link is + "un-hovered" when it gets called with empty parameters. + + Args: + link: The link which was hovered (string) + _title: The title of the hovered link (string) + _text: The text of the hovered link (string) + """ + if link: + if self._old_url is None: + self._old_url = self.text() + if self._old_urltype is None: + self._old_urltype = self._urltype + self.urltype = 'hover' + self.setText(link) + else: + self.setText(self._old_url) + self.urltype = self._old_urltype + self._old_url = None + self._old_urltype = None diff --git a/qutebrowser/widgets/statusbar/bar.py b/qutebrowser/widgets/statusbar/bar.py new file mode 100644 index 000000000..352ba1615 --- /dev/null +++ b/qutebrowser/widgets/statusbar/bar.py @@ -0,0 +1,210 @@ +# Copyright 2014 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +"""The main statusbar widget.""" + +from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt +from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy + +import qutebrowser.keyinput.modeman as modeman +from qutebrowser.widgets.statusbar._command import Command +from qutebrowser.widgets.statusbar._progress import Progress +from qutebrowser.widgets.statusbar._text import Text +from qutebrowser.widgets.statusbar._keystring import KeyString +from qutebrowser.widgets.statusbar._percentage import Percentage +from qutebrowser.widgets.statusbar._url import Url +from qutebrowser.config.style import set_register_stylesheet, get_stylesheet + + +class StatusBar(QWidget): + + """The statusbar at the bottom of the mainwindow. + + Class attributes: + STYLESHEET: The stylesheet template. + + Attributes: + cmd: The Command widget in the statusbar. + txt: The Text widget in the statusbar. + keystring: The KeyString widget in the statusbar. + percentage: The Percentage widget in the statusbar. + url: The Url widget in the statusbar. + prog: The Progress widget in the statusbar. + _hbox: The main QHBoxLayout. + _stack: The QStackedLayout with cmd/txt widgets. + + Class attributes: + _error: If there currently is an error, accessed through the error + property. + + For some reason we need to have this as class attribute so + pyqtProperty works correctly. + + Signals: + resized: Emitted when the statusbar has resized, so the completion + widget can adjust its size to it. + arg: The new size. + moved: Emitted when the statusbar has moved, so the completion widget + can move the the right position. + arg: The new position. + """ + + resized = pyqtSignal('QRect') + moved = pyqtSignal('QPoint') + _error = False + + STYLESHEET = """ + QWidget#StatusBar[error="false"] {{ + {color[statusbar.bg]} + }} + + QWidget#StatusBar[error="true"] {{ + {color[statusbar.bg.error]} + }} + + QWidget {{ + {color[statusbar.fg]} + {font[statusbar]} + }} + """ + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName(self.__class__.__name__) + self.setAttribute(Qt.WA_StyledBackground) + set_register_stylesheet(self) + + self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) + + self._option = None + + self._hbox = QHBoxLayout(self) + self._hbox.setContentsMargins(0, 0, 0, 0) + self._hbox.setSpacing(5) + + self._stack = QStackedLayout() + self._stack.setContentsMargins(0, 0, 0, 0) + + self.cmd = Command(self) + self._stack.addWidget(self.cmd) + + self.txt = Text(self) + self._stack.addWidget(self.txt) + + self.cmd.show_cmd.connect(self._show_cmd_widget) + self.cmd.hide_cmd.connect(self._hide_cmd_widget) + self._hide_cmd_widget() + + self._hbox.addLayout(self._stack) + + self.keystring = KeyString(self) + self._hbox.addWidget(self.keystring) + + self.url = Url(self) + self._hbox.addWidget(self.url) + + self.percentage = Percentage(self) + self._hbox.addWidget(self.percentage) + + self.prog = Progress(self) + self._hbox.addWidget(self.prog) + + @pyqtProperty(bool) + def error(self): + """Getter for self.error, so it can be used as Qt property.""" + # pylint: disable=method-hidden + return self._error + + @error.setter + def error(self, val): + """Setter for self.error, so it can be used as Qt property. + + Re-set the stylesheet after setting the value, so everything gets + updated by Qt properly. + """ + self._error = val + self.setStyleSheet(get_stylesheet(self.STYLESHEET)) + + def _show_cmd_widget(self): + """Show command widget instead of temporary text.""" + self._stack.setCurrentWidget(self.cmd) + self.clear_error() + + def _hide_cmd_widget(self): + """Show temporary text instead of command widget.""" + self._stack.setCurrentWidget(self.txt) + + @pyqtSlot(str) + def disp_error(self, text): + """Display an error in the statusbar.""" + self.error = True + self.txt.errortext = text + + @pyqtSlot() + def clear_error(self): + """Clear a displayed error from the status bar.""" + self.error = False + self.txt.errortext = '' + + @pyqtSlot('QKeyEvent') + def on_key_pressed(self, e): + """Hide temporary error message if a key was pressed. + + Args: + e: The original QKeyEvent. + """ + if e.key() in [Qt.Key_Control, Qt.Key_Alt, Qt.Key_Shift, Qt.Key_Meta]: + # Only modifier pressed, don't hide yet. + return + self.txt.set_temptext('') + self.clear_error() + + @pyqtSlot(str) + def on_mode_entered(self, mode): + """Mark certain modes in the commandline.""" + if mode in modeman.instance().passthrough: + self.txt.normaltext = "-- {} MODE --".format(mode.upper()) + + @pyqtSlot(str) + def on_mode_left(self, mode): + """Clear marked mode.""" + if mode in modeman.instance().passthrough: + self.txt.normaltext = "" + + def resizeEvent(self, e): + """Extend resizeEvent of QWidget to emit a resized signal afterwards. + + Args: + e: The QResizeEvent. + + Emit: + resized: Always emitted. + """ + super().resizeEvent(e) + self.resized.emit(self.geometry()) + + def moveEvent(self, e): + """Extend moveEvent of QWidget to emit a moved signal afterwards. + + Args: + e: The QMoveEvent. + + Emit: + moved: Always emitted. + """ + super().moveEvent(e) + self.moved.emit(e.pos())