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.
338 lines
15 KiB
338 lines
15 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. |
|
|
|
__all__ = ['TTkUiLoader','TTkUiSignature'] |
|
|
|
import json |
|
|
|
from TermTk import TTkLog |
|
from TermTk import TTkCfg, TTkK, TTkColor |
|
from TermTk.TTkLayouts import TTkLayout, TTkGridLayout |
|
from TermTk.TTkWidgets import * |
|
from TermTk.TTkTestWidgets import * |
|
from TermTk.TTkUiTools.uiproperties import TTkUiProperties |
|
|
|
TTkUiSignature = "TTkUi/Document" |
|
|
|
class TTkUiLoader(): |
|
'''TTkUiLoader |
|
|
|
.. _ttkDesigner: https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/ttkDesigner |
|
|
|
|
|
`ttkdesigner Tutorial <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/ttkDesigner/textEdit>`_ |
|
|
|
''' |
|
|
|
@staticmethod |
|
def loadFile(filePath, baseWidget:TTkWidget=None, kwargs=None) -> TTkWidget: |
|
'''load the file generated by ttkDesigner_ |
|
|
|
:param filePath: the file path |
|
:type filePath: str |
|
:param baseWidget: the custom widget that will be extended with this ui definition, if not defined a new :class:`~TermTk.TTkWidgets.widget.TTkWidget` will be returned,defaults to **None** |
|
:type baseWidget: :class:`~TermTk.TTkWidgets.widget.TTkWidget`, optional |
|
:param kwargs: the custom initialization args,defaults to **None** |
|
:type kwargs: dictionary, optional |
|
|
|
:return: :class:`~TermTk.TTkWidgets.widget.TTkWidget` |
|
''' |
|
with open(filePath) as f: |
|
return TTkUiLoader.loadJson(f.read(), baseWidget, kwargs) |
|
return None |
|
|
|
@staticmethod |
|
def loadJson(text, baseWidget:TTkWidget=None, kwargs=None) -> TTkWidget: |
|
'''load the json representing the ui definition of the widget |
|
|
|
:param text: the representation of the widget in Json format |
|
:type text: json generated by ttkDesigner_ |
|
:param baseWidget: the custom widget that will be extended with this ui definition, if not defined a new :class:`~TermTk.TTkWidgets.widget.TTkWidget` will be returned,defaults to **None** |
|
:type baseWidget: :class:`~TermTk.TTkWidgets.widget.TTkWidget`, optional |
|
:param kwargs: the custom initialization args,defaults to **None** |
|
:type kwargs: dictionary, optional |
|
|
|
:return: :class:`~TermTk.TTkWidgets.widget.TTkWidget` |
|
''' |
|
return TTkUiLoader.loadDict(json.loads(text), baseWidget, kwargs) |
|
|
|
def _convert_2_0_0_to_2_0_1(ui): |
|
return { |
|
"type": TTkUiSignature, |
|
"version": "2.0.1", |
|
"connections" : ui['connections'], |
|
"tui": ui['tui'] } |
|
|
|
def _convert_1_0_1_to_2_0_0(ui): |
|
def _processWidget(_wid): |
|
if issubclass(globals()[_wid['class']],TTkContainer): |
|
if hasattr(_wid,'layout'): |
|
_wid['layout']['children'] = [_processWidget(_ch) for _ch in _wid['layout']['children']] |
|
elif _wid['layout']['children']: |
|
_wid['class'] = 'TTkContainer' |
|
_wid['layout']['children'] = [_processWidget(_ch) for _ch in _wid['layout']['children']] |
|
else: |
|
_wid.pop('layout',None) |
|
_wid['params'].pop("layout",None) |
|
return _wid |
|
|
|
tui = _processWidget(ui['tui']) |
|
|
|
return { |
|
"version": "2.0.0", |
|
"connections" : ui['connections'], |
|
"tui": tui } |
|
|
|
@staticmethod |
|
def _loadDict_1_0_0(ui, *args, **kwargs): |
|
ui = TTkUiLoader._convert_1_0_1_to_2_0_0(ui) |
|
return TTkUiLoader._loadDict_2_0_0(ui, *args, **kwargs) |
|
|
|
def _loadDict_2_0_0(ui, *args, **kwargs): |
|
ui = TTkUiLoader._convert_2_0_0_to_2_0_1(ui) |
|
return TTkUiLoader._loadDict_2_0_1(ui, *args, **kwargs) |
|
|
|
@staticmethod |
|
def _loadDict_2_0_1(ui, baseWidget:TTkWidget=None, args=None): |
|
if ( |
|
ui['version'] != '2.0.1' or |
|
'type' not in ui or |
|
ui['type'] != TTkUiSignature): |
|
TTkLog.error("Ui Format not valid") |
|
return None |
|
|
|
def _setMenuButton(_menuButtonProp, _menuButton:TTkMenuButton): |
|
if 'submenu' in _menuButtonProp: |
|
for _sm in _menuButtonProp['submenu']: |
|
if _sm == 'spacer': |
|
_menuButton.addSpacer() |
|
continue |
|
_btn = _menuButton.addMenu(text=_sm['params']['Text'], checkable=_sm['params']['Checkable'], checked=_sm['params']['Checked']) |
|
_btn.setName(_sm['params']['Name']) |
|
_btn.setToolTip(_sm['params']['ToolTip']) |
|
_setMenuButton(_sm,_btn) |
|
|
|
def _setMenuBar(_menuBarProp, _menuBar:TTkMenuBarLayout): |
|
def __addMenu(__prop, __alignment): |
|
for _bp in __prop: |
|
_btn = _menuBar.addMenu(text=_bp['params']['Text'], checkable=_bp['params']['Checkable'], checked=_bp['params']['Checked'], alignment=__alignment) |
|
_btn.setName(_bp['params']['Name']) |
|
_btn.setToolTip(_bp['params']['ToolTip']) |
|
_setMenuButton(_bp, _btn) |
|
|
|
if 'left' in _menuBarProp: __addMenu(_menuBarProp['left'], TTkK.LEFT_ALIGN) |
|
if 'center' in _menuBarProp: __addMenu(_menuBarProp['center'], TTkK.CENTER_ALIGN) |
|
if 'right' in _menuBarProp: __addMenu(_menuBarProp['right'], TTkK.RIGHT_ALIGN) |
|
|
|
def _getWidget(_widProp, _baseWidget:TTkWidget=None, _args=None): |
|
properties = {} |
|
ttkClass = globals()[_widProp['class']] |
|
for cc in reversed(ttkClass.__mro__): |
|
if cc.__name__ in TTkUiProperties: |
|
properties |= TTkUiProperties[cc.__name__]['properties'] |
|
# Init params used in the constructors |
|
kwargs = {} if _args is None else _args |
|
# Init params to be configured with the setter |
|
setters = [] |
|
layout = _getLayout(_widProp['layout']) if 'layout' in _widProp else TTkLayout() |
|
# Process the widget params |
|
for pname in _widProp['params']: |
|
if pname not in properties: continue |
|
if 'init' in properties[pname]: |
|
initp = properties[pname]['init'] |
|
name = initp['name'] |
|
if initp['type'] is TTkLayout: |
|
value = layout |
|
elif initp['type'] is TTkColor: |
|
value = TTkColor.ansi(_widProp['params'][pname]) |
|
else: |
|
value = _widProp['params'][pname] |
|
# TTkLog.debug(f"{name=} {value=}") |
|
if name not in kwargs: |
|
kwargs |= {name: value} |
|
elif 'set' in properties[pname]: |
|
setp = properties[pname]['set'] |
|
setcb = setp['cb'] |
|
if setp['type'] is TTkLayout: |
|
value = layout |
|
elif setp['type'] is TTkColor: |
|
value = TTkColor.ansi(_widProp['params'][pname]) |
|
else: |
|
value = _widProp['params'][pname] |
|
setters.append({ |
|
'cb':setcb, |
|
'value': value, |
|
'multi':type(setp['type']) is list}) |
|
|
|
if _baseWidget is None: |
|
widget = ttkClass(**kwargs) |
|
else: |
|
widget = _baseWidget |
|
if issubclass(type(_baseWidget), ttkClass): |
|
ttkClass.__init__(widget,**kwargs) |
|
else: |
|
error = f"Base Widget '{_baseWidget.__class__.__name__}' is not a subclass of '{ttkClass.__name__}'" |
|
raise TypeError(error) |
|
|
|
# Init params that don't have a constrictor |
|
for s in setters: |
|
if s['multi']: |
|
s['cb'](widget, *s['value']) |
|
else: |
|
s['cb'](widget, s['value']) |
|
|
|
# Process the optional menuBar params |
|
if 'menuBar' in _widProp: |
|
if 'top' in _widProp['menuBar']: |
|
widget.setMenuBar(mb := TTkMenuBarLayout(), TTkK.TOP) |
|
_setMenuBar(_widProp['menuBar']['top'], mb) |
|
if 'bottom' in _widProp['menuBar']: |
|
widget.setMenuBar(mb := TTkMenuBarLayout(), TTkK.BOTTOM) |
|
_setMenuBar(_widProp['menuBar']['bottom'], mb) |
|
|
|
# TTkLog.debug(widget) |
|
return widget |
|
|
|
def _getLayout(_layprop, _baseWidget:TTkWidget=None): |
|
properties = {} |
|
ttkClass = globals()[_layprop['class']] |
|
for cc in reversed(ttkClass.__mro__): |
|
if cc.__name__ in TTkUiProperties: |
|
properties |= TTkUiProperties[cc.__name__]['properties'] |
|
|
|
setters = [] |
|
for pname in _layprop['params']: |
|
if 'set' in properties[pname]: |
|
setp = properties[pname]['set'] |
|
setcb = setp['cb'] |
|
if setp['type'] is TTkLayout: |
|
value = layout |
|
elif setp['type'] is TTkColor: |
|
value = TTkColor.ansi(_layprop['params'][pname]) |
|
else: |
|
value = _layprop['params'][pname] |
|
setters.append({ |
|
'cb':setcb, |
|
'value': value, |
|
'multi':type(setp['type']) is list}) |
|
|
|
layout = globals()[_layprop['class']]() |
|
# Init params that don't have a constrictor |
|
for s in setters: |
|
if s['multi']: |
|
s['cb'](layout, *s['value']) |
|
else: |
|
s['cb'](layout, s['value']) |
|
|
|
for c in _layprop['children']: |
|
row = c.get('row', 0) |
|
col = c.get('col', 0) |
|
rowspan = c.get('rowspan', 1) |
|
colspan = c.get('colspan', 1) |
|
if issubclass(ttkClass,TTkGridLayout): |
|
if issubclass(globals()[c['class']],TTkLayout): |
|
l = _getLayout(c) |
|
TTkGridLayout.addItem(layout,l,row,col,rowspan,colspan) |
|
else: |
|
w = _getWidget(c) |
|
TTkGridLayout.addWidget(layout,w,row,col,rowspan,colspan) |
|
else: |
|
if issubclass(globals()[c['class']],TTkLayout): |
|
l = _getLayout(c) |
|
l._row, l._col = row, col |
|
l._rowspan, l._colspan = rowspan, colspan |
|
layout.addItem(l) |
|
else: |
|
w = _getWidget(c) |
|
w._row, w._col = row, col |
|
w._rowspan, w._colspan = rowspan, colspan |
|
layout.addWidget(w) |
|
return layout |
|
|
|
TTkLog.debug(ui) |
|
|
|
if issubclass(globals()[ui['tui']['class']],TTkLayout): |
|
widget = _getLayout(ui['tui'], baseWidget) |
|
else: |
|
widget = _getWidget(ui['tui'], baseWidget, args) |
|
|
|
def _getSignal(sender, name): |
|
for cc in reversed(type(sender).__mro__): |
|
if cc.__name__ in TTkUiProperties: |
|
if not name in TTkUiProperties[cc.__name__]['signals']: |
|
continue |
|
signame = TTkUiProperties[cc.__name__]['signals'][name]['name'] |
|
return getattr(sender,signame) |
|
return None |
|
|
|
def _getSlot(receiver, name): |
|
for cc in reversed(type(receiver).__mro__): |
|
if cc.__name__ in TTkUiProperties: |
|
if not name in TTkUiProperties[cc.__name__]['slots']: |
|
continue |
|
slotname = TTkUiProperties[cc.__name__]['slots'][name]['name'] |
|
return getattr(receiver,slotname) |
|
return None |
|
|
|
|
|
for conn in ui['connections']: |
|
sender = widget.getWidgetByName(conn['sender']) |
|
receiver = widget.getWidgetByName(conn['receiver']) |
|
signal = conn['signal'] |
|
slot = conn['slot'] |
|
if None in (sender,receiver): continue |
|
_getSignal(sender,signal).connect(_getSlot(receiver,slot)) |
|
|
|
return widget |
|
|
|
@staticmethod |
|
def normalise(ui): |
|
cb = {'1.0.0' : TTkUiLoader._convert_1_0_1_to_2_0_0, |
|
'1.0.1' : TTkUiLoader._convert_1_0_1_to_2_0_0, |
|
'2.0.0' : TTkUiLoader._convert_2_0_0_to_2_0_1, |
|
'2.0.1' : lambda x: x |
|
}.get(ui['version'], lambda x: x) |
|
return cb(ui) |
|
|
|
@staticmethod |
|
def loadDict(ui, baseWidget:TTkWidget=None, kwargs=None) -> TTkWidget: |
|
'''load the dictionary representing the ui definition of the widget |
|
|
|
:param ui: the representation of the widget |
|
:type ui: dictionary generated by ttkDesigner_ |
|
:param baseWidget: the custom widget that will be extended with this ui definition, if not defined a new :class:`~TermTk.TTkWidgets.widget.TTkWidget` will be returned,defaults to **None** |
|
:type baseWidget: :class:`~TermTk.TTkWidgets.widget.TTkWidget`, optional |
|
:param kwargs: the custom initialization args,defaults to **None** |
|
:type kwargs: dictionary, optional |
|
|
|
:return: :class:`~TermTk.TTkWidgets.widget.TTkWidget` |
|
''' |
|
cb = {'1.0.0' : TTkUiLoader._loadDict_1_0_0, |
|
'1.0.1' : TTkUiLoader._loadDict_1_0_0, |
|
'2.0.0' : TTkUiLoader._loadDict_2_0_0, |
|
'2.0.1' : TTkUiLoader._loadDict_2_0_1, |
|
}.get(ui['version'], None) |
|
if cb: |
|
return cb(ui, baseWidget, kwargs) |
|
msg = (f"The used pyTermTk ({TTkCfg.version}) is not able to load this tui version ({ui['version']})") |
|
raise NotImplementedError(msg) |
|
|
|
|