# -*- coding: utf-8 -*-
#
# This file is subject to the terms and conditions defined in
# file 'LICENSE', which is part of this source code package.
#
import functools
import numpy as np
import Muscat.Helpers.ParserHelper as PH
import OpenPisco.QtApp.QtImplementation as QT
from Muscat.Helpers.Factory import Factory
[docs]def RegisterClass(name, classtype, constructor=None, withError = True):
return OptionFactory.RegisterClass(name,classtype, constructor=constructor, withError = withError )
[docs]class OptionFactory(Factory):
_Catalog = {}
_SetCatalog = set()
def __init__(self):
super(OptionFactory,self).__init__()
[docs]class OptionBase():
def __init__(self,name, dtype, default, valueRange=None, slider=False, label=None, pushValueFunction=None, pullValueFunction=None,readOnly=False):
super(OptionBase,self).__init__()
self.name = name
if label is None:
self.label = name
else:
self.label = label
self.dtype = dtype
self.slider = slider
self.valueRange = valueRange
self.readOnly = readOnly
self.pushValueFunction = pushValueFunction
self.pullValueFunction = pullValueFunction
self.automaticPush = False
self._modified_ = False
self.value = None
if default is None:
if self.pullValueFunction == None:
raise(Exception("Cant define initial value, need default or pullValueFunction"))
else:
self.Pull()
else:
if self.dtype in [dict, list] :
self.value = default.copy()
elif self.dtype == np.ndarray:
self.value = np.copy(default)
else:
self.value = default
self.SetValue(default)
[docs] def ApplyRange(self,val):
ok = True
if self.valueRange is not None:
if val < self.valueRange[0] :
val = self.valueRange[0]
ok = False
if val > self.valueRange[1]:
val = self.valueRange[1]
ok = False
return val,ok
[docs] def SetValue(self,value,key=None):
try:
val = PH.ReadScalar(value,self.dtype)
ok = True
except:
ok = False
if ok:
val,ok = self.ApplyRange(val)
if ok:
if self.value != val:
self._modified_ = True
if key is not None:
self.value[key] = val
else:
self.value = val
if self.automaticPush:
self.Push()
return ok
[docs] def Push(self,force=False):
if self.pushValueFunction is not None and (self._modified_ or force) :
self.pushValueFunction(self.name,self.value)
self._modified_ = False
[docs] def Pull(self):
if self.pullValueFunction is not None:
self.SetValue(self.pullValueFunction())
def __str__(self):
res = str(self.name )+ ": " + str(self.value)
return res
[docs]class OptionBool(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionBool,self).__init__(name, dtype=bool, default=default, **kwargs)
RegisterClass("bool",OptionBool)
RegisterClass(bool,OptionBool)
[docs]class OptionInt(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionInt,self).__init__(name, dtype=int, default=default, **kwargs)
RegisterClass("int",OptionInt)
RegisterClass(int,OptionInt)
[docs]class OptionFloat(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionFloat,self).__init__(name, dtype=float, default=default, **kwargs)
RegisterClass("float",OptionFloat)
RegisterClass(float,OptionFloat)
[docs]class OptionStr(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionStr,self).__init__(name, dtype=str, default=default, **kwargs)
RegisterClass("str",OptionStr)
RegisterClass(str,OptionStr)
[docs]class OptionDict(OptionBase):
def __init__(self,name, default,subtype=None, **kwargs):
super(OptionDict,self).__init__(name, dtype=dict, default=default, **kwargs)
if subtype is None :
if default is None:
raise(Exception("Need a subtype of a default value"))
else:
if len(default):
self.subtype = type(default[next(iter(default))])
else:
self.subtype = None
else:
self.subtype = subtype
[docs] def Push(self,force=False):
if self.pushValueFunction is not None and (self._modified_ or force) :
self.pushValueFunction(self.name,self.value.copy())
self._modified_ = False
[docs] def SetValue(self,value,key=None):
if self.dtype == dict:
if key is None:
for k,v in value.items():
self.SetValue(v,key=k)
return True
try:
if key in self.value:
val = PH.ReadScalar(value,type(self.value[key]) )
elif self.subtype is not None:
val = PH.ReadScalar(value,self.subtype )
else:
val = value
except:
return False
val,ok = self.ApplyRange(val)
if ok:
if key in self.value:
if self.value[key] != val:
self._modified_ = True
self.value[key] = val
else:
self.value[key] = val
self._modified_ = True
if self.automaticPush:
self.Push()
return ok
RegisterClass("dict",OptionDict)
RegisterClass(dict,OptionDict)
[docs]class OptionNumpyArray(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionNumpyArray,self).__init__(name, dtype=np.ndarray, default=default, **kwargs)
[docs] def Push(self,force=False):
if self.pushValueFunction is not None and (self._modified_ or force) :
self.pushValueFunction(self.name,np.copy(self.value))
self._modified_ = False
[docs] def SetValue(self,value,key=None):
if key is None:
val = PH.ReadVector(value,self.value.dtype.type)
else:
val = PH.ReadScalar(value,self.value.dtype.type)
val,ok = self.ApplyRange(val)
if ok:
if key is not None:
if type(key) == int:
if self.value[key] != val:
self._modified_ = True
self.value[key] = val
elif len(key) == 1:
if self.value[key[0]] != val:
self._modified_ = True
self.value[key[0]] = val
else:
if self.value[key[0],key[1]] != val:
self._modified_ = True
self.value[key[0],key[1]] = val
else:
if not np.all(np.equal(self.value,val)):
self._modified_ = True
self.value = val
if self.automaticPush:
self.Push()
return ok
RegisterClass("np.ndarray,",OptionNumpyArray)
RegisterClass(np.ndarray,OptionNumpyArray)
[docs]class OptionList(OptionBase):
def __init__(self,name, default, **kwargs):
super(OptionList,self).__init__(name, dtype=list, default=default, **kwargs)
RegisterClass("list",OptionList)
RegisterClass(list,OptionList)
floatFactor = 10000
[docs]def updateStyle(ok,modified,sender):
if ok:
if ok and modified :
sender.setStyleSheet("QLineEdit { background: rgb(0,255, 0); selection-background-color: rgb(233, 99, 0); }")
else:
sender.setStyleSheet("QLineEdit { }")
else:
sender.setStyleSheet("QLineEdit { background: rgb(255, 0, 0); selection-background-color: rgb(233, 99, 0); }")
[docs]def GenerateQLineEdit(value,readOnly):
w = QT.QLineEdit()
w.setText(str(value))
w.setReadOnly(readOnly)
if readOnly:
palette = QT.QPalette()
palette.setColor(QT.QPalette.Text,QT.Qt.gray)
w.setPalette(palette)
w.setAlignment(QT.Qt.AlignVCenter)
return w
[docs]def GenerateQTextEdit(value,readOnly):
w = QT.QTextEdit()
w.setText(str(value))
w.setReadOnly(readOnly)
if readOnly:
palette = QT.QPalette()
palette.setColor(QT.QPalette.Text,QT.Qt.gray)
w.setPalette(palette)
w.setAlignment(QT.Qt.AlignVCenter)
return w
[docs]def GenerateQCheckBox(value,readOnly):
w = QT.QCheckBox()
w.setChecked(bool(value))
w.setEnabled(not readOnly)
w.setTristate(False)
return w
[docs]class Editor(QT.QWidget):
pushEnded = QT.Signal()
pullEnded = QT.Signal()
editEnded = QT.Signal()
[docs] def PushValues(self):
for w,op,_ in self.allWidgets:
op.Push()
w.setStyleSheet("QLineEdit { }")
self.pushEnded.emit()
if self.applybutton is not None:
self.applybutton.setEnabled(False)
[docs] def PullValues(self):
for w,op,data in self.allWidgets:
op.Pull()
w.UpdateValue(w,op,data)
w.setStyleSheet("QLineEdit { }")
self.pullEnded.emit()
if self.applybutton is not None:
self.applybutton.setEnabled(False)
[docs] def SetValueScalar(self,sender,op,localset=True):
@QT.Slot()
def func():
try :
text = str(sender.text())
except:
text = str(sender.toPlainText())
ok = op.SetValue(text)
if localset:
sender.blockSignals(True)
sender.setText("{}".format(op.value))
sender.blockSignals(False)
updateStyle(ok,op._modified_,sender)
if op._modified_ :
self.editEnded.emit()
return func
[docs] def SetValueBool(self,sender,op):
@QT.Slot()
def func():
ok = op.SetValue(sender.isChecked())
updateStyle(ok,op._modified_,sender)
if op._modified_ :
self.editEnded.emit()
return func
[docs] def SetValueWithKey(self,sender,op,key):
@QT.Slot()
def func():
try :
text = str(sender.text())
except:
text = str(sender.toPlainText())
ok = op.SetValue(text,key)
updateStyle(ok,op._modified_,sender)
if op._modified_ :
self.editEnded.emit()
return func
[docs] def SetValueDict(self,sender,op,key):
@QT.Slot()
def func():
try :
text = str(sender.text())
except:
text = str(sender.toPlainText())
if key() is None:
ok = False
else:
ok = op.SetValue(text,key=key())
updateStyle(ok,op._modified_,sender)
if op._modified_ :
self.editEnded.emit()
return func
[docs] def SetValueSlider(self,sender,w1,op,f=True):
@QT.Slot()
def func():
if f :
ok = op.SetValue(str(sender.value()/floatFactor))
else:
ok = op.SetValue(str(sender.value()))
updateStyle(ok,op._modified_,w1)
if op._modified_ :
self.editEnded.emit()
return func
@QT.Slot()
def _UpdateApplybutton(self):
if self.applybutton is not None:
self.applybutton.setEnabled(True)
def __init__(self, options,description=None,father = None, ListOfTypesToDevelop=()):
self.enablePull = False
self.enablePush = False
self.options = options
QT.QWidget.__init__(self,father)
self.allWidgets = []
self.gridlayout = QT.QGridLayout(self)
self.gridlayout.setColumnStretch(0,0)
self.gridlayout.setColumnStretch(1,1)
self.gridlayout.setColumnStretch(2,1)
self.gridlayout.setColumnStretch(3,1)
self.gridlayout.setColumnStretch(3,0)
cpt = 0
if description is not None:
w = QT.QLabel(description)
w.setWordWrap(True)
self.gridlayout.addWidget(w, cpt, 0,1,4)
cpt += 1
for op in options:
if op.pullValueFunction is not None:
self.enablePull = True
if op.pushValueFunction is not None:
self.enablePush = True
if op.label == op.name:
label = QT.QLabel(op.label+":")
else:
label = QT.QLabel(op.label+" ("+op.name+"):")
self.gridlayout.addWidget(label, cpt, 0)
if not op.slider:
if op.dtype in [float,int,str]:
if op.dtype == str:
if len(op.value) > 50:
w = GenerateQTextEdit(op.value,op.readOnly)
w.textChanged.connect(self.SetValueScalar(w,op,localset=False) )
else:
w = GenerateQLineEdit(op.value,op.readOnly)
w.editingFinished.connect( self.SetValueScalar(w,op) )
else:
w = GenerateQLineEdit(op.value,op.readOnly)
w.editingFinished.connect( self.SetValueScalar(w,op) )
w.UpdateValue = lambda widget,op,data: widget.setText(str(op.value))
self.gridlayout.addWidget(w, cpt, 1,1,3,QT.Qt.AlignVCenter)
elif op.dtype is bool:
w = GenerateQCheckBox(op.value,op.readOnly)
w.stateChanged.connect(self.SetValueBool(w,op) )
w.UpdateValue = lambda widget,ops,data: widget.setChecked(bool(ops.value))
self.gridlayout.addWidget(w, cpt, 1,1,3,QT.Qt.AlignVCenter)
elif op.dtype is list:
if len(op.value) >5:
cpt +=1
continue
if len(op.value) >0 :
import inspect
if inspect.ismethod(op.value[0]):
cpt +=1
continue
for i in range(len(op.value)):
if isinstance(op.value[i],ListOfTypesToDevelop):
from OpenPisco.QtApp.QtObjectEditor import QtObjectEditor
w = QT.QPushButton()
w.setText(str(i)+ ":" + str(type(op.value[i]).__name__) )
def func(obj,name1,name2):
if father is None:
w = QtObjectEditor( obj,name1,name2)
else:
w = QtObjectEditor( obj,name1,name2,father=father )
try:
father.setWidget(w)
except:
pass
w.show()
w.clicked.connect(functools.partial(func, op.value[i],"0",str(op.name) ) )
self.gridlayout.addWidget(w, cpt, 1+i,QT.Qt.AlignVCenter)
else:
w = GenerateQLineEdit(op.value[i],op.readOnly)
w.textChanged.connect(self.SetValueWithKey(w,op,i))
self.gridlayout.addWidget(w, cpt, 1+i,QT.Qt.AlignVCenter)
w.UpdateValue = lambda widget,opo,i: widget.setText(str(opo.value[i][:]))
self.allWidgets.append([w, op, i ] )
cpt +=1
continue
elif op.dtype is dict:
# verification that all the keys are strings
ok = all([type(k) is str for k in op.value.keys()])
if not ok:
cpt +=1
continue
w = QT.QComboBox()
w2 = GenerateQLineEdit("" ,op.readOnly)
def UpdateComboBoxEntries(w,op,widgets):
nwidget, _ = widgets
nwidget.clear
lcpt =0
for k in op.value.keys():
index = nwidget.findText(k)
if index < 0:
nwidget.insertItem(lcpt,k,k)
lcpt +=1
UpdateComboBoxEntries(w,op,(w,w2))
self.gridlayout.addWidget(w, cpt, 1,1,1,QT.Qt.AlignVCenter)
def GetCurrectKey(w):
return w.itemData(w.currentIndex())
w.UpdateValue = UpdateComboBoxEntries
self.allWidgets.append((w,op,(w,w2)) )
self.gridlayout.addWidget(w2, cpt, 2,1,2,QT.Qt.AlignVCenter)
w2.textChanged.connect( self.SetValueDict(w2,op,key=functools.partial(GetCurrectKey,w)) )
def updateDict(w, op, widgets,index=None):
nwidget, dwitget = widgets
data = GetCurrectKey(nwidget)
if data is None:
return
dwitget.blockSignals(True)
dwitget.setText(str(op.value[GetCurrectKey(nwidget)]))
dwitget.blockSignals(False)
w2.UpdateValue = updateDict
w2.UpdateValue(w2,op,(w,w2))
# if we change the combobox
w.currentIndexChanged.connect( functools.partial(w2.UpdateValue,w2,op, (w,w2) ))
self.allWidgets.append([w2,op,(w,w2)])
cpt +=1
continue
elif op.dtype == np.ndarray:
# expose only 1D or 2D matrices
if len(op.value.shape) > 2:
cpt +=1
continue
elif len(op.value.shape) > 1 :
# expose only small matrices
if op.value.shape[0] > 3:
cpt +=1
continue
else:
# expose smal vectors
if op.value.size > 12:
cpt +=1
continue
#op.readOnly = True
if len(op.value.shape) == 0 :
w = GenerateQLineEdit(op.value,op.readOnly)
w.textChanged.connect(self.SetValueWithKey(w,op, (0,) ) )
self.gridlayout.addWidget(w, cpt, 1,1,3,QT.Qt.AlignVCenter)
raise NotImplementedError()
elif len(op.value.shape) == 1 :
for i in range(op.value.size):
w = GenerateQLineEdit(op.value[i],op.readOnly)
w.textChanged.connect(self.SetValueWithKey(w,op, (i,) ) )
self.gridlayout.addWidget(w, cpt, 1+i,QT.Qt.AlignVCenter)
def updateValuei(dwitget,op,data):
i = data
dwitget.blockSignals(True)
dwitget.setText(str(op.value[i]))
dwitget.blockSignals(False)
w.UpdateValue = updateValuei
self.allWidgets.append([ w, op, i ])
else:
if op.value.shape[0] > 3: continue
if op.value.size > 12: continue
for i in range(op.value.shape[0]):
for j in range(op.value.shape[1]):
def updateValueij(dwitget,op,data):
i,j = data
dwitget.blockSignals(True)
dwitget.setText(str(op.value[i,j]))
dwitget.blockSignals(False)
w = GenerateQLineEdit(op.value[i,j],op.readOnly)
w.textChanged.connect(self.SetValueWithKey(w,op,(i,j)) )
self.gridlayout.addWidget(w, cpt, 1+j,QT.Qt.AlignVCenter)
w.UpdateValue = updateValueij
self.allWidgets.append([w,op, (i,j)])
cpt += 1
cpt +=1
continue
else:
print("type " + str(op.dtype) + " Not treated yet")
cpt +=1
continue
else:
if op.dtype in [float,int]:
w = QT.QSlider(QT.Qt.Horizontal)
if op.dtype == float:
factor = floatFactor
else:
factor = 1
w.setMinimum(op.valueRange[0]*factor)
w.setMaximum(op.valueRange[1]*factor)
w.setValue(op.value*factor)
w1 = QT.QLabel(alignment=QT.Qt.AlignCenter)
w1.setNum(op.value)
self.gridlayout.addWidget(w, cpt, 1,1,2,QT.Qt.AlignVCenter)
def setLabelVal(factor,wid,val):
wid.setNum(val/factor)
def setSliderVal(factor, wid, op, data):
wid.setValue(op.value*factor)
w.valueChanged.connect(functools.partial(setLabelVal,factor,w1) )
w.valueChanged.connect( self.SetValueSlider(w,w1,op, op.dtype is float))
w.UpdateValue = functools.partial(setSliderVal,factor)
self.gridlayout.addWidget(w1, cpt, 3,1,1,QT.Qt.AlignVCenter)
else:
raise NotImplementedError("Can only handle float and int for now")
self.allWidgets.append((w,op,None) )
cpt +=1
self.applybutton = None
if self.enablePush:
self.applybutton = QT.QPushButton("Apply Changes")
self.gridlayout.addWidget(self.applybutton, cpt, 0,1,4,QT.Qt.AlignBottom)
self.gridlayout.setRowStretch(cpt,100)
cpt +=1
self.applybutton.clicked.connect(self.PushValues)
self.applybutton.setEnabled(False)
if self.enablePull:
self.pullbutton = QT.QPushButton("Pull current Values")
self.gridlayout.addWidget(self.pullbutton, cpt, 0,1,4,QT.Qt.AlignBottom)
#self.gridlayout.setRowStretch(cpt,100)
cpt +=1
self.pullbutton.clicked.connect(self.PullValues)
self.pullbutton.setEnabled(True)
self.editEnded.connect(self._UpdateApplybutton)
[docs]def CheckIntegrity(GUI=False,res = None):
import sys
app = QT.GetQApplication(sys.argv)
view = QT.QMainWindow()
class TestClassII():
def __init__(self):
super(TestClassII,self).__init__()
self.float = 0.1
def __str__(self):
return "<instance of TestClassII> "
boolVariable = OptionBool("boolVar",False,label="Bool Variable")
boolVariableReadOnly = OptionBool("boolVarReadOnly_",True,readOnly=True)
try:
error = False
stringVariable = OptionStr("desc",None)
error = True
except:
if error:
raise
class State():
def __init__(self):
self.strData = ""
def pullValueFunction(self):
return self.strData
def pushValueFunction(self,name,val):
self.strData = val
print("In push Value Function of " +str(name) +" "+ str(val))
strstate = State()
stringVariable = OptionStr("desc (automaticPush)",None,pullValueFunction = strstate.pullValueFunction , pushValueFunction=strstate.pushValueFunction)
stringVariable.automaticPush = True
dictVariable = OptionDict("dictionalry (automaticPush)",{"toto":1.,"tata":3.5 })
dictVariable.automaticPush = True
dictVariableReadonly = OptionDict("dictionaryReadOnly_",{"toto":1.,"tata":3.5 },readOnly=True)
emptyDictVariable = OptionDict("emptydic",{},subtype=int)
floatVar = OptionFloat("floatVar",3.14159)
intVar = OptionInt("intVar",10)
#heterolist = OptionList("heterolist",[1,"3"]) # this does not work
listOfClasses = OptionBase("listOfClasses",list,[TestClassII(),TestClassII(),TestClassII()])
numpyArrayReadOnly_= OptionNumpyArray("numpyArrayReadOnly_",np.array([[11,12,13],[21,22,23],[31,32,33]]))
numpyvec = OptionNumpyArray("numpyvec",np.array([1,2,3]))
numpyvect= OptionNumpyArray("numpyvect",np.array([[1],[2],[3]]).T)
# self.readOnlyData_ = 2
# stringVar OptionStr("stringVar",str,"hola"),
# OptionStr("stringVarReadOnly_",str,"ReadOnly",readOnly=True),
# OptionStr("stringVarReadOnlyLong_",str,"ReadOnly plus de 50 chars ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",readOnly=True),
#
#
floatVariableRange = OptionFloat("Float option range",0.1,valueRange=[0,12.5])
floatVariableRangeSlider = OptionFloat("Float option with slider",0.1,valueRange=[0,12.5],slider=True)
intVariableRange = OptionInt("int option range (30,40) ",30,valueRange=[30,40])
intVariableRangeSlider = OptionInt("int option with slider",30,valueRange=[30,40],slider=True)
strVariable = OptionStr("Long text","This is a very long text to edit with more than 50 characters..................")
ops = [boolVariable,
boolVariableReadOnly,
stringVariable,
dictVariable,
dictVariableReadonly,
emptyDictVariable,
floatVar,
intVar,
#heterolist,
listOfClasses,
numpyArrayReadOnly_,
numpyvec,
numpyvect,
floatVariableRange,
floatVariableRangeSlider,
intVariableRange,
intVariableRangeSlider,
strVariable
]
w = Editor(ops,description="Options Editor")
w.show()
#ops[0].SetValue(True)
#ops[1].SetValue(10.1)
#ops[3].SetValue(6.5)
#ops[4].SetValue(100)
#ops[6].SetValue(35)
#ops[7].SetValue("100")
stringVariable.SetValue("'data to push'")
floatVariableRange.SetValue(-1)
floatVariableRange.SetValue(20)
dictVariable.SetValue({"tata":3.6})
dictVariable.SetValue(3.2,key="titi")
dictVariable.Push()
numpyvec.automaticPush = True
numpyvec.SetValue(.2,key=1)
numpyvec.SetValue(3.25,[1])
numpyvec.SetValue([5,6,7])
numpyvec.Push()
numpyArrayReadOnly_.SetValue(3.25,key=np.array([1,1]))
intVariableRangeSlider.SetValue(35)
if res is not None:
res.append(app)
res.append(w)
w2 = Editor(ops)
w2.show()
print([str(x) for x in ops])
OptionFactory()
return 'ok'
if __name__ == "__main__":
res = []
print(CheckIntegrity(GUI=True,res=res ))
res[0].exec_()