diff --git a/TermTk/TTkAbstract/__init__.py b/TermTk/TTkAbstract/__init__.py new file mode 100644 index 00000000..5828fbe3 --- /dev/null +++ b/TermTk/TTkAbstract/__init__.py @@ -0,0 +1 @@ +from .abstractscrollarea import * diff --git a/TermTk/TTkAbstract/abstractscrollarea.py b/TermTk/TTkAbstract/abstractscrollarea.py new file mode 100644 index 00000000..bcc17c7b --- /dev/null +++ b/TermTk/TTkAbstract/abstractscrollarea.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 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. + +from TermTk.TTkCore.constant import TTkConstant, TTkK +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.cfg import * +from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.scrollbar import TTkScrollBar +from TermTk.TTkLayouts.gridlayout import TTkGridLayout +from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView + +class TTkAbstractScrollArea(TTkWidget): + __slots__ = ( + '_viewport', + '_verticalScrollBar', '_verticalScrollBarPolicy', + '_horizontalScrollBar', '_horizontalScrollBarPolicy',) + + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'TTkAbstractScrollArea') + self.setLayout(TTkGridLayout()) + self._verticalScrollBar = TTkScrollBar(orientation=TTkK.VERTICAL) + self._horizontalScrollBar = TTkScrollBar(orientation=TTkK.HORIZONTAL) + self._verticalScrollBarPolicy = TTkK.ScrollBarAsNeeded + self._horizontalScrollBarPolicy = TTkK.ScrollBarAsNeeded + + @pyTTkSlot() + def _viewportChanged(self): + fw, fh = self._viewport.viewFullAreaSize() + dw, dh = self._viewport.viewDisplayedSize() + ox, oy = self._viewport.getViewOffsets() + hpage = dw + vpage = dh + hrange = fw - dw + vrange = fh - dh + self._verticalScrollBar.setPageStep(vpage) + self._verticalScrollBar.setRange(0, vrange) + self._verticalScrollBar.setValue(oy) + self._horizontalScrollBar.setPageStep(hpage) + self._horizontalScrollBar.setRange(0, hrange) + self._horizontalScrollBar.setValue(ox) + + @pyTTkSlot(int) + def _vscrollMoved(self, val): + ox, _ = self._viewport.getViewOffsets() + self._viewport.viewMoveTo(ox, val) + + @pyTTkSlot(int) + def _hscrollMoved(self, val): + _, oy = self._viewport.getViewOffsets() + self._viewport.viewMoveTo(val, oy) + + def setViewport(self, viewport): + if not isinstance(viewport, TTkAbstractScrollView): + raise TypeError("TTkAbstractScrollView is required in TTkAbstractScrollArea.setVewport(viewport)") + self._viewport = viewport + self._viewport.viewChanged.connect(self._viewportChanged) + self._verticalScrollBar.sliderMoved.connect(self._vscrollMoved) + self._horizontalScrollBar.sliderMoved.connect(self._hscrollMoved) + self.layout().addWidget(viewport,0,0) + self.layout().addWidget(self._verticalScrollBar,0,1) + self.layout().addWidget(self._horizontalScrollBar,1,0) + + def setVerticalScrollBarPolicy(self, policy): + self._verticalScrollBarPolicy = policy + + def setHorizontalScrollBarPolicy(self, policy): + self._horizontalScrollBarPolicy = policy + + diff --git a/TermTk/TTkAbstract/abstractscrollview.py b/TermTk/TTkAbstract/abstractscrollview.py new file mode 100644 index 00000000..68b46f2d --- /dev/null +++ b/TermTk/TTkAbstract/abstractscrollview.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 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. + +from TermTk.TTkCore.constant import TTkConstant, TTkK +from TermTk.TTkCore.cfg import * +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal +from TermTk.TTkWidgets.widget import TTkWidget + +class TTkAbstractScrollView(TTkWidget): + __slots__ = ( + '_viewOffsetX', '_viewOffsetY', + # Signals + 'viewMovedTo', 'viewSizeChanged', 'viewChanged') + + def __init__(self, *args, **kwargs): + super().__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'TTkAbstractScrollView') + # Signals + self.viewMovedTo = pyTTkSignal(int, int) # x, y + self.viewSizeChanged = pyTTkSignal(int, int) # w, h + self.viewChanged = pyTTkSignal() + + self._viewOffsetX = 0 + self._viewOffsetY = 0 + + # Override this function + def viewFullAreaSize(self) -> (int, int): + raise NotImplementedError() + + # Override this function + def viewDisplayedSize(self) -> (int, int): + raise NotImplementedError() + + @pyTTkSlot(int, int) + def viewMoveTo(self, x, y): + fw, fh = self.viewFullAreaSize() + dw, dh = self.viewDisplayedSize() + rangex = fw - dw + rangey = fh - dh + # TTkLog.debug(f"x:{x},y:{y}, full:{fw,fh}, display:{dw,dh}, range:{rangex,rangey}") + if x>rangex: x = rangex + if y>rangey: y = rangey + if x<0 : x = 0 + if y<0 : y = 0 + # TTkLog.debug(f"x:{x},y:{y}, wo:{self._viewOffsetX,self._viewOffsetY}") + if self._viewOffsetX == x and \ + self._viewOffsetY == y: # Nothong to do + return + self._viewOffsetX = x + self._viewOffsetY = y + self.viewMovedTo.emit(x,y) + self.viewChanged.emit() + self.update() + + def wheelEvent(self, evt): + delta = TTkCfg.scrollDelta + offx, offy = self.getViewOffsets() + if evt.evt == TTkK.WHEEL_Up: + delta = -delta + self.viewMoveTo(offx, offy + delta) + return True + + def resizeEvent(self, w, h): + self.viewSizeChanged.emit(w,h) + self.viewChanged.emit() + + def getViewOffsets(self): + return self._viewOffsetX, self._viewOffsetY + diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index 84aad79d..10223e8b 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -46,6 +46,12 @@ class TTkConstant: HORIZONTAL = 0x01 VERTICAL = 0x02 + # Scroll Bar Policy + ScrollBarAsNeeded = 0x00 + ScrollBarAlwaysOff = 0x01 + ScrollBarAlwaysOn = 0x02 + + # Keys NoButton = 0x00000000 # The button state does not refer to any button (see QMouseEvent::button()). AllButtons = 0x07ffffff # This value corresponds to a mask of all possible mouse buttons. Use to set the 'acceptedButtons' property of a MouseArea to accept ALL mouse buttons. diff --git a/TermTk/TTkWidgets/__init__.py b/TermTk/TTkWidgets/__init__.py index 58ca90ef..e39091d9 100644 --- a/TermTk/TTkWidgets/__init__.py +++ b/TermTk/TTkWidgets/__init__.py @@ -1,16 +1,17 @@ -from .widget import * -from .spacer import * -from .frame import * +from .widget import * +from .spacer import * +from .frame import * from .resizableframe import * -from .splitter import * -from .label import * -from .button import * -from .checkbox import * +from .splitter import * +from .label import * +from .button import * +from .checkbox import * from .radiobutton import * -from .combobox import * -from .lineedit import * -from .texedit import * -from .scrollbar import * -from .window import * -from .table import * -from .logviewer import * +from .combobox import * +from .lineedit import * +from .texedit import * +from .scrollbar import * +from .window import * +from .table import * +from .tableview import * +from .logviewer import * diff --git a/TermTk/TTkWidgets/scrollbar.py b/TermTk/TTkWidgets/scrollbar.py index c4d68973..b619c7fe 100644 --- a/TermTk/TTkWidgets/scrollbar.py +++ b/TermTk/TTkWidgets/scrollbar.py @@ -196,6 +196,9 @@ class TTkScrollBar(TTkWidget): @pyTTkSlot(int, int) def setRange(self, min, max): + if self._minimum == min and \ + self._maximum == max : + return self.minimum = min self.maximum = max self.rangeChanged.emit(min, max) @@ -208,6 +211,8 @@ class TTkScrollBar(TTkWidget): def minimum(self): return self._minimum @minimum.setter def minimum(self, v): + if v == self._minimum: + return if v > self._maximum: v = self._maximum self._minimum = v @@ -217,6 +222,8 @@ class TTkScrollBar(TTkWidget): def maximum(self): return self._maximum @minimum.setter def maximum(self, v): + if v == self._maximum: + return if v < self._minimum: v = self._minimum self._maximum = v @@ -236,6 +243,8 @@ class TTkScrollBar(TTkWidget): def value(self): return self._value @value.setter def value(self, v): + if self._value == v: + return if v > self._maximum: v = self._maximum if v < self._minimum: v = self._minimum if self._value == v: return diff --git a/TermTk/TTkWidgets/table.py b/TermTk/TTkWidgets/table.py index 71591ae3..b1d26bdf 100644 --- a/TermTk/TTkWidgets/table.py +++ b/TermTk/TTkWidgets/table.py @@ -27,288 +27,34 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor +from TermTk.TTkWidgets.tableview import TTkTableView from TermTk.TTkLayouts.gridlayout import TTkGridLayout -from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.frame import TTkFrame -from TermTk.TTkWidgets.spacer import TTkSpacer -from TermTk.TTkWidgets.scrollbar import TTkScrollBar +from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea -class _TTkTableViewHeader(TTkWidget): - __slots__ = ( - '_header', '_showHeader', - '_alignments', '_headerColor', - '_columns') +class TTkTable(TTkAbstractScrollArea): + __slots__ = ('_tableView', 'activated') def __init__(self, *args, **kwargs): - TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , '_TTkTableViewHeader' ) - self._columns = kwargs.get('columns' , [-1] ) - self._header = [""]*len(self._columns) - self._alignments = [TTkK.NONE]*len(self._columns) - self._headerColor = kwargs.get('headerColor' , TTkColor.BOLD ) - self.setMaximumHeight(1) - self.setMinimumHeight(1) - - def setAlignment(self, alignments): - if len(alignments) != len(self._columns): - return - self._alignments = alignments - - def setHeader(self, header): - if len(header) != len(self._columns): - return - self._header = header - def setHeader(self, header): - if len(header) != len(self._columns): - return - self._header = header - - def setColumnSize(self, columns): - self._columns = columns - self._header = [""]*len(self._columns) - self._alignments = [TTkK.NONE]*len(self._columns) - - def paintEvent(self): - w,h = self.size() - total = 0 - variableCols = 0 - # Retrieve the free size - for width in self._columns: - if width > 0: - total += width - else: - variableCols += 1 - # Define the list of cols sizes - sizes = [] - for width in self._columns: - if width > 0: - sizes.append(width) - else: - sizes.append((w-total)//variableCols) - variableCols -= 1 - colors = [self._headerColor]*len(self._header) - self._canvas.drawTableLine(pos=(0,0), items=self._header, sizes=sizes, colors=colors, alignments=self._alignments) - -class _TTkTableViewData(TTkWidget): - __slots__ = ( - '_alignments', - '_columns', '_columnColors', - '_tableData', - '_selectColor', '_moveTo', '_selected') - def __init__(self, *args, **kwargs): - self._moveTo = 0 - self._tableData = [] - TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , '_TTkTableViewData' ) - self._columns = kwargs.get('columns' , [-1] ) - self._alignments = [TTkK.NONE]*len(self._columns) - self._columnColors = kwargs.get('columnColors' , [TTkColor.RST]*len(self._columns) ) - self._selectColor = kwargs.get('selectColor' , TTkColor.BOLD ) - self._selected = -1 - self.setFocusPolicy(TTkK.ClickFocus) - -class _TTkTableView(TTkWidget): - __slots__ = ( - '_header', '_showHeader', - '_alignments', '_headerColor', - '_columns', '_columnColors', - '_tableData', - '_selectColor', '_moveTo', '_selected', - # Signals - 'activated', 'tableMoved', 'displayedMaxRowsChanged', 'tablePropertiesChanged') - def __init__(self, *args, **kwargs): - self._moveTo = 0 - self._tableData = [] - TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , '_TTkTableView' ) - # define signals - self.activated = pyTTkSignal(int) # Value - self.tableMoved = pyTTkSignal(int) # Value - self.displayedMaxRowsChanged = pyTTkSignal(int) # Value - self.tablePropertiesChanged = pyTTkSignal(int, int, int, int) # selected, numItems, displayed lines, offsetView - - self._columns = kwargs.get('columns' , [-1] ) - self._header = [""]*len(self._columns) - self._showHeader = False - self._alignments = [TTkK.NONE]*len(self._columns) - self._columnColors = kwargs.get('columnColors' , [TTkColor.RST]*len(self._columns) ) - self._selectColor = kwargs.get('selectColor' , TTkColor.BOLD ) - self._headerColor = kwargs.get('headerColor' , TTkColor.BOLD ) - self._selected = -1 - self.setFocusPolicy(TTkK.ClickFocus) - - def _initSignals(self): - # self.cellActivated(int row, int column) - # self.cellChanged(int row, int column) - # self.cellClicked(int row, int column) - # self.cellDoubleClicked(int row, int column) - # self.cellEntered(int row, int column) - # self.cellPressed(int row, int column) - # self.currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) - # self.currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) - # self.itemActivated(QTableWidgetItem *item) - # self.itemChanged(QTableWidgetItem *item) - # self.itemClicked(QTableWidgetItem *item) - # self.itemDoubleClicked(QTableWidgetItem *item) - # self.itemEntered(QTableWidgetItem *item) - # self.itemPressed(QTableWidgetItem *item) - # self.itemSelectionChanged() - pass - - def items(self): return self._tableData - - def setAlignment(self, alignments): - if len(alignments) != len(self._columns): - return - self._alignments = alignments - - def setColumnSize(self, columns): - self._columns = columns - self._columnColors = [TTkColor.RST]*len(self._columns) - self._header = [""]*len(self._columns) - self._alignments = [TTkK.NONE]*len(self._columns) - - def setColumnColors(self, colors): - if len(colors) != len(self._columns): - return - self._columnColors = colors - - def appendItem(self, item): - if len(item) != len(self._columns): - return - self._tableData.append(item) - self.tablePropertiesChanged.emit(self._selected, len(self._tableData), self.height(), self._moveTo) - self.update() - - def mousePressEvent(self, evt): - x,y = evt.x, evt.y - if y >= 0: - selected = self._moveTo + y - if selected >= len(self._tableData): - selected = -1 - self._selected = selected - self.update() - self.activated.emit(self._selected) - return True - - def wheelEvent(self, evt): - delta = TTkCfg.scrollDelta - if evt.evt == TTkK.WHEEL_Up: - delta = -delta - self.scrollTo(self._moveTo + delta) - self.update() - return True - - def resizeEvent(self, w, h): - if self._moveTo > len(self._tableData)-h-1: - self._moveTo = len(self._tableData)-h-1 - if self._moveTo < 0: - self._moveTo = 0 - self.displayedMaxRowsChanged.emit(h) - self.tablePropertiesChanged.emit(self._selected, len(self._tableData), self.height(), self._moveTo) - - @pyTTkSlot(int) - def scrollTo(self, to): - # TTkLog.debug(f"to:{to},h{self._height},size:{len(self._tableData)}") - max = len(self._tableData) - self.height() - if to>max: to=max - if to<0: to=0 - self._moveTo = to - self.tableMoved.emit(to) - self.tablePropertiesChanged.emit(self._selected, len(self._tableData), self.height(), self._moveTo) - self.update() - - - def paintEvent(self): - w,h = self.size() - total = 0 - variableCols = 0 - # Retrieve the free size - for width in self._columns: - if width > 0: - total += width - else: - variableCols += 1 - # Define the list of cols sizes - sizes = [] - for width in self._columns: - if width > 0: - sizes.append(width) - else: - sizes.append((w-total)//variableCols) - variableCols -= 1 - - maxItems = len(self._tableData) - itemFrom = self._moveTo - if itemFrom > maxItems-h: itemFrom = maxItems-h - if itemFrom < 0 : itemFrom = 0 - itemTo = itemFrom + h - if itemTo > maxItems: itemTo = maxItems - # TTkLog.debug(f"moveto:{self._moveTo}, maxItems:{maxItems}, f:{itemFrom}, t{itemTo}, h:{h}, sel:{self._selected}") - - y = 0 - for it in range(itemFrom, itemTo): - item = self._tableData[it] - if self._selected > 0: - val = self._selected - itemFrom - else: - val = h//2 - if val < 0 : val = 0 - if val > h : val = h - if it == self._selected: - colors = [self._selectColor]*len(self._columnColors) - self._canvas.drawTableLine(pos=(0,y), items=item, sizes=sizes, colors=colors, alignments=self._alignments) - else: - colors = [c.modParam(val=-val) for c in self._columnColors] - self._canvas.drawTableLine(pos=(0,y), items=item, sizes=sizes, colors=colors, alignments=self._alignments) - y+=1 - - -class TTkTable(TTkWidget): - __slots__ = ('_vscroller', '_hscroller', '_tableView', '_headerView', '_showHeader', 'activated') - def __init__(self, *args, **kwargs): - TTkWidget.__init__(self, *args, **kwargs) + TTkAbstractScrollArea.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkTable' ) - if 'parent' in kwargs: - kwargs.pop('parent') - self._tableView = _TTkTableView(*args, **kwargs) - self._headerView = _TTkTableViewHeader(*args, **kwargs) + if 'parent' in kwargs: kwargs.pop('parent') + self._tableView = TTkTableView(*args, **kwargs) # Forward the signal self.activated = self._tableView.activated - self._showHeader = kwargs.get('showHeader',True) - self._vscroller = TTkScrollBar(orientation=TTkK.VERTICAL) - self._hscroller = TTkScrollBar(orientation=TTkK.HORIZONTAL) - self.setLayout(TTkGridLayout()) - self._vscroller.sliderMoved.connect(self._tableView.scrollTo) - self._tableView.tablePropertiesChanged.connect(self.handleTableProperties) - self.layout().addWidget(self._headerView,0,0) - self.layout().addWidget(self._tableView,1,0) - # self.layout().addWidget(TTkTestWidget(border=True),0,0) - self.layout().addWidget(self._vscroller,1,1) - # self.layout().addWidget(self._hscroller,1,0) - if not self._showHeader: - self._headerView.hide() self.setFocusPolicy(TTkK.ClickFocus) + self.setViewport(self._tableView) def setAlignment(self, *args, **kwargs) : self._tableView.setAlignment(*args, **kwargs) - self._headerView.setAlignment(*args, **kwargs) def setHeader(self, *args, **kwargs) : - self._headerView.setHeader(*args, **kwargs) + self._tableView.setHeader(*args, **kwargs) def setColumnSize(self, *args, **kwargs) : self._tableView.setColumnSize(*args, **kwargs) - self._headerView.setColumnSize(*args, **kwargs) def setColumnColors(self, *args, **kwargs): self._tableView.setColumnColors(*args, **kwargs) def appendItem(self, *args, **kwargs) : - self._vscroller.setRangeTo(len(self._tableView.items())) self._tableView.appendItem(*args, **kwargs) - @pyTTkSlot(int, int, int, int) - def handleTableProperties(self, selected, items, height, offset): - self._vscroller.setRange(0, items-height) - self._vscroller.setValue(offset) - diff --git a/TermTk/TTkWidgets/tableview.py b/TermTk/TTkWidgets/tableview.py index 5837be0b..7ce218e3 100644 --- a/TermTk/TTkWidgets/tableview.py +++ b/TermTk/TTkWidgets/tableview.py @@ -22,62 +22,121 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor -from TermTk.TTkLayouts.boxlayout import TTkHBoxLayout from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.spacer import TTkSpacer -from TermTk.TTkWidgets.scrollbar import TTkScrollBar +from TermTk.TTkLayouts.gridlayout import TTkGridLayout +from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView -''' +class _TTkTableViewHeader(TTkWidget): + __slots__ = ('_header', '_alignments', '_headerColor', '_columns') + def __init__(self, *args, **kwargs): + TTkWidget.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , '_TTkTableViewHeader' ) + self._columns = kwargs.get('columns' , [-1] ) + self._header = [""]*len(self._columns) + self._alignments = [TTkK.NONE]*len(self._columns) + self._headerColor = kwargs.get('headerColor' , TTkColor.BOLD ) + self.setMaximumHeight(1) + self.setMinimumHeight(1) + + def setAlignment(self, alignments): + if len(alignments) != len(self._columns): + return + self._alignments = alignments + + def setHeader(self, header): + if len(header) != len(self._columns): + return + self._header = header + + def setColumnSize(self, columns): + self._columns = columns + self._header = [""]*len(self._columns) + self._alignments = [TTkK.NONE]*len(self._columns) + + def paintEvent(self): + w,h = self.size() + total = 0 + variableCols = 0 + # Retrieve the free size + for width in self._columns: + if width > 0: + total += width + else: + variableCols += 1 + # Define the list of cols sizes + sizes = [] + for width in self._columns: + if width > 0: + sizes.append(width) + else: + sizes.append((w-total)//variableCols) + variableCols -= 1 + colors = [self._headerColor]*len(self._header) + self._canvas.drawTableLine(pos=(0,0), items=self._header, sizes=sizes, colors=colors, alignments=self._alignments) -''' -class _______TTkTable(TTkWidget): +class _TTkTableView(TTkAbstractScrollView): __slots__ = ( - '_hlayout','_vscroller', - '_header', '_showHeader', '_alignments', '_headerColor', '_columns', '_columnColors', '_tableData', - '_selectColor', '_moveTo', '_selected') + '_selectColor', '_selected', + # Signals + 'activated') def __init__(self, *args, **kwargs): - self._vscroller = None # This is required to avoid crash int he vScroller Tuning - self._moveTo = 0 self._tableData = [] - TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , 'TTkTable' ) + TTkAbstractScrollView.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , '_TTkTableView' ) + # define signals + self.activated = pyTTkSignal(int) # Value + self._columns = kwargs.get('columns' , [-1] ) - self._header = [""]*len(self._columns) - self._showHeader = False self._alignments = [TTkK.NONE]*len(self._columns) self._columnColors = kwargs.get('columnColors' , [TTkColor.RST]*len(self._columns) ) self._selectColor = kwargs.get('selectColor' , TTkColor.BOLD ) self._headerColor = kwargs.get('headerColor' , TTkColor.BOLD ) - self._hlayout = TTkHBoxLayout() - self.setLayout(self._hlayout) self._selected = -1 - TTkSpacer(parent=self) - self._vscroller = TTkScrollBar(parent=self) - self._vscroller.valueChanged.connect(self.scrollTo) self.setFocusPolicy(TTkK.ClickFocus) + def _initSignals(self): + # self.cellActivated(int row, int column) + # self.cellChanged(int row, int column) + # self.cellClicked(int row, int column) + # self.cellDoubleClicked(int row, int column) + # self.cellEntered(int row, int column) + # self.cellPressed(int row, int column) + # self.currentCellChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) + # self.currentItemChanged(QTableWidgetItem *current, QTableWidgetItem *previous) + # self.itemActivated(QTableWidgetItem *item) + # self.itemChanged(QTableWidgetItem *item) + # self.itemClicked(QTableWidgetItem *item) + # self.itemDoubleClicked(QTableWidgetItem *item) + # self.itemEntered(QTableWidgetItem *item) + # self.itemPressed(QTableWidgetItem *item) + # self.itemSelectionChanged() + pass + + def viewFullAreaSize(self) -> (int, int): + return self.width(), len(self._tableData) + + def viewDisplayedSize(self) -> (int, int): + return self.size() + + def items(self): return self._tableData + def setAlignment(self, alignments): if len(alignments) != len(self._columns): return self._alignments = alignments - def setHeader(self, header): - if len(header) != len(self._columns): - return - self._header = header - def setColumnSize(self, columns): self._columns = columns self._columnColors = [TTkColor.RST]*len(self._columns) - self._header = [""]*len(self._columns) self._alignments = [TTkK.NONE]*len(self._columns) def setColumnColors(self, colors): @@ -89,107 +148,52 @@ class _______TTkTable(TTkWidget): if len(item) != len(self._columns): return self._tableData.append(item) - self._tuneTheScroller() - - def resizeEvent(self, w, h): - if self._moveTo > len(self._tableData)-h-1: - self._moveTo = len(self._tableData)-h-1 - if self._moveTo < 0: - self._moveTo = 0 - self._tuneTheScroller() - - def _tuneTheScroller(self): - if self._vscroller is None: return - scrollTo = len(self._tableData) - self.height() - self._vscroller.setRange(0, scrollTo) - self._vscroller.pagestep = self.height() + self.viewChanged.emit() + self.update() def mousePressEvent(self, evt): - x,y = evt.x, evt.y - if x == self.width() - 1: - return False - if y > 0: - self._selected = self._moveTo + y - 1 + _,y = evt.x, evt.y + _, oy = self.getViewOffsets() + if y >= 0: + selected = oy + y + if selected >= len(self._tableData): + selected = -1 + self._selected = selected self.update() + self.activated.emit(self._selected) return True - def wheelEvent(self, evt): - # delta = self.height() - delta = 5 - if evt.evt == TTkK.WHEEL_Up: - delta = -delta - # self.scrollTo(self._moveTo + delta) - self._vscroller.value = self._moveTo + delta - return True - - @pyTTkSlot(int) - def scrollTo(self, to): - max = len(self._tableData) - self.height() - if to>max: to=max - if to<0: to=0 - self._moveTo = to - self.update() - def paintEvent(self): w,h = self.size() + _, oy = self.getViewOffsets() total = 0 variableCols = 0 - slicesize = 0 + # Retrieve the free size for width in self._columns: if width > 0: total += width else: variableCols += 1 - if variableCols > 0: - slicesize = int((w-total)/variableCols) - # TTkLog.debug(f"ss:{slicesize}, w:{w}") + # Define the list of cols sizes + sizes = [] + for width in self._columns: + if width > 0: + sizes.append(width) + else: + sizes.append((w-total)//variableCols) + variableCols -= 1 maxItems = len(self._tableData) - itemFrom = self._moveTo -1 + itemFrom = oy if itemFrom > maxItems-h: itemFrom = maxItems-h if itemFrom < 0 : itemFrom = 0 itemTo = itemFrom + h if itemTo > maxItems: itemTo = maxItems # TTkLog.debug(f"moveto:{self._moveTo}, maxItems:{maxItems}, f:{itemFrom}, t{itemTo}, h:{h}, sel:{self._selected}") - def _lineDraw(_y, _val, _item, _inColor=None): - _x = 0 - for i in range(0,len(_item)): - _txt = _item[i] - _width = self._columns[i] - _color = self._columnColors[i] - _align = self._alignments[i] - if _inColor is not None: - _color = _inColor - if _width < 0: - _width = slicesize - if _width > 0: - _line = "" - _lentxt = len(_txt) - if _lentxt > _width: - _line += _txt[0:_width] - else: - _pad = _width-_lentxt - if _align == TTkK.NONE or _align == TTkK.LEFT_ALIGN: - _line += _txt + " "*_pad - elif _align == TTkK.RIGHT_ALIGN: - _line += " "*_pad + _txt - elif _align == TTkK.CENTER_ALIGN: - _p1 = _pad//2 - _p2 = _pad-_p1 - _line += " "*_p1 + _txt+" "*_p2 - elif _align == TTkK.JUSTIFY: - # TODO: Text Justification - _line += _txt + " "*_pad - self._canvas.drawText(pos=(_x,_y), text=_line, color=_color.modParam(val=-_val)) - _line += " " - _x += _width + 1 - - _lineDraw(0,0,self._header,self._headerColor) - - y = 1 + y = 0 for it in range(itemFrom, itemTo): - item = self._tableData[it] + item = self._tableData[it] if self._selected > 0: val = self._selected - itemFrom else: @@ -197,12 +201,56 @@ class _______TTkTable(TTkWidget): if val < 0 : val = 0 if val > h : val = h if it == self._selected: - _lineDraw(y,val,item,self._selectColor) + colors = [self._selectColor]*len(self._columnColors) + self._canvas.drawTableLine(pos=(0,y), items=item, sizes=sizes, colors=colors, alignments=self._alignments) else: - _lineDraw(y,val,item) + colors = [c.modParam(val=-val) for c in self._columnColors] + self._canvas.drawTableLine(pos=(0,y), items=item, sizes=sizes, colors=colors, alignments=self._alignments) y+=1 +class TTkTableView(TTkAbstractScrollView): + __slots__ = ( '_header', '_tableView', '_showHeader', 'activated') + + def __init__(self, *args, **kwargs): + TTkAbstractScrollView.__init__(self, *args, **kwargs) + self._name = kwargs.get('name' , 'TTkTableView' ) + if 'parent' in kwargs: kwargs.pop('parent') + self._showHeader = kwargs.get('showHeader', True) + self.setLayout(TTkGridLayout()) + self._tableView = _TTkTableView(*args, **kwargs) + self._header = _TTkTableViewHeader(*args, **kwargs) + self.layout().addWidget(self._header,0,0) + self.layout().addWidget(self._tableView,1,0) + # Forward the tableSignals + self.viewMovedTo = self._tableView.viewMovedTo + self.viewSizeChanged = self._tableView.viewSizeChanged + self.activated = self._tableView.activated + self.viewChanged = self._tableView.viewChanged + if not self._showHeader: + self._header.hide() + + @pyTTkSlot(int, int) + def viewMoveTo(self, x, y): + self._tableView.viewMoveTo(x, y) + def getViewOffsets(self): + return self._tableView.getViewOffsets() + def viewFullAreaSize(self) -> (int, int): + return self._tableView.viewFullAreaSize() + def viewDisplayedSize(self) -> (int, int): + return self._tableView.viewDisplayedSize() + def setAlignment(self, *args, **kwargs) : + self._tableView.setAlignment(*args, **kwargs) + self._header.setAlignment(*args, **kwargs) + def setHeader(self, *args, **kwargs) : + self._header.setHeader(*args, **kwargs) + def setColumnSize(self, *args, **kwargs) : + self._tableView.setColumnSize(*args, **kwargs) + self._header.setColumnSize(*args, **kwargs) + def setColumnColors(self, *args, **kwargs): + self._tableView.setColumnColors(*args, **kwargs) + def appendItem(self, *args, **kwargs) : + self._tableView.appendItem(*args, **kwargs) diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index cfcdd780..84e2b928 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -27,56 +27,44 @@ from TermTk.TTkCore.log import TTkLog from TermTk.TTkWidgets.widget import * from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkWidgets.scrollbar import TTkScrollBar +from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea +from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView -class _TTkTextEditView(TTkWidget): - __slots__ = ('_lines','_moveTo') +class _TTkTextEditView(TTkAbstractScrollView): + __slots__ = ('_lines') def __init__(self, *args, **kwargs): - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkTextEditView' ) self._lines = [] - self._moveTo = 0 @pyTTkSlot(str) def setText(self, text): self._lines = text.split('\n') self.update() + def viewFullAreaSize(self) -> (int, int): + return self.width(), len(self._lines) + + def viewDisplayedSize(self) -> (int, int): + return self.size() + def paintEvent(self): + _, oy = self.getViewOffsets() y = 0 - for t in self._lines[self._moveTo:]: + for t in self._lines[oy:]: self._canvas.drawText(pos=(0,y), text=t) y+=1 - def wheelEvent(self, evt): - delta = TTkCfg.scrollDelta - if evt.evt == TTkK.WHEEL_Up: - delta = -delta - self.scrollTo(self._moveTo + delta) - self.update() - return True - - @pyTTkSlot(int) - def scrollTo(self, to): - # TTkLog.debug(f"to:{to},h{self._height},size:{len(self._tableData)}") - max = len(self._lines) - self.height() - if to>max: to=max - if to<0: to=0 - self._moveTo = to - self.update() - -class TTkTextEdit(TTkWidget): - __slots__ = ('_textEdit', '_vscroller', '_hscroller') +class TTkTextEdit(TTkAbstractScrollArea): + __slots__ = ('_textEditView') def __init__(self, *args, **kwargs): - TTkWidget.__init__(self, *args, **kwargs) + super().__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkTextEdit' ) - self._textEdit = _TTkTextEditView() - self._vscroller = TTkScrollBar(orientation=TTkK.VERTICAL) - self._hscroller = TTkScrollBar(orientation=TTkK.HORIZONTAL) - self.setLayout(TTkGridLayout()) - self.layout().addWidget(self._textEdit,0,0) - self.layout().addWidget(self._vscroller,0,1) + self._textEditView = _TTkTextEditView() + self.setViewport(self._textEditView) + @pyTTkSlot(str) def setText(self, text): - self._textEdit.setText(text) + self._textEditView.setText(text) diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 460c0359..22e0d775 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -149,7 +149,7 @@ class TTkWidget: self.resize(w, h) self.move(x, y) - def getPadding(self): + def getPadding(self) -> (int, int, int, int) : return self._padt, self._padb, self._padl, self._padr def setPadding(self, top, bottom, left, right): @@ -161,16 +161,16 @@ class TTkWidget: self._padr = right self.update(repaint=True, updateLayout=True) - def mouseDoubleClickEvent(self, evt): return False - def mouseMoveEvent(self, evt): return False - def mouseDragEvent(self, evt): return False - def mousePressEvent(self, evt): return False - def mouseReleaseEvent(self, evt): return False - def wheelEvent(self, evt): return False - def enterEvent(self, evt): return False - def leaveEvent(self, evt): return False - def keyPressEvent(self, evt): return False - def keyReleaseEvent(self, evt): return False + def mouseDoubleClickEvent(self, evt) -> bool : return False + def mouseMoveEvent(self, evt) -> bool : return False + def mouseDragEvent(self, evt) -> bool : return False + def mousePressEvent(self, evt) -> bool : return False + def mouseReleaseEvent(self, evt) -> bool : return False + def wheelEvent(self, evt) -> bool : return False + def enterEvent(self, evt) -> bool : return False + def leaveEvent(self, evt) -> bool : return False + def keyPressEvent(self, evt) -> bool : return False + def keyReleaseEvent(self, evt) -> bool : return False @staticmethod def _mouseEventLayoutHandle(evt, layout): diff --git a/TermTk/__init__.py b/TermTk/__init__.py index 904632ad..c9621139 100644 --- a/TermTk/__init__.py +++ b/TermTk/__init__.py @@ -2,4 +2,5 @@ from .TTkCore import * from .TTkGui import * from .TTkLayouts import * from .TTkWidgets import * -from .TTkTestWidgets import * \ No newline at end of file +from .TTkTestWidgets import * +from .TTkAbstract import * \ No newline at end of file diff --git a/docs/BUGS.md b/docs/BUGS.md index aa6a0274..9023f53d 100644 --- a/docs/BUGS.md +++ b/docs/BUGS.md @@ -6,3 +6,4 @@ - [Minor] Window does not reshape to the max/min layout during initialization - [Minor] test.ui.009.py - Starting layout size is wrong - [Major] ~~[Canvas Paint] The Top Left corner start with the bottom right corner color~~ +- [Minor] DrawText, handle "tabs" diff --git a/docs/TODO.md b/docs/TODO.md index aea778be..79ca13d6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -49,6 +49,11 @@ - [ ] Add addLayout method - Nested layouts - [x] Add Grid Layout +### AbstractScrollArea +- [ ] Implement something that mimic the QAbstactScrollArea + https://doc.qt.io/qt-5/qabstractscrollarea.html + https://doc.qt.io/qt-5/qscrollarea.html + ### Overlay widget - [ ] Rewrite the Handling (ttk.py) It would be nice to have it as child outside the layour