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
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))
|
|
|