"""
.. 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)