forked from M-Labs/thermostat
linuswck
9acff86547
- Bugs fix: 1. Params Tree user input will not get overwritten by incoming report thermostat_data_model. 2. PID Autotune Sampling Period is now set according to Thermostat sampling interval 3. PID Autotune won't get stuck in Fail State 4. Various types disconnection related Bugs 5. Number of Samples stored in the plot cannot be set 6. Limit the max settable output current to be 2000mA - Improvement: 1. Params Tree settings can be changed with external json 2. Use a Tab system to show a single channel of config instead of two 3. Expose PID Autotune lookback params 4. Icon is changed to Artiq logo - Restructure: 1. Restructure the code to follow Model-View-Delegate Design Pattern
127 lines
3.6 KiB
Python
127 lines
3.6 KiB
Python
# A Custom Class that allows defining a QObject Property Dynamically
|
|
# Adapted from: https://stackoverflow.com/questions/48425316/how-to-create-pyqt-properties-dynamically
|
|
|
|
from functools import wraps
|
|
|
|
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
|
|
|
|
|
|
class PropertyMeta(type(QObject)):
|
|
"""Lets a class succinctly define Qt properties."""
|
|
|
|
def __new__(cls, name, bases, attrs):
|
|
for key in list(attrs.keys()):
|
|
attr = attrs[key]
|
|
if not isinstance(attr, Property):
|
|
continue
|
|
|
|
types = {list: "QVariantList", dict: "QVariantMap"}
|
|
type_ = types.get(attr.type_, attr.type_)
|
|
|
|
notifier = pyqtSignal(type_)
|
|
attrs[f"{key}_update"] = notifier
|
|
attrs[key] = PropertyImpl(type_=type_, name=key, notify=notifier)
|
|
|
|
return super().__new__(cls, name, bases, attrs)
|
|
|
|
|
|
class Property:
|
|
"""Property definition.
|
|
|
|
Instances of this class will be replaced with their full
|
|
implementation by the PropertyMeta metaclass.
|
|
"""
|
|
|
|
def __init__(self, type_):
|
|
self.type_ = type_
|
|
|
|
|
|
class PropertyImpl(pyqtProperty):
|
|
"""Property implementation: gets, sets, and notifies of change."""
|
|
|
|
def __init__(self, type_, name, notify):
|
|
super().__init__(type_, self.getter, self.setter, notify=notify)
|
|
self.name = name
|
|
|
|
def getter(self, instance):
|
|
return getattr(instance, f"_{self.name}")
|
|
|
|
def setter(self, instance, value):
|
|
signal = getattr(instance, f"{self.name}_update")
|
|
|
|
if type(value) in {list, dict}:
|
|
value = make_notified(value, signal)
|
|
|
|
setattr(instance, f"_{self.name}", value)
|
|
signal.emit(value)
|
|
|
|
|
|
class MakeNotified:
|
|
"""Adds notifying signals to lists and dictionaries.
|
|
|
|
Creates the modified classes just once, on initialization.
|
|
"""
|
|
|
|
change_methods = {
|
|
list: [
|
|
"__delitem__",
|
|
"__iadd__",
|
|
"__imul__",
|
|
"__setitem__",
|
|
"append",
|
|
"extend",
|
|
"insert",
|
|
"pop",
|
|
"remove",
|
|
"reverse",
|
|
"sort",
|
|
],
|
|
dict: [
|
|
"__delitem__",
|
|
"__ior__",
|
|
"__setitem__",
|
|
"clear",
|
|
"pop",
|
|
"popitem",
|
|
"setdefault",
|
|
"update",
|
|
],
|
|
}
|
|
|
|
def __init__(self):
|
|
if not hasattr(dict, "__ior__"):
|
|
# Dictionaries don't have | operator in Python < 3.9.
|
|
self.change_methods[dict].remove("__ior__")
|
|
self.notified_class = {
|
|
type_: self.make_notified_class(type_) for type_ in [list, dict]
|
|
}
|
|
|
|
def __call__(self, seq, signal):
|
|
"""Returns a notifying version of the supplied list or dict."""
|
|
notified_class = self.notified_class[type(seq)]
|
|
notified_seq = notified_class(seq)
|
|
notified_seq.signal = signal
|
|
return notified_seq
|
|
|
|
@classmethod
|
|
def make_notified_class(cls, parent):
|
|
notified_class = type(f"notified_{parent.__name__}", (parent,), {})
|
|
for method_name in cls.change_methods[parent]:
|
|
original = getattr(notified_class, method_name)
|
|
notified_method = cls.make_notified_method(original, parent)
|
|
setattr(notified_class, method_name, notified_method)
|
|
return notified_class
|
|
|
|
@staticmethod
|
|
def make_notified_method(method, parent):
|
|
@wraps(method)
|
|
def notified_method(self, *args, **kwargs):
|
|
result = getattr(parent, method.__name__)(self, *args, **kwargs)
|
|
self.signal.emit(self)
|
|
return result
|
|
|
|
return notified_method
|
|
|
|
|
|
make_notified = MakeNotified()
|