Source code for TermTk.TTkUiTools.uiloader

# 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"

[docs] class TTkUiLoader(): '''TTkUiLoader .. _ttkDesigner: https://github.com/ceccopierangiolieugenio/pyTermTk/tree/main/tools/ttkDesigner `ttkdesigner Tutorial <https://github.com/ceccopierangiolieugenio/pyTermTk/blob/main/tutorial/ttkDesigner/textEdit>`_ '''
[docs] @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 :py:class:`TTkWidget` will be returned,defaults to **None** :type baseWidget: :py:class:`TTkWidget`, optional :param kwargs: the custom initialization args,defaults to **None** :type kwargs: dictionary, optional :return: :py:class:`TTkWidget` ''' with open(filePath) as f: return TTkUiLoader.loadJson(f.read(), baseWidget, kwargs) return None
[docs] @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 :py:class:`TTkWidget` will be returned,defaults to **None** :type baseWidget: :py:class:`TTkWidget`, optional :param kwargs: the custom initialization args,defaults to **None** :type kwargs: dictionary, optional :return: :py:class:`TTkWidget` ''' return TTkUiLoader.loadDict(json.loads(text), baseWidget, kwargs)
def _convert_2_0_2_to_2_1_0(ui): return { "type": TTkUiSignature, "version": "2.1.0", "connections" : ui['connections'], "tui": ui['tui'] } def _convert_2_0_1_to_2_0_2(ui): return { "type": TTkUiSignature, "version": "2.0.2", "connections" : ui['connections'], "tui": ui['tui'] } def _convert_2_0_0_to_2_0_2(ui): return { "type": TTkUiSignature, "version": "2.0.2", "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) -> None: 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) -> None: ui = TTkUiLoader._convert_2_0_0_to_2_0_2(ui) return TTkUiLoader._loadDict_2_0_2(ui, *args, **kwargs) def _loadDict_2_0_1(ui, *args, **kwargs) -> None: ui = TTkUiLoader._convert_2_0_1_to_2_0_2(ui) return TTkUiLoader._loadDict_2_0_2(ui, *args, **kwargs) def _loadDict_2_0_2(ui, *args, **kwargs) -> None: ui = TTkUiLoader._convert_2_0_2_to_2_1_0(ui) return TTkUiLoader._loadDict_2_1_0(ui, *args, **kwargs) @staticmethod def _loadDict_2_1_0(ui, baseWidget:TTkWidget=None, args=None): if ( ui['version'] != '2.1.0' 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 name not 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 name not 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
[docs] @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_2, '2.0.1' : TTkUiLoader._convert_2_0_1_to_2_0_2, '2.0.2' : TTkUiLoader._convert_2_0_2_to_2_1_0, '2.1.0' : lambda x: x }.get(ui['version'], lambda x: x) return cb(ui)
[docs] @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 :py:class:`TTkWidget` will be returned,defaults to **None** :type baseWidget: :py:class:`TTkWidget`, optional :param kwargs: the custom initialization args,defaults to **None** :type kwargs: dictionary, optional :return: :py:class:`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, '2.0.2' : TTkUiLoader._loadDict_2_0_2, '2.1.0' : TTkUiLoader._loadDict_2_1_0, }.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)