You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

267 lines
13 KiB

# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import TermTk as ttk
class PropertyEditor(ttk.TTkGridLayout):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._detail = ttk.TTkTree()
self._detail.setHeaderLabels(["Property","Value"])
self._widget = ttk.TTkWidget()
self._superWidget = ttk.TTkWidget()
self.addWidget(self._detail)
@ttk.pyTTkSlot(ttk.TTkWidget, ttk.TTkWidget)
def setDetail(self, widget, superWidget):
self._widget = widget
self._superWidget = superWidget
# TBD
# Override: 'name', 'radiogroup',
# ro geometry if grid layout
self._makeDetail(widget, *superWidget.getSuperProperties())
def _makeDetail(self, domwBase, additions, exceptions, exclude):
def _boundValue(_f,_w,_v):
def _ret():
_f(_w,_v)
self._superWidget.updateAll()
return _ret
def _bound(_f,_w,_l):
def _ret(_v):
_f(_w,_l(_v))
self._superWidget.updateAll()
return _ret
def _boundFlags(_f,_g,_w,_l,_flag):
def _ret(_v):
_val = _g(_w)
_val = _val|_flag if _l(_v) else _val&~_flag
_f(_w,_val)
self._superWidget.updateAll()
return _ret
def _boundTextEdit(_f,_w,_te):
def _ret():
_v = _te.getTTkString()
_f(_w,_v)
self._superWidget.updateAll()
return _ret
def _boundLineEdit(_f,_w,_te):
def _ret(t):
_v = str(t)
_f(_w,_v)
self._superWidget.updateAll()
return _ret
# Multi Flag Fields
# ▼ Input Type │ - (0x0001)
# • Text │[X] 0x0001
# • Number │[ ] 0x0002
# • Password │[ ] 0x0004
def _processMultiFlag(name, prop, domw):
flags = prop['get']['flags']
ret = ttk.TTkTreeWidgetItem([name,f" - (0x{prop['get']['cb'](domw):04X})"],expanded=True)
# value = ttk.TTkFrame(layout=ttk.TTkVBoxLayout(), height=len(flags), border=False)
for fl in flags:
if 'set' in prop:
fcb = ttk.TTkCheckbox(text=f" 0x{flags[fl]:04X}", checked=bool(prop['get']['cb'](domw)&flags[fl]), height=1)
fcb.stateChanged.connect(_boundFlags(
prop['set']['cb'], prop['get']['cb'],
domw, lambda v: v==ttk.TTkK.Checked, flags[fl]))
else:
fcb = ttk.TTkCheckbox(text=f" 0x{flags[fl]:04X}", checked=bool(prop['get']['cb'](domw)&flags[fl]), height=1, enabled=False)
ret.addChild(ttk.TTkTreeWidgetItem([f"{fl}", fcb]))
return ret
# Single Flag Fields
# • Check State │[Partially Checked ^]│
# │┌───────────────────┐ │
# ││Checked │ │
# ││Unchecked │ │
# ││Partially Checked │ │
# │└───────────────────┘ │
def _processSingleFlag(name, prop, domw):
flags = prop['get']['flags']
items = [(k,v) for k,v in flags.items()]
if 'set' in prop:
value = ttk.TTkComboBox(list=[n for n,_ in items], height=1, textAlign=ttk.TTkK.LEFT_ALIGN)
value.setCurrentIndex([cs for _,cs in items].index(prop['get']['cb'](domw)))
value.currentTextChanged.connect(_bound(prop['set']['cb'],domw, lambda v:flags[v]))
else:
value = ttk.TTkLabel(text=items[[cs for _,cs in items].index(prop['get']['cb'](domw))][0])
return ttk.TTkTreeWidgetItem([name,value])
# List Fields
# property in this format:
# 'Position' : {
# 'init': {'name':'pos', 'type': [
# { 'name': 'x', 'type':int } ,
# { 'name': 'y', 'type':int } ] },
# 'get': { 'cb':pos, 'type': [
# { 'name': 'x', 'type':int } ,
# { 'name': 'y', 'type':int } ] },
# 'set': { 'cb':move, 'type': [
# { 'name': 'x', 'type':int } ,
# { 'name': 'y', 'type':int } ] } },
#
def _processList(name, prop, domw):
def _getter(_i, _p):
def _ret(_w):
return _p['get']['cb'](_w)[_i]
return _ret
def _setter(_i, _p):
def _ret(_w,_v):
__vals = list(_p['get']['cb'](_w))
__vals[_i]=_v
_p['set']['cb'](_w, *__vals)
return _ret
curVal = prop['get']['cb'](domw)
value = ttk.TTkLabel(text=f"{curVal}")
ret = ttk.TTkTreeWidgetItem([name,value])
for _i, _prop in enumerate(prop['get']['type']):
# Defining a proxy property to set or get a single value
_newProp = {
'get' : { 'cb': _getter(_i, prop), 'type':_prop['type'] },
'set' : { 'cb': _setter(_i, prop), 'type':_prop['type'] }
}
# ret.addChild(ttk.TTkTreeWidgetItem([_prop['name'],f"{curVal[_i]}"]))
ret.addChild(_processProp(_prop['name'], _newProp, domw))
return ret
# Dict Fields
def _processDict(name, prop, domw):
curVal = prop['get']['cb'](domw)
value = ttk.TTkLabel(text=f"{curVal} - TBD")
ret = ttk.TTkTreeWidgetItem([name,value])
return ret
# Boolean Fields
def _processBool(name, prop, domw):
getval = prop['get']['cb'](domw)
value = ttk.TTkCheckbox(text=f" {p}", checked=getval, height=1)
value.stateChanged.connect(_bound(prop['set']['cb'],domw, lambda v:v==ttk.TTkK.Checked))
return ttk.TTkTreeWidgetItem([name,value])
# Integer Fields
def _processInt(name, prop, domw):
getval = prop['get']['cb'](domw)
value = ttk.TTkSpinBox(value=getval, height=1, maximum=0x10000, minimum=-0x10000)
value.valueChanged.connect(_bound(prop['set']['cb'],domw,lambda v:v))
return ttk.TTkTreeWidgetItem([name,value])
# String Fields
def _processStr(name, prop, domw):
getval = prop['get']['cb'](domw)
value = ttk.TTkLineEdit(text=getval, height=len(getval.split('\n')))
value.textChanged.connect(_boundLineEdit(prop['set']['cb'],domw,value))
return ttk.TTkTreeWidgetItem([name,value])
# String Fields
def _processTTkString(name, prop, domw, multiLine=True):
getval = prop['get']['cb'](domw)
value = ttk.TTkTextPicker(text=getval, height=len(getval.split('\n')), autoSize=True, multiLine=multiLine)
value.textChanged.connect(_boundTextEdit(prop['set']['cb'],domw,value))
return ttk.TTkTreeWidgetItem([name,value])
# Color Fields
def _processTTkColor(name, prop, domw):
getval = prop['get']['cb'](domw)
value = ttk.TTkContainer(layout=ttk.TTkHBoxLayout(), height=1)
value.layout().addWidget(_cb := ttk.TTkColorButtonPicker(color=getval, height=1))
value.layout().addWidget(_rc := ttk.TTkButton(text=ttk.TTkString('x',ttk.TTkColor.fg('#FFAA00')),maxWidth=3))
_cb.colorSelected.connect(_bound(prop['set']['cb'],domw,lambda v:v))
_rc.clicked.connect(_boundValue(prop['set']['cb'],domw,ttk.TTkColor.RST))
_rc.clicked.connect(lambda :_cb.setColor(ttk.TTkColor.RST))
return ttk.TTkTreeWidgetItem([name,value])
# Layout field
def _processTTkLayout(name, prop, domw):
value = ttk.TTkComboBox(list=['TTkLayout','TTkGridLayout','TTkHBoxLayout','TTkVBoxLayout'], height=1)
value.setCurrentText(prop['get']['cb'](domw).__class__.__name__)
value.currentTextChanged.connect(_bound(prop['set']['cb'],domw, lambda v:globals()[v]()))
return ttk.TTkTreeWidgetItem([name,value])
# Add a button Control
def _processButton(name, prop, domw):
value = ttk.TTkButton(text=f" {prop['get']['text']}", border=False)
value.clicked.connect(lambda :prop['get']['cb'](domw))
return ttk.TTkTreeWidgetItem([name,value])
# Unrecognised Field
def _processUnknown(name, prop, domw):
getval = prop['get']['cb'](domw)
if type(prop['get']['type']) == str:
getval = f"{prop['get']['type']} = {getval}"
elif issubclass(prop['get']['type'], ttk.TTkLayout):
getval = getval.__class__.__name__
value = ttk.TTkLabel(minSize=(30,1), maxHeight=1, text=f"{getval}", height=1)
return ttk.TTkTreeWidgetItem([name,value])
def _processProp(name, prop, domw):
if 'fw_obj' in prop['get']:
domw = prop['get']['fw_obj'](domw)
if 'get' in prop:
if prop['get']['type'] == 'multiflags':
return _processMultiFlag(name, prop, domw)
elif prop['get']['type'] == 'singleflag':
return _processSingleFlag(name, prop, domw)
elif prop['get']['type'] == bool and 'set' in prop:
return _processBool(name, prop, domw)
elif prop['get']['type'] == int and 'set' in prop:
return _processInt(name, prop, domw)
elif prop['get']['type'] == str and 'set' in prop:
return _processStr(name, prop, domw)
elif prop['get']['type'] == ttk.TTkString and 'set' in prop:
return _processTTkString(p,prop,domw,multiLine=True)
elif prop['get']['type'] == 'singleLineTTkString':
return _processTTkString(p,prop,domw,multiLine=False)
elif prop['get']['type'] == ttk.TTkColor and 'set' in prop:
return _processTTkColor(name, prop, domw)
elif prop['get']['type'] == ttk.TTkLayout and 'set' in prop:
return _processTTkLayout(name, prop, domw)
elif type(prop['get']['type']) == list:
return _processList(name, prop, domw)
elif type(prop['get']['type']) == dict:
return _processDict(name, prop, domw)
elif prop['get']['type'] == 'button':
return _processButton(p, prop, domw)
else:
return _processUnknown(name, prop, domw)
proplist = exclude
self._detail.clear()
for cc in reversed(type(domwBase).__mro__):
# if hasattr(cc,'_ttkProperties'):
if issubclass(cc, ttk.TTkWidget) or issubclass(cc, ttk.TTkLayout):
ccName = cc.__name__
classItem = ttk.TTkTreeWidgetItem([ccName,''], expanded=True)
self._detail.addTopLevelItem(classItem)
if ccName in ttk.TTkUiProperties:
if ccName in additions:
for p in additions[ccName]:
if p not in proplist:
proplist.append(p)
classItem.addChild(_processProp(p, additions[ccName][p], domwBase))
for p in ttk.TTkUiProperties[ccName]['properties']:
if p in exceptions:
prop = exceptions[p]
else:
prop = ttk.TTkUiProperties[ccName]['properties'][p]
if p not in proplist:
proplist.append(p)
classItem.addChild(_processProp(p, prop, domwBase))