Files
linuxcncAllWasm/lib/python/qtvcp/widgets/bar.py
2026-08-20 06:17:39 +08:00

545 lines
18 KiB
Python

import enum
from qtpy import QtWidgets
from qtpy.QtGui import QColor, QBrush, QPainter, QLinearGradient
from qtpy.QtCore import (Qt, Slot, Property, Signal, QRectF,
QSize)
try:
from qtpy.QtCore import QEnum
except ImportError:
try:
from qtpy.QtCore import Q_ENUM as QEnum
except ImportError:
# before Qt 5.10
from qtpy.QtCore import Q_ENUMS as QEnum
from qtvcp.widgets.widget_baseclass import _HalWidgetBase
import hal
# Set up logging
from qtvcp import logger
LOG = logger.getLogger(__name__)
# Force the log level for this module
#LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
class Bar(QtWidgets.QWidget):
valueChanged = Signal([int],[float])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._vertical = False
self._opposite = False
self._minimum = 0
self._maximum = 200
self._value = 100
self.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding
)
# set default number of colored steps
self.setSteps(None)
self._bar_solid_percent = 0.9
self._background_color = QColor('black')
self._padding = 2.0 # n-pixel gap around edge.
self._split = .2 # percent of full scale indicator
self._useMultiColor = True
self._singleIndicatorColor = QColor('blue')
def paintEvent(self, e):
painter = QPainter(self)
brush = QBrush()
brush.setColor(self._background_color)
brush.setStyle(Qt.SolidPattern)
rect = QRectF(0, 0, painter.device().width(), painter.device().height())
painter.fillRect(rect, brush)
# Get current state.
vmin, vmax = self.minimum(), self.maximum()
value = self.value()
# Define our canvas.
d_height = painter.device().height() - (self._padding * 2)
d_width = painter.device().width() - (self._padding * 2)
if self._vertical:
# Draw the vertical bars.
step_size = d_height / self.n_steps
bar_height = step_size * self._bar_solid_percent
bar_spacer = step_size * (1 - self._bar_solid_percent) / 2
# Calculate the y-stop position, from the value in range.
pc = (value - vmin) / (vmax - vmin)
n_steps_to_draw = int(pc * self.n_steps)
partial = (pc * self.n_steps) % 1
#print('pre',value,pc, n_steps_to_draw, partial)
n =-1 # default fall through value if n_steps_to_draw = 0
# draw complete step bars
for n in range(self.n_steps):
brush.setColor(QColor(self._step_color_list[n]))
if self._opposite:
top = self._padding + (( + n) * step_size) + bar_spacer
else:
top = self._padding + d_height - ((1 + n) * step_size) + bar_spacer
# full scale indicator:
# QRextF(left, top, width and height )
rect = QRectF(
self._padding,
top,
d_width * self._split,
bar_height
)
painter.fillRect(rect, brush)
# value indicator:
if n < n_steps_to_draw:
rect2 = QRectF(
self._padding + (d_width * self._split),
top ,
d_width * (1-self._split),
bar_height
)
if self._useMultiColor:
color = QColor(self._step_color_list[n])
else:
color = self._singleIndicatorColor
gradient = QLinearGradient(0, 0, 1, 0)
gradient.setColorAt(0.0, color)
gradient.setColorAt(0.5, color.lighter())
gradient.setColorAt(1.0, color)
gradient.setCoordinateMode(gradient.ObjectMode)
painter.fillRect(rect2, QBrush(gradient))
# draw partial step bar for in-between values
if partial > 0:
n = n_steps_to_draw
brush.setColor(QColor(self._step_color_list[n]))
# right to left
height = (bar_height* partial)
if self._opposite:
top = self._padding + (n * step_size) + bar_spacer
else:
top = self._padding - (n * step_size) + d_height - height- bar_spacer
# full scale indicator
rect = QRectF(
self._padding,
top,
d_width * self._split,
height
)
painter.fillRect(rect, brush)
# value indicator
rect2 = QRectF(
self._padding + d_width * self._split,
top ,
d_width *(1- self._split),
height
)
if self._useMultiColor:
color = QColor(self._step_color_list[n])
else:
color = self._singleIndicatorColor
gradient = QLinearGradient(0, 0, 1, 0)
gradient.setColorAt(0.0, color)
gradient.setColorAt(0.5, color.lighter())
gradient.setColorAt(1.0, color)
gradient.setCoordinateMode(gradient.ObjectMode)
painter.fillRect(rect2, QBrush(gradient))
else:
# Draw the horizontal bars.
step_size = d_width / self.n_steps
bar_width = step_size * self._bar_solid_percent
bar_spacer = step_size * (1 - self._bar_solid_percent) / 2
# Calculate the y-stop position, from the value in range.
pc = (value - vmin) / (vmax - vmin)
n_steps_to_draw = int(pc * self.n_steps)
partial = (pc * self.n_steps) % 1
#print('pre',value,pc, n_steps_to_draw, partial)
n=-1 # default fall through value if n_steps_to_draw = 0
for n in range(self.n_steps):
brush.setColor(QColor(self._step_color_list[n]))
# left, top, width and height floats
if self._opposite:
left = self._padding + d_width - ((1 + n) * step_size) + bar_spacer
else:
left = self._padding + bar_spacer + ((0 + n) * step_size)
rect = QRectF(
left ,
self._padding ,
bar_width,
d_height * self._split
)
painter.fillRect(rect, brush)
if n < n_steps_to_draw:
rect2 = QRectF(
left ,
self._padding + d_height * self._split,
bar_width,
d_height * (1-self._split)
)
if self._useMultiColor:
color = QColor(self._step_color_list[n])
else:
color = self._singleIndicatorColor
gradient = QLinearGradient(0, 1, 0, 0)
gradient.setColorAt(0.0, color)
gradient.setColorAt(0.5, color.lighter())
gradient.setColorAt(1.0, color)
gradient.setCoordinateMode(gradient.ObjectMode)
painter.fillRect(rect2, QBrush(gradient))
# draw partial step bar for in-between values
if partial > 0:
n = n_steps_to_draw
brush.setColor(QColor(self._step_color_list[n]))
# right to left
width = (bar_width* partial)
if self._opposite:
left = self._padding - (n * step_size) + d_width - width - bar_spacer
# left to right
else:
left = self._padding + bar_spacer + (n * step_size)
# full scale indicator
rect = QRectF(
left,
self._padding ,
width,
d_height * self._split
)
painter.fillRect(rect, brush)
# value indicator
rect2 = QRectF(
left ,
self._padding + d_height * self._split,
width,
d_height * (1-self._split)
)
if self._useMultiColor:
color = QColor(self._step_color_list[n])
else:
color = self._singleIndicatorColor
gradient = QLinearGradient(0, 1, 0, 0)
gradient.setColorAt(0.0, color)
gradient.setColorAt(0.5, color.lighter())
gradient.setColorAt(1.0, color)
gradient.setCoordinateMode(gradient.ObjectMode)
painter.fillRect(rect2, QBrush(gradient))
painter.end()
# list of color strings or an integer of steps
def setSteps(self, steps):
if steps is None:
steps = ['green', 'green', '#00b600', '#00b600', '#00d600',
'#00d600', 'yellow', 'yellow', 'red', 'red']
self.n_steps = len(steps)
self._step_color_list = steps
if isinstance(steps, list):
# list of colors.
self.n_steps = len(steps)
self._step_color_list = steps
elif isinstance(steps, int):
# int number of bars, defaults to red.
self.n_steps = steps
self._step_color_list = ['red'] * steps
else:
raise TypeError('steps must be a list or int')
def value(self):
return self._value
@Slot(float)
@Slot(int)
def setValue(self, data):
if data != self._value:
if data > self._maximum: data = self._maximum
if data < self._minimum : data = self._minimum
self._value = data
self.valueChanged.emit(int(data))
self.valueChanged[float].emit(float(data))
self.update()
def sizeHint(self):
if self._vertical:
return QSize(40, 120)
else:
return QSize(40, 20)
def minimum(self):
return self._minimum
def maximum(self):
return self._maximum
def setRange(self, low, hi):
self._minimum = low
self._maximum = hi
def setFormat(self, data):
pass
def invertBool(self, value):
if value:
return False
return True
def getInvertedAppearance(self):
return self._opposite
def setInvertedAppearance(self, data):
self._opposite = bool(data)
self.update()
def resetInvertedAppearance(self, data):
self._opposite = False
self.update()
#########################################################################
# This is how designer can interact with our widget properties.
# designer will show the Property properties in the editor
# it will use the get set and reset calls to do those actions
########################################################################
def set_step_color_l(self, data):
# convert stylesheet list of single string to list of multiple
if ',' in data[0]:
self.setSteps(data[0].split(','))
else:
self.setSteps(data)
def get_step_color_l(self):
return self._step_color_list
def reset_step_color_l(self):
self.setSteps( ['green', 'green', '#00b600', '#00b600',
'#00d600', '#00d600', 'yellow', 'yellow', 'red', 'red'])
def getBackgroundColor(self):
return self._background_color
@Slot(QColor)
def setBackgroundColor(self, value):
self._background_color = value
self.update()
def get_indicatorColor(self):
return self._singleIndicatorColor
@Slot(QColor)
def set_indicatorColor(self, value):
self._singleIndicatorColor = value
self.update()
def reset_indicatorColor(self, value):
self._singleIndicatorColor = QColor('blue')
self.update()
def getSplit(self):
return int(self._split * 100)
def setSplit(self, data):
if data <0: data == 0
elif data >100: data == 100
self._split = data/100
self.update()
def resetSplit(self):
self._split = .2
self.update()
def getMax(self):
return self._maximum
def setMax(self, data):
self._maximum = data
def resetMax(self):
self._maximum = 200
def getMin(self):
return self._minimum
def setMin(self, data):
self._minimum = data
def resetMin(self):
self._minimum = 0
def getMultiColor(self):
return self._useMultiColor
def setMultiColor(self, data):
self._useMultiColor = data
self.update()
def resetMultiColor(self):
self._useMultiColor = False
self.update()
def getVert(self):
return self._vertical
def setVert(self, data):
self._vertical = data
self.update()
def resetVert(self):
self._vertical = False
self.update()
stepColorList = Property(
'QStringList',
get_step_color_l, set_step_color_l, reset_step_color_l)
backgroundColor = Property(QColor, getBackgroundColor, setBackgroundColor)
indicatorColor = Property(QColor, get_indicatorColor, set_indicatorColor, reset_indicatorColor)
useMultiColorIndicator = Property(bool, getMultiColor, setMultiColor, resetMultiColor)
split = Property(int, getSplit, setSplit, resetSplit)
setMaximum = Property(int, getMax, setMax, resetMax)
setMinimum = Property(int, getMin, setMin, resetMin)
setVertical = Property(bool, getVert, setVert, resetVert)
setInverted = Property(bool, getInvertedAppearance, setInvertedAppearance, resetInvertedAppearance)
class HALPinType(enum.IntEnum):
NONE = 0
S32 = hal.HAL_S32
FLOAT = hal.HAL_FLOAT
class HalBar(Bar, _HalWidgetBase):
HALPinType = HALPinType
QEnum(HALPinType)
# older version of pyqt5 need this as well as QEnum
NONE = 0
S32 = hal.HAL_S32
FLOAT = hal.HAL_FLOAT
def __init__(self, *args, **kwargs):
super(). __init__( *args, **kwargs)
self._pin_type = HALPinType.S32
self._pin_name = ''
self._invert_negative = False
self._superOpposite = self._opposite
def _hal_init(self):
if self._pin_name == '':
pname = self.HAL_NAME_
else:
pname = self._pin_name
self._superOpposite = self._opposite
if self._pin_type == HALPinType.FLOAT:
self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_FLOAT, hal.HAL_IN)
self.hal_pin.value_changed.connect(lambda data: self.updateDisplay(data))
elif self._pin_type == HALPinType.S32:
self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_S32, hal.HAL_IN)
self.hal_pin.value_changed.connect(lambda data: self.updateDisplay(data))
def updateDisplay(self, data):
if data < 0 and self._invert_negative:
self._opposite = self.invertBool(self._superOpposite)
data = data * -1
else:
self._opposite = self._superOpposite
self.setValue(data)
def set_pin_type(self, value):
self._pin_type = value
def get_pin_type(self):
return self._pin_type
def reset_pin_type(self):
self._pin_type = HALPinType.S32
def set_pin_name(self, value):
self._pin_name = value
def get_pin_name(self):
return self._pin_name
def reset_pin_name(self):
self._pin_name = ''
def set_invert_negative(self, value):
self._invert_negative = value
def get_invert_negative(self):
return self._invert_negative
def reset_invert_negative(self):
self._invert_negative = False
pinType = Property(HALPinType, get_pin_type, set_pin_type, reset_pin_type)
pinName = Property(str, get_pin_name, set_pin_name, reset_pin_name)
invertOnNegative = Property(bool, get_invert_negative, set_invert_negative, reset_invert_negative)
if __name__ == '__main__':
from qtpy.QtWidgets import (QLabel, QSlider, QWidget, QVBoxLayout,
QHBoxLayout, QPushButton, QCheckBox)
app = QtWidgets.QApplication([])
w = QWidget()
w.setGeometry(100, 100, 200, 100)
w.setWindowTitle('Bar widget')
layout = QVBoxLayout(w)
label = QLabel()
bar = HalBar()
bar.setProperty('setInverted',True)
bar.setProperty('setVertical',False)
#bar.setValue(51)
slider = QSlider(Qt.Horizontal)
slider.setMinimum(0)
slider.setMaximum(200)
slider.setSingleStep(10)
slider.setPageStep(100)
slider.valueChanged.connect(bar.setValue)
slider.valueChanged.connect(label.setNum)
slider.setValue(51)
layout.addWidget(label)
layout.addWidget(bar)
layout.addWidget(slider)
button = QPushButton('Vertical')
button.setCheckable(True)
button.setChecked(bar.getVert())
button.toggled.connect(bar.setVert)
layout.addWidget(button)
button = QPushButton('Use Multi Color')
button.setCheckable(True)
button.setChecked(bar.getMultiColor())
button.toggled.connect(bar.setMultiColor)
layout.addWidget(button)
lyt = QHBoxLayout()
check1 = QCheckBox('o%')
check1.setAutoExclusive (True)
check1.stateChanged.connect(lambda value: bar.setSplit(0))
check2 = QCheckBox('20%')
check2.setAutoExclusive (True)
check2.stateChanged.connect(lambda value: bar.setSplit(20))
check3 = QCheckBox('50%')
check3.setAutoExclusive (True)
check3.stateChanged.connect(lambda value: bar.setSplit(50))
lyt.addWidget(check1)
lyt.addWidget(check2)
lyt.addWidget(check3)
layout.addLayout(lyt)
w.show()
app.exec()