From 45cc1c2e9907779e592eb261d1e416079d0c807a Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Wed, 4 Sep 2024 13:41:59 +0100 Subject: [PATCH] TTkTble, added rows,cols,all selection --- TermTk/TTkWidgets/TTkModelView/tablewidget.py | 153 ++++++++++++------ tests/t.ui/test.ui.032.table.03.py | 4 + tests/timeit/02.array.03.check copy.py | 58 +++++++ tests/timeit/02.array.04.modify.py | 58 +++++++ tests/timeit/30.rendering.01.TTkTable.py | 152 +++++++++++++++++ 5 files changed, 379 insertions(+), 46 deletions(-) create mode 100755 tests/timeit/02.array.03.check copy.py create mode 100755 tests/timeit/02.array.04.modify.py create mode 100755 tests/timeit/30.rendering.01.TTkTable.py diff --git a/TermTk/TTkWidgets/TTkModelView/tablewidget.py b/TermTk/TTkWidgets/TTkModelView/tablewidget.py index 813a462d..61547b94 100644 --- a/TermTk/TTkWidgets/TTkModelView/tablewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/tablewidget.py @@ -233,6 +233,8 @@ class TTkTableWidget(TTkAbstractScrollView): return super().leaveEvent(evt) def _findCell(self, x, y): + showHS = self._showHSeparators + showVS = self._showVSeparators showVH = self._verticalHeader.isVisible() showHH = self._horizontallHeader.isVisible() hhs = self._hHeaderSize if showHH else 0 @@ -242,24 +244,28 @@ class TTkTableWidget(TTkAbstractScrollView): rp = self._rowsPos cp = self._colsPos - for row,py in enumerate(rp): - if py>y: - break - for col,px in enumerate(cp): - if px>x: - break + row = -1 + col = -1 + + if y<0: + row = -1 + else: + y += 0 if showHS else 0 + for row,py in enumerate(rp): + if py>=y: + break + if x<0: + col = -1 + else: + x += 0 if showVS else 0 + for col,px in enumerate(cp): + if px>=x: + break return row,col def mouseMoveEvent(self, evt) -> bool: - showVH = self._verticalHeader.isVisible() - showHH = self._horizontallHeader.isVisible() - hhs = self._hHeaderSize if showHH else 0 - vhs = self._vHeaderSize if showVH else 0 x,y = evt.x,evt.y self._hoverPos = None - if x bool: x,y = evt.x, evt.y ox, oy = self.getViewOffsets() + showHS = self._showHSeparators + showVS = self._showVSeparators showVH = self._verticalHeader.isVisible() showHH = self._horizontallHeader.isVisible() hhs = self._hHeaderSize if showHH else 0 @@ -276,44 +284,66 @@ class TTkTableWidget(TTkAbstractScrollView): self._vSeparatorSelected = None # Handle Header Events - if y < hhs: - x += ox-vhs + # And return if handled + # This is important to handle the header selection in the next part + if showVS and y < hhs: + _x = x+ox-vhs for i, c in enumerate(self._colsPos): - if x == c: + if _x == c: # I-th separator selected self._hSeparatorSelected = i self.update() - break - elif x < c: + return True + elif _x < c: # # I-th header selected # order = not self._sortOrder if self._sortColumn == i else TTkK.AscendingOrder # self.sortItems(i, order) break - return True - elif x < vhs: - y += oy-hhs + # return True + elif showHS and x < vhs: + _y = y+oy-hhs for i, r in enumerate(self._rowsPos): - if y == r: + if _y == r: # I-th separator selected self._vSeparatorSelected = i self.update() - break - elif y < r: + return True + elif _y < r: # # I-th header selected # order = not self._sortOrder if self._sortColumn == i else TTkK.AscendingOrder # self.sortItems(i, order) break - return True - else: - row,col = self._findCell(x,y) + # return True + + row,col = self._findCell(x,y) + if not row==col==-1: self._dragPos = [(row,col),(row,col)] - if evt.mod==TTkK.ControlModifier: - self._selected[row][col] = not self._selected[row][col] - else: - self._selected[row][col] = True - self._hoverPos = None - self.update() - return True + rows = self._tableModel.rowCount() + cols = self._tableModel.columnCount() + _ctrl = evt.mod==TTkK.ControlModifier + if row==col==-1: + # Corner Press + # Select Everything + self._selected = [[True]*cols for _ in range(rows)] + elif col==-1: + # Row select + state = all(self._selected[row]) + if not _ctrl: + self._selected = [[False]*cols for _ in range(rows)] + self._selected[row] = [not state]*cols + elif row==-1: + # Col select + state = all(line[col] for line in self._selected) + if not _ctrl: + self._selected = [[False]*cols for _ in range(rows)] + for line in self._selected: + line[col] = not state + else: + # Cell Select + if _ctrl: self._selected[row][col] = not self._selected[row][col] + else: self._selected[row][col] = True + self._hoverPos = None + self.update() return True def mouseDragEvent(self, evt) -> bool: @@ -377,14 +407,28 @@ class TTkTableWidget(TTkAbstractScrollView): cols = self._tableModel.columnCount() state = True (rowa,cola),(rowb,colb) = self._dragPos + if evt.mod==TTkK.ControlModifier: - state = self._selected[rowa][cola] + # Pick the status to be applied to the selection if CTRL is Pressed + # In case of line/row selection I choose the element 0 of that line + state = self._selected[max(0,rowa)][max(0,cola)] else: + # Clear the selection if no ctrl has been pressed self._selected = [[False]*cols for _ in range(rows)] - cola,colb=min(cola,colb),max(cola,colb) - rowa,rowb=min(rowa,rowb),max(rowa,rowb) + + if rowa == -1: + cola,colb=min(cola,colb),max(cola,colb) + rowa,rowb=0,rows + elif cola == -1: + rowa,rowb=min(rowa,rowb),max(rowa,rowb) + cola,colb=0,cols + else: + cola,colb=min(cola,colb),max(cola,colb) + rowa,rowb=min(rowa,rowb),max(rowa,rowb) + for line in self._selected[rowa:rowb+1]: line[cola:colb+1] = [state]*(colb-cola+1) + self._hoverPos = None self._dragPos = None self.update() @@ -507,7 +551,7 @@ class TTkTableWidget(TTkAbstractScrollView): if xa>w : break if xb +# +# 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 timeit + + +array1 = [[True for _ in range(1000)] for _ in range(1000)] +array2 = [[False for _ in range(1000)] for _ in range(1000)] +array3 = [[x==y==999 for x in range(1000)] for y in range(1000)] +array4 = [[not (x==y==999) for x in range(1000)] for y in range(1000)] +array5 = [[x==y==500 for x in range(1000)] for y in range(1000)] +array6 = [[not (x==y==500) for x in range(1000)] for y in range(1000)] + +def test1(): return all(all(r) for r in array1) +def test2(): return all(all(r) for r in array2) +def test3(): return all(all(r) for r in array3) +def test4(): return all(all(r) for r in array4) +def test5(): return all(all(r) for r in array5) +def test6(): return all(all(r) for r in array6) + +def test7(): return any(any(r) for r in array1) +def test8(): return any(any(r) for r in array2) +def test9(): return any(any(r) for r in array3) +def test10(): return any(any(r) for r in array4) +def test11(): return any(any(r) for r in array5) +def test12(): return any(any(r) for r in array6) + +loop = 100 + +a = {} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 diff --git a/tests/timeit/02.array.04.modify.py b/tests/timeit/02.array.04.modify.py new file mode 100755 index 00000000..17a267e9 --- /dev/null +++ b/tests/timeit/02.array.04.modify.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2024 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 timeit + +array1 = [[True for _ in range(1000)] for _ in range(1000)] +array2 = [[False for _ in range(1000)] for _ in range(1000)] +array3 = [[x==y==999 for x in range(1000)] for y in range(1000)] +array4 = [[not (x==y==999) for x in range(1000)] for y in range(1000)] +array5 = [[x==y==500 for x in range(1000)] for y in range(1000)] +array6 = [[not (x==y==500) for x in range(1000)] for y in range(1000)] + +def test_ti_1_1(): + array1[500] = [True]*1000 + return True + +def test_ti_1_2(): + array1[500][0:1000] = [True]*1000 + return True + +def test_ti_2_1(): + for line in array1[400:600]: + line[400:600] = [True]*(600-400) + return True + +def test_ti_3_1(): + array1 = [[True]*1000 for _ in range(1000)] + return True + +loop = 1000 + +a = {} +# while (testName := f'test{iii}') and (testName in globals()): +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tests/timeit/30.rendering.01.TTkTable.py b/tests/timeit/30.rendering.01.TTkTable.py new file mode 100755 index 00000000..a734b0c3 --- /dev/null +++ b/tests/timeit/30.rendering.01.TTkTable.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2024 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 operator +import json +import timeit + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +imagesFile = os.path.join(os.path.dirname(os.path.abspath(__file__)),'../ansi.images.json') +with open(imagesFile) as f: + d = json.load(f) + # Image exported by the Dumb Paint Tool + pepper = ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['pepper']) + python = ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['python']) + fire = ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['fire']) + fireMini = ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['fireMini']) + img_pepper = ttk.TTkString(pepper) + img_python = ttk.TTkString(python) + img_fire = ttk.TTkString(fire) + img_fireMini = ttk.TTkString(fireMini) + +class MyTableModel(ttk.TTkAbstractTableModel): + def __init__(self, mylist, *args): + super().__init__(*args) + self.mylist = mylist + + def rowCount(self): return len(self.mylist) + def columnCount(self): return len(self.mylist[0]) + def data(self, row, col): return self.mylist[row][col] + def headerData(self, num, orientation): + prefix = ['aa','bb','cc','dd','ee','ff','gg','Euge'] + return f"{prefix[num%len(prefix)]}:{num:03}" + +# use numbers for numeric data to sort properly +txt1 = "Text" +txt2 = txt1*10 +txt3 = '\n'.join([txt1*2]*5) +txt4 = '\n'.join([txt1*5]*10) +txt5 = ttk.TTkString(txt4, ttk.TTkColor.RED + ttk.TTkColor.BG_BLUE) + +data_lists = [ + [[txt1 for y in range(100)] for x in range(1000)], + [[txt2 for y in range(100)] for x in range(1000)], + [[txt3 for y in range(100)] for x in range(1000)], + [[txt4 for y in range(100)] for x in range(1000)], + [[txt5 for y in range(100)] for x in range(1000)], + [[1234567 for y in range(100)] for x in range(1000)], + [[1234567.123456 for y in range(100)] for x in range(1000)], + [[img_fireMini for y in range(100)] for x in range(1000)], + [[img_python for y in range(100)] for x in range(1000)], + [[fireMini for y in range(100)] for x in range(1000)], + [[python for y in range(100)] for x in range(1000)]] + +table_models = [MyTableModel(dl) for dl in data_lists] +tables = [ttk.TTkTableWidget(tableModel=tm) for tm in table_models] + +def paint(table): + canvas = table.getCanvas() + return table.paintEvent(canvas) + +def resize1(): + [t.resize(200,50) for t in tables] +def resize2(): + [t.resize(500,200) for t in tables] + +tests = [ + lambda : [t.resize(200,50) for t in tables], + lambda : paint(tables[0]), + lambda : paint(tables[1]), + lambda : paint(tables[2]), + lambda : paint(tables[3]), + lambda : paint(tables[4]), + lambda : paint(tables[5]), + lambda : paint(tables[6]), + lambda : paint(tables[7]), + lambda : paint(tables[8]), + lambda : paint(tables[9]), + lambda : paint(tables[10]), + + lambda : [t.resize(500,200) for t in tables], + lambda : paint(tables[0]), + lambda : paint(tables[1]), + lambda : paint(tables[2]), + lambda : paint(tables[3]), + lambda : paint(tables[4]), + lambda : paint(tables[5]), + lambda : paint(tables[6]), + lambda : paint(tables[7]), + lambda : paint(tables[8]), + lambda : paint(tables[9]), + lambda : paint(tables[10]) ] + +def test_ti_01(): return tests[0]() +def test_ti_02(): return tests[1]() +def test_ti_03(): return tests[2]() +def test_ti_04(): return tests[3]() +def test_ti_05(): return tests[4]() +def test_ti_06(): return tests[5]() +def test_ti_07(): return tests[6]() +def test_ti_08(): return tests[7]() +def test_ti_09(): return tests[8]() +def test_ti_10(): return tests[9]() +def test_ti_11(): return tests[10]() +def test_ti_12(): return tests[11]() +def test_ti_13(): return tests[12]() +def test_ti_14(): return tests[13]() +def test_ti_15(): return tests[14]() +def test_ti_16(): return tests[15]() +def test_ti_17(): return tests[16]() +def test_ti_18(): return tests[17]() +def test_ti_19(): return tests[18]() +def test_ti_20(): return tests[19]() +def test_ti_21(): return tests[20]() +def test_ti_22(): return tests[21]() + +for t in tables: t.resize(500,200) + +loop = 10 + +a = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")