#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: kushal
GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007
"""
from PyQt5 import QtCore, QtGui, QtWidgets
import inspect
from builtins import int, float, str, bool
from typing import *
widget_mapping = {
int: QtWidgets.QSpinBox,
float: QtWidgets.QDoubleSpinBox,
bool: QtWidgets.QCheckBox,
str: QtWidgets.QLineEdit,
}
val_setters = {
QtWidgets.QSpinBox: "setValue",
QtWidgets.QDoubleSpinBox: "setValue",
QtWidgets.QCheckBox: "setChecked",
QtWidgets.QLineEdit: "setText",
}
[docs]class Arg(QtCore.QObject):
acceptable_types = (int, float, str, bool)
sig_changed = QtCore.pyqtSignal(object)
[docs] def __init__(
self,
name: str,
typ: type,
val: Union[int, float, str, bool],
parent: QtWidgets.QWidget,
vlayout: QtWidgets.QVBoxLayout,
tooltip: Optional[str] = None,
**kwargs
):
"""
Creates the appropriate QWidget interface.
Parameters
----------
name : str
argument name
typ : type
function type, one of ``int``, ``float``, ``str`` or ``bool``.
Used for determining the correct QWidget to be used
val : Union[int, float, str, bool]
default value for the widget
parent : QtWidgets.QWidget
parent widget
vlayout : QtWidgets.QVBoxLayout
parent VBoxLayout
tooltip : str
toolTip
Attributes
----------
sig_changed : object
emits ``self.val`` when GUI value is changed.
"""
super(Arg, self).__init__(parent)
self.parent = parent
self.vlayout = vlayout
self.hlayout = QtWidgets.QHBoxLayout()
self._qlabel = QtWidgets.QLabel(self.parent)
self.hlayout.addWidget(self._qlabel)
self.name = name
self.typ = typ
self.widget: QtWidgets.QWidget #: QWidget for the argument
self.widget = widget_mapping[self.typ](self.parent)
self.hlayout.addWidget(self.widget)
if typ is not str:
self.hlayout.addSpacerItem(
QtWidgets.QSpacerItem(
40, 20, QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Minimum
)
)
self.val = val
self.vlayout.addLayout(self.hlayout)
if self.typ is str:
self.widget.textEdited.connect(lambda v: setattr(self, '_val', v))
self.widget.textEdited.connect(lambda: self.sig_changed.emit(self.val))
elif self.typ is bool:
self.widget.toggled.connect(lambda v: setattr(self, '_val', v))
self.widget.toggled.connect(lambda: self.sig_changed.emit(self.val))
if tooltip is not None:
self._qlabel.setToolTip(tooltip)
self.widget.setToolTip(tooltip)
@property
def name(self) -> str:
"""argument name"""
return self._name
@name.setter
def name(self, n):
self._name = n
self._qlabel.setText(f'{self._name}: ')
@property
def val(self) -> Union[int, float, str, bool]:
"""current argument value"""
return self._val
@val.setter
def val(self, v: Union[int, float, str, bool]):
if v is None:
self._val = v
return
assert isinstance(v, self.acceptable_types)
self._val = v
# use the correct func to set the value based on type
getattr(self.widget, val_setters[type(self.widget)])(self.val)
def __repr__(self):
return f"name:\t{self.name}\n" f"val:\t{self.val}\n" f"typ:\t{self.typ}"
[docs]class ArgNumeric(Arg):
acceptable_types = (int, float)
[docs] def __init__(
self,
name: str,
typ: type,
val: Union[int, float],
parent: QtWidgets.QWidget,
vlayout: QtWidgets.QVBoxLayout,
minmax: tuple = (-1, 999),
step: Union[int, float] = 1,
use_slider: bool = False,
suffix: str = None,
**kwargs
):
"""
Creates numerical QWidget interface
Parameters
----------
minmax : tuple
min & max values
step : Union[int, float]
step size for the spin box
use_slider : Optional[bool]
adds a slider below the spin box
suffix : Optional[str]
text suffix for the spin box, like data units
**kwargs
passed to Arg
"""
super().__init__(name, typ, val, parent, vlayout, **kwargs)
self.slider = None
self.minmax = minmax
self.step = step
if use_slider:
self.slider = QtWidgets.QSlider(self.parent)
self.slider.setOrientation(QtCore.Qt.Horizontal)
self.slider.setMaximum(self.max)
self.slider.setMinimum(self.min)
self.widget.valueChanged.connect(self.slider.setValue)
self.slider.valueChanged.connect(self.widget.setValue)
self.vlayout.addWidget(self.slider)
self.suffix = suffix
if self.suffix is not None:
self.widget.setSuffix(self.suffix)
self.widget.valueChanged.connect(lambda v: setattr(self, '_val', v))
self.widget.valueChanged.connect(lambda: self.sig_changed.emit(self.val))
self.val = val
def set_slider(self):
if self.slider is not None:
self.slider.setMaximum(self.max)
self.slider.setMinimum(self.min)
@property
def minmax(self) -> tuple:
"""minmax limits for the widget"""
return tuple(self._minmax)
@minmax.setter
def minmax(self, minmax: tuple):
self._minmax = list(minmax)
self.min = self.minmax[0]
self.max = self.minmax[1]
@property
def min(self) -> Union[int, float]:
"""min value limit for the widget"""
return self._min
@min.setter
def min(self, v: Union[int, float]):
assert isinstance(v, (int, float))
self._min = v
self._minmax[0] = v
self.widget.setMinimum(v)
self.set_slider()
@property
def max(self) -> Union[int, float]:
"""max value limit for the widget"""
return self._max
@max.setter
def max(self, v: Union[int, float]):
assert isinstance(v, (int, float))
self._max = v
self._minmax[1] = v
self.widget.setMaximum(v)
self.set_slider()
@property
def step(self) -> Union[int, float]:
"""step size for the widget"""
return self._step
@step.setter
def step(self, v: Union[int, float]):
assert isinstance(v, (int, float))
self._step = v
self.widget.setSingleStep(v)
def __repr__(self):
return (
f"{super(ArgNumeric, self).__repr__()}\n"
f"minmax:\t{self.minmax}\n"
f"step:\t{self.step}\n"
f"suffix:\t{self.suffix}"
)