From 56ecf087f2e7353619df07a5196a5990a9503460 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Mon, 9 Oct 2023 18:39:13 +0100 Subject: [PATCH] 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 TTkConstant: # 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 TTkListWidget(TTkAbstractScrollView): __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 @@ class TTkListWidget(TTkAbstractScrollView): 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 @@ class TTkListWidget(TTkAbstractScrollView): 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 @@ class TTkListWidget(TTkAbstractScrollView): 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 @@ class TTkListWidget(TTkAbstractScrollView): 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 @@ class TTkListWidget(TTkAbstractScrollView): 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 @@ from showcase._showcasehelper import getUtfWord 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 demoList(root= None): 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()