"""QLed module.
Based on Robert Kent's LED Widget for the PyQt Framework, available on
https://pypi.python.org/pypi/QLed or https://github.com/jazzycamel/QLed.
"""
import os as _os
from colorsys import rgb_to_hls, hls_to_rgb
from qtpy.QtWidgets import QApplication, QWidget, QGridLayout, \
QStyleOption, QFrame
from qtpy.QtGui import QPainter, QColor
from qtpy.QtCore import Signal, Qt, QSize, QTimer, QByteArray, \
QRectF, Property, Q_ENUMS, QFile
from qtpy.QtSvg import QSvgRenderer
[docs]
class ShapeMap:
"""Shape enum mapping class."""
Circle = 1
Round = 2
Square = 3
Triangle = 4
Rectangle = 5
[docs]
class QLed(QFrame, ShapeMap):
"""QLed class."""
ShapeMap = ShapeMap
Q_ENUMS(ShapeMap)
abspath = _os.path.abspath(_os.path.dirname(__file__))
shapesdict = dict()
f = QFile(_os.path.join(abspath, 'resources/led_shapes/circle.svg'))
if f.open(QFile.ReadOnly):
shapesdict[ShapeMap.Circle] = str(f.readAll(), 'utf-8')
f.close()
f = QFile(_os.path.join(abspath, 'resources/led_shapes/round.svg'))
if f.open(QFile.ReadOnly):
shapesdict[ShapeMap.Round] = str(f.readAll(), 'utf-8')
f.close()
f = QFile(_os.path.join(abspath, 'resources/led_shapes/square.svg'))
if f.open(QFile.ReadOnly):
shape = str(f.readAll(), 'utf-8')
shapesdict[ShapeMap.Square] = shape
shapesdict[ShapeMap.Rectangle] = shape
f.close()
f = QFile(_os.path.join(abspath, 'resources/led_shapes/triangle.svg'))
if f.open(QFile.ReadOnly):
shapesdict[ShapeMap.Triangle] = str(f.readAll(), 'utf-8')
f.close()
Green = QColor(15, 105, 0)
DarkGreen = QColor(20, 80, 10)
LightGreen = QColor(0, 140, 0)
Red = QColor(207, 0, 0)
DarkRed = QColor(120, 0, 0)
Blue = QColor(0, 0, 255)
Gray = QColor(90, 90, 90)
SelColor = QColor(0, 0, 0)
NotSelColor1 = QColor(251, 244, 252)
NotSelColor2 = QColor(173, 173, 173)
Yellow = QColor(210, 205, 0)
clicked = Signal()
selected = Signal(bool)
def __init__(self, parent=None, **kwargs):
"""Class constructor."""
super().__init__(parent, **kwargs)
self.m_state = 0
self.m_stateColors = [self.Red, self.Green]
self.m_dsblColor = self.Gray
self.m_shape = self.ShapeMap.Circle
self._pressed = False
self._isselected = False
self.renderer = QSvgRenderer()
[docs]
def getState(self):
"""Value property getter."""
return self.m_state
[docs]
def setState(self, value):
"""Value property setter."""
self.m_state = value
self.update()
state = Property(bool, getState, setState)
[docs]
def getOnColor(self):
"""On color property getter."""
return self.m_stateColors[1]
[docs]
def setOnColor(self, newColor):
"""On color property setter."""
self.m_stateColors[1] = newColor
self.update()
onColor = Property(QColor, getOnColor, setOnColor)
[docs]
def getOffColor(self):
"""Off color property getter."""
return self.m_stateColors[0]
[docs]
def setOffColor(self, newColor):
"""Off color property setter."""
self.m_stateColors[0] = newColor
self.update()
offColor = Property(QColor, getOffColor, setOffColor)
@property
def stateColors(self):
"""Color list property getter."""
return list(self.m_stateColors)
@stateColors.setter
def stateColors(self, new_colors):
"""Color list property setter."""
if not isinstance(new_colors, (list, tuple)) or\
len(new_colors) < 2 or not isinstance(new_colors[0], QColor):
return
self.m_stateColors = list(new_colors)
[docs]
def getDsblColor(self):
"""Disabled color property getter."""
return self.m_dsblColor
[docs]
def setDsblColor(self, newColor):
"""Disabled color property setter."""
self.m_dsblColor = newColor
self.update()
dsblColor = Property(QColor, getDsblColor, setDsblColor)
[docs]
def getShape(self):
"""Shape property getter."""
return self.m_shape
[docs]
def setShape(self, newShape):
"""Shape property setter."""
self.m_shape = newShape
self.update()
shape = Property(ShapeMap, getShape, setShape)
[docs]
def sizeHint(self):
"""Return the base size of the widget according to shape."""
if self.m_shape == self.ShapeMap.Triangle:
return QSize(48, 36)
elif self.m_shape == self.ShapeMap.Round:
return QSize(72, 36)
elif self.m_shape == self.ShapeMap.Rectangle:
return QSize(36, 72)
return QSize(36, 36)
[docs]
def adjust(self, r, g, b):
"""Adjust the color to set on svg code."""
def normalise(x):
return x/255.0
def denormalise(x):
if x <= 1:
return int(x*255.0)
else:
return 255.0
(h, l, s) = rgb_to_hls(normalise(r), normalise(g), normalise(b))
(nr, ng, nb) = hls_to_rgb(h, l*1.5, s)
return (denormalise(nr), denormalise(ng), denormalise(nb))
[docs]
def getRGBfromQColor(self, qcolor):
"""Convert QColors to a tupple of rgb colors to set on svg code."""
redhex = qcolor.red()
greenhex = qcolor.green()
bluehex = qcolor.blue()
return (redhex, greenhex, bluehex)
[docs]
def paintEvent(self, event):
"""Handle appearence of the widget on state updates."""
self.style().unpolish(self)
self.style().polish(self)
option = QStyleOption()
option.initFrom(self)
h = option.rect.height()
w = option.rect.width()
if self.m_shape in (self.Triangle, self.Round, self.Rectangle):
aspect = (4/3.0) if self.m_shape == self.ShapeMap.Triangle \
else 2.0 if self.m_shape == self.ShapeMap.Round \
else (1/2.0)
ah = w/aspect
aw = w
if ah > h:
ah = h
aw = h*aspect
x = abs(aw-w)/2.0
y = abs(ah-h)/2.0
bounds = QRectF(x, y, aw, ah)
else:
size = min(w, h)
x = abs(size-w)/2.0
y = abs(size-h)/2.0
bounds = QRectF(x, y, size, size)
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
ind = self.m_state % len(self.m_stateColors)
dark_r, dark_g, dark_b = self.getRGBfromQColor(self.m_stateColors[ind])
if not self.isEnabled():
dark_r, dark_g, dark_b = self.getRGBfromQColor(self.m_dsblColor)
sel1_r, sel1_g, sel1_b = self.getRGBfromQColor(self.SelColor)
sel2_r, sel2_g, sel2_b = self.getRGBfromQColor(self.SelColor)
opc = '1.000'
if not self.isSelected():
sel1_r, sel1_g, sel1_b = self.getRGBfromQColor(self.NotSelColor1)
sel2_r, sel2_g, sel2_b = self.getRGBfromQColor(self.NotSelColor2)
opc = '0.145'
dark_str = "rgb(%d,%d,%d)" % (dark_r, dark_g, dark_b)
light_str = "rgb(%d,%d,%d)" % self.adjust(dark_r, dark_g, dark_b)
sel1_str = "rgb(%d,%d,%d)" % (sel1_r, sel1_g, sel1_b)
sel2_str = "rgb(%d,%d,%d)" % (sel2_r, sel2_g, sel2_b)
shape_bytes = bytes(self.shapesdict[self.m_shape] % (
sel1_str, opc, sel2_str, dark_str, light_str), 'utf-8')
self.renderer.load(QByteArray(shape_bytes))
self.renderer.render(painter, bounds)
[docs]
def mousePressEvent(self, event):
"""Handle mouse press event."""
self._pressed = True
super().mousePressEvent(event)
[docs]
def mouseReleaseEvent(self, event):
"""Handle mouse release event."""
if self._pressed:
self._pressed = False
self.clicked.emit()
super().mouseReleaseEvent(event)
[docs]
def toggleState(self):
"""Toggle state property."""
self.m_state = 0 if self.m_state else 1
self.update()
[docs]
def isSelected(self):
"""Return selected state of object."""
return self._isselected
[docs]
def setSelected(self, sel):
"""Configure selected state of object."""
self._isselected = bool(sel)
self.selected.emit(self._isselected)
self.update()
[docs]
def toggleSelected(self):
"""Toggle isSelected property."""
self.setSelected(not self.isSelected())
if __name__ == "__main__":
from sys import argv, exit
class Test(QWidget):
"""Test class."""
def __init__(self, parent=None):
"""Test class constructor."""
QWidget.__init__(self, parent)
self.setWindowTitle("QLed Test")
_l = QGridLayout()
self.setLayout(_l)
colorsdict = {'Red': QColor(207, 0, 0),
'Green': QColor(15, 105, 0),
'Yellow': QColor(210, 205, 0),
'Orange': QColor(218, 70, 21),
'Purple': QColor(135, 0, 131),
'Blue': QColor(0, 3, 154)}
self.leds = []
for row, shape in enumerate(QLed.shapesdict.keys()):
for col, color in enumerate(colorsdict.keys()):
led = QLed(self)
led.setOnColor(colorsdict[color])
led.setOffColor(QColor(90, 90, 90))
led.setShape(shape)
_l.addWidget(led, row, col, Qt.AlignCenter)
self.leds.append(led)
self.toggleLeds()
def toggleLeds(self):
"""Toggle leds state."""
for led in self.leds:
led.toggleState()
QTimer.singleShot(1000, self.toggleLeds)
a = QApplication(argv)
t = Test()
t.show()
t.raise_()
exit(a.exec_())