Source code for pyqtmessagebar

"""
.. module:: pyqtmessagebar

.. moduleauthor: E.R. Uber <eruber@gmail.com>

**PyQtMessageBar**

This messagebar subclasses the standard Qt Statusbar to implement a custom 
statusbar with the following capabilities:
	
	* Buffers StatusBar messages & supports keyboard input to scroll through
	  the buffered messages
	* Qt.Key_Up
	* Qt.Key_Home
	* Qt.Key_Down
	* Qt.Key_End
	* Qt.Key_PageUp
	* Qt.Key_PageDown
	* Deletion of individual messages
	* Deletion of entire message buffer
	* Saving message buffer to a file
	* Multiple messages with timeouts are placed on a wait queue

In Qt, only the QWidget::setFocusPolicy() function affects click-to-focus.

LOGGING
-------
This module creates a logging handler named 'PyQtMessageBar' and always
configures a Null handler.
See `Configuring Logging for a Library <https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library>`_.

REFERENCES
----------
The following Qt references proved helpful:

	* `Qt StatusBar <https://doc.qt.io/qt-5/qstatusbar.html>`_
	* `Qt Keyboard Enumerations <https://doc.qt.io/qt-5/qt.html#Key-enum>`_


LICENSE GPL
-----------
This file is part of **PyQtMessageBar**.

PyQtMessageBar 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.

PyQtMessageBar 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 PyQtMessageBar in the file named LICENSE_GPL. If not, 
see <https://www.gnu.org/licenses/>.

COPYRIGHT (C) 2020 E.R. Uber (eruber@gmail.com)

Class PyQtMessageBar
--------------------

"""
# ----------------------------------------------------------------------------
# ------------------------ Python Standard Library ---------------------------
# ----------------------------------------------------------------------------
import os
import queue  # This is a First-In-First-Out Queue
from datetime import datetime
import logging

# ----------------------------------------------------------------------------
# -------------------------- Third Party Packages ----------------------------
# ----------------------------------------------------------------------------
from PyQt5.Qt import Qt
from PyQt5.QtCore import QEvent, QSize, QByteArray, QFileInfo, QTimer
from PyQt5.QtGui import QIcon, QImage, QPixmap, QFont
from PyQt5.QtWidgets import (
	QStatusBar, QApplication, QLabel, QFrame, QPushButton, QSizePolicy,
	QFileIconProvider, QFileDialog, 
	)

from colour import Color

# ----------------------------------------------------------------------------
# -------------------------- Application Packages ----------------------------
# ----------------------------------------------------------------------------
from pyqtmessagebar.pyqtlineeditprogressbar import PyQtLineEditProgressBar

import pyqtmessagebar.pyqtlineeditprogressbar as PYQTPROGBAR


# ----------------------------------------------------------------------------
# ----------------------- Module Global & Constants --------------------------
# ----------------------------------------------------------------------------
from pyqtmessagebar.gfxmodule import questionmark_opened_data as LQM
from pyqtmessagebar.gfxmodule import questionmark_filled_data as DQM
from pyqtmessagebar.aboutdialog import AboutDialog

# ----------------------------------------------------------------------------
# --------------------- Module Classes & Functions ---------------------------
# ----------------------------------------------------------------------------
DEFAULT_BUFFER_SIZE = 100  # Message Queue can never get smaller than this
DEFAULT_PAGE_SIZE   = 10   # As used by Key_PageUp and Key_PageDown

DEFAULT_BG_COLOR_QUEUED_MSGS_WAITING = 'rgba(170,255,255,255)'

#https://doc.qt.io/qt-5/qt.html#Key-enum
KEY_MOVE = [
	Qt.Key_Up,         # -1 Entry
	Qt.Key_Down,       # +1 Entry
	Qt.Key_Home,       # 0th Entry
	Qt.Key_End,        # Max Entry
	Qt.Key_PageUp,     # -pagesize Entries
	Qt.Key_PageDown,   # +pagesize Entries
]

KEY_CMDS = [
	Qt.Key_X,          # Delete current message, +ALT delete all messages
	Qt.Key_S,          # +ALT Save queue to file
	Qt.Key_L,          # +ALT Load queue from file	
]

KEY_MAP = KEY_MOVE + KEY_CMDS

