class AutoSaver(QObject): """ Class implementing the auto saver. """ AUTOSAVE_IN = 1000 * 3 MAXWAIT = 1000 * 15 def __init__(self, parent, save): """ Constructor @param parent reference to the parent object (QObject) @param save slot to be called to perform the save operation @exception RuntimeError raised, if no parent is given """ super(AutoSaver, self).__init__(parent) if parent is None: raise RuntimeError("AutoSaver: parent must not be None.") self.__save = save self.__timer = QBasicTimer() self.__firstChange = QTime() def changeOccurred(self): """ Public slot handling a change. """ if self.__firstChange.isNull(): self.__firstChange.start() if self.__firstChange.elapsed() > self.MAXWAIT: self.saveIfNeccessary() else: self.__timer.start(self.AUTOSAVE_IN, self) def timerEvent(self, evt): """ Protected method handling timer events. @param evt reference to the timer event (QTimerEvent) """ if evt.timerId() == self.__timer.timerId(): self.saveIfNeccessary() else: super(AutoSaver, self).timerEvent(evt) def saveIfNeccessary(self): """ Public method to activate the save operation. """ if not self.__timer.isActive(): return self.__timer.stop() self.__firstChange = QTime() self.__save()
class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets. _text_queue: A deque of (error, text) tuples to be displayed. error: True if message is an error, False otherwise _text_pop_timer: A Timer displaying the error messages. _stopwatch: A QTime for the last displayed message. _timer_was_active: Whether the _text_pop_timer was active before hiding the command widget. _previous_widget: A PreviousWidget member - the widget which was displayed when an error interrupted it. _win_id: The window ID the statusbar is associated with. Class attributes: _severity: The severity of the current message, a Severity member. For some reason we need to have this as class attribute so pyqtProperty works correctly. _prompt_active: If we're currently in prompt-mode. For some reason we need to have this as class attribute so pyqtProperty works correctly. _insert_active: If we're currently in insert mode. For some reason we need to have this as class attribute so pyqtProperty works correctly. _command_active: If we're currently in command mode. For some reason we need to have this as class attribute so pyqtProperty works correctly. _caret_mode: The current caret mode (off/on/selection). 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 to the right position. arg: The new position. """ resized = pyqtSignal('QRect') moved = pyqtSignal('QPoint') _severity = None _prompt_active = False _insert_active = False _command_active = False _caret_mode = CaretMode.off STYLESHEET = """ QWidget#StatusBar, QWidget#StatusBar QLabel, QWidget#StatusBar QLineEdit { font: {{ font['statusbar'] }}; background-color: {{ color['statusbar.bg'] }}; color: {{ color['statusbar.fg'] }}; } QWidget#StatusBar[caret_mode="on"], QWidget#StatusBar[caret_mode="on"] QLabel, QWidget#StatusBar[caret_mode="on"] QLineEdit { color: {{ color['statusbar.fg.caret'] }}; background-color: {{ color['statusbar.bg.caret'] }}; } QWidget#StatusBar[caret_mode="selection"], QWidget#StatusBar[caret_mode="selection"] QLabel, QWidget#StatusBar[caret_mode="selection"] QLineEdit { color: {{ color['statusbar.fg.caret-selection'] }}; background-color: {{ color['statusbar.bg.caret-selection'] }}; } QWidget#StatusBar[severity="error"], QWidget#StatusBar[severity="error"] QLabel, QWidget#StatusBar[severity="error"] QLineEdit { color: {{ color['statusbar.fg.error'] }}; background-color: {{ color['statusbar.bg.error'] }}; } QWidget#StatusBar[severity="warning"], QWidget#StatusBar[severity="warning"] QLabel, QWidget#StatusBar[severity="warning"] QLineEdit { color: {{ color['statusbar.fg.warning'] }}; background-color: {{ color['statusbar.bg.warning'] }}; } QWidget#StatusBar[prompt_active="true"], QWidget#StatusBar[prompt_active="true"] QLabel, QWidget#StatusBar[prompt_active="true"] QLineEdit { color: {{ color['statusbar.fg.prompt'] }}; background-color: {{ color['statusbar.bg.prompt'] }}; } QWidget#StatusBar[insert_active="true"], QWidget#StatusBar[insert_active="true"] QLabel, QWidget#StatusBar[insert_active="true"] QLineEdit { color: {{ color['statusbar.fg.insert'] }}; background-color: {{ color['statusbar.bg.insert'] }}; } QWidget#StatusBar[command_active="true"], QWidget#StatusBar[command_active="true"] QLabel, QWidget#StatusBar[command_active="true"] QLineEdit { color: {{ color['statusbar.fg.command'] }}; background-color: {{ color['statusbar.bg.command'] }}; } """ def __init__(self, win_id, parent=None): super().__init__(parent) objreg.register('statusbar', self, scope='window', window=win_id) self.setObjectName(self.__class__.__name__) self.setAttribute(Qt.WA_StyledBackground) style.set_register_stylesheet(self) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) self._win_id = win_id self._option = None self._stopwatch = QTime() self._hbox = QHBoxLayout(self) self.set_hbox_padding() objreg.get('config').changed.connect(self.set_hbox_padding) self._hbox.setSpacing(5) self._stack = QStackedLayout() self._hbox.addLayout(self._stack) self._stack.setContentsMargins(0, 0, 0, 0) self.cmd = command.Command(win_id) self._stack.addWidget(self.cmd) objreg.register('status-command', self.cmd, scope='window', window=win_id) self.txt = textwidget.Text() self._stack.addWidget(self.txt) self._timer_was_active = False self._text_queue = collections.deque() self._text_pop_timer = usertypes.Timer(self, 'statusbar_text_pop') self._text_pop_timer.timeout.connect(self._pop_text) self.set_pop_timer_interval() objreg.get('config').changed.connect(self.set_pop_timer_interval) self.prompt = prompt.Prompt(win_id) self._stack.addWidget(self.prompt) self._previous_widget = PreviousWidget.none self.cmd.show_cmd.connect(self._show_cmd_widget) self.cmd.hide_cmd.connect(self._hide_cmd_widget) self._hide_cmd_widget() prompter = objreg.get('prompter', scope='window', window=self._win_id) prompter.show_prompt.connect(self._show_prompt_widget) prompter.hide_prompt.connect(self._hide_prompt_widget) self._hide_prompt_widget() self.keystring = keystring.KeyString() self._hbox.addWidget(self.keystring) self.url = url.UrlText() self._hbox.addWidget(self.url) self.percentage = percentage.Percentage() self._hbox.addWidget(self.percentage) self.tabindex = tabindex.TabIndex() self._hbox.addWidget(self.tabindex) # We add a parent to Progress here because it calls self.show() based # on some signals, and if that happens before it's added to the layout, # it will quickly blink up as independent window. self.prog = progress.Progress(self) self._hbox.addWidget(self.prog) objreg.get('config').changed.connect(self.maybe_hide) QTimer.singleShot(0, self.maybe_hide) def __repr__(self): return utils.get_repr(self) @config.change_filter('ui', 'hide-statusbar') def maybe_hide(self): """Hide the statusbar if it's configured to do so.""" hide = config.get('ui', 'hide-statusbar') if hide: self.hide() else: self.show() @config.change_filter('ui', 'statusbar-padding') def set_hbox_padding(self): padding = config.get('ui', 'statusbar-padding') self._hbox.setContentsMargins(padding.left, 0, padding.right, 0) @pyqtProperty(str) def severity(self): """Getter for self.severity, so it can be used as Qt property. Return: The severity as a string (!) """ if self._severity is None: return "" else: return self._severity.name def _set_severity(self, severity): """Set the severity for the current message. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. Args: severity: A Severity member. """ if self._severity == severity: # This gets called a lot (e.g. if the completion selection was # changed), and setStyleSheet is relatively expensive, so we ignore # this if there's nothing to change. return log.statusbar.debug("Setting severity to {}".format(severity)) self._severity = severity self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) if severity != Severity.normal: # If we got an error while command/prompt was shown, raise the text # widget. self._stack.setCurrentWidget(self.txt) @pyqtProperty(bool) def prompt_active(self): """Getter for self.prompt_active, so it can be used as Qt property.""" return self._prompt_active def _set_prompt_active(self, val): """Setter for self.prompt_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ log.statusbar.debug("Setting prompt_active to {}".format(val)) self._prompt_active = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) @pyqtProperty(bool) def command_active(self): """Getter for self.command_active, so it can be used as Qt property.""" return self._command_active @pyqtProperty(bool) def insert_active(self): """Getter for self.insert_active, so it can be used as Qt property.""" return self._insert_active @pyqtProperty(str) def caret_mode(self): """Getter for self._caret_mode, so it can be used as Qt property.""" return self._caret_mode.name def set_mode_active(self, mode, val): """Setter for self.{insert,command,caret}_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ if mode == usertypes.KeyMode.insert: log.statusbar.debug("Setting insert_active to {}".format(val)) self._insert_active = val if mode == usertypes.KeyMode.command: log.statusbar.debug("Setting command_active to {}".format(val)) self._command_active = val elif mode == usertypes.KeyMode.caret: webview = objreg.get('tabbed-browser', scope='window', window=self._win_id).currentWidget() log.statusbar.debug("Setting caret_mode - val {}, selection " "{}".format(val, webview.selection_enabled)) if val: if webview.selection_enabled: self._set_mode_text("{} selection".format(mode.name)) self._caret_mode = CaretMode.selection else: self._set_mode_text(mode.name) self._caret_mode = CaretMode.on else: self._caret_mode = CaretMode.off self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _set_mode_text(self, mode): """Set the mode text.""" text = "-- {} MODE --".format(mode.upper()) self.txt.set_text(self.txt.Text.normal, text) def _pop_text(self): """Display a text in the statusbar and pop it from _text_queue.""" try: severity, text = self._text_queue.popleft() except IndexError: self._set_severity(Severity.normal) self.txt.set_text(self.txt.Text.temp, '') self._text_pop_timer.stop() # If a previous widget was interrupted by an error, restore it. if self._previous_widget == PreviousWidget.prompt: self._stack.setCurrentWidget(self.prompt) elif self._previous_widget == PreviousWidget.command: self._stack.setCurrentWidget(self.cmd) elif self._previous_widget == PreviousWidget.none: self.maybe_hide() else: raise AssertionError("Unknown _previous_widget!") return self.show() log.statusbar.debug("Displaying message: {} (severity {})".format( text, severity)) log.statusbar.debug("Remaining: {}".format(self._text_queue)) self._set_severity(severity) self.txt.set_text(self.txt.Text.temp, text) def _show_cmd_widget(self): """Show command widget instead of temporary text.""" self._set_severity(Severity.normal) self._previous_widget = PreviousWidget.command if self._text_pop_timer.isActive(): self._timer_was_active = True self._text_pop_timer.stop() self._stack.setCurrentWidget(self.cmd) self.show() def _hide_cmd_widget(self): """Show temporary text instead of command widget.""" log.statusbar.debug("Hiding cmd widget, queue: {}".format( self._text_queue)) self._previous_widget = PreviousWidget.none if self._timer_was_active: # Restart the text pop timer if it was active before hiding. self._pop_text() self._text_pop_timer.start() self._timer_was_active = False self._stack.setCurrentWidget(self.txt) self.maybe_hide() def _show_prompt_widget(self): """Show prompt widget instead of temporary text.""" if self._stack.currentWidget() is self.prompt: return self._set_severity(Severity.normal) self._set_prompt_active(True) self._previous_widget = PreviousWidget.prompt if self._text_pop_timer.isActive(): self._timer_was_active = True self._text_pop_timer.stop() self._stack.setCurrentWidget(self.prompt) self.show() def _hide_prompt_widget(self): """Show temporary text instead of prompt widget.""" self._set_prompt_active(False) self._previous_widget = PreviousWidget.none log.statusbar.debug("Hiding prompt widget, queue: {}".format( self._text_queue)) if self._timer_was_active: # Restart the text pop timer if it was active before hiding. self._pop_text() self._text_pop_timer.start() self._timer_was_active = False self._stack.setCurrentWidget(self.txt) self.maybe_hide() def _disp_text(self, text, severity, immediately=False): """Inner logic for disp_error and disp_temp_text. Args: text: The message to display. severity: The severity of the messages. immediately: If set, message gets displayed immediately instead of queued. """ log.statusbar.debug("Displaying text: {} (severity={})".format( text, severity)) mindelta = config.get('ui', 'message-timeout') if self._stopwatch.isNull(): delta = None self._stopwatch.start() else: delta = self._stopwatch.restart() log.statusbar.debug("queue: {} / delta: {}".format( self._text_queue, delta)) if not self._text_queue and (delta is None or delta > mindelta): # If the queue is empty and we didn't print messages for long # enough, we can take the short route and display the message # immediately. We then start the pop_timer only to restore the # normal state in 2 seconds. log.statusbar.debug("Displaying immediately") self._set_severity(severity) self.show() self.txt.set_text(self.txt.Text.temp, text) self._text_pop_timer.start() elif self._text_queue and self._text_queue[-1] == (severity, text): # If we get the same message multiple times in a row and we're # still displaying it *anyways* we ignore the new one log.statusbar.debug("ignoring") elif immediately: # This message is a reaction to a keypress and should be displayed # immediately, temporarily interrupting the message queue. # We display this immediately and restart the timer.to clear it and # display the rest of the queue later. log.statusbar.debug("Moving to beginning of queue") self._set_severity(severity) self.show() self.txt.set_text(self.txt.Text.temp, text) self._text_pop_timer.start() else: # There are still some messages to be displayed, so we queue this # up. log.statusbar.debug("queueing") self._text_queue.append((severity, text)) self._text_pop_timer.start() @pyqtSlot(str, bool) def disp_error(self, text, immediately=False): """Display an error in the statusbar. Args: text: The message to display. immediately: If set, message gets displayed immediately instead of queued. """ self._disp_text(text, Severity.error, immediately) @pyqtSlot(str, bool) def disp_warning(self, text, immediately=False): """Display a warning in the statusbar. Args: text: The message to display. immediately: If set, message gets displayed immediately instead of queued. """ self._disp_text(text, Severity.warning, immediately) @pyqtSlot(str, bool) def disp_temp_text(self, text, immediately): """Display a temporary text in the statusbar. Args: text: The message to display. immediately: If set, message gets displayed immediately instead of queued. """ self._disp_text(text, Severity.normal, immediately) @pyqtSlot(str) def set_text(self, val): """Set a normal (persistent) text in the status bar.""" self.txt.set_text(self.txt.Text.normal, val) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" keyparsers = objreg.get('keyparsers', scope='window', window=self._win_id) if keyparsers[mode].passthrough: self._set_mode_text(mode.name) if mode in (usertypes.KeyMode.insert, usertypes.KeyMode.command, usertypes.KeyMode.caret): self.set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): """Clear marked mode.""" keyparsers = objreg.get('keyparsers', scope='window', window=self._win_id) if keyparsers[old_mode].passthrough: if keyparsers[new_mode].passthrough: self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') if old_mode in (usertypes.KeyMode.insert, usertypes.KeyMode.command, usertypes.KeyMode.caret): self.set_mode_active(old_mode, False) @config.change_filter('ui', 'message-timeout') def set_pop_timer_interval(self): """Update message timeout when config changed.""" self._text_pop_timer.setInterval(config.get('ui', 'message-timeout')) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. Args: e: The QResizeEvent. """ 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. """ super().moveEvent(e) self.moved.emit(e.pos()) def minimumSizeHint(self): """Set the minimum height to the text height plus some padding.""" padding = config.get('ui', 'statusbar-padding') width = super().minimumSizeHint().width() height = self.fontMetrics().height() + padding.top + padding.bottom return QSize(width, height)
def init(scriptDir, *args, **kwargs): ''' Initialise code parameters at startup. Parameters --------- sriptDir : str location of the code and the yaml file(s) Return the settings dictionnary and an error code (0 if ok, -1 if error). ''' file = opath.join(scriptDir, 'settings.yaml') # If the file does not exist, a default one is created if not opath.isfile(file): default(file) # Load configuration option from setting file with open(file, 'r') as f: settings = load(f, Loader=Loader) # If a key is missing, the setting file is saved and a new one is created with default value errCode = 0 for i in ['font', 'color', 'x', 'y', 'opacity', 'blinkPeriod', 'blinkFreq', 'blinkNb']: if i not in settings.keys(): print('Error in setting file %s. The key %s was missing. Generating a new default configuration file instead.' %(file, i)) # Save copy path, newname = opath.split(file) newname = opath.join(path, r'~%s' %newname) os.rename(file, newname) # Generate and load new default settings default(file) settings = load(file, Loader=Loader) errCode = -1 ################################################ # Check parameters # ################################################ # X coordinate if not isinstance(settings['x'], int) or settings['x'] < 0: print('Given x coordinate is < 0 or is not an int. Using 0 as default value instead.') settings['x'] = 0 else: settings['x'] = int(settings['x']) # Y coordinate if not isinstance(settings['y'], int) or settings['y'] < 0: print('Given y coordinate is < 0 or is not an int. Using 0 as default value instead.') settings['y'] = 0 else: settings['y'] = int(settings['y']) # Opacity if not isinstance(settings['opacity'], (int, float)) or settings['opacity'] < 0 or settings['opacity'] > 1: print('Given opacity is not in the range [0, 1] or is not an int/float. Using 1 as default value instead.') settings['opacity'] = 1 # Period is changed from a string to a PyQt Qtime object period = QTime().fromString(settings['blinkPeriod']) if period.isNull(): print('Given blinking period could not be broadcasted to a valid PyQt QTime object. Using 1s as default value instead.') settings['blinkPeriod'] = Qtime(0, 0, 1) else: settings['blinkPeriod'] = period # Blinking frequency if not isinstance(settings['blinkFreq'], (int, float)): print('Given bliking frequency is not an int/float. Using 100ms as default value instead.') settings['blinkFreq'] = 100 else: if settings['blinkFreq'] < 50: print('Given blinking frequency is below minimum value. Clipping to 50ms as default value instead.') settings['blinkFreq'] = 50 elif settings['blinkFreq'] > 10000: print('Given bliking frequency is above maximum value. Clipping to 10s as default value instead.') settings['blinkFreq'] = 10000 else: settings['blinkFreq'] = int(settings['blinkFreq']) # Blinking number if not isinstance(settings['blinkNb'], int) or settings['blinkNb'] <= 0: print('Given blinking number is <= 0 or is not an int. Using 3 as default value instead.') settings['blinkNb'] = 3 return settings, errCode
class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets. _text_queue: A deque of (error, text) tuples to be displayed. error: True if message is an error, False otherwise _text_pop_timer: A Timer displaying the error messages. _stopwatch: A QTime for the last displayed message. _timer_was_active: Whether the _text_pop_timer was active before hiding the command widget. _previous_widget: A PreviousWidget member - the widget which was displayed when an error interrupted it. _win_id: The window ID the statusbar is associated with. 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. _prompt_active: If we're currently in prompt-mode. For some reason we need to have this as class attribute so pyqtProperty works correctly. _insert_active: If we're currently in insert mode. 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 _prompt_active = False _insert_active = False STYLESHEET = """ QWidget#StatusBar { {{ color['statusbar.bg'] }} } QWidget#StatusBar[insert_active="true"] { {{ color['statusbar.bg.insert'] }} } QWidget#StatusBar[prompt_active="true"] { {{ color['statusbar.bg.prompt'] }} } QWidget#StatusBar[error="true"] { {{ color['statusbar.bg.error'] }} } QLabel, QLineEdit { {{ color['statusbar.fg'] }} {{ font['statusbar'] }} } """ def __init__(self, win_id, parent=None): super().__init__(parent) objreg.register('statusbar', self, scope='window', window=win_id) self.setObjectName(self.__class__.__name__) self.setAttribute(Qt.WA_StyledBackground) style.set_register_stylesheet(self) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) self._win_id = win_id self._option = None self._stopwatch = QTime() self._hbox = QHBoxLayout(self) self._hbox.setContentsMargins(0, 0, 0, 0) self._hbox.setSpacing(5) self._stack = QStackedLayout() self._hbox.addLayout(self._stack) self._stack.setContentsMargins(0, 0, 0, 0) self.cmd = command.Command(win_id) self._stack.addWidget(self.cmd) objreg.register('status-command', self.cmd, scope='window', window=win_id) self.txt = textwidget.Text() self._stack.addWidget(self.txt) self._timer_was_active = False self._text_queue = collections.deque() self._text_pop_timer = usertypes.Timer(self, 'statusbar_text_pop') self._text_pop_timer.timeout.connect(self._pop_text) self.set_pop_timer_interval() objreg.get('config').changed.connect(self.set_pop_timer_interval) self.prompt = prompt.Prompt(win_id) self._stack.addWidget(self.prompt) self._previous_widget = PreviousWidget.none self.cmd.show_cmd.connect(self._show_cmd_widget) self.cmd.hide_cmd.connect(self._hide_cmd_widget) self._hide_cmd_widget() prompter = objreg.get('prompter', scope='window', window=self._win_id) prompter.show_prompt.connect(self._show_prompt_widget) prompter.hide_prompt.connect(self._hide_prompt_widget) self._hide_prompt_widget() self.keystring = keystring.KeyString() self._hbox.addWidget(self.keystring) self.url = url.UrlText() self._hbox.addWidget(self.url) self.percentage = percentage.Percentage() self._hbox.addWidget(self.percentage) # We add a parent to Progress here because it calls self.show() based # on some signals, and if that happens before it's added to the layout, # it will quickly blink up as independent window. self.prog = progress.Progress(self) self._hbox.addWidget(self.prog) def __repr__(self): return utils.get_repr(self) @pyqtProperty(bool) def error(self): """Getter for self.error, so it can be used as Qt property.""" # pylint: disable=method-hidden return self._error def _set_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. """ if self._error == val: # This gets called a lot (e.g. if the completion selection was # changed), and setStyleSheet is relatively expensive, so we ignore # this if there's nothing to change. return log.statusbar.debug("Setting error to {}".format(val)) self._error = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) if val: # If we got an error while command/prompt was shown, raise the text # widget. self._stack.setCurrentWidget(self.txt) @pyqtProperty(bool) def prompt_active(self): """Getter for self.prompt_active, so it can be used as Qt property.""" # pylint: disable=method-hidden return self._prompt_active def _set_prompt_active(self, val): """Setter for self.prompt_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ log.statusbar.debug("Setting prompt_active to {}".format(val)) self._prompt_active = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) @pyqtProperty(bool) def insert_active(self): """Getter for self.insert_active, so it can be used as Qt property.""" # pylint: disable=method-hidden return self._insert_active def _set_insert_active(self, val): """Setter for self.insert_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ log.statusbar.debug("Setting insert_active to {}".format(val)) self._insert_active = val self.setStyleSheet(style.get_stylesheet(self.STYLESHEET)) def _pop_text(self): """Display a text in the statusbar and pop it from _text_queue.""" try: error, text = self._text_queue.popleft() except IndexError: self._set_error(False) self.txt.set_text(self.txt.Text.temp, '') self._text_pop_timer.stop() # If a previous widget was interrupted by an error, restore it. if self._previous_widget == PreviousWidget.prompt: self._stack.setCurrentWidget(self.prompt) elif self._previous_widget == PreviousWidget.command: self._stack.setCurrentWidget(self.command) elif self._previous_widget == PreviousWidget.none: pass else: raise AssertionError("Unknown _previous_widget!") return log.statusbar.debug("Displaying {} message: {}".format( 'error' if error else 'text', text)) log.statusbar.debug("Remaining: {}".format(self._text_queue)) self._set_error(error) self.txt.set_text(self.txt.Text.temp, text) def _show_cmd_widget(self): """Show command widget instead of temporary text.""" self._set_error(False) self._previous_widget = PreviousWidget.prompt if self._text_pop_timer.isActive(): self._timer_was_active = True self._text_pop_timer.stop() self._stack.setCurrentWidget(self.cmd) def _hide_cmd_widget(self): """Show temporary text instead of command widget.""" log.statusbar.debug("Hiding cmd widget, queue: {}".format( self._text_queue)) self._previous_widget = PreviousWidget.none if self._timer_was_active: # Restart the text pop timer if it was active before hiding. self._pop_text() self._text_pop_timer.start() self._timer_was_active = False self._stack.setCurrentWidget(self.txt) def _show_prompt_widget(self): """Show prompt widget instead of temporary text.""" if self._stack.currentWidget() is self.prompt: return self._set_error(False) self._set_prompt_active(True) self._previous_widget = PreviousWidget.prompt if self._text_pop_timer.isActive(): self._timer_was_active = True self._text_pop_timer.stop() self._stack.setCurrentWidget(self.prompt) def _hide_prompt_widget(self): """Show temporary text instead of prompt widget.""" self._set_prompt_active(False) self._previous_widget = PreviousWidget.none log.statusbar.debug("Hiding prompt widget, queue: {}".format( self._text_queue)) if self._timer_was_active: # Restart the text pop timer if it was active before hiding. self._pop_text() self._text_pop_timer.start() self._timer_was_active = False self._stack.setCurrentWidget(self.txt) def _disp_text(self, text, error, immediately=False): """Inner logic for disp_error and disp_temp_text. Args: text: The message to display. error: Whether it's an error message (True) or normal text (False) immediately: If set, message gets displayed immediately instead of queued. """ log.statusbar.debug("Displaying text: {} (error={})".format( text, error)) mindelta = config.get('ui', 'message-timeout') if self._stopwatch.isNull(): delta = None self._stopwatch.start() else: delta = self._stopwatch.restart() log.statusbar.debug("queue: {} / delta: {}".format( self._text_queue, delta)) if not self._text_queue and (delta is None or delta > mindelta): # If the queue is empty and we didn't print messages for long # enough, we can take the short route and display the message # immediately. We then start the pop_timer only to restore the # normal state in 2 seconds. log.statusbar.debug("Displaying immediately") self._set_error(error) self.txt.set_text(self.txt.Text.temp, text) self._text_pop_timer.start() elif self._text_queue and self._text_queue[-1] == (error, text): # If we get the same message multiple times in a row and we're # still displaying it *anyways* we ignore the new one log.statusbar.debug("ignoring") elif immediately: # This message is a reaction to a keypress and should be displayed # immediately, temporarily interrupting the message queue. # We display this immediately and restart the timer.to clear it and # display the rest of the queue later. log.statusbar.debug("Moving to beginning of queue") self._set_error(error) self.txt.set_text(self.txt.Text.temp, text) self._text_pop_timer.start() else: # There are still some messages to be displayed, so we queue this # up. log.statusbar.debug("queueing") self._text_queue.append((error, text)) self._text_pop_timer.start() @pyqtSlot(str, bool) def disp_error(self, text, immediately=False): """Display an error in the statusbar. Args: text: The message to display. immediately: If set, message gets displayed immediately instead of queued. """ self._disp_text(text, True, immediately) @pyqtSlot(str, bool) def disp_temp_text(self, text, immediately): """Display a temporary text in the statusbar. Args: text: The message to display. immediately: If set, message gets displayed immediately instead of queued. """ self._disp_text(text, False, immediately) @pyqtSlot(str) def set_text(self, val): """Set a normal (persistent) text in the status bar.""" self.txt.set_text(self.txt.Text.normal, val) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" mode_manager = objreg.get('mode-manager', scope='window', window=self._win_id) if mode in mode_manager.passthrough: text = "-- {} MODE --".format(mode.name.upper()) self.txt.set_text(self.txt.Text.normal, text) if mode == usertypes.KeyMode.insert: self._set_insert_active(True) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Clear marked mode.""" mode_manager = objreg.get('mode-manager', scope='window', window=self._win_id) if mode in mode_manager.passthrough: self.txt.set_text(self.txt.Text.normal, '') if mode == usertypes.KeyMode.insert: self._set_insert_active(False) @config.change_filter('ui', 'message-timeout') def set_pop_timer_interval(self): """Update message timeout when config changed.""" self._text_pop_timer.setInterval(config.get('ui', 'message-timeout')) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. Args: e: The QResizeEvent. """ 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. """ super().moveEvent(e) self.moved.emit(e.pos())