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.
236 lines
9.2 KiB
236 lines
9.2 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 |
|
|
|
from .superobj.superwidget import SuperWidget |
|
from .superobj.superlayout import SuperLayout |
|
|
|
class _SignalSlotItem(ttk.TTkTreeWidgetItem): |
|
__slots__ = ('_sender', '_signal', '_receiver', '_slot', '_designer', '_signalData', '_slotData', '_avoidRecursion') |
|
def __init__(self, designer, *args, **kwargs): |
|
self._designer = designer |
|
self._sender = ttk.TTkComboBox(height=1, list=['<sender>' ], index=0) |
|
self._signal = ttk.TTkComboBox(height=1, list=['<signal>' ], index=0) |
|
self._receiver = ttk.TTkComboBox(height=1, list=['<receiver>'], index=0) |
|
self._slot = ttk.TTkComboBox(height=1, list=['<slot>' ], index=0) |
|
self._signalData = {} |
|
self._slotData = {} |
|
self._avoidRecursion = False |
|
self._sender.currentTextChanged.connect( self._senderChanged) |
|
self._signal.currentTextChanged.connect( self._signalChanged) |
|
self._receiver.currentTextChanged.connect(self._receiverChanged) |
|
self._slot.currentTextChanged.connect( self._slotChanged) |
|
self._designer.widgetNameChanged.connect( self._widgetNameChanged) |
|
self._designer.weModified.connect(self.updateWidgets) |
|
self.updateWidgets() |
|
super().__init__([self._sender,self._signal,self._receiver,self._slot], *args, **kwargs) |
|
|
|
def isValid(self): |
|
curSender = str(self._sender.currentText()) |
|
curReceiver = str(self._receiver.currentText()) |
|
curSignal = str(self._signal.currentText()) |
|
curSlot = str(self._slot.currentText()) |
|
if curSender=='<sender>' or curReceiver == '<receiver>': |
|
return False |
|
ret = False |
|
for ccName in self._signalData: |
|
if curSignal in self._signalData[ccName]: |
|
ret = True |
|
break |
|
for ccName in self._slotData: |
|
if curSlot in self._slotData[ccName]: |
|
ret &= True |
|
break |
|
return ret |
|
|
|
def importConnection(self, connection): |
|
self._sender.setCurrentText(connection['sender']) |
|
self._receiver.setCurrentText(connection['receiver']) |
|
self._signal.setCurrentText(connection['signal']) |
|
self._slot.setCurrentText(connection['slot']) |
|
|
|
def dumpDict(self): |
|
curSender = str(self._sender.currentText()) |
|
curReceiver = str(self._receiver.currentText()) |
|
curSignal = str(self._signal.currentText()) |
|
curSlot = str(self._slot.currentText()) |
|
return { |
|
'sender': curSender, |
|
'receiver': curReceiver, |
|
'signal': curSignal, |
|
'slot': curSlot } |
|
|
|
@ttk.pyTTkSlot() |
|
def updateWidgets(self): |
|
self._widgetNameChanged() |
|
|
|
@staticmethod |
|
def typeToString(t): |
|
if type(t) in (list,tuple): |
|
return ",".join([_SignalSlotItem.typeToString(x) for x in t]) |
|
return {bool : 'bool', |
|
int : 'int', |
|
float : 'float', |
|
complex : 'complex', |
|
str : 'str', |
|
None : ''}.get(t,f"UNDEFINED {t}") |
|
|
|
@ttk.pyTTkSlot(str, str) |
|
def _widgetNameChanged(self, oldName='', newName=''): |
|
names = [w.name() for w in self._designer.getWidgets()] |
|
|
|
def _setCB(cb:ttk.TTkComboBox, base): |
|
text = cb.currentText() |
|
text = text if text!=oldName else newName |
|
cb.clear() |
|
cb.addItems([base]+names) |
|
cb.setCurrentText(text) |
|
|
|
_setCB(self._sender, '<sender>') |
|
_setCB(self._receiver, '<receiver>') |
|
|
|
@ttk.pyTTkSlot(str) |
|
def _senderChanged(self, text): |
|
self._signalData, _ = self.getSignalSlot(text) |
|
self._refreshSignals() |
|
|
|
def _refreshSignals(self): |
|
curSignal = str(self._signal.currentText()) |
|
curSlot = str(self._slot.currentText()) |
|
filter = None |
|
for c in self._slotData: |
|
if not str(curSlot) in self._slotData[c]: continue |
|
filter = self._slotData[c][curSlot]['type'] |
|
break |
|
|
|
signals = ['<signal>'] |
|
for ccName in self._signalData: |
|
signals.append(ttk.TTkString(f"{ccName}:",ttk.TTkColor.fg("#FFFF88")+ttk.TTkColor.UNDERLINE)) |
|
for s in self._signalData[ccName]: |
|
if filter in (None,self._signalData[ccName][s]['type']): |
|
signals.append(s) |
|
self._signal.clear() |
|
self._signal.addItems(signals) |
|
self._signal.setCurrentText(curSignal) |
|
|
|
@ttk.pyTTkSlot(str) |
|
def _signalChanged(self, text): |
|
if self._avoidRecursion: return |
|
self._avoidRecursion = True |
|
self._refreshSlots() |
|
self._avoidRecursion = False |
|
|
|
|
|
@ttk.pyTTkSlot(str) |
|
def _receiverChanged(self, text): |
|
_, self._slotData = self.getSignalSlot(text) |
|
self._refreshSlots() |
|
|
|
def _refreshSlots(self): |
|
curSignal = self._signal.currentText() |
|
curSlot = self._slot.currentText() |
|
filter = 'ALL' |
|
for c in self._signalData: |
|
if not str(curSignal) in self._signalData[c]: continue |
|
filter = self._signalData[c][curSignal]['type'] |
|
break |
|
|
|
slots = ['<slot>'] |
|
for ccName in self._slotData: |
|
slots.append(ttk.TTkString(f"{ccName}:",ttk.TTkColor.fg("#FFFF88")+ttk.TTkColor.UNDERLINE)) |
|
for s in self._slotData[ccName]: |
|
if filter in ('ALL',self._slotData[ccName][s]['type']) or not self._slotData[ccName][s]['type']: |
|
slots.append(s) |
|
|
|
self._slot.clear() |
|
self._slot.addItems(slots) |
|
self._slot.setCurrentText(curSlot) |
|
|
|
@ttk.pyTTkSlot(str) |
|
def _slotChanged(self, text): |
|
if self._avoidRecursion: return |
|
self._avoidRecursion = True |
|
self._refreshSignals() |
|
self._avoidRecursion = False |
|
|
|
def getSignalSlot(self, name): |
|
if not (widget := {w.name():w for w in self._designer.getWidgets()}.get(name,None)): |
|
return {},{} |
|
ttk.TTkLog.debug(f"Selected {widget=}") |
|
slots = {} |
|
signals = {} |
|
for cc in reversed(type(widget).__mro__): |
|
if issubclass(cc, ttk.TTkWidget) or issubclass(cc, ttk.TTkLayout): |
|
ccName = cc.__name__ |
|
if ccName in ttk.TTkUiProperties: |
|
if ttk.TTkUiProperties[ccName]['slots']: |
|
slots[ccName] = ttk.TTkUiProperties[ccName]['slots'] |
|
if ttk.TTkUiProperties[ccName]['signals']: |
|
signals[ccName] = ttk.TTkUiProperties[ccName]['signals'] |
|
return signals,slots |
|
|
|
class SignalSlotEditor(ttk.TTkWidget): |
|
__slots__ = ('_items', '_designer') |
|
def __init__(self, designer, *args, **kwargs): |
|
super().__init__(*args, **kwargs) |
|
self.setLayout(ttk.TTkGridLayout()) |
|
self._items = [] |
|
self._designer = designer |
|
|
|
self._detail = ttk.TTkTree() |
|
self._detail.setHeaderLabels(["Sender","Signal","Receiver","Slot"]) |
|
self.layout().addWidget(addb := ttk.TTkButton(border=False, text="ADD"),1,1) |
|
self.layout().addWidget(delb := ttk.TTkButton(border=False, text="DEL"),1,2) |
|
self.layout().addWidget(ttk.TTkLabel(text=" Signal/Slot Editor "),1,3) |
|
self.layout().addWidget(self._detail,2,1,1,3) |
|
|
|
addb.clicked.connect(self._addStuff) |
|
delb.clicked.connect(self._delStuff) |
|
|
|
def _addStuff(self): |
|
item = _SignalSlotItem(self._designer) |
|
self._items.append(item) |
|
self._detail.addTopLevelItem(item) |
|
|
|
def _delStuff(self): |
|
if not (items := self._detail.selectedItems()): |
|
return |
|
for item in items: |
|
if (index := self._detail.indexOfTopLevelItem(item)) is not None: |
|
self._detail.takeTopLevelItem(index) |
|
|
|
def importConnections(self, connections): |
|
self._items = [] |
|
self._detail.clear() |
|
for c in connections: |
|
item = _SignalSlotItem(self._designer) |
|
item.importConnection(c) |
|
self._items.append(item) |
|
self._detail.addTopLevelItem(item) |
|
|
|
def dumpDict(self): |
|
ret = [] |
|
for i in self._items: |
|
if i.isValid(): |
|
ret.append(i.dumpDict()) |
|
return ret
|
|
|