# ----------------------------------------------------------------------------
[docs]def iconFromBinary(binary_str, img_format='PNG'): """Returns a Qt Icon from a binary graphics file. Parameters --------- binary_str : bytes A binary string of bytes that represents a graphic image. img_format : str, optional A string indicating the type of image presented by the binary bytes. Returns ------- Qt Icon A Qt icon object """ pixmap = QPixmap() pixmap.loadFromData(QByteArray(binary_str), img_format) icon = QIcon(pixmap) return icon
# ----------------------------------------------------------------------------
[docs]class VLine(QFrame): """A simple VLine, like the one you get from Qt Designer. See `How to Add Separator to StatusBar <https://www.geeksforgeeks.org/pyqt5-how-to-add-separator-in-status-bar/>`_. """ def __init__(self): super(VLine, self).__init__() self.setFrameShape(self.VLine|self.Sunken)
# ---------------------------------------------------------------------------- # https://doc.qt.io/qt-5/qstatusbar.html class PyQtMessageBar(QStatusBar): def __init__(self, parent=None, msg_buffer_size=DEFAULT_BUFFER_SIZE, enable_separators=False, help_icon_file=None, enable_dark_help_icon=False, parent_logger_name=None, save_msg_buffer_dir='.', ): """Constructor for the PyQtMessageBar class 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. enable_dark_help_icon : bool, optional If True and help_icon_file is not specified, then the dark built-in help icon will be used. parent_logger_name : str, optional If specified, this logger will be used to enable logging; otherwise, all log calls will go to the Null Handler. 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. """ 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._enable_dark_help_icon = enable_dark_help_icon self._parent_loggger_name = parent_logger_name self._save_msg_buffer_dir = save_msg_buffer_dir # Initializing internal data self._progressbar_delta = None self._timer_progressbar_update = None if self._parent_loggger_name: self._logr = logging.getLogger(self._parent_loggger_name + '.PyQtMessageBar') else: self._logr = logging.getLogger('PyQtMessageBar') # This should keep any PyQtMessageBar logging events of warning or above # from being output to stderr if the application using this module # provides no logging support. # See: https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library self._logr.addHandler(logging.NullHandler()) 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 self._logr.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._enable_dark_help_icon: self._icon = iconFromBinary(DQM) else: self._icon = iconFromBinary(LQM) 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) self._logr.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 self._logr.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()) #self._logr.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) + ' [-]' #self._logr.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) def _msg_timeout_fired(self, timed_msg=False): """Remove the widget whose timer fired""" self._logr.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() self._logr.debug("Dequeued waiting message: {}".format(msg_entry[0])) self._buffer_this_entry(msg_entry) 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) self._logr.debug("Displaying & Buffering Msg: {}".format(msg)) self._format_text(fg, bg, bold) if timeout > 0: self._logr.debug("... timeout > 0...") if self._timer is None: self._logr.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 self._logr.debug("... timer greater than 2 seconds...") self._progressbar_delta = 1 / (timeout/1000) self._logr.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: self._logr.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: self._logr.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)) self._logr.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): self._logr.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 # ------------------------------------------------------------------------- # Yes, it is true that the Pythonic way is to expose properties that have # both setters and getters; however, this module subclasses Qt's QStatusBar # and we endeavor to expose an interface that looks PyQt-ish rather than # Pythonic, so we instead exposed the traditional Qt set and get methods # separately. # ------------------------------------------------------------------------- # Methods ----------------------------------------------------------------- def setBufferSize(self, size_int): """Queue size never goes below the default queue 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): return(self._buffer_size ) def setEnableSeparators(self, flag): if isinstance(flag, bool): self._enable_separators = flag def getEnableSeparators(self): return(self._enable_separators) def setEnableDarkIcon(self, flag): if isinstance(flag, bool): self._enable_dark_help_icon = flag else: self._enable_dark_help_icon = False def getEnableDarkIcon(self): return(self._enable_dark_help_icon) def setProgressBarColor(self, color_text): self._msg_idx_label.setProgressBarColor(color_text) def getProgressBarColor(self): self._progressbar_color = self._msg_idx_label.getProgressBarColor() return(self._progressbar_color) def clearMessage(self): """This method added to distinguish clearing (or removing) the msg 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() # ------------------------------------------------------------------------- # These are a few 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): """ Yellow FG, Brick Red BG, Bold Text, No Timeout""" self.showMessage(msg, fg='#ffff00', bg='#aa0000', bold=True) def showMessageWarning(self, msg): """ Black FG, Yellow BG, Bold Text, No Timeout""" self.showMessage(msg, fg='#000000', bg='#ffff00', bold=True) def showMessageAskForInput(self, msg): """ White FG, Forest Green BG, Bold Text, No Timeout""" self.showMessage(msg, fg='#ffffff', bg='#005500', bold=True) # ------------------------------------------------------------------------- # Overriding these Qt QStatusBar methods # ------------------------------------------------------------------------- def keyPressEvent(self, e): """ https://doc.qt.io/qt-5/qwidget.html#keyPressEvent It is very important that you call the base class implementation if you do not act upon the key. """ 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': self._logr.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) self._logr.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): """We just add the ability to insert a separator if so enabled""" if self._enable_separators: super(PyQtMessageBar, self).addPermanentWidget(VLine()) super(PyQtMessageBar, self).addPermanentWidget(widget, stretch) def insertPermanentWidget(self, widget, stretch=0): """We just add the ability to insert a separator if so enabled""" if self._enable_separators: super(PyQtMessageBar, self).insertPermanentWidget(VLine()) super(PyQtMessageBar, self).insertPermanentWidget(widget, stretch) def currentMessage(self): if self._widget: msg = self._widget.text() else: msg = '' return(msg) def showMessage(self, msg, timeout=0, fg=None, bg=None, bold=False): """We completely REPLACE Qt's standard QStatusBar.ShowMessage() because we need inner knowledge of when messages timeout. We also add colors for foreground, fg, and background, bg. Note, we do not provide the stretch parameter because we control it.""" # pad msg with a leading space... msg = ' ' + msg self._logr.debug("showMessage called...") if self._widget: self._logr.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: self._logr.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: self._logr.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(): self._logr.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: self.debug("Wait Q NOT empty, enqueue current message...") self._enqueue_to_wait_q(msg, timeout, fg, bg, bold)