From 003344e0803cbd6a9c0eac48a09e158bf9a6dc36 Mon Sep 17 00:00:00 2001 From: Pier CeccoPierangioliEugenio Date: Mon, 15 Dec 2025 13:19:52 +0000 Subject: [PATCH] chore(table): add support for enum type (#548) --- .../TTkModelView/table_edit_proxy.py | 30 +++- ...y => test.ui.032.table.14.list.01.item.py} | 0 .../test.ui.032.table.14.list.02.custom.py | 96 ++++++++++++ .../test.ui.032.table.15.test.clipboard.py | 141 ++++++++++++++++++ 4 files changed, 262 insertions(+), 5 deletions(-) rename tests/t.ui/{test.ui.032.table.15.list.item.py => test.ui.032.table.14.list.01.item.py} (100%) create mode 100755 tests/t.ui/test.ui.032.table.14.list.02.custom.py create mode 100755 tests/t.ui/test.ui.032.table.15.test.clipboard.py diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py index 16c9f7fa..f406e7a1 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py @@ -359,10 +359,6 @@ class _BoolListProxy(_ListBaseProxy): Specialized list proxy that presents True/False choices for boolean cell values. Displays as a compact 2-item list. ''' - def __init__(self, **kwargs): - ''' Initialize the boolean list proxy - ''' - super().__init__(**kwargs|{'size':(7,4), 'layout':TTkGridLayout()}) @pyTTkSlot(TTkAbstractListItem) def _itemClicked(self, item:TTkAbstractListItem): @@ -397,6 +393,29 @@ class _BoolListProxy(_ListBaseProxy): ''' return self._list.selectedItems()[0].data() +class _EnumListProxy(_BoolListProxy): + ''' Enum editor for table cells + + Specialized list proxy that presents choices for enum cell values. + ''' + + @staticmethod + def editWidgetFactory(data: Any) -> TTkTableProxyEditWidget: + ''' Factory method to create a enum editor from enum data + + :param data: The initial enum value + :type data: Enum + :return: A new enum list proxy instance + :rtype: :py:class:`TTkTableProxyEditWidget` + :raises ValueError: If data is not a enum + ''' + if not isinstance(data, Enum): + raise ValueError(f"{data} is not an Enum") + items = list(type(data)) + value = TTkCellListType(value=data, items=items) + sb = _BoolListProxy(value=value, items=items) + return sb + class _TextEditViewProxy(TTkTextEditView, TTkTableProxyEditWidget): ''' Text editor view for table cells @@ -996,8 +1015,9 @@ class TTkTableProxyEdit(): Proxies are checked in order, with custom proxies taking precedence. ''' self._proxies = [ - TTkProxyEditDef(class_def=_ListBaseProxy, types=(TTkCellListTypeBase)), + TTkProxyEditDef(class_def=_ListBaseProxy, types=(TTkCellListTypeBase)), TTkProxyEditDef(class_def=_BoolListProxy, types=(bool)), + TTkProxyEditDef(class_def=_EnumListProxy, types=(Enum)), TTkProxyEditDef(class_def=_SpinBoxProxy, types=(int, float)), TTkProxyEditDef(class_def=_TextEditProxy, types=(str,)), TTkProxyEditDef(class_def=_TextEditProxy, types=(TTkString,), flags=TTkTableProxyEditFlag.BASE), diff --git a/tests/t.ui/test.ui.032.table.15.list.item.py b/tests/t.ui/test.ui.032.table.14.list.01.item.py similarity index 100% rename from tests/t.ui/test.ui.032.table.15.list.item.py rename to tests/t.ui/test.ui.032.table.14.list.01.item.py diff --git a/tests/t.ui/test.ui.032.table.14.list.02.custom.py b/tests/t.ui/test.ui.032.table.14.list.02.custom.py new file mode 100755 index 00000000..bffe9f80 --- /dev/null +++ b/tests/t.ui/test.ui.032.table.14.list.02.custom.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +# Demo inspired from: +# https://www.daniweb.com/programming/software-development/code/447834/applying-pyside-s-qabstracttablemodel + +import os +import sys +import argparse +import random +from enum import Enum + +from random import choice +from enum import Enum, auto +from typing import Tuple + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-f', help='Full Screen (default)', action='store_true') +parser.add_argument('-w', help='Windowed', action='store_true') + +args = parser.parse_args() + +fullScreen = not args.w +mouseTrack = True + +class MyEnum(Enum): + Foo=auto() + Bar=auto() + Baz=auto() + + def __str__(self): + return self.name + +class MyEnumYesNo(Enum): + Yes=True + No=False + + def __str__(self): + return self.name + def __bool__(self): + return self.value + +data = [ + [ + bool(random.randint(0,1)), + MyEnum.Foo, + MyEnum.Bar, + MyEnum.Baz, + MyEnumYesNo.Yes, + MyEnumYesNo.No, + random.choice(list(MyEnum)), + random.choice(list(MyEnumYesNo)), + ] for y in range(20) +] + +root = ttk.TTk( + title="pyTermTk Table Demo", + mouseTrack=mouseTrack) + +if fullScreen: + rootTable = root + root.setLayout(ttk.TTkGridLayout()) +else: + rootTable = ttk.TTkWindow(parent=root,pos = (0,0), size=(150,40), title="Test Table 1", layout=ttk.TTkGridLayout(), border=True) + +table_model = ttk.TTkTableModelList(data=data) +table = ttk.TTkTable(parent=rootTable, tableModel=table_model) + +table.resizeRowsToContents() +table.resizeColumnsToContents() + +root.mainloop() \ No newline at end of file diff --git a/tests/t.ui/test.ui.032.table.15.test.clipboard.py b/tests/t.ui/test.ui.032.table.15.test.clipboard.py new file mode 100755 index 00000000..1d6b305a --- /dev/null +++ b/tests/t.ui/test.ui.032.table.15.test.clipboard.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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 os +import sys +import argparse +import random +import datetime +from enum import Enum + +from random import choice +from enum import Enum, auto +from typing import Tuple + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-f', help='Full Screen (default)', action='store_true') +parser.add_argument('-w', help='Windowed', action='store_true') + +args = parser.parse_args() + +fullScreen = not args.w +mouseTrack = True + +# Random date between two dates +def random_date(start_date, end_date): + time_between = end_date - start_date + days_between = time_between.days + random_days = random.randrange(days_between) + return start_date + datetime.timedelta(days=random_days) + +# Random time +def random_time(): + hour = random.randint(0, 23) + minute = random.randint(0, 59) + second = random.randint(0, 59) + return datetime.time(hour, minute, second) + +# Random datetime +def random_datetime(start_datetime, end_datetime): + time_between = end_datetime - start_datetime + total_seconds = int(time_between.total_seconds()) + random_seconds = random.randrange(total_seconds) + return start_datetime + datetime.timedelta(seconds=random_seconds) + + +class MyEnum(Enum): + Foo=auto() + Bar=auto() + Baz=auto() + + def __str__(self): + return self.name + +class MyEnumYesNo(Enum): + Yes=True + No=False + + def __str__(self): + return self.name + def __bool__(self): + return self.value + +data_list = [ + 'Pippo', 'Pluto', 'Paperino', + 'Qui', 'Quo', 'Qua', # L'accento non ci va + 'Minnie', 'Topolino' +] + +data = [ + [ + bool(random.randint(0,1)), + 'Pippo', + 'Pluto', + ttk.TTkCellListType(value=random.choice(data_list), items=data_list) + ] for y in range(20) +] + +data = [ + [ + f"0x{random.randint(0,0xFFFF):04X}", + ttk.TTkString(f"0x{random.randint(0,0xFFFF):04X}", ttk.TTkColor.YELLOW), + bool(random.randint(0,1)), + ttk.TTkCellListType(value=random.choice(data_list), items=data_list), + random.choice(list(MyEnum)), + random.choice(list(MyEnumYesNo)), + random_time(), + random_date(datetime.date(2020,1,1), datetime.date(2025,12,31)), + random_datetime(datetime.datetime(2020,1,1), datetime.datetime(2025,12,31)), + + ] for y in range(20) +] + +root = ttk.TTk( + title="pyTermTk Table Demo", + mouseTrack=mouseTrack, + sigmask=( + ttk.TTkTerm.Sigmask.CTRL_Z | + ttk.TTkTerm.Sigmask.CTRL_C )) + +if fullScreen: + rootContainer = root + root.setLayout(ttk.TTkGridLayout()) +else: + rootContainer = ttk.TTkWindow(parent=root,pos = (0,0), size=(150,40), title="Test Table 1", layout=ttk.TTkGridLayout(), border=True) + +table_model = ttk.TTkTableModelList(data=data) +table = ttk.TTkTable(tableModel=table_model) +btn_quit = ttk.TTkButton(text='quit', maxSize=(8,3), border=True) +rootContainer.layout().addWidget(table,1,0,1,2) +rootContainer.layout().addWidget(btn_quit,0,0) + +table.resizeRowsToContents() +table.resizeColumnsToContents() + +btn_quit.clicked.connect(root.quit) + +root.mainloop() \ No newline at end of file