From 5750a7bd6efdf82f05e6b39b26f19fa25ea19641 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 8 Oct 2023 00:10:58 +0100 Subject: [PATCH 1/9] Q/A: #185 Add simple focus example --- tutorial/examples/TTkWidget/Focus.01.py | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tutorial/examples/TTkWidget/Focus.01.py diff --git a/tutorial/examples/TTkWidget/Focus.01.py b/tutorial/examples/TTkWidget/Focus.01.py new file mode 100644 index 00000000..fbc3a719 --- /dev/null +++ b/tutorial/examples/TTkWidget/Focus.01.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# 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. + +# Those 2 lines are required to use the TermTk library straight from the main folder +import sys, os +sys.path.append(os.path.join(sys.path[0],'../../..')) + +import TermTk as ttk + +root=ttk.TTk() + +btn1 = ttk.TTkButton(parent=root, pos=(5,2), text='Button 1 - unfocussed') +btn2 = ttk.TTkButton(parent=root, pos=(5,3), text='Button 2 - unfocussed') +btn3 = ttk.TTkButton(parent=root, pos=(5,4), text='Button 3 - unfocussed') +btn4 = ttk.TTkButton(parent=root, pos=(5,5), text='Button 4 - focussed') +btn5 = ttk.TTkButton(parent=root, pos=(5,6), text='Button 5 - unfocussed') + +# Force the focus on the button 4 +btn4.setFocus() + +root.mainloop() \ No newline at end of file From 6eedf773c73ee082e728e49bc76a1e87a27fc911 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 8 Oct 2023 00:17:39 +0100 Subject: [PATCH 2/9] Initial implementation of the DragDrop in the list widget --- TermTk/TTkWidgets/kodetab.py | 4 +-- TermTk/TTkWidgets/list_.py | 3 +- TermTk/TTkWidgets/listwidget.py | 62 +++++++++++++++++++++++++++------ tests/test.ui.014.list.03.py | 12 +++---- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/TermTk/TTkWidgets/kodetab.py b/TermTk/TTkWidgets/kodetab.py index 2cc2f9c9..d98478aa 100644 --- a/TermTk/TTkWidgets/kodetab.py +++ b/TermTk/TTkWidgets/kodetab.py @@ -79,11 +79,11 @@ def _importMenu(self, kt): kt._tabBarTopLayout.update() def dragEnterEvent(self, evt) -> bool: - TTkLog.debug(f"leave") + TTkLog.debug(f"Drag Enter") return True def dragLeaveEvent(self, evt) -> bool: - TTkLog.debug(f"leave") + TTkLog.debug(f"Drag Leave") self._frameOverlay = None self.update() return True diff --git a/TermTk/TTkWidgets/list_.py b/TermTk/TTkWidgets/list_.py index 52850f2a..6c68eb98 100644 --- a/TermTk/TTkWidgets/list_.py +++ b/TermTk/TTkWidgets/list_.py @@ -31,7 +31,7 @@ class TTkList(TTkAbstractScrollArea): '_listView', 'itemClicked', 'textClicked', # Forwarded Methods 'items', 'addItem', 'addItemAt', 'indexOf', 'itemAt', - 'moveItem', 'removeAt', 'removeItem', + 'moveItem', 'removeAt', 'removeItem', 'removeItems', 'setSelectionMode', 'selectedItems', 'selectedLabels', 'setCurrentRow', 'setCurrentItem', ) @@ -51,6 +51,7 @@ def __init__(self, *args, **kwargs): self.moveItem = self._listView.moveItem self.removeAt = self._listView.removeAt self.removeItem = self._listView.removeItem + self.removeItems = self._listView.removeItems self.addItem = self._listView.addItem self.addItemAt = self._listView.addItemAt self.setSelectionMode = self._listView.setSelectionMode diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 097e8acd..b79644d7 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -27,9 +27,10 @@ from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.string import TTkString +from TermTk.TTkGui.drag import TTkDrag from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.label import TTkLabel from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView class TTkAbstractListItem(TTkWidget): @@ -193,6 +194,7 @@ def _placeItems(self): for y,item in enumerate(self._items): item.setGeometry(0,y,minw,1) self.viewChanged.emit() + self.update() def addItemAt(self, item, pos, data=None): '''addItemAt''' @@ -226,15 +228,20 @@ def moveItem(self, fr, to): def removeItem(self, item): '''removeItem''' - item.listItemClicked.disconnect(self._labelSelectedHandler) - item._setSelected(False) - item._setHighlighted(False) - self.layout().removeWidget(item) - self._items.remove(item) - if item in self._selectedItems: - self._selectedItems.remove(item) - if item == self._highlighted: - self._highlighted = None + self.removeItems([item]) + + def removeItems(self, items): + '''removeItems''' + self.layout().removeWidgets(items) + for item in items.copy(): + item.listItemClicked.disconnect(self._labelSelectedHandler) + item._setSelected(False) + item._setHighlighted(False) + self._items.remove(item) + if item in self._selectedItems: + self._selectedItems.remove(item) + if item == self._highlighted: + self._highlighted = None self._placeItems() def removeAt(self, pos): @@ -261,6 +268,41 @@ def _moveToHighlighted(self): elif index <= offy: self.viewMoveTo(offx, index) + def mouseDragEvent(self, evt) -> bool: + TTkLog.debug("Start DnD") + if not (items:=self._selectedItems.copy()): + return True + drag = TTkDrag() + data = (self,items) + h = min(3,ih:=len(items)) + 2 + (1 if ih>3 else 0) + w = min(20,iw:=max([it.text().termWidth() for it in items[:3]])) + 2 + pm = TTkCanvas(width=w,height=h) + for y,it in enumerate(items[:3],1): + txt = it.text() + pm.drawText(pos=(1,y), text=it.text()) + if txt.termWidth() >= 20: + pm.drawText(pos=(18,y), text='...') + if ih>3: + pm.drawText(pos=(1,4), text='...') + pm.drawBox(pos=(0,0),size=(w,h)) + drag.setPixmap(pm) + drag.setData(data) + drag.exec() + return True + + def dropEvent(self, evt) -> bool: + TTkLog.debug(f"Drop pos={evt.pos()}") + offx,offy = self.getViewOffsets() + wid,items = evt.data() + # check the correct wid type + if wid and items: + wid.removeItems(items) + for it in reversed(items): + it.setCurrentStyle(it.style()['default']) + self.addItemAt(it,offy+evt.y) + return True + return False + def keyEvent(self, evt): if not self._highlighted: return False if ( evt.type == TTkK.Character and evt.key==" " ) or \ diff --git a/tests/test.ui.014.list.03.py b/tests/test.ui.014.list.03.py index 619dfb4f..8200919d 100755 --- a/tests/test.ui.014.list.03.py +++ b/tests/test.ui.014.list.03.py @@ -82,10 +82,10 @@ def _moveToLeft1(): @ttk.pyTTkSlot() def _delSelected(): - for i in listWidgetMulti.selectedItems().copy(): - listWidgetMulti.removeItem(i) - for i in listWidgetSingle.selectedItems().copy(): - listWidgetSingle.removeItem(i) + items = listWidgetMulti.selectedItems() + listWidgetMulti.removeItems(items) + items = listWidgetSingle.selectedItems() + listWidgetSingle.removeItems(items) btn_mv1.clicked.connect(_moveToRight2) @@ -99,7 +99,7 @@ def _delSelected(): # populate the lists with random entries for i in range(10): - listWidgetSingle.addItem(f"{i}) {getWord()} {getWord()}") - listWidgetMulti.addItem(f"{getWord()} {getWord()}") + listWidgetSingle.addItem(f"S-{i}) {getWord()} {getWord()}") + listWidgetMulti.addItem(f"M-{i}) {getWord()} {getWord()}") root.mainloop() From 873755e2154caeb2d970200b6b201b3c0fe8d9f0 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 8 Oct 2023 16:17:10 +0100 Subject: [PATCH 3/9] Added drop marker in the list widget --- TermTk/TTkWidgets/listwidget.py | 37 ++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index b79644d7..9713a74b 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -107,13 +107,17 @@ def paintEvent(self, canvas): class TTkListWidget(TTkAbstractScrollView): '''TTkListWidget''' - __slots__ = ('itemClicked', 'textClicked', '_selectedItems', '_selectionMode', '_highlighted', '_items') + __slots__ = ('itemClicked', 'textClicked', + '_selectedItems', '_selectionMode', + '_highlighted', '_items', + '_dragPos') def __init__(self, *args, **kwargs): # Default Class Specific Values self._selectionMode = kwargs.get("selectionMode", TTkK.SingleSelection) self._selectedItems = [] self._items = [] self._highlighted = None + self._dragPos = None # Signals self.itemClicked = pyTTkSignal(TTkWidget) self.textClicked = pyTTkSignal(str) @@ -290,8 +294,24 @@ def mouseDragEvent(self, evt) -> bool: drag.exec() return True + def dragEnterEvent(self, evt): + return self.dragMoveEvent(evt) + + def dragMoveEvent(self, evt): + offx,offy = self.getViewOffsets() + y=min(evt.y+offy,len(self._items)) + self._dragPos = (offx+evt.x, y) + self.update() + return True + + def dragLeaveEvent(self, evt): + self._dragPos = None + self.update() + return True + def dropEvent(self, evt) -> bool: TTkLog.debug(f"Drop pos={evt.pos()}") + self._dragPos = None offx,offy = self.getViewOffsets() wid,items = evt.data() # check the correct wid type @@ -351,3 +371,18 @@ def focusInEvent(self): def focusOutEvent(self): if self._highlighted: self._highlighted._setHighlighted(False) + self._dragPos = None + + # Stupid hack to paint on top of the child widgets + def paintChildCanvas(self): + super().paintChildCanvas() + if self._dragPos: + canvas = self.getCanvas() + x,y = self._dragPos + offx,offy = self.getViewOffsets() + p1 = (0,y-offy-1) + p2 = (0,y-offy) + canvas.drawText(pos=p1,text="╙─╼", color=TTkColor.fg("#FFFF00")+TTkColor.bg("#008855")) + canvas.drawText(pos=p2,text="╓─╼", color=TTkColor.fg("#FFFF00")+TTkColor.bg("#008855")) + + From eea8b00ab536c92f65a01e4edc448fb9f9e1a4dd Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 8 Oct 2023 16:57:19 +0100 Subject: [PATCH 4/9] Added dataclass in the listWidget drop --- TermTk/TTkWidgets/listwidget.py | 20 ++++++++++++++++---- tests/test.ui.014.list.03.py | 13 ++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 9713a74b..76e22c34 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -22,6 +22,8 @@ __all__ = ['TTkAbstractListItem', 'TTkListWidget'] +from dataclasses import dataclass + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -106,6 +108,11 @@ def paintEvent(self, canvas): canvas.drawTTkString(pos=(0,0), width=w, color=style['color'] ,text=self._text) class TTkListWidget(TTkAbstractScrollView): + @dataclass(frozen=True) + class _DropListData: + widget: TTkAbstractScrollView + items: list + '''TTkListWidget''' __slots__ = ('itemClicked', 'textClicked', '_selectedItems', '_selectionMode', @@ -277,7 +284,7 @@ def mouseDragEvent(self, evt) -> bool: if not (items:=self._selectedItems.copy()): return True drag = TTkDrag() - data = (self,items) + data =TTkListWidget._DropListData(widget=self,items=items) h = min(3,ih:=len(items)) + 2 + (1 if ih>3 else 0) w = min(20,iw:=max([it.text().termWidth() for it in items[:3]])) + 2 pm = TTkCanvas(width=w,height=h) @@ -295,7 +302,9 @@ def mouseDragEvent(self, evt) -> bool: return True def dragEnterEvent(self, evt): - return self.dragMoveEvent(evt) + if issubclass(type(evt.data()),TTkListWidget._DropListData): + return self.dragMoveEvent(evt) + return False def dragMoveEvent(self, evt): offx,offy = self.getViewOffsets() @@ -312,9 +321,11 @@ def dragLeaveEvent(self, evt): def dropEvent(self, evt) -> bool: TTkLog.debug(f"Drop pos={evt.pos()}") self._dragPos = None + if not issubclass(type(evt.data()) ,TTkListWidget._DropListData): + return False offx,offy = self.getViewOffsets() - wid,items = evt.data() - # check the correct wid type + wid = evt.data().widget + items = evt.data().items if wid and items: wid.removeItems(items) for it in reversed(items): @@ -386,3 +397,4 @@ def paintChildCanvas(self): canvas.drawText(pos=p2,text="╓─╼", color=TTkColor.fg("#FFFF00")+TTkColor.bg("#008855")) + diff --git a/tests/test.ui.014.list.03.py b/tests/test.ui.014.list.03.py index 8200919d..9e7505f3 100755 --- a/tests/test.ui.014.list.03.py +++ b/tests/test.ui.014.list.03.py @@ -2,7 +2,7 @@ # MIT License # -# Copyright (c) 2021 Eugenio Parodi +# Copyright (c) 2023 Eugenio Parodi # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -27,9 +27,16 @@ sys.path.append(os.path.join(sys.path[0],'..')) import TermTk as ttk -words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +zc1 = chr(0x07a6) # Zero width chars oަ +zc2 = chr(0x20D7) # Zero width chars o⃗ +zc3 = chr(0x065f) # Zero width chars oٟ +utfwords = [ + f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-", + "d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "t😜mpor", "inci😜di😜dunt", "u😜t", "l😜abore", "et", "d😜olore", "m😜a😜gna", "ali😜qua😜.", "Ut", "enim", "😜a😜d😜", "minim", "veniam,", "😜q😜uis", "😜nostrud", "exer😜c😜i😜tation", "ullamco", "labo😜ris", "n😜isi", "ut", "aliq😞ip", "e😜x😜", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] def getWord(): - return random.choice(words) + return random.choice(utfwords) + # return random.choice(words) parser = argparse.ArgumentParser() parser.add_argument('-t', help='Track Mouse', action='store_true') From 54d4b10da68e08bc4a566b87db67118cf275eb20 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Mon, 9 Oct 2023 08:59:19 +0100 Subject: [PATCH 5/9] Tuned list widget colors to make them more readable --- TermTk/TTkWidgets/listwidget.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 76e22c34..d489e0a6 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -40,9 +40,9 @@ class TTkAbstractListItem(TTkWidget): classStyle = TTkWidget.classStyle | { 'default': {'color': TTkColor.RST}, - 'highlighted': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')+TTkColor.UNDERLINE}, - 'hover': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0088FF')}, - 'selected': {'color': TTkColor.fg('#00FF00')+TTkColor.bg('#0055FF')}, + 'highlighted': {'color': TTkColor.bg('#008855')+TTkColor.UNDERLINE}, + 'hover': {'color': TTkColor.bg('#0088FF')}, + 'selected': {'color': TTkColor.bg('#0055FF')}, 'clicked': {'color': TTkColor.fg('#FFFF00')}, 'disabled': {'color': TTkColor.fg('#888888')}, } @@ -95,17 +95,17 @@ def _setHighlighted(self, highlighted): self.update() def paintEvent(self, canvas): - style = self.currentStyle() - if style == self.classStyle['hover']: - pass - elif self._highlighted: - style = self.style()['highlighted'] - elif self._selected: - style = self.style()['selected'] + color = (style:=self.currentStyle())['color'] + if self._highlighted: + color = color+self.style()['highlighted']['color'] + if self._selected: + color = color+self.style()['selected']['color'] + if style==self.style()['hover']: + color = color+self.style()['hover']['color'] w = self.width() - canvas.drawTTkString(pos=(0,0), width=w, color=style['color'] ,text=self._text) + canvas.drawTTkString(pos=(0,0), width=w, color=color ,text=self._text) class TTkListWidget(TTkAbstractScrollView): @dataclass(frozen=True) From 4bd41a2c0fea2da1098587f7441c35bcc60dc418 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Mon, 9 Oct 2023 09:23:22 +0100 Subject: [PATCH 6/9] Improved ListWidget with service functions --- TermTk/TTkWidgets/list_.py | 8 ++++++-- TermTk/TTkWidgets/listwidget.py | 33 +++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/TermTk/TTkWidgets/list_.py b/TermTk/TTkWidgets/list_.py index 6c68eb98..b2d9e6af 100644 --- a/TermTk/TTkWidgets/list_.py +++ b/TermTk/TTkWidgets/list_.py @@ -30,8 +30,10 @@ class TTkList(TTkAbstractScrollArea): __slots__ = ( '_listView', 'itemClicked', 'textClicked', # Forwarded Methods - 'items', 'addItem', 'addItemAt', 'indexOf', 'itemAt', - 'moveItem', 'removeAt', 'removeItem', 'removeItems', + 'items', + 'addItem', 'addItemAt', 'addItems', 'addItemsAt', + 'indexOf', 'itemAt', 'moveItem', + 'removeAt', 'removeItem', 'removeItems', 'setSelectionMode', 'selectedItems', 'selectedLabels', 'setCurrentRow', 'setCurrentItem', ) @@ -53,7 +55,9 @@ def __init__(self, *args, **kwargs): self.removeItem = self._listView.removeItem self.removeItems = self._listView.removeItems self.addItem = self._listView.addItem + self.addItems = self._listView.addItems self.addItemAt = self._listView.addItemAt + self.addItemsAt = self._listView.addItemsAt self.setSelectionMode = self._listView.setSelectionMode self.selectedItems = self._listView.selectedItems self.selectedLabels = self._listView.selectedLabels diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index d489e0a6..bab2f486 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -198,6 +198,10 @@ def addItem(self, item, data=None): '''addItem''' self.addItemAt(item, len(self._items), data) + def addItems(self, items): + '''addItems''' + self.addItemAt(items, len(self._items)) + def _placeItems(self): minw = self.width() for item in self._items: @@ -210,12 +214,19 @@ def _placeItems(self): def addItemAt(self, item, pos, data=None): '''addItemAt''' if isinstance(item, str) or isinstance(item, TTkString): - #label = TTkAbstractListItem(text=item, width=max(len(item),self.width())) - label = TTkAbstractListItem(text=item, data=data) - return self.addItemAt(label,pos) - item.listItemClicked.connect(self._labelSelectedHandler) - self._items.insert(pos,item) - self.layout().addWidget(item) + item = TTkAbstractListItem(text=item, data=data) + return self.addItemsAt([item],pos) + + def addItemsAt(self, items, pos): + '''addItemsAt''' + for item in items: + if not issubclass(type(item),TTkAbstractListItem): + TTkLog.error(f"{item=} is not an TTkAbstractListItem") + return + for item in items: + item.listItemClicked.connect(self._labelSelectedHandler) + self._items[pos:pos] = items + self.layout().addWidgets(items) self._placeItems() def indexOf(self, item): @@ -290,8 +301,10 @@ def mouseDragEvent(self, evt) -> bool: pm = TTkCanvas(width=w,height=h) for y,it in enumerate(items[:3],1): txt = it.text() - pm.drawText(pos=(1,y), text=it.text()) - if txt.termWidth() >= 20: + if txt.termWidth() < 20: + pm.drawText(pos=(1,y), text=it.text()) + else: + pm.drawText(pos=(1,y), text=it.text(), width=17) pm.drawText(pos=(18,y), text='...') if ih>3: pm.drawText(pos=(1,4), text='...') @@ -328,9 +341,9 @@ def dropEvent(self, evt) -> bool: items = evt.data().items if wid and items: wid.removeItems(items) - for it in reversed(items): + for it in items: it.setCurrentStyle(it.style()['default']) - self.addItemAt(it,offy+evt.y) + self.addItemsAt(items,offy+evt.y) return True return False From 56ecf087f2e7353619df07a5196a5990a9503460 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Mon, 9 Oct 2023 18:39:13 +0100 Subject: [PATCH 7/9] Added dragDropMode in the list widget --- TermTk/TTkCore/canvas.py | 1 - TermTk/TTkCore/constant.py | 12 ++++ TermTk/TTkWidgets/listwidget.py | 20 +++++- demo/showcase/list.py | 78 +++++++++++++++++----- tests/test.ui.014.list.04.py | 112 ++++++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 20 deletions(-) create mode 100755 tests/test.ui.014.list.04.py diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index a82de402..7ffc0543 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -37,7 +37,6 @@ class TTkCanvas: ''' __slots__ = ( '_width', '_height', '_newWidth', '_newHeight', - '_theme', '_data', '_colors', '_bufferedData', '_bufferedColors', '_visible', '_transparent', '_doubleBuffer') diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index efd19d96..833200e4 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -128,6 +128,18 @@ class InsertPolicy(int): # InsertAlphabetically = 0x06 # '''The string is inserted in the alphabetic order in the combobox.''' + class DragDropMode(int): + '''Specifies the Drag and Drop mode allowed by this widget''' + NoDragDrop = 0x00 + '''No Drag and Drop is allowed''' + AllowDrag = 0x01 + '''Drag allowed''' + AllowDrop = 0x02 + '''Drop allowed''' + NoDragDrop = DragDropMode.NoDragDrop + AllowDrag = DragDropMode.AllowDrag + AllowDrop = DragDropMode.AllowDrop + class ChildIndicatorPolicy(int): ShowIndicator = 0x00 #The controls for expanding and collapsing will be shown for this item even if there are no children. DontShowIndicator = 0x01 #The controls for expanding and collapsing will never be shown even if there are children. If the node is forced open the user will not be able to expand or collapse the item. diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index bab2f486..74025c89 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -117,7 +117,7 @@ class _DropListData: __slots__ = ('itemClicked', 'textClicked', '_selectedItems', '_selectionMode', '_highlighted', '_items', - '_dragPos') + '_dragPos', '_dndMode') def __init__(self, *args, **kwargs): # Default Class Specific Values self._selectionMode = kwargs.get("selectionMode", TTkK.SingleSelection) @@ -125,6 +125,8 @@ def __init__(self, *args, **kwargs): self._items = [] self._highlighted = None self._dragPos = None + self._dndMode = kwargs.get("dragDropMode", + TTkK.DragDropMode.AllowDrag | TTkK.DragDropMode.AllowDrop ) # Signals self.itemClicked = pyTTkSignal(TTkWidget) self.textClicked = pyTTkSignal(str) @@ -161,6 +163,14 @@ def _labelSelectedHandler(self, label:TTkAbstractListItem): self.itemClicked.emit(label) self.textClicked.emit(label.text()) + def dragDropMode(self): + '''dragDropMode''' + return self._dndMode + + def setDragDropMode(self, dndMode): + '''setDragDropMode''' + self._dndMode = dndMode + def setSelectionMode(self, mode): '''setSelectionMode''' self._selectionMode = mode @@ -291,7 +301,8 @@ def _moveToHighlighted(self): self.viewMoveTo(offx, index) def mouseDragEvent(self, evt) -> bool: - TTkLog.debug("Start DnD") + if not(self._dndMode & TTkK.DragDropMode.AllowDrag): + return False if not (items:=self._selectedItems.copy()): return True drag = TTkDrag() @@ -315,6 +326,8 @@ def mouseDragEvent(self, evt) -> bool: return True def dragEnterEvent(self, evt): + if not(self._dndMode & TTkK.DragDropMode.AllowDrop): + return False if issubclass(type(evt.data()),TTkListWidget._DropListData): return self.dragMoveEvent(evt) return False @@ -332,7 +345,8 @@ def dragLeaveEvent(self, evt): return True def dropEvent(self, evt) -> bool: - TTkLog.debug(f"Drop pos={evt.pos()}") + if not(self._dndMode & TTkK.DragDropMode.AllowDrop): + return False self._dragPos = None if not issubclass(type(evt.data()) ,TTkListWidget._DropListData): return False diff --git a/demo/showcase/list.py b/demo/showcase/list.py index c72b060d..f18a76a7 100755 --- a/demo/showcase/list.py +++ b/demo/showcase/list.py @@ -33,23 +33,43 @@ def demoList(root= None): # Define the main Layout - splitter = ttk.TTkSplitter(parent=root, orientation=ttk.TTkK.HORIZONTAL) - frame2 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout()) - frame1 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout()) - frame3 = ttk.TTkFrame(parent=splitter, border=0, layout=ttk.TTkVBoxLayout()) + retFrame = ttk.TTkFrame(parent=root, layout=(rootLayout:=ttk.TTkGridLayout())) - # Multi Selection List - ttk.TTkLabel(parent=frame1, text="[ MultiSelect ]",maxHeight=2) - listWidgetMulti = ttk.TTkList(parent=frame1, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection) + # Define the main Layout + win1 = ttk.TTkWindow(title="Single List", layout=ttk.TTkVBoxLayout()) + win2 = ttk.TTkWindow(title="Multi List", layout=ttk.TTkVBoxLayout()) + win3 = ttk.TTkWindow(title="Log", layout=ttk.TTkVBoxLayout()) + win4 = ttk.TTkWindow(title="Oly Drag Allowed", layout=ttk.TTkVBoxLayout()) + win5 = ttk.TTkWindow(title="Oly Drop Allowed", layout=ttk.TTkVBoxLayout()) + layout1 = ttk.TTkLayout() + + # Place the widgets in the root layout + rootLayout.addWidget(win1,0,0) + rootLayout.addWidget(win2,0,1) + rootLayout.addWidget(win3,0,2,1,3) + rootLayout.addItem(layout1,1,0,1,3) + rootLayout.addWidget(win4,1,3) + rootLayout.addWidget(win5,1,4) # Single Selection List - ttk.TTkLabel(parent=frame2, text="[ SingleSelect ]",maxHeight=2) - listWidgetSingle = ttk.TTkList(parent=frame2, maxWidth=40, minWidth=10) + listWidgetSingle = ttk.TTkList(parent=win1, maxWidth=40, minWidth=10) + + # Multi Selection List + listWidgetMulti = ttk.TTkList(parent=win2, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection) + + # Multi Selection List - Drag Allowed + listWidgetDrag = ttk.TTkList(parent=win4, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.DragDropMode.AllowDrag) + listWidgetDrop = ttk.TTkList(parent=win5, maxWidth=40, minWidth=10, dragDropMode=ttk.TTkK.DragDropMode.AllowDrop) # Log Viewer - label1 = ttk.TTkLabel(parent=frame3, text="[ list1 ]",maxHeight=2) - label2 = ttk.TTkLabel(parent=frame3, text="[ list2 ]",maxHeight=2) - ttk.TTkLogViewer(parent=frame3)#, border=True) + label1 = ttk.TTkLabel(pos=(10,0), text="[ list1 ]",maxHeight=2) + label2 = ttk.TTkLabel(pos=(10,1), text="[ list2 ]",maxHeight=2) + ttk.TTkLogViewer(parent=win3) + + btn_mv1 = ttk.TTkButton(pos=(0,0), text=" >> ") + btn_mv2 = ttk.TTkButton(pos=(0,1), text=" << ") + btn_del = ttk.TTkButton(pos=(0,2), text="Delete") + layout1.addWidgets([label1,label2,btn_mv1,btn_mv2,btn_del]) @ttk.pyTTkSlot(str) def _listCallback1(label): @@ -61,16 +81,42 @@ def _listCallback2(label): ttk.TTkLog.info(f'Clicked label2: "{label}" - selected: {[str(s) for s in listWidgetMulti.selectedLabels()]}') label2.setText(f'[ list2 ] clicked "{label}" - {[str(s) for s in listWidgetMulti.selectedLabels()]}') + @ttk.pyTTkSlot() + def _moveToRight2(): + for i in listWidgetSingle.selectedItems().copy(): + listWidgetSingle.removeItem(i) + listWidgetMulti.addItemAt(i,0) + + @ttk.pyTTkSlot() + def _moveToLeft1(): + for i in listWidgetMulti.selectedItems().copy(): + listWidgetMulti.removeItem(i) + listWidgetSingle.addItemAt(i,0) + + @ttk.pyTTkSlot() + def _delSelected(): + items = listWidgetMulti.selectedItems() + listWidgetMulti.removeItems(items) + items = listWidgetSingle.selectedItems() + listWidgetSingle.removeItems(items) + + + btn_mv1.clicked.connect(_moveToRight2) + btn_mv2.clicked.connect(_moveToLeft1) + btn_del.clicked.connect(_delSelected) + + # Connect the signals to the 2 slots defines listWidgetSingle.textClicked.connect(_listCallback1) listWidgetMulti.textClicked.connect(_listCallback2) # populate the lists with random entries - for i in range(100): - listWidgetSingle.addItem(f"{i}) {getUtfWord()} {getUtfWord()}") - listWidgetMulti.addItem(f"{getUtfWord()} {getUtfWord()}") + for i in range(50): + listWidgetSingle.addItem(f"S-{i}) {getUtfWord()} {getUtfWord()}") + listWidgetMulti.addItem( f"M-{i}){getUtfWord()} {getUtfWord()}") + listWidgetDrag.addItem( f"D-{i}){getUtfWord()} {getUtfWord()}") - return splitter + return retFrame def main(): parser = argparse.ArgumentParser() diff --git a/tests/test.ui.014.list.04.py b/tests/test.ui.014.list.04.py new file mode 100755 index 00000000..64590d63 --- /dev/null +++ b/tests/test.ui.014.list.04.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# 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 sys, os, argparse, math, random + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +zc1 = chr(0x07a6) # Zero width chars oަ +zc2 = chr(0x20D7) # Zero width chars o⃗ +zc3 = chr(0x065f) # Zero width chars oٟ +utfwords = [ + f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-", + "d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "t😜mpor", "inci😜di😜dunt", "u😜t", "l😜abore", "et", "d😜olore", "m😜a😜gna", "ali😜qua😜.", "Ut", "enim", "😜a😜d😜", "minim", "veniam,", "😜q😜uis", "😜nostrud", "exer😜c😜i😜tation", "ullamco", "labo😜ris", "n😜isi", "ut", "aliq😞ip", "e😜x😜", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +def getWord(): + return random.choice(utfwords) + # return random.choice(words) + +parser = argparse.ArgumentParser() +parser.add_argument('-t', help='Track Mouse', action='store_true') +args = parser.parse_args() +mouseTrack = args.t + +root = ttk.TTk(title="pyTermTk List Demo", mouseTrack=mouseTrack) + +# Define the main Layout +frame1 = ttk.TTkWindow(parent=root, pos=( 0, 0), size=(30,30), title="Single List", border=0, layout=ttk.TTkVBoxLayout()) +frame2 = ttk.TTkWindow(parent=root, pos=(30, 0), size=(30,30), title="Multi List", border=0, layout=ttk.TTkVBoxLayout()) +frame3 = ttk.TTkWindow(parent=root, pos=(60, 0), size=(80,30), title="Log", border=0, layout=ttk.TTkVBoxLayout()) + +# Single Selection List +listWidgetSingle = ttk.TTkList(parent=frame1, maxWidth=40, minWidth=10) + +# Multi Selection List +listWidgetMulti = ttk.TTkList(parent=frame2, maxWidth=40, minWidth=10, selectionMode=ttk.TTkK.MultiSelection) + +# Log Viewer +label1 = ttk.TTkLabel(parent=root, pos=(10,30), text="[ list1 ]",maxHeight=2) +label2 = ttk.TTkLabel(parent=root, pos=(10,31), text="[ list2 ]",maxHeight=2) +ttk.TTkLogViewer(parent=frame3)#, border=True) + +btn_mv1 = ttk.TTkButton(parent=root, pos=(0,30), text=" >> ") +btn_mv2 = ttk.TTkButton(parent=root, pos=(0,31), text=" << ") +btn_del = ttk.TTkButton(parent=root, pos=(0,32), text="Delete") + +@ttk.pyTTkSlot(str) +def _listCallback1(label): + ttk.TTkLog.info(f'Clicked label1: "{label}"') + label1.setText(f'[ list1 ] clicked "{label}" - Selected: {[str(s) for s in listWidgetSingle.selectedLabels()]}') + +@ttk.pyTTkSlot(str) +def _listCallback2(label): + ttk.TTkLog.info(f'Clicked label2: "{label}" - selected: {[str(s) for s in listWidgetMulti.selectedLabels()]}') + label2.setText(f'[ list2 ] clicked "{label}" - {[str(s) for s in listWidgetMulti.selectedLabels()]}') + +@ttk.pyTTkSlot() +def _moveToRight2(): + for i in listWidgetSingle.selectedItems().copy(): + listWidgetSingle.removeItem(i) + listWidgetMulti.addItemAt(i,0) + +@ttk.pyTTkSlot() +def _moveToLeft1(): + for i in listWidgetMulti.selectedItems().copy(): + listWidgetMulti.removeItem(i) + listWidgetSingle.addItemAt(i,0) + +@ttk.pyTTkSlot() +def _delSelected(): + items = listWidgetMulti.selectedItems() + listWidgetMulti.removeItems(items) + items = listWidgetSingle.selectedItems() + listWidgetSingle.removeItems(items) + + +btn_mv1.clicked.connect(_moveToRight2) +btn_mv2.clicked.connect(_moveToLeft1) +btn_del.clicked.connect(_delSelected) + + +# Connect the signals to the 2 slots defines +listWidgetSingle.textClicked.connect(_listCallback1) +listWidgetMulti.textClicked.connect(_listCallback2) + +# populate the lists with random entries +for i in range(10): + listWidgetSingle.addItem(f"S-{i}) {getWord()} {getWord()}") + listWidgetMulti.addItem(f"M-{i}) {getWord()} {getWord()}") + +root.mainloop() From 663cb032fd33ffd889c2ce1ce3b5731b0b109e3c Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Tue, 10 Oct 2023 11:31:04 +0100 Subject: [PATCH 8/9] Added list support in TTkDesigner + other stuff --- TermTk/TTkUiTools/properties/list_.py | 46 ++++++++++++++++++- TermTk/TTkWidgets/list_.py | 6 ++- TermTk/TTkWidgets/listwidget.py | 6 ++- demo/showcase/list.py | 2 +- ttkDesigner/app/propertyeditor.py | 2 +- ttkDesigner/app/superobj/__init__.py | 4 +- ttkDesigner/app/superobj/superwidget.py | 5 +- .../superobj/superwidgetabstractscrollarea.py | 44 ++++++++++++++++++ ttkDesigner/app/superobj/superwidgetlist.py | 29 ++++++++++++ ttkDesigner/app/widgetbox.py | 3 +- 10 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 ttkDesigner/app/superobj/superwidgetabstractscrollarea.py create mode 100644 ttkDesigner/app/superobj/superwidgetlist.py diff --git a/TermTk/TTkUiTools/properties/list_.py b/TermTk/TTkUiTools/properties/list_.py index cb1c7693..338d19ec 100644 --- a/TermTk/TTkUiTools/properties/list_.py +++ b/TermTk/TTkUiTools/properties/list_.py @@ -22,4 +22,48 @@ __all__ = ['TTkListProperties'] -TTkListProperties = {'properties' : {},'signals' : {},'slots' : {}} \ No newline at end of file +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkWidgets.list_ import TTkList +from TermTk.TTkWidgets.listwidget import TTkListWidget, TTkAbstractListItem + + +TTkListProperties = { + 'properties' : { + 'Selection Mode' : { + 'init': {'name':'selectionMode', 'type':'singleflag', + 'flags':{ + 'Single Seelction' : TTkK.SingleSelection, + 'Multi Selection' : TTkK.MultiSelection, + }}, + 'get': {'cb':lambda w: w.selectionMode(), 'type':'singleflag', + 'flags':{ + 'Single Seelction' : TTkK.SingleSelection, + 'Multi Selection' : TTkK.MultiSelection, + }}, + 'set': {'cb':lambda w,v: w.setSelectionMode(v), 'type':'singleflag', + 'flags':{ + 'Single Seelction' : TTkK.SingleSelection, + 'Multi Selection' : TTkK.MultiSelection, + }}}, + 'DnD Mode' : { + 'init': {'name':'dragDropMode', 'type':'multiflags', + 'flags':{ + 'Allow Drag' : TTkK.DragDropMode.AllowDrag, + 'Allow Drop' : TTkK.DragDropMode.AllowDrop, + }}, + 'get': {'cb':lambda w: w.dragDropMode(), 'type':'multiflags', + 'flags':{ + 'Allow Drag' : TTkK.DragDropMode.AllowDrag, + 'Allow Drop' : TTkK.DragDropMode.AllowDrop, + }}, + 'set': {'cb':lambda w,v: w.setDragDropMode(v), 'type':'multiflags', + 'flags':{ + 'Allow Drag' : TTkK.DragDropMode.AllowDrag, + 'Allow Drop' : TTkK.DragDropMode.AllowDrop, + }}}, + }, + 'signals' : { + 'itemClicked(TTkAbstractListItem)' : {'name': 'itemClicked', 'type' : TTkAbstractListItem}, + 'textClicked(str)' : {'name': 'textClicked', 'type' : str}, + }, + 'slots' : {}} \ No newline at end of file diff --git a/TermTk/TTkWidgets/list_.py b/TermTk/TTkWidgets/list_.py index b2d9e6af..b6450e30 100644 --- a/TermTk/TTkWidgets/list_.py +++ b/TermTk/TTkWidgets/list_.py @@ -31,10 +31,11 @@ class TTkList(TTkAbstractScrollArea): '_listView', 'itemClicked', 'textClicked', # Forwarded Methods 'items', + 'dragDropMode', 'setDragDropMode', 'addItem', 'addItemAt', 'addItems', 'addItemsAt', 'indexOf', 'itemAt', 'moveItem', 'removeAt', 'removeItem', 'removeItems', - 'setSelectionMode', 'selectedItems', 'selectedLabels', + 'selectionMode', 'setSelectionMode', 'selectedItems', 'selectedLabels', 'setCurrentRow', 'setCurrentItem', ) def __init__(self, *args, **kwargs): @@ -58,9 +59,12 @@ def __init__(self, *args, **kwargs): self.addItems = self._listView.addItems self.addItemAt = self._listView.addItemAt self.addItemsAt = self._listView.addItemsAt + self.selectionMode = self._listView.selectionMode self.setSelectionMode = self._listView.setSelectionMode self.selectedItems = self._listView.selectedItems self.selectedLabels = self._listView.selectedLabels self.setCurrentRow = self._listView.setCurrentRow self.setCurrentItem = self._listView.setCurrentItem + self.dragDropMode = self._listView.dragDropMode + self.setDragDropMode = self._listView.setDragDropMode diff --git a/TermTk/TTkWidgets/listwidget.py b/TermTk/TTkWidgets/listwidget.py index 74025c89..4e54deb4 100644 --- a/TermTk/TTkWidgets/listwidget.py +++ b/TermTk/TTkWidgets/listwidget.py @@ -128,7 +128,7 @@ def __init__(self, *args, **kwargs): self._dndMode = kwargs.get("dragDropMode", TTkK.DragDropMode.AllowDrag | TTkK.DragDropMode.AllowDrop ) # Signals - self.itemClicked = pyTTkSignal(TTkWidget) + self.itemClicked = pyTTkSignal(TTkAbstractListItem) self.textClicked = pyTTkSignal(str) # Init Super TTkAbstractScrollView.__init__(self, *args, **kwargs) @@ -171,6 +171,10 @@ def setDragDropMode(self, dndMode): '''setDragDropMode''' self._dndMode = dndMode + def selectionMode(self): + '''selectionMode''' + return self._selectionMode + def setSelectionMode(self, mode): '''setSelectionMode''' self._selectionMode = mode diff --git a/demo/showcase/list.py b/demo/showcase/list.py index f18a76a7..7a703170 100755 --- a/demo/showcase/list.py +++ b/demo/showcase/list.py @@ -33,7 +33,7 @@ def demoList(root= None): # Define the main Layout - retFrame = ttk.TTkFrame(parent=root, layout=(rootLayout:=ttk.TTkGridLayout())) + retFrame = ttk.TTkFrame(parent=root, border=False, layout=(rootLayout:=ttk.TTkGridLayout())) # Define the main Layout win1 = ttk.TTkWindow(title="Single List", layout=ttk.TTkVBoxLayout()) diff --git a/ttkDesigner/app/propertyeditor.py b/ttkDesigner/app/propertyeditor.py index bbdbfa2b..527a6ada 100644 --- a/ttkDesigner/app/propertyeditor.py +++ b/ttkDesigner/app/propertyeditor.py @@ -183,7 +183,7 @@ def _processTTkString(name, prop, multiLine=True): # Color Fields def _processTTkColor(name, prop): getval = prop['get']['cb'](domw) - value = ttk.TTkWidget(layout=ttk.TTkHBoxLayout(), height=1) + 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)) diff --git a/ttkDesigner/app/superobj/__init__.py b/ttkDesigner/app/superobj/__init__.py index 97c78875..00d94415 100644 --- a/ttkDesigner/app/superobj/__init__.py +++ b/ttkDesigner/app/superobj/__init__.py @@ -25,10 +25,12 @@ from .superwidget import SuperWidget from .superwidgetcontainer import SuperWidgetContainer -from .superwidgettextedit import SuperWidgetTextEdit +from .superwidgetabstractscrollarea import SuperWidgetAbstractScrollArea +# from .superwidgettextedit import SuperWidgetTextEdit from .superwidgetradiobutton import SuperWidgetRadioButton from .superwidgetframe import SuperWidgetFrame from .superwidgetsplitter import SuperWidgetSplitter +# from .superwidgetlist import SuperWidgetList from .superwidgetmenubutton import SuperWidgetMenuButton from .superlayout import SuperLayout diff --git a/ttkDesigner/app/superobj/superwidget.py b/ttkDesigner/app/superobj/superwidget.py index 7688e516..216da1e0 100644 --- a/ttkDesigner/app/superobj/superwidget.py +++ b/ttkDesigner/app/superobj/superwidget.py @@ -99,14 +99,17 @@ def _swFromWidget(wid, swClass, *args, **kwargs): def swFromWidget(wid:object, *args, **kwargs): swClass = so.SuperWidget for c, sc in { - ttk.TTkTextEdit: so.SuperWidgetTextEdit, + # ttk.TTkTextEdit: so.SuperWidgetTextEdit, ttk.TTkRadioButton: so.SuperWidgetRadioButton, # ttk.TTkResizableFrame: so.SuperWidgetFrame, # ttk.TTkWindow: so.SuperWidgetFrame, ttk.TTkSplitter: so.SuperWidgetSplitter, + # ttk.TTkList: so.SuperWidgetList, ttk.TTkMenuButton: so.SuperWidgetMenuButton, ttk.TTkFrame: so.SuperWidgetFrame, + ttk.TTkAbstractScrollArea: so.SuperWidgetAbstractScrollArea, ttk.TTkContainer: so.SuperWidgetContainer, + ttk.TTkWidget: so.SuperWidget, }.items(): if c in type(wid).mro(): swClass = sc diff --git a/ttkDesigner/app/superobj/superwidgetabstractscrollarea.py b/ttkDesigner/app/superobj/superwidgetabstractscrollarea.py new file mode 100644 index 00000000..bcf33ad8 --- /dev/null +++ b/ttkDesigner/app/superobj/superwidgetabstractscrollarea.py @@ -0,0 +1,44 @@ +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# 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 +import ttkDesigner.app.superobj as so +from .superobj import SuperObject + + +class SuperWidgetAbstractScrollArea(so.SuperWidgetContainer): + @staticmethod + def _swFromWidget(wid, swClass, *args, **kwargs): + return swClass(wid=wid, *args, **kwargs) + + def getSuperProperties(self): + additions, exceptions, exclude = super().getSuperProperties() + exclude += ['Layout','Padding'] + return additions, exceptions, exclude + + def dumpDict(self): + wid = self._wid + ret = { + 'class' : wid.__class__.__name__, + 'params' : SuperObject.dumpParams(wid,exclude=['Layout','Padding']), + } + return ret diff --git a/ttkDesigner/app/superobj/superwidgetlist.py b/ttkDesigner/app/superobj/superwidgetlist.py new file mode 100644 index 00000000..4734e41c --- /dev/null +++ b/ttkDesigner/app/superobj/superwidgetlist.py @@ -0,0 +1,29 @@ +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# 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 +import ttkDesigner.app.superobj as so +from .superobj import SuperObject + + +class SuperWidgetList(so.SuperWidgetAbstractScrollArea): + pass diff --git a/ttkDesigner/app/widgetbox.py b/ttkDesigner/app/widgetbox.py index 3d68d518..86544028 100644 --- a/ttkDesigner/app/widgetbox.py +++ b/ttkDesigner/app/widgetbox.py @@ -56,7 +56,8 @@ }, 'Widgets':{ "Label" : { "class":ttk.TTkLabel, "params":{'size':(20,1), 'text':'Label'}}, - "List" : { "class":ttk.TTkListWidget, "params":{'size':(20,1)}, "disabled": True}, + "List" : { "class":ttk.TTkList, "params":{'size':(20,5)}}, + # "List Widget" : { "class":ttk.TTkListWidget, "params":{'size':(20,5)}}, "Scroll Area" : { "class":ttk.TTkScrollArea, "params":{'size':(20,5)}, "disabled": True}, "Spacer" : { "class":ttk.TTkSpacer, "params":{'size':(10,5)}}, "Tab Widget" : { "class":ttk.TTkTabWidget, "params":{'size':(20,3)}, "disabled": True}, From 14536dde1850c1dbabe507127d75819fae676a53 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Tue, 10 Oct 2023 11:36:30 +0100 Subject: [PATCH 9/9] changed demo list --- demo/showcase/list.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/demo/showcase/list.py b/demo/showcase/list.py index 7a703170..1015196a 100755 --- a/demo/showcase/list.py +++ b/demo/showcase/list.py @@ -44,12 +44,12 @@ def demoList(root= None): layout1 = ttk.TTkLayout() # Place the widgets in the root layout - rootLayout.addWidget(win1,0,0) - rootLayout.addWidget(win2,0,1) - rootLayout.addWidget(win3,0,2,1,3) - rootLayout.addItem(layout1,1,0,1,3) - rootLayout.addWidget(win4,1,3) - rootLayout.addWidget(win5,1,4) + rootLayout.addWidget(win1,0,0,2,1) + rootLayout.addWidget(win2,0,1,2,1) + rootLayout.addWidget(win3,0,2,2,3) + rootLayout.addItem(layout1,2,0,1,3) + rootLayout.addWidget(win4,2,3) + rootLayout.addWidget(win5,2,4) # Single Selection List listWidgetSingle = ttk.TTkList(parent=win1, maxWidth=40, minWidth=10)