308 lines
10 KiB
Python
308 lines
10 KiB
Python
#----------------------------------------------------------------------
|
|
# Name: wx.lib.gizmos.ledctrl
|
|
# Purpose:
|
|
#
|
|
# Author: Robin Dunn
|
|
#
|
|
# Created: 26-Oct-2017
|
|
# Copyright: (c) 2018 by Total Control Software
|
|
# Licence: wxWindows license
|
|
# Tags:
|
|
#----------------------------------------------------------------------
|
|
"""
|
|
Widget to display a series of digits, spaces, dashes or colons in a segmented style.
|
|
"""
|
|
|
|
import wx
|
|
|
|
LED_ALIGN_LEFT = 0x01
|
|
LED_ALIGN_RIGHT = 0x02
|
|
LED_ALIGN_CENTER = 0x04
|
|
LED_ALIGN_MASK = 0x07
|
|
|
|
LED_DRAW_FADED = 0x08
|
|
|
|
|
|
class LEDNumberCtrl(wx.Control):
|
|
"""
|
|
The LEDNumberCtrl can be used to display a series of digits, (plus spaces,
|
|
colons or dashes,) using a style reminiscent of old-timey segmented
|
|
digital displays.
|
|
"""
|
|
|
|
# constants used internally
|
|
class const:
|
|
LINE1 = 1
|
|
LINE2 = 2
|
|
LINE3 = 4
|
|
LINE4 = 8
|
|
LINE5 = 16
|
|
LINE6 = 32
|
|
LINE7 = 64
|
|
DECIMALSIGN = 128
|
|
COLON = 256
|
|
|
|
DIGITS = {
|
|
'0': LINE1 | LINE2 | LINE3 | LINE4 | LINE5 | LINE6,
|
|
'1': LINE2 | LINE3,
|
|
'2': LINE1 | LINE2 | LINE4 | LINE5 | LINE7,
|
|
'3': LINE1 | LINE2 | LINE3 | LINE4 | LINE7,
|
|
'4': LINE2 | LINE3 | LINE6 | LINE7,
|
|
'5': LINE1 | LINE3 | LINE4 | LINE6 | LINE7,
|
|
'6': LINE1 | LINE3 | LINE4 | LINE5 | LINE6 | LINE7,
|
|
'7': LINE1 | LINE2 | LINE3,
|
|
'8': LINE1 | LINE2 | LINE3 | LINE4 | LINE5 | LINE6 | LINE7,
|
|
'9': LINE1 | LINE2 | LINE3 | LINE6 | LINE7,
|
|
'-': LINE7,
|
|
':': COLON,
|
|
}
|
|
DIGITALL = 0xFFFF
|
|
|
|
|
|
|
|
def __init__(self, *args, **kw):
|
|
"""
|
|
Create a new LEDNumberCtrl.
|
|
|
|
Both the normal constructor style with all parameters, or wxWidgets
|
|
2-phase style default constructor is supported. If the default
|
|
constructor is used then the Create method will need to be called
|
|
later before the widget can actually be used.
|
|
"""
|
|
if not args and not kw:
|
|
self._init_default()
|
|
else:
|
|
self._init_full(*args, **kw)
|
|
|
|
def _init_default(self):
|
|
super(LEDNumberCtrl, self).__init__()
|
|
self._init()
|
|
|
|
def _init_full(self, parent, id=wx.ID_ANY,
|
|
pos=wx.DefaultPosition, size=wx.DefaultSize,
|
|
style=LED_ALIGN_LEFT|LED_DRAW_FADED, name='ledctrl'):
|
|
super(LEDNumberCtrl, self).__init__(parent, id, pos, size, style, name=name)
|
|
self._init()
|
|
self._post_create()
|
|
|
|
|
|
def Create(self, parent, id=wx.ID_ANY,
|
|
pos=wx.DefaultPosition, size=wx.DefaultSize,
|
|
style=LED_ALIGN_LEFT|LED_DRAW_FADED, name='ledctrl'):
|
|
super(LEDNumberCtrl, self).Create(parent, id, pos, size, style, name=name)
|
|
return self._post_create()
|
|
|
|
|
|
def _init(self):
|
|
# set default attributes
|
|
self.m_alignment = LED_ALIGN_LEFT
|
|
self.m_lineMargin = -1
|
|
self.m_digitMargin = -1
|
|
self.m_lineLength = -1
|
|
self.m_lineWidth = -1
|
|
self.m_drawFaded = False
|
|
self.m_leftStartPos = -1
|
|
self.m_value = ''
|
|
|
|
|
|
def _post_create(self):
|
|
self.SetBackgroundColour(wx.BLACK)
|
|
self.SetForegroundColour(wx.GREEN)
|
|
self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
|
|
|
|
# flags
|
|
style = self.GetWindowStyle()
|
|
if style & LED_DRAW_FADED:
|
|
self.SetDrawFaded(True)
|
|
if style & LED_ALIGN_MASK:
|
|
self.SetAlignment(style & LED_ALIGN_MASK)
|
|
|
|
# event bindings
|
|
self.Bind(wx.EVT_ERASE_BACKGROUND, lambda evt: None)
|
|
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
|
self.Bind(wx.EVT_SIZE, self.OnSize)
|
|
|
|
return True
|
|
|
|
|
|
def GetAlignment(self):
|
|
return self.m_alignment
|
|
|
|
|
|
def GetDrawFaded(self):
|
|
return self.m_drawFaded
|
|
|
|
|
|
def GetValue(self):
|
|
return self.m_value
|
|
|
|
|
|
def SetAlignment(self, alignment, redraw=True):
|
|
"""
|
|
Set how the digits will be aligned within the widget.
|
|
|
|
Supported values are ``LED_ALIGN_LEFT``, ``LED_ALIGN_RIGHT``,
|
|
and ``LED_ALIGN_CENTER``.
|
|
"""
|
|
if alignment != self.m_alignment:
|
|
self.m_alignment = alignment
|
|
self._recalcInternals(self.GetClientSize())
|
|
|
|
if redraw:
|
|
self.Refresh(False)
|
|
|
|
|
|
def SetDrawFaded(self, drawFaded, redraw=True):
|
|
"""
|
|
Set whether unlit segments will still be draw with a faded version of
|
|
the foreground colour.
|
|
"""
|
|
if drawFaded != self.m_drawFaded:
|
|
self.m_drawFaded = drawFaded
|
|
|
|
if redraw:
|
|
self.Refresh(False)
|
|
|
|
|
|
def SetValue(self, value, redraw=True):
|
|
"""
|
|
Set the string value to be displayed.
|
|
"""
|
|
if value != self.m_value:
|
|
for ch in value:
|
|
assert ch in '0123456789-.: ', "LEDNumberCtrl can only display numeric string values."
|
|
|
|
self.m_value = value
|
|
self._recalcInternals(self.GetClientSize())
|
|
|
|
if redraw:
|
|
self.Refresh(False)
|
|
|
|
|
|
Alignment = property(GetAlignment, SetAlignment)
|
|
DrawFaded = property(GetDrawFaded, SetDrawFaded)
|
|
Value = property(GetValue, SetValue)
|
|
|
|
|
|
def OnSize(self, evt):
|
|
self._recalcInternals(evt.GetSize())
|
|
evt.Skip()
|
|
|
|
|
|
def OnPaint(self, evt):
|
|
c = self.const
|
|
dc = wx.AutoBufferedPaintDC(self)
|
|
|
|
# Draw the background
|
|
dc.SetBrush(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID))
|
|
dc.DrawRectangle(wx.Rect((0, 0), self.GetClientSize()))
|
|
|
|
# Iterate the digits and draw each
|
|
offset = 0
|
|
for i, ch in enumerate(self.m_value):
|
|
i -= offset
|
|
# Draw faded lines if wanted.
|
|
if self.m_drawFaded and ch != '.':
|
|
self._drawDigit(dc, c.DIGITALL, i);
|
|
|
|
if ch == '.':
|
|
# draw the decimal point in the previous segment
|
|
self._drawDigit(dc, c.DECIMALSIGN, i-1)
|
|
offset += 1
|
|
elif ch == ' ':
|
|
# skip spaces
|
|
continue
|
|
else:
|
|
self._drawDigit(dc, c.DIGITS[ch], i)
|
|
|
|
|
|
def _recalcInternals(self, size):
|
|
height = size.Height
|
|
|
|
if (height * 0.075) < 1:
|
|
self.m_lineMargin = 1
|
|
else:
|
|
self.m_lineMargin = int(height * 0.075)
|
|
|
|
if (height * 0.275) < 1:
|
|
self.m_lineLength = 1
|
|
else:
|
|
self.m_lineLength = int(height * 0.275)
|
|
|
|
self.m_lineWidth = self.m_lineMargin
|
|
self.m_digitMargin = self.m_lineMargin * 4
|
|
|
|
# Count the number of characters in the string; '.' characters are not
|
|
# included because they do not take up space in the display
|
|
count = 0;
|
|
for ch in self.m_value:
|
|
if ch != '.':
|
|
count += 1
|
|
|
|
valueWidth = (self.m_lineLength + self.m_digitMargin) * count
|
|
clientWidth = size.Width
|
|
|
|
if self.m_alignment == LED_ALIGN_LEFT:
|
|
self.m_leftStartPos = self.m_lineMargin
|
|
elif self.m_alignment == LED_ALIGN_RIGHT:
|
|
self.m_leftStartPos = clientWidth - valueWidth - self.m_lineMargin
|
|
elif self.m_alignment == LED_ALIGN_CENTER:
|
|
self.m_leftStartPos = int((clientWidth - valueWidth) / 2)
|
|
else:
|
|
raise AssertionError("Unknown alignment value for LEDNumberCtrl.")
|
|
|
|
|
|
def _drawDigit(self, dc, digit, column):
|
|
lineColor = self.GetForegroundColour()
|
|
c = self.const
|
|
|
|
if digit == c.DIGITALL:
|
|
R = int(lineColor.Red() / 8)
|
|
G = int(lineColor.Green() / 8)
|
|
B = int(lineColor.Blue() / 8)
|
|
lineColor.Set(R, G, B)
|
|
|
|
XPos = self.m_leftStartPos + column * (self.m_lineLength + self.m_digitMargin)
|
|
|
|
# Create a pen and draw the lines.
|
|
dc.SetPen(wx.Pen(lineColor, self.m_lineWidth))
|
|
|
|
if digit & c.LINE1:
|
|
dc.DrawLine(XPos + self.m_lineMargin * 2, self.m_lineMargin,
|
|
XPos + self.m_lineLength + self.m_lineMargin * 2, self.m_lineMargin)
|
|
|
|
if digit & c.LINE2:
|
|
dc.DrawLine(XPos + self.m_lineLength + self.m_lineMargin * 3, self.m_lineMargin * 2,
|
|
XPos + self.m_lineLength + self.m_lineMargin * 3, self.m_lineLength + (self.m_lineMargin * 2))
|
|
|
|
if digit & c.LINE3:
|
|
dc.DrawLine(XPos + self.m_lineLength + self.m_lineMargin * 3, self.m_lineLength + (self.m_lineMargin * 4),
|
|
XPos + self.m_lineLength + self.m_lineMargin * 3, self.m_lineLength * 2 + (self.m_lineMargin * 4))
|
|
|
|
if digit & c.LINE4:
|
|
dc.DrawLine(XPos + self.m_lineMargin * 2, self.m_lineLength * 2 + (self.m_lineMargin * 5),
|
|
XPos + self.m_lineLength + self.m_lineMargin * 2, self.m_lineLength * 2 + (self.m_lineMargin * 5))
|
|
|
|
if digit & c.LINE5:
|
|
dc.DrawLine(XPos + self.m_lineMargin, self.m_lineLength + (self.m_lineMargin * 4),
|
|
XPos + self.m_lineMargin, self.m_lineLength * 2 + (self.m_lineMargin * 4))
|
|
|
|
if digit & c.LINE6:
|
|
dc.DrawLine(XPos + self.m_lineMargin, self.m_lineMargin * 2,
|
|
XPos + self.m_lineMargin, self.m_lineLength + (self.m_lineMargin * 2))
|
|
|
|
if digit & c.LINE7:
|
|
dc.DrawLine(XPos + self.m_lineMargin * 2, self.m_lineLength + (self.m_lineMargin * 3),
|
|
XPos + self.m_lineMargin * 2 + self.m_lineLength, self.m_lineLength + (self.m_lineMargin * 3))
|
|
|
|
if digit & c.DECIMALSIGN:
|
|
dc.DrawLine(XPos + self.m_lineLength + self.m_lineMargin * 4, self.m_lineLength * 2 + (self.m_lineMargin * 5),
|
|
XPos + self.m_lineLength + self.m_lineMargin * 4, self.m_lineLength * 2 + (self.m_lineMargin * 5))
|
|
|
|
if digit & c.COLON:
|
|
dc.SetBrush(wx.Brush(lineColor))
|
|
centerX = XPos + (self.m_lineLength + self.m_digitMargin) / 2
|
|
radius = self.m_lineWidth / 2
|
|
dc.DrawCircle(centerX, (self.m_lineLength + (self.m_lineMargin * 3)) / 2, radius)
|
|
dc.DrawCircle(centerX, (self.m_lineLength * 2 + (self.m_lineMargin * 5)) * 3 / 4, radius)
|