"""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_())