diff --git a/README.md b/README.md index b942c325..5285f7b1 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,9 @@ cprofilev -f profiler.txt - [Textual](https://github.com/Textualize/textual) - TUI (Text User Interface) framework for Python inspired by modern web development - [Rich](https://github.com/Textualize/rich) - Python library for rich text and beautiful formatting in the terminal - [PyCuT](https://github.com/ceccopierangiolieugenio/pyCuT) - terminal graphic library loosely based on QT api (my previous failed attempt) + - [pyTooling.TerminalUI](https://github.com/pyTooling/pyTooling.TerminalUI) - A set of helpers to implement a text user interface (TUI) in a terminal. - Non Python - [Turbo Vision](http://tvision.sourceforge.net) - [ncurses](https://en.wikipedia.org/wiki/Ncurses) - - [tui.el](https://github.com/ebpa/tui.el) - An experimental text-based UI framework for Emacs modeled after React \ No newline at end of file + - [tui.el](https://github.com/ebpa/tui.el) - An experimental text-based UI framework for Emacs modeled after React diff --git a/TermTk/TTkCore/TTkTerm/term.py b/TermTk/TTkCore/TTkTerm/term.py index 534ca78a..42b418ad 100644 --- a/TermTk/TTkCore/TTkTerm/term.py +++ b/TermTk/TTkCore/TTkTerm/term.py @@ -29,9 +29,6 @@ except Exception as e: print(f'ERROR: {e}') exit(1) -from tkinter import OFF -from turtle import onclick - class TTkTerm(): CLEAR = "\033[2J\033[0;0f" # Clear screen and set cursor to position 0,0 ALT_SCREEN = "\033[?1049h" #* Switch to alternate screen diff --git a/TermTk/TTkCore/string.py b/TermTk/TTkCore/string.py index 7010092c..eb6d23d4 100644 --- a/TermTk/TTkCore/string.py +++ b/TermTk/TTkCore/string.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from hashlib import new import re from TermTk.TTkCore.constant import TTkK @@ -31,11 +30,40 @@ from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor, _TTkColor class TTkString(): + ''' TermTk String Helper + + The TTkString constructor creates a terminal String object. + + :param text: text of the string, defaults to "" + :type text: str, optional + :param color: the color of the string, defaults to :class:`~TermTk.TTkCore.color.TTkColor.RST` + :type color: :class:`~TermTk.TTkCore.color.TTkColor`, optional + + Example: + + .. code:: python + + # No params Constructor + str1 = TTkString() + "test 1" + str2 = TTkString() + TTkColor.BOLD + "test 2" + + # Indexed params constructor + str3 = TTkString("test 3") + str4 = TTkString("test 4", TTkColor.ITALIC) + + # Named params constructor + str5 = TTkString(text="test 5") + str6 = TTkString(text="test 6", color=TTkColor.ITALIC+TTkColor.bg("000044")) + + # Combination of constructors (Highly Unrecommended) + str7 = TTkString("test 7", color=TTkColor.fg('#FF0000')) + ''' __slots__ = ('_text','_colors','_baseColor') - def __init__(self): - self._baseColor = TTkColor.RST - self._text = "" - self._colors = [] + + def __init__(self, text="", color=TTkColor.RST): + self._text = text + self._baseColor = color + self._colors = [self._baseColor]*len(self._text) def __len__(self): return len(self._text) @@ -84,9 +112,11 @@ class TTkString(): def __ge__(self, other): return self._text >= other._text def toAscii(self): + ''' Return the ascii representation of the string ''' return self._text def toAansi(self): + ''' Return the ansii (terminal colors/events) representation of the string ''' out = "" color = None for ch, col in zip(self._text, self._colors): @@ -97,6 +127,15 @@ class TTkString(): return out def align(self, width=None, color=TTkColor.RST, alignment=TTkK.NONE): + ''' Align the string + + :param width: the new width + :type width: int, optional + :param color: the color of the padding, defaults to :class:`~TermTk.TTkCore.color.TTkColor.RST` + :type color: :class:`~TermTk.TTkCore.color.TTkColor`, optional + :param alignment: the alignment of the text to the full width :class:`~TermTk.TTkCore.constant.TTkConstant.Alignment.NONE` + :type alignment: :class:`~TermTk.TTkCore.constant.TTkConstant.Alignment`, optional + ''' lentxt = len(self._text) if not width or width == lentxt: return self @@ -126,6 +165,17 @@ class TTkString(): return ret def replace(self, *args, **kwargs): + ''' **replace** (*old*, *new*, *count*) + + Replace "**old**" match with "**new**" string for "**count**" times + + :param old: the match to be placed + :type old: str + :param new: the match to replace + :type new: str, optional + :param count: the number of occurrences + :type count: int, optional + ''' old = args[0] new = args[1] count = args[2] if len(args)==3 else 0x1000000 @@ -160,7 +210,20 @@ class TTkString(): return ret - def setColor(self, color, match=None, posFrom=0, posTo=0): + def setColor(self, color, match=None, posFrom=None, posTo=None): + ''' Set the color of the entire string or a slice of it + + If only the color is specified, the entore sting is colorized + + :param color: the color to be used, defaults to :class:`~TermTk.TTkCore.color.TTkColor.RST` + :type color: :class:`~TermTk.TTkCore.color.TTkColor` + :param match: the match to colorize + :type match: str, optional + :param posFrom: the initial position of the color + :type posFrom: int, optional + :param posTo: the final position of the color + :type posTo: int, optional + ''' ret = TTkString() ret._text += self._text if match: @@ -171,21 +234,37 @@ class TTkString(): start = pos+lenMatch for i in range(pos, pos+lenMatch): ret._colors[i] = color + elif posFrom == posTo == None: + ret._colors = [color]*len(self._text) elif posFrom < posTo: ret._colors += self._colors for i in range(posFrom, posTo): ret._colors[i] = color else: - ret._colors = [color]*len(self._text) + ret._colors += self._colors return ret def substring(self, fr=None, to=None): + ''' Return the substring + + :param fr: the starting of the slice, defaults to 0 + :type fr: int, optional + :param to: the ending of the slice, defaults to the end of the string + :type to: int, optional + ''' ret = TTkString() ret._text = self._text[fr:to] ret._colors = self._colors[fr:to] return ret def split(self, separator ): + ''' Split the string using a separator + + .. note:: Only a one char separator is currently supported + + :param separator: the "**char**" separator to be used + :type separator: str + ''' ret = [] pos = 0 if len(separator)==1: @@ -203,7 +282,37 @@ class TTkString(): return (self._text,self._colors) def search(self, regexp, ignoreCase=False): + ''' Return the **re.match** of the **regexp** + + :param regexp: the regular expression to be matched + :type regexp: str + :param ignoreCase: Ignore case, defaults to **False** + :type ignoreCase: bool + ''' return re.search(regexp, self._text, re.IGNORECASE if ignoreCase else 0) def findall(self, regexp, ignoreCase=False): - return re.findall(regexp, self._text, re.IGNORECASE if ignoreCase else 0) \ No newline at end of file + ''' FindAll the **regexp** matches in the string + + :param regexp: the regular expression to be matched + :type regexp: str + :param ignoreCase: Ignore case, defaults to **False** + :type ignoreCase: bool + ''' + return re.findall(regexp, self._text, re.IGNORECASE if ignoreCase else 0) + + def getIndexes(self, char): + return [i for i,c in enumerate(self._text) if c==char] + + def join(self, strings): + ''' Join the input strings using the current as separator + + :param strings: the list of strings to be joined + :type strings: list + ''' + if not strings: + return TTkString() + ret = TTkString() + strings[0] + for s in strings[1:]: + ret += self + s + return ret diff --git a/TermTk/TTkWidgets/scrollbar.py b/TermTk/TTkWidgets/scrollbar.py index b619c7fe..b82e236d 100644 --- a/TermTk/TTkWidgets/scrollbar.py +++ b/TermTk/TTkWidgets/scrollbar.py @@ -22,8 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import math - from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index 15aef22d..079fe8d1 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import os from TermTk.TTkCore.log import TTkLog from TermTk.TTkWidgets.widget import * from TermTk.TTkLayouts.gridlayout import TTkGridLayout @@ -32,35 +31,45 @@ from TermTk.TTkWidgets.scrollbar import TTkScrollBar from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView - class _TTkTextEditView(TTkAbstractScrollView): - __slots__ = ('_lines', '_hsize') + __slots__ = ( + '_lines', '_hsize', + '_cursorPos', '_cursorParams', '_selectionFrom', '_selectionTo', + '_replace', + '_readOnly' + ) def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkTextEditView' ) + self._readOnly = True self._hsize = 0 - self._lines = [] + self._lines = [''] + self._replace = False + self._cursorPos = (0,0) + self._selectionFrom = (0,0) + self._selectionTo = (0,0) + self._cursorParams = None + self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) - @pyTTkSlot(str) - def setText(self, text): - self._lines = [line for line in text.split('\n')] - self.viewMoveTo(0, 0) - self._updateSize() - self.viewChanged.emit() - self.update() + + def isReadOnly(self) -> bool : + return self._readOnly + + def setReadOnly(self, ro): + self._readOnly = ro @pyTTkSlot(str) - def setLines(self, lines): - self._lines = lines + def setText(self, text): + if type(text) == str: + text = TTkString() + text + self._lines = text.split('\n') self.viewMoveTo(0, 0) self._updateSize() self.viewChanged.emit() self.update() def _updateSize(self): - self._hsize = 0 - for l in self._lines: - self._hsize = max(self._hsize, len(l)) + self._hsize = max( [ len(l) for l in self._lines ] ) def viewFullAreaSize(self) -> (int, int): return self._hsize, len(self._lines) @@ -68,17 +77,257 @@ class _TTkTextEditView(TTkAbstractScrollView): def viewDisplayedSize(self) -> (int, int): return self.size() + def _pushCursor(self): + if self._readOnly or not self.hasFocus(): + return + ox, oy = self.getViewOffsets() + + x = self._cursorPos[0]-ox + y = self._cursorPos[1]-oy + + if x > self.width() or y>=self.height() or \ + x<0 or y<0: + TTkHelper.hideCursor() + return + + # Avoid the show/move cursor to be called again if in the same position + if self._cursorParams and \ + self._cursorParams['pos'] == (x,y) and \ + self._cursorParams['replace'] == self._replace: + return + + self._cursorParams = {'pos': (x,y), 'replace': self._replace} + TTkHelper.moveCursor(self,x,y) + if self._replace: + TTkHelper.showCursor(TTkK.Cursor_Blinking_Block) + else: + TTkHelper.showCursor(TTkK.Cursor_Blinking_Bar) + self.update() + + def _setCursorPos(self, x, y): + y = max(0,min(y,len(self._lines)-1)) + # The replace cursor need to be aligned to the char + # The Insert cursor must be placed between chars + if self._replace: + x = max(0,min(x,len(self._lines[y])-1)) + else: + x = max(0,min(x,len(self._lines[y]))) + self._cursorPos = (x,y) + self._selectionFrom = (x,y) + self._selectionTo = (x,y) + self._scrolToInclude(x,y) + + def _scrolToInclude(self, x, y): + # Scroll the area (if required) to include the position x,y + _,_,w,h = self.geometry() + offx, offy = self.getViewOffsets() + offx = max(min(offx, x),x-w) + offy = max(min(offy, y),y-h+1) + self.viewMoveTo(offx, offy) + + def _selection(self) -> bool: + return self._selectionFrom != self._selectionTo + + def _eraseSelection(self): + if self._selection(): # delete selection + sx1,sy1 = self._selectionFrom + sx2,sy2 = self._selectionTo + self._cursorPos = self._selectionFrom + self._selectionTo = self._selectionFrom + self._lines[sy1] = self._lines[sy1].substring(to=sx1) + \ + self._lines[sy2].substring(fr=sx2) + self._lines = self._lines[:sy1+1] + self._lines[sy2+1:] + + def mousePressEvent(self, evt) -> bool: + if self._readOnly: + return super().mousePressEvent(evt) + ox, oy = self.getViewOffsets() + y = max(0,min(evt.y + oy,len(self._lines))) + x = max(0,min(evt.x + ox,len(self._lines[y]))) + self._cursorPos = (x,y) + self._selectionFrom = (x,y) + self._selectionTo = (x,y) + # TTkLog.debug(f"{self._cursorPos=}") + self.update() + return True + + def mouseDragEvent(self, evt) -> bool: + if self._readOnly: + return super().mouseDragEvent(evt) + ox, oy = self.getViewOffsets() + y = max(0,min(evt.y + oy,len(self._lines))) + x = max(0,min(evt.x + ox,len(self._lines[y]))) + cx = self._cursorPos[0] + cy = self._cursorPos[1] + + if y < cy: # Mouse Dragged above the cursor + self._selectionFrom = ( x, y ) + self._selectionTo = ( cx, cy ) + elif y > cy: # Mouse Dragged below the cursor + self._selectionFrom = ( cx, cy ) + self._selectionTo = ( x, y ) + else: # Mouse on the same line of the cursor + self._selectionFrom = ( min(cx,x), y ) + self._selectionTo = ( max(cx,x), y ) + + self._scrolToInclude(x,y) + + self.update() + return True + + def mouseDoubleClickEvent(self, evt) -> bool: + if self._readOnly: + return super().mouseDoubleClickEvent(evt) + ox, oy = self.getViewOffsets() + y = max(0,min(evt.y + oy,len(self._lines))) + x = max(0,min(evt.x + ox,len(self._lines[y]))) + self._cursorPos = (x,y) + + before = self._lines[y].substring(to=x) + after = self._lines[y].substring(fr=x) + + xFrom = len(before) + xTo = len(before) + + selectRE = '[a-zA-Z0-9:,./]*' + + if m := before.search(selectRE+'$'): + xFrom -= len(m.group(0)) + if m := after.search('^'+selectRE): + xTo += len(m.group(0)) + + self._selectionFrom = ( xFrom, y ) + self._selectionTo = ( xTo, y ) + + self.update() + return True + + def mouseTapEvent(self, evt) -> bool: + if self._readOnly: + return super().mouseTapEvent(evt) + ox, oy = self.getViewOffsets() + y = max(0,min(evt.y + oy,len(self._lines))) + x = max(0,min(evt.x + ox,len(self._lines[y]))) + self._cursorPos = (x,y) + + self._selectionFrom = ( 0 , y ) + self._selectionTo = ( len(self._lines[y]) , y ) + + self.update() + return True + + def mouseReleaseEvent(self, evt) -> bool: + if self._readOnly: + return super().mouseReleaseEvent(evt) + ox, oy = self.getViewOffsets() + y = max(0,min(evt.y + oy,len(self._lines))) + x = max(0,min(evt.x + ox,len(self._lines[y]))) + self._cursorPos = (x,y) + self.update() + return True + + def keyEvent(self, evt): + if self._readOnly: + return super().keyEvent(evt) + if evt.type == TTkK.SpecialKey: + _,_,w,h = self.geometry() + + cx = self._cursorPos[0] + cy = self._cursorPos[1] + # Don't Handle the special tab key, for now + if evt.key == TTkK.Key_Tab: + return False + if evt.key == TTkK.Key_Up: self._setCursorPos(cx , cy-1) + elif evt.key == TTkK.Key_Down: self._setCursorPos(cx , cy+1) + elif evt.key == TTkK.Key_Left: self._setCursorPos(cx-1, cy ) + elif evt.key == TTkK.Key_Right: self._setCursorPos(cx+1, cy ) + elif evt.key == TTkK.Key_End: self._setCursorPos(len(self._lines[cy]) , cy ) + elif evt.key == TTkK.Key_Home: self._setCursorPos(0 , cy ) + elif evt.key == TTkK.Key_PageUp: self._setCursorPos(cx , cy - h) + elif evt.key == TTkK.Key_PageDown: self._setCursorPos(cx , cy + h) + elif evt.key == TTkK.Key_Insert: + self._replace = not self._replace + self._setCursorPos(cx , cy) + elif evt.key == TTkK.Key_Delete: + if self._selection(): + self._eraseSelection() + else: + l = self._lines[cy] + if cx < len(l): # Erase next caracter on the same line + self._lines[cy] = l.substring(to=cx) + l.substring(fr=cx+1) + elif (cy+1) 0: # Erase the previous character + cx-=1 + self._lines[cy] = l.substring(to=cx) + l.substring(fr=cx+1) + self._setCursorPos(cx, cy) + elif cy>0: # Beginning of the line, remove "\n" and merge with the previous line + cx = len(self._lines[cy-1]) + self._lines[cy-1] += l + self._lines = self._lines[:cy] + self._lines[cy+1:] + self._setCursorPos(cx, cy-1) + elif evt.key == TTkK.Key_Enter: + self._eraseSelection() + l = self._lines[cy] + self._lines[cy] = l.substring(to=cx) + self._lines = self._lines[:cy+1] + [l.substring(fr=cx)] + self._lines[cy+1:] + self._setCursorPos(0,cy+1) + self.update() + return True + else: # Input char + self._eraseSelection() + cpx,cpy = self._cursorPos + l = self._lines[cpy] + self._lines[cpy] = l.substring(to=cpx) + evt.key + l.substring(fr=cpx) + self._setCursorPos(cpx+1,cpy) + self.update() + return True + + return super().keyEvent(evt) + + def focusInEvent(self): + self.update() + + def focusOutEvent(self): + TTkHelper.hideCursor() + def paintEvent(self): ox, oy = self.getViewOffsets() - for y, t in enumerate(self._lines[oy:]): + if self.hasFocus(): + color = TTkCfg.theme.lineEditTextColorFocus + selectColor = TTkCfg.theme.lineEditTextColorSelected + else: + color = TTkCfg.theme.lineEditTextColor + selectColor = TTkCfg.theme.lineEditTextColorSelected + + h = self.height() + for y, t in enumerate(self._lines[oy:oy+h]): + if self._selectionFrom[1] <= y+oy <= self._selectionTo[1]: + pf = 0 if y+oy > self._selectionFrom[1] else self._selectionFrom[0] + pt = len(t) if y+oy < self._selectionTo[1] else self._selectionTo[0] + t = t.setColor(color=selectColor, posFrom=pf, posTo=pt ) self._canvas.drawText(pos=(-ox,y), text=t) + self._pushCursor() class TTkTextEdit(TTkAbstractScrollArea): - __slots__ = ('_textEditView', 'setText', 'setColoredLines') + __slots__ = ( + '_textEditView', + # Forwarded Methods + 'setText', 'isReadOnly', 'setReadOnly' + ) def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) self._name = kwargs.get('name' , 'TTkTextEdit' ) self._textEditView = _TTkTextEditView() self.setViewport(self._textEditView) self.setText = self._textEditView.setText - self.setLines = self._textEditView.setLines + self.isReadOnly = self._textEditView.isReadOnly + self.setReadOnly = self._textEditView.setReadOnly + diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 9793c7f5..c50156dd 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -22,8 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import time - from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -55,7 +53,7 @@ class TTkWidget(TMouseEvents,TKeyEvents): The TTkWidget class is the base class of all user interface objects - :param str name: the name of the widget, defaults to "" + :param name: the name of the widget, defaults to "" :type name: str, optional :param parent: the parent widget, defaults to None :type parent: :class:`TTkWidget`, optional diff --git a/demo/demo.py b/demo/demo.py index 38c1d0d7..8fc6aa69 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -46,6 +46,7 @@ from showcase.colorpicker import demoColorPicker from showcase.tree import demoTree from showcase.fancytable import demoFancyTable from showcase.fancytree import demoFancyTree +from showcase.textedit import demoTextEdit words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] def getWord(): @@ -149,6 +150,7 @@ def demoShowcase(root=None, border=True): listMenu.addItem(f"Widgets") tabWidgets = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) tabWidgets.addTab(demoFormWidgets(), " Form Test ") + tabWidgets.addTab(demoTextEdit(), " Text Edit ") tabWidgets.addTab(demoList(), " List Test ") tabWidgets.addTab(demoTree(), " Tree Test") tabWidgets.addTab(demoTab(), " Tab Test ") @@ -156,6 +158,7 @@ def demoShowcase(root=None, border=True): tabWidgets.addTab(demoFancyTree(), " Old Tree ") tabWidgetsSources = [ 'showcase/formwidgets.py', + 'showcase/textedit.py', 'showcase/list.py', 'showcase/tree.py', 'showcase/tab.py', diff --git a/demo/showcase/textedit.py b/demo/showcase/textedit.py new file mode 100755 index 00000000..50fa944f --- /dev/null +++ b/demo/showcase/textedit.py @@ -0,0 +1,69 @@ +#!/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. + +import os +import sys +import random +import argparse + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +def randColor(): + return [ + ttk.TTkColor.RST, + ttk.TTkColor.fg('#FFFF00'), + ttk.TTkColor.fg('#00FFFF'), + ttk.TTkColor.fg('#FF00FF') + ][random.randint(0,3)] +def getWord(): + return ttk.TTkString(random.choice(words),randColor()) +def getSentence(a,b,i): + return ttk.TTkString(" ").join([f"{i} "]+[getWord() for i in range(0,random.randint(a,b))]) + +def demoTextEdit(root=None): + te = ttk.TTkTextEdit(parent=root) + te.setReadOnly(False) + te.setText(ttk.TTkString('\n').join([ getSentence(5,25,i) for i in range(50)])) + return te + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen', action='store_true') + args = parser.parse_args() + + ttk.TTkLog.use_default_file_logging() + + root = ttk.TTk() + if args.f: + rootTree = root + root.setLayout(ttk.TTkGridLayout()) + else: + rootTree = ttk.TTkWindow(parent=root,pos = (0,0), size=(70,40), title="Test Text Edit", layout=ttk.TTkGridLayout(), border=True) + demoTextEdit(rootTree) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/docs/MDNotes/TODO.md b/docs/MDNotes/TODO.md index b16ef579..7fdd9545 100644 --- a/docs/MDNotes/TODO.md +++ b/docs/MDNotes/TODO.md @@ -11,6 +11,9 @@ - [ ] Support Hyperlink: (gnome-terminal) https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda - [x] Process child events before parent +- [ ] Rewrite the way focus is handled + https://doc.qt.io/qt-5/focus.html + Ref: https://github.com/ceccopierangiolieugenio/scripts/blob/master/Programming/python/pyqt5/textedit.001.py ## Terminal Helper - [ ] Events