class Dialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog.__init__(self, parent) mainLayout = QtWidgets.QVBoxLayout() self.setWindowTitle("PyQtLineEditProgressBar Behaviors") self.lineedit1 = PyQtLineEditProgressBar( behavior=pqtpbar.STARTS_EMPTY_FILLS_LEFT_TO_RIGHT) self.lineedit1.setAlignment(QtCore.Qt.AlignCenter) self.lineedit1.setText(self.lineedit1.getBehavior().upper().replace( '-', '_')) self.lineedit2 = PyQtLineEditProgressBar( behavior=pqtpbar.STARTS_EMPTY_FILLS_RIGHT_TO_LEFT) self.lineedit2.setAlignment(QtCore.Qt.AlignCenter) self.lineedit2.setText(self.lineedit2.getBehavior().upper().replace( '-', '_')) self.lineedit3 = PyQtLineEditProgressBar( behavior=pqtpbar.STARTS_FULL_EMPTIES_LEFT_TO_RIGHT) self.lineedit3.setAlignment(QtCore.Qt.AlignCenter) self.lineedit3.setText(self.lineedit3.getBehavior().upper().replace( '-', '_')) self.lineedit4 = PyQtLineEditProgressBar( behavior=pqtpbar.STARTS_FULL_EMPTIES_RIGHT_TO_LEFT) self.lineedit4.setAlignment(QtCore.Qt.AlignCenter) self.lineedit4.setText(self.lineedit4.getBehavior().upper().replace( '-', '_')) mainLayout.addWidget(self.lineedit1) mainLayout.addWidget(self.lineedit2) mainLayout.addWidget(self.lineedit3) mainLayout.addWidget(self.lineedit4) button = QtWidgets.QPushButton('The Four Behaviors') button.clicked.connect(self.buttonClicked) mainLayout.addWidget(button) self.setLayout(mainLayout) def buttonClicked(self): self.lineedit1.updateProgress(0.1) #self.lineedit1.setText(self.lineedit1.getBehavior()) self.lineedit2.updateProgress(0.1) #self.lineedit2.setText(self.lineedit2.getBehavior()) self.lineedit3.updateProgress(0.1) #self.lineedit3.setText(self.lineedit3.getBehavior()) self.lineedit4.updateProgress(0.1) #self.lineedit4.setText(self.lineedit4.getBehavior()) time.sleep(0.5)
class Dialog(QtWidgets.QDialog): def __init__(self, parent=None): QtWidgets.QDialog .__init__(self, parent) mainLayout = QtWidgets.QVBoxLayout() self.setWindowTitle("PyQtLineEditProgressBar Demo") self.lineedit1 = PyQtLineEditProgressBar() self.lineedit1.setAlignment(QtCore.Qt.AlignCenter) #self.lineedit1.setSizePolicy() self.lineedit1.setText(self.lineedit1.getBehavior()) self.lineedit2 = PyQtLineEditProgressBar(behavior=pqtpbar.STARTS_EMPTY_FILLS_RIGHT_TO_LEFT, progressbar_color=pqtpbar.DEFAULT_COLOR_RED) self.lineedit2.setAlignment(QtCore.Qt.AlignCenter) self.lineedit2.setText(self.lineedit2.getBehavior()) self.lineedit3 = PyQtLineEditProgressBar(behavior=pqtpbar.STARTS_FULL_EMPTIES_LEFT_TO_RIGHT, progressbar_color=pqtpbar.DEFAULT_COLOR_ORANGE) self.lineedit3.setAlignment(QtCore.Qt.AlignCenter) self.lineedit3.setText(self.lineedit3.getBehavior()) self.lineedit4 = PyQtLineEditProgressBar(behavior=pqtpbar.STARTS_FULL_EMPTIES_RIGHT_TO_LEFT, progressbar_color=pqtpbar.DEFAULT_COLOR_BLUE) self.lineedit4.setAlignment(QtCore.Qt.AlignCenter) self.lineedit4.setText(self.lineedit4.getBehavior()) self.lineedit5 = PyQtLineEditProgressBar(progressbar_color=pqtpbar.DEFAULT_COLOR_YELLOW) self.lineedit5.setAlignment(QtCore.Qt.AlignCenter) self.lineedit5.setText(self.lineedit5.getBehavior()) self.lineedit6 = PyQtLineEditProgressBar(behavior=pqtpbar.STARTS_EMPTY_FILLS_RIGHT_TO_LEFT, progressbar_color=pqtpbar.DEFAULT_COLOR_PURPLE) self.lineedit6.setAlignment(QtCore.Qt.AlignCenter) self.lineedit6.setText(self.lineedit6.getBehavior()) mainLayout.addWidget(self.lineedit1) mainLayout.addWidget(self.lineedit2) mainLayout.addWidget(self.lineedit3) mainLayout.addWidget(self.lineedit4) mainLayout.addWidget(self.lineedit5) mainLayout.addWidget(self.lineedit6) button = QtWidgets.QPushButton('Update Progress') button.clicked.connect(self.buttonClicked) mainLayout.addWidget(button) self.setLayout(mainLayout) def buttonClicked(self): self.lineedit1.updateProgress(0.1) #self.lineedit1.setText(self.lineedit1.getBehavior()) self.lineedit2.updateProgress(0.1) #self.lineedit2.setText(self.lineedit2.getBehavior()) self.lineedit3.updateProgress(0.1) #self.lineedit3.setText(self.lineedit3.getBehavior()) self.lineedit4.updateProgress(0.1) #self.lineedit4.setText(self.lineedit4.getBehavior()) self.lineedit5.updateProgress(0.1) #self.lineedit5.setText(self.lineedit5.getBehavior()) self.lineedit6.updateProgress(0.1) #self.lineedit6.setText(self.lineedit6.getBehavior()) time.sleep(0.5)
class PyQtMessageBar(QStatusBar): def __init__( self, parent=None, msg_buffer_size=DEFAULT_BUFFER_SIZE, enable_separators=False, help_icon_file=None, built_in_help_icon=BUILT_IN_HELP_ICON_LIGHT, parent_logger_name=None, save_msg_buffer_dir=None, timer_wait_q_emptied_signal=None, ): """Constructor for the **PyQtMessageBar** class which subclasses QStatusBar. See `Qt's QStatusBar Documentation <https://doc.qt.io/qt-5/qstatusbar.html>`_. It adds a buffered message index which includes a wait queue depth and it adds a messagebar help icon. Parameters ---------- parent : qtwidget, optional Reference to this widget's parent widget msg_buffer_size : int, optional The number of messages to buffer before removing the oldest enable_separators : bool, optional If True, any addPermanentWidget() calls will include a vertical separator to the left of the widget added. help_icon_file : str, optional If specified, this file should be a 24x24 pixel image file to be used to replace the built-in help icon image. If specified, this icon will have prescedence over any built-in icon. built_in_help_icon : str, optional This is a string constant that can be one of three values 'Light', 'Dark', 'Two'. Use the module constants BUILT_IN_HELP_ICON_LIGHT, BUILT_IN_HELP_ICON_DARK, or BUILT_IN_HELP_ICON_TWO_TONE. save_msg_buffer_dir : str, optional A directory where any saved message buffers will be written to. If specified as None, then saving the message buffer will be disabled. timer_wait_q_emptied_signal : WaitQueueEmptiedSignal object, optional Provides a custom signal and allows user to connect their own slot method to the timer wait queue becoming empty. See WaitQueueEmptiedSignal :ref:`waitqueuesignal_usage_label` for a code example of how to set this signal up. """ super(PyQtMessageBar, self).__init__(parent=parent) # Constructor Parameters self._parent = parent self._buffer_size = msg_buffer_size self._enable_separators = enable_separators self._help_icon_file = help_icon_file self._built_in_help_icon = built_in_help_icon self._save_msg_buffer_dir = save_msg_buffer_dir self._timer_wait_q_emptied_signal = timer_wait_q_emptied_signal # Initializing internal data self._progressbar_delta = None self._timer_progressbar_update = None self._field_width = len(str(self._buffer_size)) format_1 = "{" + "0:0{}d".format(self._field_width) + "}" format_2 = "{" + "1:0{}d".format(self._field_width) + "}" format_3 = " [{2:1d}]" self._displayed_msg_idx_format = format_1 + "/" + format_2 + format_3 logger.debug("Msg Index Format Spec: '{}'".format( self._displayed_msg_idx_format)) self._displayed_msg_idx = -1 # _bufferd_msgs is empty self._buf_page_size = DEFAULT_PAGE_SIZE # A _bufferd_msgs entry consists of: (msg, timeout, fg, bg, bold, enqueue_time) self._bufferd_msgs = list() # We use a single timer, so messages that would generate overlapping timers are put on a wait queue # until this currently displayed message timer fires. self._timer = None self._timer_wait_q = queue.Queue() # FIFO (First In First Out) self._process_zero_timeout_timer = None # This is the widget that is currently being displayed, if this is None, then # nothing is being displayed self._widget = None # Intialize our user interface StatusBar and widgets a few... self._initUI() # ------------------------------------------------------------------------- # Private Pythonic Interface ---------------------------------------------- # ------------------------------------------------------------------------- def _initUI(self): # This will be the default color for the countdown timer progressbar self._progressbar_color = PYQTPROGBAR.DEFAULT_COLOR_PURPLE # Add permanent QLabel for displayed message index self._msg_idx_label = PyQtLineEditProgressBar( behavior=PYQTPROGBAR.STARTS_FULL_EMPTIES_RIGHT_TO_LEFT, progressbar_color=self._progressbar_color, text_for_bounding_rect=" 88888/888 [88] ", ) self._msg_idx_label.setMaxLength((2 * self._field_width) + 1 + 6) self._msg_idx_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._clear_msg_idx_label( ) # rather than: self._update_msg_idx_label() self._msg_idx_label_tt_msg = "..out of {}".format(self._buffer_size) self._msg_idx_label.setToolTip(self._msg_idx_label_tt_msg) self.addPermanentWidget(self._msg_idx_label) # Add permanent Help Icon self._help_button = QPushButton('', self) if self._help_icon_file: # Load Help Icon from user file self._icon = QIcon(self._help_icon_file) else: # Load Help Icon from binary data if self._built_in_help_icon == BUILT_IN_HELP_ICON_DARK: self._icon = QIcon(':/gfx/baseline_help_black_24dp.png') elif self._built_in_help_icon == BUILT_IN_HELP_ICON_LIGHT: self._icon = QIcon(':/gfx/baseline_help_outline_24dp.png') elif self._built_in_help_icon == BUILT_IN_HELP_ICON_TWO_TONE: self._icon = QIcon(':/gfx/baseline_help_twotone_24dp.png') else: self._icon = QIcon(':/gfx/baseline_help_outline_24dp.png') self._help_button.setIcon(self._icon) self._help_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self._help_button.clicked.connect(self._statusbar_help_dialog) self._help_button.setToolTip("Statusbar Help...") self.addPermanentWidget(self._help_button) self._msg_idx_label.removeProgressBar() def _buffer_entry(self, entry_tuple): """The entry_tuple looks like: (msg, timeout, fg, bg, bold)""" # Before we can update the msg index, we need to insure # that we have not reached the limit of the buffer size if self._displayed_msg_idx >= self._buffer_size - 1: # Reached limit of buffer, so we delete the oldest entry entry_to_throw_away = self._bufferd_msgs.pop(0) logger.warning( "Reached limit of buffer throwing away top entry at idx 0: {}". format(entry_to_throw_away)) # unpack the tuple msg, timeout, fg, bg, bold = entry_tuple # Let's add a timestamp to this entry now = datetime.now() timestamp = now.strftime("%Y-%m-%d %H:%M:%S") # enqueue the message self._bufferd_msgs.append((msg, timeout, fg, bg, bold, timestamp)) self._displayed_msg_idx = self._displayed_msg_idx + 1 logger.warning( "At idx: {} Buffered: {}, to:{}, fg:{}, bg:{}, bold:{}, ts:{}". format(self._displayed_msg_idx, msg, timeout, fg, bg, bold, timestamp)) def _update_msg_idx_label(self): """Use the _displayed_msg_idx to update the _msg_idx_label widget""" if self._displayed_msg_idx < 0: # If there are no message yet displayed, the label is all dashes #label_text = '-' * (len(str(self._buffer_size)) + 1) self._clear_msg_idx_label() return () label_text = self._displayed_msg_idx_format.format( self._displayed_msg_idx, len(self._bufferd_msgs) - 1, self._timer_wait_q.qsize()) #logger.debug("Updating msg index label text: {}".format(label_text)) self._msg_idx_label.setText(label_text) #self._msg_idx_label.setAlignment(Qt.AlignCenter) def _clear_msg_idx_label(self): label_text = '-' * (len(str(self._buffer_size)) + 1) + '/' + '-' * ( len(str(self._buffer_size)) + 1) + ' [-]' #logger.debug("label text: {}".format(label_text)) self._msg_idx_label.setText(label_text) self._msg_idx_label.setAlignment(Qt.AlignCenter) def _move_viewport(self, delta): # Enforce displayed msg idx wrapping if len(self._bufferd_msgs) == 0: # The queue is empty self._displayed_msg_idx = -1 else: # There are messages in the queue # These are special cases for the page size commands (PAGE_UP, PAGE_DOWN) if (self._displayed_msg_idx == 0) and (delta == -1 * self._buf_page_size): # if we are displaying the top of the buffer and we get a PAGE_UP, go to the end of the buffer self._displayed_msg_idx = len(self._bufferd_msgs) - 1 elif (self._displayed_msg_idx == len(self._bufferd_msgs) - 1) and (delta == self._buf_page_size): # if we are displaying the bottom of the buffer and we get a PAGE_DOWN, go to the top of the buffer self._displayed_msg_idx = 0 elif (self._displayed_msg_idx < self._buf_page_size) and (delta == -1 * self._buf_page_size): # if we are displaying a location that is less than the page size away from the top of the buffer, # and we get a PAGE_UP, go to the top of the buffer self._displayed_msg_idx = 0 elif (self._displayed_msg_idx > len(self._bufferd_msgs) - 1 - self._buf_page_size) and \ delta == self._buf_page_size: # if we are displaying a location that is within a page size of the end of the bugger, # and we get a PAGE_DOWN, go to the bottom of the buffer self._displayed_msg_idx = len(self._bufferd_msgs) - 1 else: # Normal cases, we are moving by just one location up or down self._displayed_msg_idx += delta if self._displayed_msg_idx < 0: # we are beyond the top of the buffer, wrap to end self._displayed_msg_idx = len(self._bufferd_msgs) - 1 if self._displayed_msg_idx > len(self._bufferd_msgs) - 1: # we are beyond the end of the buffer, wrap to beginning (home) self._displayed_msg_idx = 0 self._display_viewport() def _format_text(self, fg, bg, bold): if self._widget: if bg and fg: self._widget.setStyleSheet( "color: {}; background-color: {};".format(fg, bg)) elif bg: self._widget.setStyleSheet("background-color: {};".format(bg)) elif fg: self._widget.setStyleSheet("color: {};".format(fg)) if bold: font = QFont() font.setBold(True) self._widget.setFont(font) def _display_viewport(self): self.clearMessage() # We ignore the timeout when we are moving through the buffer msg, timeout, fg, bg, bold, time_not_used = self._bufferd_msgs[ self._displayed_msg_idx] self._widget = QLabel(msg) self._format_text(fg, bg, bold) self.addWidget(self._widget, stretch=10) self._update_msg_idx_label() def _add_key_modifiers(self, key=None): # https://stackoverflow.com/questions/8772595/how-to-check-if-a-keyboard-modifier-is-pressed-shift-ctrl-alt if key: QModifiers = QApplication.keyboardModifiers() self._key_modifiers = [] if (QModifiers & Qt.ControlModifier) == Qt.ControlModifier: self._key_modifiers.append('control') if (QModifiers & Qt.AltModifier) == Qt.AltModifier: self._key_modifiers.append('alt') if (QModifiers & Qt.ShiftModifier) == Qt.ShiftModifier: self._key_modifiers.append('shift') new_key = '' for modifier in self._key_modifiers: new_key += modifier + '-' new_key += key return (new_key) @pyqtSlot() def _timer_wait_q_emptied(self): print("WAIT QUEUE EMPTIED") def _msg_timeout_fired(self, timed_msg=False): """Remove the widget whose timer fired""" logger.debug("SingleShot Timer Fired") if self._timer_progressbar_update: self._timer_progressbar_update.stop() # If the progressbar starts filled rather than empty, # it will end filled, so clear it with this call. # If the progressbar starts empty rather than filled, # this call is superfluous. self._msg_idx_label.removeProgressBar() self._timer = None if not self._timer_wait_q.empty(): msg_entry = self._timer_wait_q.get() logger.debug("Dequeued waiting message: {}".format(msg_entry[0])) self._buffer_this_entry(msg_entry) if self._timer_wait_q.empty(): if self._timer_wait_q_emptied_signal: self._timer_wait_q_emptied_signal.empty() else: if timed_msg: # This was a msg with a non-zero timeout, so clear it self.clearMessage() def _update_progressbar(self): self._msg_idx_label.updateProgress(self._progressbar_delta) self._timer_progressbar_update.start(1000) def _buffer_this_entry(self, msg_entry): msg, timeout, fg, bg, bold = msg_entry self.clearMessage() self._widget = QLabel(msg) logger.debug("Displaying & Buffering Msg: {}".format(msg)) self._format_text(fg, bg, bold) if timeout > 0: logger.debug("... timeout > 0...") if self._timer is None: logger.debug("... no timer running...") # No currently active timer, so we set one up... # but first lets setup the countdown progressbar if timer is long enough if timeout > 2000: # 2 seconds logger.debug("... timer greater than 2 seconds...") self._progressbar_delta = 1 / (timeout / 1000) logger.info("Setting up ProgressBar Timer: {}".format( self._progressbar_delta)) self._msg_idx_label.updateProgress(self._progressbar_delta) self._timer_progressbar_update = QTimer() self._timer_progressbar_update.timeout.connect( self._update_progressbar) self._timer_progressbar_update.start(1000) self._timer = QTimer() self._timer.singleShot( timeout, lambda: self._msg_timeout_fired(timed_msg=True)) else: logger.debug( "... there is a pending timer, so wait queue this msg") # There is a pending timer, so put this message on the wait queue self._timer_wait_q.put((msg, timeout, fg, bg, bold)) else: logger.debug( "... timer = 0, setting up a 1.5 second single shot timer...") # timeout == 0 self._process_zero_timeout_timer = QTimer() self._process_zero_timeout_timer.singleShot( 1500, lambda: self._msg_timeout_fired(timed_msg=False)) self._buffer_entry((msg, timeout, fg, bg, bold)) logger.debug("...adding message widget to statusbar...") self.addWidget(self._widget, stretch=10) self._update_msg_idx_label() def _enqueue_to_wait_q(self, msg, timeout, fg, bg, bold): # if timeout == 0: # # We need some small timeout here so the entry gets removed from the # # wait queue by the firing of the singleShot timer # timeout = 1000 # 1000 = 1 second msg_entry = (msg, timeout, fg, bg, bold) self._timer_wait_q.put(msg_entry) self._update_msg_idx_label() def _save_message_buffer_to_file(self, msg_buffer_file): logger.info( "Writing message buffer to file '{}'".format(msg_buffer_file)) width = len(str(len(self._bufferd_msgs))) + 1 fmt = "{" + "0:0{}d".format(width) + "}" idx = 0 with open(msg_buffer_file, 'w') as mbf: for entry in self._bufferd_msgs: idx_str = fmt.format(idx) msg, timeout, fg, bg, bold, timestamp = entry mbf.writelines( "{}: {} {} msecs FG:{} BG:{} BOLD:{} @ {}\n".format( idx_str, msg, timeout, fg, bg, bold, timestamp)) idx += 1 def _statusbar_help_dialog(self): self.about_dialog = AboutDialog(self) self.about_dialog.exec_() # ------------------------------------------------------------------------- # PyQtMessageBar's Public API # ------------------------------------------------------------------------- # Properties -------------------------------------------------------------- # @property # def buffersize(self): # return(self._buffer_size) # Methods ----------------------------------------------------------------- def getWaitQueueDepth(self): """Returns the wait queue depth (int)""" if self._timer_wait_q.empty(): return (0) else: return (self._timer_wait_q.qsize()) def waitQueueIsEmpty(self): """Returns True if wait queue is empty, false otherwise.""" return (self._timer_wait_q.empty()) # def setBufferSize(self, size_int): # """Set message buffer size. # Parameters # ---------- # size_int : int # This is the number of messages that can be buffered before the oldest # message is lost # Returns # ------- # Nothing # Nothing # Note that the buffer size is never allowed to go below the default buffer size. # Which is the constant **pyqtmessagebar.DEFAULT_BUFFER_SIZE**. # """ # if isinstance(size_int, int): # if size_int < DEFAULT_BUFFER_SIZE: # size_int = DEFAULT_BUFFER_SIZE # else: # size_int = DEFAULT_BUFFER_SIZE # self._buffer_size = size_int def getBufferSize(self): """Returns the current message buffer size.""" return (self._buffer_size) # def setEnableSeparators(self, flag): # """Sets the enable separators boolean to flag value. # The effect of this flag being True is that any # widgets added to the statusbar via the addPermanentWidget() # call will have a separator placed to the left of the widget added. # """ # if isinstance(flag, bool): # self._enable_separators = flag def getEnableSeparators(self): """Returns the value of the enable separators flag.""" return (self._enable_separators) # def setEnableDarkIcon(self, help_icon_indicator): # """Sets the built-in help icon to the help_icon_indicator. # Parameters # ---------- # help_icon_indicator : str constant # Must be one of the three following values module constants # BUILT_IN_HELP_ICON_LIGHT, BUILT_IN_HELP_ICON_DARK, or # BUILT_IN_HELP_ICON_TWO_TONE. The default is LIGHT. # Note the built-in help icons can be replaced using specifying the # **help_icon_file** parameter to the **PyQtMessageBar** constructor. # """ # if isinstance(help_icon_indicator, str): # if help_icon_indicator in HELP_ICON_INDICATORS: # self._built_in_help_icon = help_icon_indicator # else: # self._built_in_help_icon = BUILT_IN_HELP_ICON_LIGHT # else: # self._built_in_help_icon = BUILT_IN_HELP_ICON_LIGHT def getBuiltInHelpIcon(self): """Returns the value of the built-in help icon indicator.""" return (self._built_in_help_icon) def setProgressBarColor(self, color_text): """Sets the color of the countdown timer progress bar to the color value specified by color_text. Note that color_text can be any color representation supported by the `colour package <https://pypi.org/project/colour/>`_. """ self._msg_idx_label.setProgressBarColor(color_text) def getProgressBarColor(self): """Returns the value of the countdown timer progress bar color.""" self._progressbar_color = self._msg_idx_label.getProgressBarColor() return (self._progressbar_color) def clearMessage(self): """This method added to distinguish clearing (or removing) the currently displayed message and removing some other custom widget that the user may have added.""" if self._widget: self.removeWidget(self._widget) self._widget = None self._clear_msg_idx_label() # ------------------------------------------------------------------------- # Here a three helper methods that serve as shorthand for setting up # common color schemes for certain types of messages: # showMessage(self, msg, timeout=0, fg=None, bg=None, bold=False) # ------------------------------------------------------------------------- def showMessageError(self, msg): """This is a helper method that serves as a short-hand call to **showMessage()** that configures the message to be an error message which looks like: Yellow FG, Brick Red BG, Bold Text, No Timeout .. image:: _static/error_message.png :align: center """ self.showMessage(msg, fg='#ffff00', bg='#aa0000', bold=True) def showMessageWarning(self, msg): """This is a helper method that serves as a short-hand call to **showMessage()** that configures the message to be an warning message which looks like: Black FG, Yellow BG, Bold Text, No Timeout .. image:: _static/warning_message.png :align: center """ self.showMessage(msg, fg='#000000', bg='#ffff00', bold=True) def showMessageAskForInput(self, msg): """his is a helper method that serves as a short-hand call to **showMessage()** that configures the message to be an affirmation message which looks like: White FG, Forest Green BG, Bold Text, No Timeout .. image:: _static/affirmation_message.png :align: center """ self.showMessage(msg, fg='#ffffff', bg='#005500', bold=True) # ------------------------------------------------------------------------- # Overriding these Qt QStatusBar methods # ------------------------------------------------------------------------- def keyPressEvent(self, e): """ **Overrides Qt keyPressEvent()** This method overrides the Qt keyPressEvent method to add keyboard input processing. Any keys NOT processed here are passed on to the base class implementation of keyPressEvent(). See `QWidget keyPressEvent docs <https://doc.qt.io/qt-5/qwidget.html#keyPressEvent>`_. The following keys are recognized, all other keys are passed to the base class implementation of keyPressEvent(): * Qt.Key_Up * Qt.Key_Home. * Qt.Key_Down * Qt.Key_End * Qt.Key_PageUp * Qt.Key_PageDown * control-alt-X * control-alt-shift-X * control-alt-S * control-alt-shift-S .. note:: The two key sequences based on the S key, will be disabled if the **PyQtMessageBar** constructor is called without specifying the **save_msg_buffer_dir** parameter. Parameters ---------- e : QEvent This event returns the key via the **e.key()** method call. Returns ------- Nothing None, but does call the appropriate PyQtMessageBar method to handle recognized keys. """ if e.type() == QEvent.KeyPress: #print("SMARTSTATUSBAR: press {}".format(e.key())) key = e.key() if key in KEY_MAP: if key in KEY_MOVE: # https://doc.qt.io/qt-5/qt.html#Key-enum if key == Qt.Key_Up: #print("UP ARROW") if self._widget: # only if there is a widget being displayed to we decrement # if there is no widget being displayed the idx is already # pointing to the bottom of the buffer, so no need to decrement. delta = -1 else: delta = 0 if key == Qt.Key_Down: #print("DOWN ARROW") delta = 1 if key == Qt.Key_PageUp: #print("PAGE UP") delta = -1 * self._buf_page_size if key == Qt.Key_PageDown: #print("PAGE DOWN") delta = self._buf_page_size if key == Qt.Key_Home: #print("HOME") self._displayed_msg_idx = 0 delta = 0 if key == Qt.Key_End: #print("END") self._displayed_msg_idx = len(self._bufferd_msgs) - 1 delta = 0 self._move_viewport(delta) elif key in KEY_CMDS: if key == Qt.Key_X: key = self._add_key_modifiers('X') if key == 'control-alt-shift-X': logger.debug("Deleting entire message buffer...") self._displayed_msg_idx = -1 self._bufferd_msgs.clear() self.clearMessage() if key == 'control-alt-X': entry_to_throw_away = self._bufferd_msgs.pop( self._displayed_msg_idx) logger.debug("Deleted message @ idx {}: {}".format( self._displayed_msg_idx, entry_to_throw_away)) self._move_viewport(0) if key == Qt.Key_S: if self._save_msg_buffer_dir: key = self._add_key_modifiers('S') if key == 'control-alt-shift-S': msg_buffer_file = datetime.now().strftime( "%Y-%m-%d-%Hh%Mm%Ss%f") + '.msgs' start_file_name = os.path.join( self._save_msg_buffer_dir, msg_buffer_file) msg_buffer_file, _ = QFileDialog.getSaveFileName( self, 'Save Message Buffer File', start_file_name, "Msg Buf Files (*.msgs)") self._save_message_buffer_to_file( msg_buffer_file) if key == 'control-alt-S': msg_buffer_file = os.path.join( self._save_msg_buffer_dir, datetime.now().strftime( "%Y-%m-%d-%Hh%Mm%Ss%f") + '.msgs') self._save_message_buffer_to_file( msg_buffer_file) else: # If we do not act on the key, then we need to call the base class's # implementation of keyPressEvent() so some other widget may act on it. super(PyQtMessageBar, self).keyPressEvent(e) def addPermanentWidget(self, widget, stretch=0): """**Overrides QStatusBar.addPermanentWidget()** This method overrides the base class implementation so we can add the ability to insert a separator if so enabled. Then the base class addPermantWidget() is called.""" if self._enable_separators: super(PyQtMessageBar, self).addPermanentWidget(VLine()) super(PyQtMessageBar, self).addPermanentWidget(widget, stretch) def insertPermanentWidget(self, widget, stretch=0): """**Overrides QStatusBar.insertPermanentWidget()** This method overrides the base class implementation so we can add the ability to insert a separator if so enabled. Then the base class insertPermanentWidget() is called.""" if self._enable_separators: super(PyQtMessageBar, self).insertPermanentWidget(VLine()) super(PyQtMessageBar, self).insertPermanentWidget(widget, stretch) def currentMessage(self): """**Overrides QStatusBar.currentMessage()** Returns the currently displayed message or the empty string if there is no currently dislayed message.""" if self._widget: msg = self._widget.text() else: msg = '' return (msg) def showMessage(self, msg, timeout=0, fg=None, bg=None, bold=False): """**Overrides QStatusBar.showMessage()** This method completely replaces Qt's QStatusBar.ShowMessage() because we need inner knowledge of when messages timeout. We also add colors for foreground (fg), background (bg), and a flag for enabling bold text. Note, we do not provide the stretch parameter because we control the layout, at least we think we do. :) Parameters ---------- msg : str The statusbar message to be displayed and buffered. timeout : int If non-zero the message will have a timeout and be removed from the statusbar display once the timeout expires. fg : str (color) The foreground color (text color) of the message to be displayed. The color value can be any color representation supported by the `colour package <https://pypi.org/project/colour/>`_. If not specified, the system default color is used. bg : str (color) The background color of the message to be displayed. The color value can be any color representation supported by the `colour package <https://pypi.org/project/colour/>`_. If not specified, the system default color is used. bold: bool If True the text of the message will be bold. """ # pad msg with a leading space... msg = ' ' + msg logger.debug("showMessage called...") if self._widget: logger.debug("msg widget already being displayed...") # There is a msg being displayed... # If there are other pending messages that have not yet been displayed, # we enqueue this message to the wait queue. # If msg being displayed has no timeout, we clear it and show this msg. if self._timer: logger.debug("Wait Queueing message: '{}'".format(msg)) # We are waiting the currently diplayed message to timeout, # or we have older pending messages that have not yet been displayed; # so put this message on the wait queue self._enqueue_to_wait_q(msg, timeout, fg, bg, bold) return () else: logger.debug("No wait Q timer running...") # No message being displayed, however, we may have previous message in the wait queue # being processed by the singleShot Timer; only if the wait queue is empty do we # process the caller's message; otherwise, we put it on the wait queue. if self._timer_wait_q.empty(): logger.debug( "Wait Q empty, clear displayed message and buffer new msg.") # self.clearMessage() -->> This is called first thing in _buffer_this_entry() below self._buffer_this_entry((msg, timeout, fg, bg, bold)) else: logger.debug("Wait Q NOT empty, enqueue current message...") self._enqueue_to_wait_q(msg, timeout, fg, bg, bold)