From 1d54180cbb2c2594895218cc72ff3fc9abb0023c Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 8 Sep 2024 22:10:07 +0100 Subject: [PATCH] TTkTable: Added Edit cells --- TermTk/TTkAbstract/abstracttablemodel.py | 14 +- TermTk/TTkWidgets/TTkModelView/tablewidget.py | 158 ++++++-- TermTk/TTkWidgets/TTkPickers/textpicker.py | 35 +- TermTk/TTkWidgets/spinbox.py | 13 +- TermTk/TTkWidgets/texedit.py | 2 +- tests/t.ui/test.ui.032.table.05.py | 6 +- tests/t.ui/test.ui.032.table.06.py | 357 ++++++++++++++++++ tests/timeit/02.array.04.modify.py | 14 +- tests/timeit/30.rendering.01.TTkTable.py | 74 ++-- tools/dumbPaintTool/app/paintarea.py | 2 +- 10 files changed, 579 insertions(+), 96 deletions(-) create mode 100755 tests/t.ui/test.ui.032.table.06.py diff --git a/TermTk/TTkAbstract/abstracttablemodel.py b/TermTk/TTkAbstract/abstracttablemodel.py index 7b2ca75c..71fc2bd9 100644 --- a/TermTk/TTkAbstract/abstracttablemodel.py +++ b/TermTk/TTkAbstract/abstracttablemodel.py @@ -41,9 +41,21 @@ class TTkAbstractTableModel(): def columnCount(self) -> int: raise NotImplementedError() - def data(self, row:int, col:int) -> TTkString: + def data(self, row:int, col:int) -> object: return TTkString() + def ttkStringData(self, row:int, col:int) -> TTkString: + data = self.data(row,col) + if isinstance(data,TTkString): + return data + elif type(data) == str: + return TTkString(data) + else: + return TTkString(str(data)) + + def setData(self, row:int, col:int, data:object): + raise NotImplementedError() + def headerData(self, pos:int, orientation:TTkK.Direction) -> TTkString: if orientation==TTkK.HORIZONTAL: return TTkString(str(pos)) diff --git a/TermTk/TTkWidgets/TTkModelView/tablewidget.py b/TermTk/TTkWidgets/TTkModelView/tablewidget.py index b4c34f90..2326e22b 100644 --- a/TermTk/TTkWidgets/TTkModelView/tablewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/tablewidget.py @@ -28,10 +28,17 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.color import TTkColor -# from TermTk.TTkWidgets.TTkModelView.tablewidgetitem import TTkTableWidgetItem +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot + +from TermTk.TTkGui.textdocument import TTkTextDocument + +from TermTk.TTkWidgets.texedit import TTkTextEdit +from TermTk.TTkWidgets.lineedit import TTkLineEdit +from TermTk.TTkWidgets.spinbox import TTkSpinBox +from TermTk.TTkWidgets.TTkPickers.textpicker import TTkTextPicker, TTkTextDialogPicker + from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView from TermTk.TTkAbstract.abstracttablemodel import TTkAbstractTableModel -from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot class _DefaultTableModel(TTkAbstractTableModel): def __init__(self, **args): @@ -80,8 +87,8 @@ class TTkTableWidget(TTkAbstractScrollView): 'color': TTkColor.RST, 'lineColor': TTkColor.fg("#444444"), 'headerColor': TTkColor.fg("#FFFFFF")+TTkColor.bg("#444444")+TTkColor.BOLD, - 'hoverColor': TTkColor.fg("#4444FF")+TTkColor.bg("#AAAA44")+TTkColor.BOLD, - 'selectedColor': TTkColor.bg("#888800"), + 'hoverColor': TTkColor.fg("#FFFF00")+TTkColor.bg("#0088AA")+TTkColor.BOLD, + 'selectedColor': TTkColor.bg("#0066AA"), 'separatorColor': TTkColor.fg("#555555")+TTkColor.bg("#444444")}, 'disabled': { 'color': TTkColor.fg("#888888"), @@ -101,6 +108,7 @@ class TTkTableWidget(TTkAbstractScrollView): '_selected', '_hSeparatorSelected', '_vSeparatorSelected', '_hoverPos', '_dragPos', '_sortColumn', '_sortOrder', + '_fastCheck', '_guessDataEdit', # Signals # 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' ) @@ -116,6 +124,8 @@ class TTkTableWidget(TTkAbstractScrollView): # self.itemDoubleClicked = pyTTkSignal(TTkTableWidgetItem, int) # self.itemExpanded = pyTTkSignal(TTkTableWidgetItem) # self.itemCollapsed = pyTTkSignal(TTkTableWidgetItem) + self._fastCheck = True + self._guessDataEdit = True self._showHSeparators = vSeparator self._showVSeparators = hSeparator @@ -130,22 +140,31 @@ class TTkTableWidget(TTkAbstractScrollView): self._sortColumn = -1 self._sortOrder = TTkK.AscendingOrder self._tableModel = tableModel if tableModel else _DefaultTableModel() - self._refreshLayout() super().__init__(**kwargs) + self._refreshLayout() self.setMinimumHeight(1) self.setFocusPolicy(TTkK.ClickFocus) # self._rootItem = TTkTableWidgetItem(expanded=True) # self.clear() - self.setPadding(1,0,0,0) self.viewChanged.connect(self._viewChangedHandler) - self._verticalHeader.visibilityUpdated.connect(lambda:self.viewChanged.emit()) - self._horizontallHeader.visibilityUpdated.connect(lambda:self.viewChanged.emit()) + self._verticalHeader.visibilityUpdated.connect( self._headerVisibilityChanged) + self._horizontallHeader.visibilityUpdated.connect(self._headerVisibilityChanged) + + @pyTTkSlot() + def _headerVisibilityChanged(self): + showVH = self._verticalHeader.isVisible() + showHH = self._horizontallHeader.isVisible() + vhs = self._vHeaderSize if showVH else 0 + hhs = self._hHeaderSize if showHH else 0 + self.setPadding(hhs,0,vhs,0) + self.viewChanged.emit() def _refreshLayout(self): rows = self._tableModel.rowCount() cols = self._tableModel.columnCount() - self._vHeaderSize = vhs= 1+max(len(self._tableModel.headerData(_p, TTkK.VERTICAL)) for _p in range(rows) ) - self._hHeaderSize = hhs= 1 + self._vHeaderSize = vhs = 1+max(len(self._tableModel.headerData(_p, TTkK.VERTICAL)) for _p in range(rows) ) + self._hHeaderSize = hhs = 1 + self.setPadding(hhs,0,vhs,0) if self._showVSeparators: self._colsPos = [(1+x)*11 for x in range(cols)] else: @@ -197,7 +216,6 @@ class TTkTableWidget(TTkAbstractScrollView): def setHSeparatorVisibility(self, visibility:bool): if self._showHSeparators == visibility: return self._showHSeparators = visibility - if visibility: self._rowsPos = [v+i for i,v in enumerate(self._rowsPos,1)] else: @@ -250,12 +268,16 @@ class TTkTableWidget(TTkAbstractScrollView): def _columnContentsSize(self, column:int) -> int: def _wid(_c): - txt = self._tableModel.data(_c, column) - if isinstance(txt,TTkString): pass - elif type(txt) == str: txt = TTkString(txt) - else: txt = TTkString(f"{txt}") + txt = self._tableModel.ttkStringData(_c, column) return max(t.termWidth() for t in txt.split('\n')) - return max(_wid(i) for i in range(self._tableModel.rowCount())) + rows = self._tableModel.rowCount() + if self._fastCheck: + w,h = self.size() + row,_ = self._findCell(w//2, h//2, False) + rowa,rowb = max(0,row-100), min(row+100,rows) + else: + rowa,rowb = 0,rows + return max(_wid(i) for i in range(rowa,rowb)) @pyTTkSlot(int) def resizeColumnToContents(self, column:int) -> None: @@ -289,12 +311,16 @@ class TTkTableWidget(TTkAbstractScrollView): def _rowContentsSize(self, row:int) -> int: def _hei(_c): - txt = self._tableModel.data(row, _c) - if isinstance(txt,TTkString): pass - elif type(txt) == str: txt = TTkString(txt) - else: txt = TTkString(f"{txt}") + txt = self._tableModel.ttkStringData(row, _c) return len(txt.split('\n')) - return max(_hei(i) for i in range(self._tableModel.columnCount())) + cols = self._tableModel.columnCount() + if self._fastCheck: + w,h = self.size() + _,col = self._findCell(w//2, h//2, False) + cola,colb = max(0,col-30), min(col+30,cols) + else: + cola,colb = 0,cols + return max(_hei(i) for i in range(cola,colb)) @pyTTkSlot(int) def resizeRowToContents(self, row:int) -> None: @@ -341,6 +367,75 @@ class TTkTableWidget(TTkAbstractScrollView): return row,col + def _editStr(self, x,y,w,h, row, col, data): + _te = TTkTextEdit( + parent=self, pos=(x, y), size=(w,h), + readOnly=False, wrapMode=TTkK.NoWrap) + _te.setText(data) + _te.setFocus() + + @pyTTkSlot(bool) + def _processClose(change): + if change: + self.focusChanged.disconnect(_processClose) + txt = _te.toPlainText() + self._tableModel.setData(row,col,txt) + self.update() + _te.close() + + self.focusChanged.connect(_processClose) + + _dataEditType = { + str : '', + TTkString : '' + } + + def _editNum(self, x,y,w,h, row, col, data): + _sb = TTkSpinBox( + parent=self, pos=(x, y), size=(w,1), + minimum=-1000000, maximum=1000000, + value=data) + _sb.setFocus() + + @pyTTkSlot(bool) + def _processClose(change): + if change: + self.focusChanged.disconnect(_processClose) + val = _sb.value() + self._tableModel.setData(row,col,val) + self.update() + _sb.close() + + self.focusChanged.connect(_processClose) + + _dataEditType = { + str : '', + TTkString : '' + } + + def _editTTkString(self, x,y,w,h, row, col, data): + _tp = TTkTextPicker( + parent=self, pos=(x, y), size=(w,h), + text=data, autoSize=False, wrapMode=TTkK.NoWrap) + + _tp.setFocus() + + @pyTTkSlot(bool) + def _processClose(change): + if change: + self.focusChanged.disconnect(_processClose) + txt = _tp.getTTkString() + self._tableModel.setData(row,col,txt) + self.update() + _tp.close() + + self.focusChanged.connect(_processClose) + + _dataEditType = { + str : '', + TTkString : '' + } + def mouseDoubleClickEvent(self, evt): x,y = evt.x, evt.y ox, oy = self.getViewOffsets() @@ -354,6 +449,9 @@ class TTkTableWidget(TTkAbstractScrollView): self._hSeparatorSelected = None self._vSeparatorSelected = None + rp = self._rowsPos + cp = self._colsPos + # Handle Header Events # And return if handled # This is important to handle the header selection in the next part @@ -374,6 +472,19 @@ class TTkTableWidget(TTkAbstractScrollView): self.resizeRowToContents(i) return True + row,col = self._findCell(x,y, headers=False) + xa,xb = 1+cp[col-1] if col>0 else 0, cp[col] + (0 if showVS else 1) + ya,yb = 1+rp[row-1] if row>0 else 0, rp[row] + (0 if showHS else 1) + + data = self._tableModel.data(row, col) + if type(data) is str: + self._editStr(xa,ya,xb-xa,yb-ya,row,col,data) + elif type(data) in [int,float]: + self._editNum(xa,ya,xb-xa,yb-ya,row,col,data) + else: + data = self._tableModel.ttkStringData(row, col) + self._editTTkString(xa,ya,xb-xa,yb-ya,row,col,data) + return True def mouseMoveEvent(self, evt) -> bool: @@ -694,10 +805,7 @@ class TTkTableWidget(TTkAbstractScrollView): _cellsCache.append([row,col,xa,xb,ya,yb,cellColor]) def _drawCellContent(_col,_row,_xa,_xb,_ya,_yb,_color): - txt = self._tableModel.data(_row, _col) - if isinstance(txt,TTkString): pass - elif type(txt) == str: txt = TTkString(txt, _color) - else: txt = TTkString(f"{txt}", _color) + txt = self._tableModel.ttkStringData(_row, _col) if _color != TTkColor.RST: txt = txt.completeColor(_color) for i,line in enumerate(txt.split('\n')): diff --git a/TermTk/TTkWidgets/TTkPickers/textpicker.py b/TermTk/TTkWidgets/TTkPickers/textpicker.py index d2b37c64..8f4884ad 100644 --- a/TermTk/TTkWidgets/TTkPickers/textpicker.py +++ b/TermTk/TTkWidgets/TTkPickers/textpicker.py @@ -137,11 +137,10 @@ class _emojiPicker(TTkResizableFrame): self.emojiClicked = epa.viewport().emojiClicked class TTkTextDialogPicker(TTkWindow): - __slots__ = ('_textEdit', '_autoSize', '_multiLine') - def __init__(self, *args, **kwargs): - self._autoSize = kwargs.get('autoSize',False) - self._multiLine = kwargs.get('multiLine',True) - super().__init__(*args, **kwargs) + __slots__ = ('_textEdit', '_autoSize') + def __init__(self, *, autoSize=False, multiLine=True, wrapMode=TTkK.WidgetWidth, **kwargs): + self._autoSize = autoSize + super().__init__(**kwargs) fontLayout = TTkGridLayout(columnMinWidth=1) # Char Fg/Bg buttons fontLayout.addWidget(cb_fg := TTkCheckbox(text=" FG"),0,0) @@ -160,9 +159,9 @@ class TTkTextDialogPicker(TTkWindow): fontLayout.addWidget(_superSimpleHorizontalLine(),0,10,2,1) - self._textEdit = TTkTextEdit(document=kwargs.get('document',TTkTextDocument()),multiLine=self._multiLine) + self._textEdit = TTkTextEdit(document=kwargs.get('document',TTkTextDocument()),multiLine=multiLine) self._textEdit.setReadOnly(False) - self._textEdit.setLineWrapMode(TTkK.WidgetWidth) + self._textEdit.setLineWrapMode(wrapMode) self._textEdit.setLineNumber('\n' in self._textEdit.toPlainText()) @pyTTkSlot() @@ -264,16 +263,15 @@ class TTkTextPicker(TTkContainer): Do not use it unless you know what you are doing And I've no idea what I am doing ''' - __slots__ = ('_teButton','_textEdit', 'documentViewChanged', 'textChanged', '_autoSize', '_multiLine') - def __init__(self, *args, **kwargs): + __slots__ = ('_teButton','_textEdit', 'documentViewChanged', 'textChanged', '_autoSize') + def __init__(self, *, text='', autoSize=False, multiLine=True, wrapMode=TTkK.WidgetWidth, **kwargs): self.documentViewChanged = pyTTkSignal(int,int) - self._autoSize = kwargs.get('autoSize',False) - self._multiLine = kwargs.get('multiLine',True) - super().__init__(*args, **kwargs|{'layout':TTkHBoxLayout()}) - self._textEdit = TTkTextEdit(pos=(0,0), size=(self.width()-2,self.height()),multiLine=self._multiLine) - self._textEdit.setText(kwargs.get('text','')) + self._autoSize = autoSize + super().__init__(**kwargs|{'layout':TTkHBoxLayout()}) + self._textEdit = TTkTextEdit(pos=(0,0), size=(self.width()-2,self.height()),multiLine=multiLine) + self._textEdit.setText(text) self._textEdit.setReadOnly(False) - self._textEdit.setLineWrapMode(TTkK.WidgetWidth) + self._textEdit.setLineWrapMode(wrapMode) self.textChanged = self._textEdit.textChanged self._teButton = TTkButton(border=True, text='◉', borderColor=TTkColor.fg("#AAAAFF")+TTkColor.bg("#002244") , pos=(self.width()-2,0), @@ -284,7 +282,9 @@ class TTkTextPicker(TTkContainer): @pyTTkSlot() def _showTextDialogPicker(): w,h = self.size() - tdp = TTkTextDialogPicker(size=(50,8+h), document=self._textEdit.document(), autoSize=self._autoSize, multiLine=self._multiLine) + tdp = TTkTextDialogPicker(size=(50,8+h), + document=self._textEdit.document(), + autoSize=autoSize, multiLine=multiLine, wrapMode=wrapMode) TTkHelper.overlay(self, tdp, -1, -7, modal=True) tdp.focusTextEdit() @@ -292,6 +292,9 @@ class TTkTextPicker(TTkContainer): self._textEdit.viewport().viewChanged.connect(self._textPickerViewChanged) + def setFocus(self): + return self._textEdit.setFocus() + def getTTkString(self): return self._textEdit.toRawText() diff --git a/TermTk/TTkWidgets/spinbox.py b/TermTk/TTkWidgets/spinbox.py index 4e4c419d..0a871a93 100644 --- a/TermTk/TTkWidgets/spinbox.py +++ b/TermTk/TTkWidgets/spinbox.py @@ -100,9 +100,20 @@ class TTkSpinBox(TTkContainer): self._maximum = maximum self.setValue(self._value) + @staticmethod + def _isFloat(num): + try: + float(str(num)) + return True + except: + return False + @pyTTkSlot(str) def _textEdited(self, text): - self.setValue(int(str(text))) + if self._isFloat(text): + self.setValue(float(str(text))) + else: + self.setValue(int(str(text))) self._lineEdit.setText(str(self._value)) def wheelEvent(self, evt): diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index 66d56832..e838e267 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -148,7 +148,7 @@ class TTkTextEditView(TTkAbstractScrollView): self.undoAvailable = pyTTkSignal(bool) self.redoAvailable = pyTTkSignal(bool) self.textChanged = pyTTkSignal() - self._readOnly = kwargs.get('readOnly', True) + self._readOnly = kwargs.get('readOnly', False) self._multiLine = kwargs.get('multiLine', True) self._multiCursor = True self._hsize = 0 diff --git a/tests/t.ui/test.ui.032.table.05.py b/tests/t.ui/test.ui.032.table.05.py index 05815c99..3652dff9 100755 --- a/tests/t.ui/test.ui.032.table.05.py +++ b/tests/t.ui/test.ui.032.table.05.py @@ -107,6 +107,7 @@ class MyTableModel(ttk.TTkAbstractTableModel): def rowCount(self): return len(self.mylist) if not self.size else self.size[0] def columnCount(self): return len(self.mylist[0]) if not self.size else self.size[1] def data(self, row, col): return self.mylist[row][col] + def setData(self, row, col, data): self.mylist[row][col] = data def headerData(self, num, orientation): if orientation == ttk.TTkK.HORIZONTAL: prefix = ['H-aa','H-bb','H-cc','H-dd','H-ee','H-ff','H-gg','Parodi','H-hh',] @@ -138,7 +139,7 @@ data_list1 = [ [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ [y for y in range(10)]+ [y+0.123 for y in range(10)] - for x in range(1000)] + for x in range(5000)] data_list2 = [ [txt1,txt2,txt3]+ @@ -158,6 +159,9 @@ data_list3 = [ [random.choice(tiles*p1)]+ [random.choice(tiles*p1)]+ [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ [random.choice(tiles*p1)] for x in range(30)] diff --git a/tests/t.ui/test.ui.032.table.06.py b/tests/t.ui/test.ui.032.table.06.py new file mode 100755 index 00000000..72ccb616 --- /dev/null +++ b/tests/t.ui/test.ui.032.table.06.py @@ -0,0 +1,357 @@ +#!/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. + +# Demo inspired from: +# https://www.daniweb.com/programming/software-development/code/447834/applying-pyside-s-qabstracttablemodel + +import os +import sys +import csv +import re +import argparse +import operator +import json +import random + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +parser = argparse.ArgumentParser() +parser.add_argument('-f', help='Full Screen', action='store_true') +parser.add_argument('-t', help='Track Mouse', action='store_true') +parser.add_argument('--csv', help='Open CSV File', type=argparse.FileType('r')) + +args = parser.parse_args() + +fullScreen = args.f +mouseTrack = args.t + +# csvData = [] +# if args.csv: +# sniffer = csv.Sniffer() +# has_header = sniffer.has_header(args.csv.read(2048)) +# args.csv.seek(0) +# csvreader = csv.reader(args.csv) +# for row in csvreader: +# csvData.append(row) + + +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 - Removing the extra '\n' at the end + diamond = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['diamond' ])[0:-1]) + fire = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['fire' ])[0:-1]) + fireMini = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['fireMini'])[0:-1]) + key = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['key' ])[0:-1]) + peach = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['peach' ])[0:-1]) + pepper = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['pepper' ])[0:-1]) + python = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['python' ])[0:-1]) + ring = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['ring' ])[0:-1]) + sword = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['sword' ])[0:-1]) + whip = ttk.TTkString(ttk.TTkUtil.base64_deflate_2_obj(d['compressed']['whip' ])[0:-1]) + tiles = [diamond,fire,key,peach,ring,sword,whip] + images = [fireMini,pepper,python] + + +class CustomColorModifier(ttk.TTkAlternateColor): + colors = ( + [ ttk.TTkColor.bg("#000066"), ttk.TTkColor.bg("#0000FF") ] * 3 + + [ ttk.TTkColor.bg("#003300"), ttk.TTkColor.bg("#006600") ] + + [ ttk.TTkColor.bg("#000066"), ttk.TTkColor.bg("#0000FF") ] * 3 + + [ttk.TTkColor.RST] * 5 + + [ #Rainbow-ish + ttk.TTkColor.fgbg("#00FFFF","#880000") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#00FFFF","#FF0000") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#0000FF","#FFFF00") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#FF00FF","#00FF00") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#FF0000","#00FFFF") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#FFFF00","#0000FF") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#00FF00","#FF00FF") + ttk.TTkColor.BOLD, + ttk.TTkColor.fgbg("#00FF00","#880088") + ttk.TTkColor.BOLD] + + [ttk.TTkColor.RST] * 3 + + [ttk.TTkColor.bg("#0000FF")] + + [ttk.TTkColor.RST] * 2 + + [ttk.TTkColor.bg("#0000FF")] + + [ttk.TTkColor.RST] + + [ttk.TTkColor.bg("#0000FF")] + + [ttk.TTkColor.RST] + + [ttk.TTkColor.bg("#0000FF")] * 2 + + [ttk.TTkColor.RST] * 3 + + [ttk.TTkColor.bg("#0000FF")] * 3 + + [ttk.TTkColor.RST] * 5 + + #Rainbow-ish 2 + [ttk.TTkColor.fgbg("#00FF00","#880088")] * 2 + + [ttk.TTkColor.fgbg("#00FF00","#FF00FF")] * 2 + + [ttk.TTkColor.fgbg("#FFFF00","#0000FF")] * 2 + + [ttk.TTkColor.fgbg("#FF0000","#00FFFF")] * 2 + + [ttk.TTkColor.fgbg("#FF00FF","#00FF00")] * 2 + + [ttk.TTkColor.fgbg("#0000FF","#FFFF00")] * 2 + + [ttk.TTkColor.fgbg("#00FFFF","#FF0000")] * 2 + + [ttk.TTkColor.fgbg("#00FFFF","#880000")] * 2 + + [ttk.TTkColor.RST] * 2 + ) + def __init__(self): + super().__init__() + + def exec(self, x:int, y:int, base_color:ttk.TTkColor) -> ttk.TTkColor: + c = CustomColorModifier.colors + return c[y%len(c)] + + +class MyTableModel(ttk.TTkAbstractTableModel): + def __init__(self, mylist, size=None): + self.mylist = mylist + self.size=size + super().__init__() + + def rowCount(self): return len(self.mylist) if not self.size else self.size[0] + def columnCount(self): return len(self.mylist[0]) if not self.size else self.size[1] + def data(self, row, col): return self.mylist[row][col] + def setData(self, row, col, data): self.mylist[row][col] = data + def headerData(self, num, orientation): + if orientation == ttk.TTkK.HORIZONTAL: + prefix = ['H-aa','H-bb','H-cc','H-dd','H-ee','H-ff','H-gg','Parodi','H-hh',] + return f"{prefix[num%len(prefix)]}:{num:03}" + if orientation == ttk.TTkK.VERTICAL: + prefix = ['aa','bb','cc','dd','ee','ff','gg','Euge'] + return f"{prefix[num%len(prefix)]}:{num:03}" + return super().headerData(num, orientation) + +class MyTableModelCSV(ttk.TTkAbstractTableModel): + def __init__(self, fd): + self.mylist = [] + self.hheader = [] + self.vheader = [] + self._csvImport(fd) + super().__init__() + + def _csvImport(self, fd): + sniffer = csv.Sniffer() + has_header = sniffer.has_header(fd.read(2048)) + fd.seek(0) + csvreader = csv.reader(fd) + for row in csvreader: + self.mylist.append(row) + if has_header: + self.hheader = self.mylist.pop(0) + # check if the first column include an index: + if self._checkIndexColumn(): + self.hheader.pop(0) + for l in self.mylist: + self.vheader.append(l.pop(0)) + + def _checkIndexColumn(self): + if all(l[0].isdigit() for l in self.mylist): + num = int(self.mylist[0][0]) + return all(num+i==int(l[0]) for i,l in enumerate(self.mylist)) + return False + + 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 setData(self, row, col, data): self.mylist[row][col] = data + def headerData(self, num, orientation): + if orientation == ttk.TTkK.HORIZONTAL: + if self.hheader: + return self.hheader[num] + prefix = ['H-aa','H-bb','H-cc','H-dd','H-ee','H-ff','H-gg','Parodi','H-hh',] + return f"{prefix[num%len(prefix)]}:{num:03}" + if orientation == ttk.TTkK.VERTICAL: + if self.vheader: + return self.vheader[num] + prefix = ['aa','bb','cc','dd','ee','ff','gg','Euge'] + return f"{prefix[num%len(prefix)]}:{num:03}" + return super().headerData(num, orientation) + +txt1 = "Text" +txt2 = txt1*5 +txt3 = 'M1: '+' -M1\n'.join([txt1*2]*3) +txt4 = 'M2: '+' -M2\n'.join([txt1*5]*5) +txt5 = ttk.TTkString(txt4, ttk.TTkColor.RED + ttk.TTkColor.BG_YELLOW) + +# use numbers for numeric data to sort properly +p1 = 4 +p2 = 2 +p3 = 2 +p4 = 2 + +data_list1 = [ + [f"{x:04}"]+ + [txt1,txt2,txt3,txt4,txt5]+ + [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ + [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ + [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ + [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ + [random.choice(tiles*p1+images*p2+[txt1,txt2]*p3+[123,234,345,567,890,123.001,234.02,345.3,567.04,890.01020304,1]*p4)]+ + [y for y in range(10)]+ + [y+0.123 for y in range(10)] + for x in range(5000)] + +data_list2 = [ + [txt1,txt2,txt3]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [y for y in range(10)]+ + [y+0.123 for y in range(10)] + for x in range(1000)] + +data_list3 = [ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)]+ + [random.choice(tiles*p1)] + for x in range(30)] + + +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) + +splitter = ttk.TTkSplitter(parent=rootTable,orientation=ttk.TTkK.VERTICAL) + + + +table_model1 = MyTableModel(data_list1) +table_model2 = MyTableModel(data_list2) +table_model3 = MyTableModel(data_list3) +# table_model = MyTableModel(data_list, size=(15,10)) + +table = ttk.TTkTable(parent=splitter, tableModel=table_model1) + +# # set column width to fit contents (set font first!) +# table.resizeColumnsToContents() +# # enable sorting +# table.setSortingEnabled(True) + +table.setSelection((0,0),(2,2),1) +table.setSelection((3,0),(1,2),1) + +table.setSelection((1,3),(2,4),1) +table.setSelection((2,5),(2,4),1) +table.setSelection((0,9),(2,3),1) + +table.setSelection((1,59),(1,2),1) +table.setSelection((3,59),(1,2),1) + +controlAndLogsSplitter = ttk.TTkSplitter() + +splitter.addWidget(controlAndLogsSplitter,size=8,title="LOGS") + +controls = ttk.TTkContainer() + +defaultStyle = table.style()['default'] + +tableStyle1 = {'default': defaultStyle|{'color': ttk.TTkColor.RST} } +tableStyle2 = {'default': defaultStyle|{'color': ttk.TTkColor.bg("#000066", modifier=ttk.TTkAlternateColor(alternateColor=ttk.TTkColor.BG_BLUE))} } +tableStyle3 = {'default': defaultStyle|{ + 'color': ttk.TTkColor.bg("#006600", modifier=ttk.TTkAlternateColor(alternateColor=ttk.TTkColor.bg("#003300"))), + 'selectedColor': ttk.TTkColor.bg("#00AA00"), + 'hoverColor': ttk.TTkColor.bg("#00AAAA")} } +tableStyle4 = {'default': defaultStyle|{'color': ttk.TTkColor.bg("#660066", modifier=ttk.TTkAlternateColor(alternateColor=ttk.TTkColor.RST))} } +tableStyle5 = {'default': defaultStyle|{ + 'color': ttk.TTkColor.bg("#000000", modifier=CustomColorModifier()), + 'lineColor': ttk.TTkColor.fg("#FFFF00"), + 'headerColor': ttk.TTkColor.fg("#FFFF00")+ttk.TTkColor.bg("#660066"), + 'hoverColor': ttk.TTkColor.bg("#AAAAAA"), + 'selectedColor': ttk.TTkColor.bg("#FFAA66"), + 'separatorColor': ttk.TTkColor.fg("#330055")+ttk.TTkColor.bg("#660066")} } + +# Header Checkboxes +ttk.TTkLabel(parent=controls, pos=(1,0), text="Header:") +ht = ttk.TTkCheckbox(parent=controls, pos=( 1,1), size=(15,1), text=' Top ',checked=True) +hl = ttk.TTkCheckbox(parent=controls, pos=(10,1), size=(15,1), text=' Left',checked=True) + +ht.toggled.connect(table.horizontalHeader().setVisible) +hl.toggled.connect(table.verticalHeader().setVisible) + +# Lines/Separator Checkboxes +ttk.TTkLabel(parent=controls, pos=(1,2), text="Lines:") +vli = ttk.TTkCheckbox(parent=controls, pos=( 1,3), size=(5,1), text=' V',checked=True) +hli = ttk.TTkCheckbox(parent=controls, pos=(10,3), size=(5,1), text=' H',checked=True) + +vli.toggled.connect(table.setVSeparatorVisibility) +hli.toggled.connect(table.setHSeparatorVisibility) + + +# Themes Control +t1 = ttk.TTkRadioButton(parent=controls, pos=(20,1), size=(11,1), text=' Theme 1', radiogroup='Themes', checked=True) +t2 = ttk.TTkRadioButton(parent=controls, pos=(20,2), size=(11,1), text=' Theme 2', radiogroup='Themes') +t3 = ttk.TTkRadioButton(parent=controls, pos=(20,3), size=(11,1), text=' Theme 3', radiogroup='Themes') +t4 = ttk.TTkRadioButton(parent=controls, pos=(20,4), size=(11,1), text=' Theme 4', radiogroup='Themes') +t5 = ttk.TTkRadioButton(parent=controls, pos=(20,5), size=(11,1), text=' Theme 5', radiogroup='Themes') + +t1.clicked.connect(lambda : table.mergeStyle(tableStyle1)) +t2.clicked.connect(lambda : table.mergeStyle(tableStyle2)) +t3.clicked.connect(lambda : table.mergeStyle(tableStyle3)) +t4.clicked.connect(lambda : table.mergeStyle(tableStyle4)) +t5.clicked.connect(lambda : table.mergeStyle(tableStyle5)) + + +# Model Picker +m1 = ttk.TTkRadioButton(parent=controls, pos=(33,1), size=(11,1), text=' Model 1', radiogroup='Models', checked=True) +m2 = ttk.TTkRadioButton(parent=controls, pos=(33,2), size=(11,1), text=' Model 2', radiogroup='Models') +m3 = ttk.TTkRadioButton(parent=controls, pos=(33,3), size=(11,1), text=' Model 3', radiogroup='Models') + +m1.clicked.connect(lambda : table.setModel(table_model1)) +m2.clicked.connect(lambda : table.setModel(table_model2)) +m3.clicked.connect(lambda : table.setModel(table_model3)) + +if args.csv: + table_model_csv = MyTableModelCSV(args.csv) + m_csv = ttk.TTkRadioButton(parent=controls, pos=(33,4), size=(11,1), text=' CSV', radiogroup='Models') + m_csv.clicked.connect(lambda : table.setModel(table_model_csv)) + + + +# Resize Button +rvb = ttk.TTkButton(parent=controls, pos=(1,5), size=( 3,1), text="V", border=False) +rhb = ttk.TTkButton(parent=controls, pos=(4,5), size=( 3,1), text="H", border=False) +rb = ttk.TTkButton(parent=controls, pos=(7,5), size=(11,1), text="Resize", border=False) + +rhb.clicked.connect(table.resizeRowsToContents) +rvb.clicked.connect(table.resizeColumnsToContents) +rb.clicked.connect( table.resizeRowsToContents) +rb.clicked.connect( table.resizeColumnsToContents) + +controlAndLogsSplitter.addWidget(controls, size=50) +controlAndLogsSplitter.addWidget(ttk.TTkLogViewer()) + +# winKey = ttk.TTkWindow(title="KeyPress",layout=ttk.TTkGridLayout(), size=(30,7)) +# winKey.layout().addWidget(ttk.TTkKeyPressView(maxHeight=3)) +# ttk.TTkHelper.overlay(None, winKey, 10, 4, toolWindow=True) + +root.mainloop() \ No newline at end of file diff --git a/tests/timeit/02.array.04.modify.py b/tests/timeit/02.array.04.modify.py index 17a267e9..00de92c9 100755 --- a/tests/timeit/02.array.04.modify.py +++ b/tests/timeit/02.array.04.modify.py @@ -48,7 +48,19 @@ def test_ti_3_1(): array1 = [[True]*1000 for _ in range(1000)] return True -loop = 1000 +def test_ti_4_1(): + array1 = [[True]*10000 for _ in range(10000)] + return True + +def test_ti_5_1(): + array1 = [[True]*100 for _ in range(100000)] + return True + +def test_ti_6_1(): + array1 = [[True]*11 for _ in range(2000000)] + return True + +loop = 10 a = {} # while (testName := f'test{iii}') and (testName in globals()): diff --git a/tests/timeit/30.rendering.01.TTkTable.py b/tests/timeit/30.rendering.01.TTkTable.py index a734b0c3..64f6775b 100755 --- a/tests/timeit/30.rendering.01.TTkTable.py +++ b/tests/timeit/30.rendering.01.TTkTable.py @@ -90,55 +90,31 @@ def resize1(): 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]() +def test_ti_A_00_200x50(): return [t.resize(200,50) for t in tables] +def test_ti_A_01(): return paint(tables[0]) +def test_ti_A_02(): return paint(tables[1]) +def test_ti_A_03(): return paint(tables[2]) +def test_ti_A_04(): return paint(tables[3]) +def test_ti_A_05(): return paint(tables[4]) +def test_ti_A_06(): return paint(tables[5]) +def test_ti_A_07(): return paint(tables[6]) +def test_ti_A_08(): return paint(tables[7]) +def test_ti_A_09(): return paint(tables[8]) +def test_ti_A_10(): return paint(tables[9]) +def test_ti_A_11(): return paint(tables[10]) +def test_ti_B_00_500x200(): return [t.resize(500,200) for t in tables] +def test_ti_B_01(): return paint(tables[0]) +def test_ti_B_02(): return paint(tables[1]) +def test_ti_B_03(): return paint(tables[2]) +def test_ti_B_04(): return paint(tables[3]) +def test_ti_B_05(): return paint(tables[4]) +def test_ti_B_06(): return paint(tables[5]) +def test_ti_B_07(): return paint(tables[6]) +def test_ti_B_08(): return paint(tables[7]) +def test_ti_B_09(): return paint(tables[8]) +def test_ti_B_10(): return paint(tables[9]) +def test_ti_B_11(): return paint(tables[10]) + for t in tables: t.resize(500,200) diff --git a/tools/dumbPaintTool/app/paintarea.py b/tools/dumbPaintTool/app/paintarea.py index a24063c3..e5a8d039 100644 --- a/tools/dumbPaintTool/app/paintarea.py +++ b/tools/dumbPaintTool/app/paintarea.py @@ -117,7 +117,7 @@ class PaintArea(ttk.TTkAbstractScrollView): def viewFullAreaSize(self) -> tuple[int,int]: _,_,w,h = self._getGeometry() - return w,h + return w+1,h+1 def viewDisplayedSize(self) -> tuple: return self.size()