diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 56521f2b..0098da68 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -22,6 +22,8 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import unicodedata + from TermTk.TTkCore.TTkTerm.term import TTkTerm from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -29,7 +31,6 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString - class TTkCanvas: ''' Init the Canvas object @@ -153,24 +154,7 @@ class TTkCanvas: color = colors[i] align = alignments[i] if w > 0: - line = "" - lentxt = len(txt) - if lentxt > w: - line += txt[0:w] - else: - pad = w-lentxt - if align in [TTkK.NONE,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.drawText(pos=(x,y), text=line, color=color) + self.drawTTkString(pos=(x,y), text=txt, width=w, color=color, alignment=align) x += w + 1 def drawChar(self, pos, char, color=TTkColor.RST): @@ -178,6 +162,46 @@ class TTkCanvas: x,y = pos self._set(y, x, char, color) + + def drawTTkString(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): + ''' + NOTE: + drawText is one of the most abused functions, + there is some redundant code here in order to reduce the footprint + ''' + if not self._visible: return + + # Check the size and bounds + x,y = pos + if y<0 or y>=self._height : return + + lentxt = text.termWidth() + if width is None or width<0: + width = lentxt + + if x+width<0 or x>=self._width : return + + text = text.align(width=width, alignment=alignment, color=color) + txt, colors = text.tab2spaces().getData() + if forceColor: + colors=[color]*len(colors) + a,b = max(0,-x), min(len(txt),self._width-x) + for i in range(a,b): + #self._set(y, x+i, txt[i-x], colors[i-x]) + self._data[y][x+i] = txt[i] + if colors[i] == TTkColor.RST != color: + self._colors[y][x+i] = color.mod(x+i,y) + else: + self._colors[y][x+i] = colors[i].mod(x+i,y) + # Check the full wide chars on the edge of the two canvasses + if self._data[y][x+a] == '': + self._data[y][x+a] = TTkCfg.theme.unicodeWideOverflowCh[0] + self._colors[y][x+a] = TTkCfg.theme.unicodeWideOverflowColor + if ( len(ch:=self._data[y][x+b-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y][x+b-1] = TTkCfg.theme.unicodeWideOverflowCh[1] + self._colors[y][x+b-1] = TTkCfg.theme.unicodeWideOverflowColor + def drawText(self, pos, text, width=None, color=TTkColor.RST, alignment=TTkK.NONE, forceColor=False): ''' NOTE: @@ -185,6 +209,8 @@ class TTkCanvas: there is some redundant code here in order to reduce the footprint ''' if not self._visible: return + if isinstance(text, TTkString): + return self.drawTTkString(pos, text, width, color, alignment, forceColor) # Check the size and bounds x,y = pos @@ -196,39 +222,26 @@ class TTkCanvas: if x+width<0 or x>=self._width : return - if isinstance(text, TTkString): - text = text.align(width=width, alignment=alignment, color=color) - txt, colors = text.tab2spaces().getData() - if forceColor: - colors=[color]*len(colors) - for i in range(max(0,-x), min(len(txt),self._width-x)): - #self._set(y, x+i, txt[i-x], colors[i-x]) - self._data[y][x+i] = txt[i] - if colors[i] == TTkColor.RST != color: - self._colors[y][x+i] = color.mod(x+i,y) - else: - self._colors[y][x+i] = colors[i].mod(x+i,y) + text = text.replace('\t',' ') + if lentxt < width: + pad = width-lentxt + if alignment in [TTkK.NONE, TTkK.LEFT_ALIGN]: + text = text + " "*pad + elif alignment == TTkK.RIGHT_ALIGN: + text = " "*pad + text + elif alignment == TTkK.CENTER_ALIGN: + p1 = pad//2 + p2 = pad-p1 + text = " "*p1 + text+" "*p2 + elif alignment == TTkK.JUSTIFY: + # TODO: Text Justification + text = text + " "*pad else: - text = text.replace('\t',' ') - if lentxt < width: - pad = width-lentxt - if alignment in [TTkK.NONE, TTkK.LEFT_ALIGN]: - text = text + " "*pad - elif alignment == TTkK.RIGHT_ALIGN: - text = " "*pad + text - elif alignment == TTkK.CENTER_ALIGN: - p1 = pad//2 - p2 = pad-p1 - text = " "*p1 + text+" "*p2 - elif alignment == TTkK.JUSTIFY: - # TODO: Text Justification - text = text + " "*pad - else: - text=text[:width] + text=text[:width] - arr = list(text) - for i in range(0, min(len(arr),self._width-x)): - self._set(y, x+i, arr[i], color) + arr = list(text) + for i in range(0, min(len(arr),self._width-x)): + self._set(y, x+i, arr[i], color) def drawBoxTitle(self, pos, size, text, align=TTkK.CENTER_ALIGN, color=TTkColor.RST, colorText=TTkColor.RST, grid=0): if not self._visible: return @@ -555,7 +568,7 @@ class TTkCanvas: self._set(y,x+width-1, mb[5], color) off = 0 for i in shortcuts: - self._set(y,x+i+off, text[i], shortcutColor) + self._set(y,x+i+off, text.charAt(i), shortcutColor) def execPaint(self, winw, winh): pass @@ -599,8 +612,26 @@ class TTkCanvas: hslice = h if y+h < by+bh else by+bh-y for iy in range(yoffset,hslice): - self._data[y+iy][x+xoffset:x+wslice] = canvas._data[iy][xoffset:wslice] - self._colors[y+iy][x+xoffset:x+wslice] = canvas._colors[iy][xoffset:wslice] + a, b = x+xoffset, x+wslice + self._data[y+iy][a:b] = canvas._data[iy][xoffset:wslice] + self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice] + + # Check the full wide chars on the edge of the two canvasses + if self._data[y+iy][a]=='': + self._data[y+iy][a] = TTkCfg.theme.unicodeWideOverflowCh[0] + self._colors[y+iy][a] = TTkCfg.theme.unicodeWideOverflowColor + if ( len(ch:=self._data[y+iy][b-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowCh[1] + self._colors[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowColor + if ( a and len(ch:=self._data[y+iy][a-1])==1 + and unicodedata.east_asian_width(ch)=='W'): + self._data[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowCh[1] + self._colors[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowColor + if ( b other if type(other) is str else self._text > other._text def __ge__(self, other): return self._text >= other if type(other) is str else self._text >= other._text + def isdigit(self): + return self._text.isdigit() + + def lstrip(self, ch): + ret = TTkString() + ret._text = self._text.lstrip(ch) + ret._colors = self._colors[-len(ret._text):] + return ret + def charAt(self, pos): return self._text[pos] def setCharAt(self, pos, char): self._text = self._text[:pos]+char+self._text[pos+1:] + self._checkWidth() return self def colorAt(self, pos): @@ -160,15 +181,16 @@ class TTkString(): ret._colors += self._colors[0:pos] for s in slices[1:]: c = self._colors[pos] - lentxt = len(ret._text) + lentxt = ret.termWidth() spaces = tabSpaces - (lentxt+tabSpaces)%tabSpaces ret._text += " "*spaces + s ret._colors += [c]*spaces + self._colors[pos+1:pos+1+len(s)] + ret._checkWidth() pos+=len(s)+1 return ret def tabCharPos(self, pos, tabSpaces=4, alignTabRight=False): - '''Return the char position in the string from the position in its representation with the tab solved + '''Return the char position in the string from the position in its representation with the tab and variable char sizes are solved i.e. @@ -176,12 +198,14 @@ class TTkString(): pos X = 11 tab2Spaces |----------|---------------------| - Tabs |--| | |-| |-| | - _text Lorem ipsum dolor sit amet, - chars .....t .....t .....t ...t..... - ret x = 8 (tab is a char) + Tabs |-| | |-| |-| | + _text L😁rem ipsum dolor sit amet, + chars .. ...t .....t .....t ...t..... + ret x = 7 (tab is a char) + ''' - if not self._hasTab: return pos + if not self._hasTab and not self._hasSpecialWidth: return pos + if self._hasSpecialWidth: return self._tabCharPosWideChar(pos, tabSpaces, alignTabRight) slices = self._text.split("\t") postxt = 0 # position of the text lentxt = 0 # length of the text with resolved tabs @@ -202,6 +226,37 @@ class TTkString(): postxt += 1 return len(self._text) + def _tabCharPosWideChar(self, pos, tabSpaces=4, alignTabRight=False): + '''Return the char position in the string from the position in its representation with the tab and variable char sizes are solved + + i.e. + + :: + + pos X = 11 + tab2Spaces |----------|---------------------| + Tabs |-| | |-| |-| | + _text L😁rem ipsum dolor sit amet, + chars .. ...t .....t .....t ...t..... + ret x = 7 (tab is a char) + + ''' + # get pos in the slice: + dx = pos + pp = 0 + for i,ch in enumerate(self._text): + if ch=='\t': + pp += tabSpaces - (pp+tabSpaces)%tabSpaces + elif unicodedata.east_asian_width(ch) == 'W': + pp += 2 + elif unicodedata.category(ch) in ('Me','Mn'): + pass + else: + pp += 1 + if dx < pp: + return i + return len(self._text) + def toAscii(self): ''' Return the ascii representation of the string ''' return self._text @@ -227,7 +282,7 @@ class TTkString(): :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) + lentxt = self.termWidth() if not width or width == lentxt: return self ret = TTkString() @@ -249,11 +304,32 @@ class TTkString(): # TODO: Text Justification ret._text = self._text + " " *pad ret._colors = self._colors + [color]*pad + elif self._hasSpecialWidth: + # Trim the string to a fixed size taking care of the variable width unicode chars + rt = "" + sz = 0 + for ch in self._text: + if unicodedata.category(ch) in ('Me','Mn'): + rt += ch + continue + if sz == width: + ret._text = rt + ret._colors = self._colors[:len(rt)] + break + elif sz > width: + ret._text = rt[:-1]+TTkCfg.theme.unicodeWideOverflowCh[1] + ret._colors = self._colors[:len(ret._text)] + ret._colors[-1] = TTkCfg.theme.unicodeWideOverflowColor + break + rt += ch + sz += 2 if unicodedata.east_asian_width(ch) == 'W' else 1 else: + # Legacy, trim the string ret._text = self._text[:width] ret._colors = self._colors[:width] ret._hasTab = '\t' in ret._text + ret._checkWidth() return ret @@ -301,6 +377,7 @@ class TTkString(): ret._text = self._text.replace(*args, **kwargs) ret._hasTab = '\t' in ret._text + ret._checkWidth() return ret @@ -321,6 +398,7 @@ class TTkString(): ret = TTkString() ret._text += self._text ret._hasTab = self._hasTab + ret._hasSpecialWidth = self._hasSpecialWidth if match: ret._colors += self._colors start=0 @@ -359,6 +437,7 @@ class TTkString(): ret = TTkString() ret._text += self._text ret._hasTab = self._hasTab + ret._hasSpecialWidth = self._hasSpecialWidth if match: ret._colors += self._colors start=0 @@ -389,6 +468,7 @@ class TTkString(): ret._text = self._text[fr:to] ret._colors = self._colors[fr:to] ret._hasTab = '\t' in ret._text + ret._checkWidth() return ret def split(self, separator ): @@ -413,7 +493,10 @@ class TTkString(): return ret def getData(self): - return (self._text,self._colors) + if self._hasSpecialWidth: + return self._getDataW() + else: + return (tuple(self._text), self._colors) def search(self, regexp, ignoreCase=False): ''' Return the **re.match** of the **regexp** @@ -425,6 +508,9 @@ class TTkString(): ''' return re.search(regexp, self._text, re.IGNORECASE if ignoreCase else 0) + def find(self, *args, **kwargs): + return self._text.find(*args, **kwargs) + def findall(self, regexp, ignoreCase=False): ''' FindAll the **regexp** matches in the string @@ -450,3 +536,69 @@ class TTkString(): for s in strings[1:]: ret += self + s return ret + + # Zero/Half/Normal sized chars helpers: + @staticmethod + def _isSpecialWidthChar(ch): + return ( unicodedata.east_asian_width(ch) == 'W' or + unicodedata.category(ch) in ('Me','Mn') ) + + @staticmethod + def _getWidthText(txt): + return ( len(txt) + + sum(unicodedata.east_asian_width(ch) == 'W' for ch in txt) - + sum(unicodedata.category(ch) in ('Me','Mn') for ch in txt) ) + + @staticmethod + def _getLenTextWoZero(txt): + return ( len(txt) - + sum(unicodedata.category(ch) in ('Me','Mn') for ch in txt) ) + + def nextPos(self, pos): + pos += 1 + for i,ch in enumerate(self._text[pos:]): + if not unicodedata.category(ch) in ('Me','Mn'): + return pos+i + return len(self._text) + + def prevPos(self, pos): + # from TermTk.TTkCore.log import TTkLog + # TTkLog.debug(f"->{self._text[:pos]}<- {pos=}") + # TTkLog.debug(f"{str(reversed(self._text[:pos]))} {pos=}") + for i,ch in enumerate(reversed(self._text[:pos])): + # TTkLog.debug(f"{i}---> {ch} ") + if not unicodedata.category(ch) in ('Me','Mn'): + return pos-i-1 + return 0 + + def _checkWidth(self): + self._hasSpecialWidth = ( + any(unicodedata.east_asian_width(ch) == 'W' for ch in self._text) or + any(unicodedata.category(ch) in ('Me','Mn') for ch in self._text) ) + + def _termWidthW(self): + ''' String displayed length + + This value consider the displayed size (Zero, Half, Full) of each character. + ''' + return ( len(self._text) + + sum(unicodedata.east_asian_width(ch) == 'W' for ch in self._text) - + sum(unicodedata.category(ch) in ('Me','Mn') for ch in self._text) ) + + def _getDataW(self): + retTxt = [] + retCol = [] + for i,ch in enumerate(self._text): + if unicodedata.east_asian_width(ch) == 'W': + retTxt += (ch,'') + retCol += (self._colors[i],self._colors[i]) + elif unicodedata.category(ch) in ('Me','Mn'): + if retTxt: + retTxt[-1]+=ch + #else: + # retTxt = [f"{ch}"] + # retCol = [TTkColor.RST] + else: + retTxt.append(ch) + retCol.append(self._colors[i]) + return (retTxt, retCol) diff --git a/TermTk/TTkGui/textcursor.py b/TermTk/TTkGui/textcursor.py index 109f932e..7a98d9f8 100644 --- a/TermTk/TTkGui/textcursor.py +++ b/TermTk/TTkGui/textcursor.py @@ -283,12 +283,14 @@ class TTkTextCursor(): currPos = self.position().toNum() def moveRight(cID,p,_): if p.pos < len(self._document._dataLines[p.line]): - self.setPosition(p.line, p.pos+1, moveMode, cID=cID) + nextPos = self._document._dataLines[p.line].nextPos(p.pos) + self.setPosition(p.line, nextPos, moveMode, cID=cID) elif p.line < len(self._document._dataLines)-1: self.setPosition(p.line+1, 0, moveMode, cID=cID) def moveLeft(cID,p,_): if p.pos > 0: - self.setPosition(p.line, p.pos-1, moveMode, cID=cID) + prevPos = self._document._dataLines[p.line].prevPos(p.pos) + self.setPosition(p.line, prevPos, moveMode, cID=cID) elif p.line > 0: self.setPosition(p.line-1, len(self._document._dataLines[p.line-1]) , moveMode, cID=cID) def moveUpDown(offset): @@ -327,10 +329,13 @@ class TTkTextCursor(): # the newline is not replaced for p in self._properties: if not p.hasSelection(): - line = p.position.line + line = p.position.line pos = p.position.pos + lenWoZero = TTkString._getLenTextWoZero(text) size = len(self._document._dataLines[line]) - pos = min(size,pos+len(text)) + for _ in range(lenWoZero): + pos = self._document._dataLines[line].nextPos(pos) + pos = min(size,pos) p.anchor.set(line,pos) return self.insertText(text) @@ -447,7 +452,7 @@ class TTkTextCursor(): splitAfter = self._document._dataLines[line].substring(fr=pos) xFrom = pos xTo = pos - selectRE = '[a-zA-Z0-9:,./]*' + selectRE = '[^ \t\r\n\(\)\[\]\.\,\+\-\*\/]*' if m := splitBefore.search(selectRE+'$'): xFrom -= len(m.group(0)) if m := splitAfter.search('^'+selectRE): diff --git a/TermTk/TTkGui/textwrap.py b/TermTk/TTkGui/textwrap.py index af957f1e..21f3ded7 100644 --- a/TermTk/TTkGui/textwrap.py +++ b/TermTk/TTkGui/textwrap.py @@ -91,7 +91,7 @@ class TTkTextWrap(): return while len(l): fl = l.tab2spaces(self._tabSpaces) - if len(fl) <= w: + if fl.termWidth() <= w: self._lines.append((i,(fr,fr+len(l)+1))) l=[] else: @@ -113,7 +113,7 @@ class TTkTextWrap(): for i, (dt, (fr, to)) in enumerate(self._lines): if dt == line and fr <= pos <= to: l = self._textDocument._dataLines[dt].substring(fr,pos).tab2spaces(self._tabSpaces) - return len(l), i + return l.termWidth(), i return 0,0 def screenToDataPosition(self, x, y): @@ -135,5 +135,5 @@ class TTkTextWrap(): x = max(0,x) s = self._textDocument._dataLines[dt].substring(fr,to) x = s.tabCharPos(x, self._tabSpaces) - x = len(s.substring(0,x).tab2spaces(self._tabSpaces)) + x = s.substring(0,x).tab2spaces(self._tabSpaces).termWidth() return x, y diff --git a/TermTk/TTkTemplates/text.py b/TermTk/TTkTemplates/text.py index f1299c10..9636267d 100644 --- a/TermTk/TTkTemplates/text.py +++ b/TermTk/TTkTemplates/text.py @@ -22,10 +22,16 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from TermTk.TTkCore.string import TTkString + class TText(): #__slots__ = ('_text') def __init__(self, *args, **kwargs): - self._text = kwargs.get('text', "" ) + text = kwargs.get('text', TTkString() ) + if issubclass(type(text), TTkString): + self._text = text + else: + self._text = TTkString(text) def textUpdated(self, text): pass @@ -36,6 +42,9 @@ class TText(): @text.setter def text(self, text): if self.text != text: - self._text = text + if issubclass(type(text), TTkString): + self._text = text + else: + self._text = TTkString(text) self.textUpdated(text) diff --git a/TermTk/TTkTestWidgets/logviewer.py b/TermTk/TTkTestWidgets/logviewer.py index 4c63a333..5440d1da 100644 --- a/TermTk/TTkTestWidgets/logviewer.py +++ b/TermTk/TTkTestWidgets/logviewer.py @@ -26,6 +26,7 @@ import os from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkAbstract.abstractscrollarea import TTkAbstractScrollArea from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView @@ -35,7 +36,7 @@ class _TTkLogViewer(TTkAbstractScrollView): def __init__(self, *args, **kwargs): TTkAbstractScrollView.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkLogViewer' ) - self._messages = [""] + self._messages = [TTkString()] self._cwd = os.getcwd() self._follow = kwargs.get('follow' , False ) TTkLog.installMessageHandler(self.loggingCallback) @@ -46,7 +47,7 @@ class _TTkLogViewer(TTkAbstractScrollView): self.update() def viewFullAreaSize(self) -> (int, int): - w = max( len(m) for m in self._messages) + w = max( m.termWidth() for m in self._messages) h = len(self._messages) return w , h @@ -55,13 +56,13 @@ class _TTkLogViewer(TTkAbstractScrollView): def loggingCallback(self, mode, context, message): logType = "NONE" - if mode == TTkLog.InfoMsg: logType = "INFO " - elif mode == TTkLog.DebugMsg: logType = "DEBUG" - elif mode == TTkLog.ErrorMsg: logType = "ERROR" - elif mode == TTkLog.FatalMsg: logType = "FATAL" - elif mode == TTkLog.WarningMsg: logType = "WARNING " - elif mode == TTkLog.CriticalMsg: logType = "CRITICAL" - self._messages.append(f"{logType}: {context.file}:{context.line} {message}".replace(self._cwd,"_")) + if mode == TTkLog.InfoMsg: logType = TTkString("INFO " ,TTkColor.fg("#00ff00")) + elif mode == TTkLog.DebugMsg: logType = TTkString("DEBUG" ,TTkColor.fg("#00ffff")) + elif mode == TTkLog.ErrorMsg: logType = TTkString("ERROR" ,TTkColor.fg("#ff0000")) + elif mode == TTkLog.FatalMsg: logType = TTkString("FATAL" ,TTkColor.fg("#ff0000")) + elif mode == TTkLog.WarningMsg: logType = TTkString("WARNING ",TTkColor.fg("#ff0000")) + elif mode == TTkLog.CriticalMsg: logType = TTkString("CRITICAL",TTkColor.fg("#ff0000")) + self._messages.append(logType+TTkString(f": {context.file}:{context.line} {message}".replace(self._cwd,"_"))) offx, offy = self.getViewOffsets() _,h = self.size() if self._follow or offy == len(self._messages)-h-1: @@ -73,14 +74,8 @@ class _TTkLogViewer(TTkAbstractScrollView): def paintEvent(self): ox,oy = self.getViewOffsets() _,h = self.size() - for y, message in enumerate(self._messages[oy:]): - self._canvas.drawText(pos=(0,y),text=message[ox:]) - c = TTkColor.RST - if message.startswith("INFO ") : c = TTkColor.fg("#00ff00") - elif message.startswith("DEBUG") : c = TTkColor.fg("#00ffff") - elif message.startswith("ERROR") : c = TTkColor.fg("#ff0000") - elif message.startswith("FATAL") : c = TTkColor.fg("#ff0000") - self._canvas.drawText(pos=(-ox,y),text=message[:5], color=c) + for y, message in enumerate(self._messages[oy:oy+h]): + self._canvas.drawTTkString(pos=(-ox,y),text=message) class TTkLogViewer(TTkAbstractScrollArea): __slots__ = ('_logView') diff --git a/TermTk/TTkTestWidgets/testwidget.py b/TermTk/TTkTestWidgets/testwidget.py index 6d1a148f..feb5109c 100644 --- a/TermTk/TTkTestWidgets/testwidget.py +++ b/TermTk/TTkTestWidgets/testwidget.py @@ -33,14 +33,14 @@ from TermTk.TTkWidgets.frame import * class _TestContent(TTkWidget): def paintEvent(self): # TTkLog.debug(f"Test Paint - {self._name}") - y=0; self._canvas.drawText(pos=(-5,y),color=TTkColor.fg("#ff0000"),text=" Lorem ipsum dolor sit amet,") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#ff8800"),text="consectetur adipiscing elit,") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#ffff00"),text="sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#00ff00"),text="Ut enim ad minim veniam,") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#00ffff"),text="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#0088ff"),text="Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#0000ff"),text="Excepteur sint occaecat cupidatat non proident,") - y+=1; self._canvas.drawText(pos=(0,y),color=TTkColor.fg("#ff00ff"),text="sunt in culpa qui officia deserunt mollit anim id est laborum.") + y=0; self._canvas.drawText(pos=(-5,y),text=TTkString(color=TTkColor.fg("#ff0000") ,text=" L😎rem ipsum dolor sit amet,")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ff8800") ,text="consectetur adipiscing elit,")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ffff00") ,text="sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#00ff00") ,text="Ut enim ad minim veniam,")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#00ffff") ,text="quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#0088ff") ,text="Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#0000ff") ,text="Excepteur sint occaecat cupidatat non proident,")) + y+=1; self._canvas.drawText(pos=(0,y), text=TTkString(color=TTkColor.fg("#ff00ff") ,text="sunt in culpa qui officia deserunt mollit anim id est laborum.")) y+=1; self._canvas.drawGrid( pos=(0,y),size=(self._width,self._height-y), hlines=(2,5,7), vlines=(4,7,15,30), diff --git a/TermTk/TTkTheme/draw_ascii.py b/TermTk/TTkTheme/draw_ascii.py index 02f69393..8c6dfcc2 100644 --- a/TermTk/TTkTheme/draw_ascii.py +++ b/TermTk/TTkTheme/draw_ascii.py @@ -126,3 +126,5 @@ class TTkTheme(): 'X','X','X','X','X','X','X','X','X','X','X','X','X','X','X','X', 'X','X','X','X','X','X','X','X','X','X','X','X','X','X','X','X', 'X','X','X','X','X','X','X','X','X','X','X','X','X','X','X','X') + + unicodeWideOverflowCh = ('<','>') \ No newline at end of file diff --git a/TermTk/TTkTheme/draw_utf8.py b/TermTk/TTkTheme/draw_utf8.py index 0d0cfda6..c73789e2 100644 --- a/TermTk/TTkTheme/draw_utf8.py +++ b/TermTk/TTkTheme/draw_utf8.py @@ -223,3 +223,5 @@ class TTkTheme(): '⡅','⡍','⡕','⡝','⡥','⡭','⡵','⡽','⣅','⣍','⣕','⣝','⣥','⣭','⣵','⣽', '⡆','⡎','⡖','⡞','⡦','⡮','⡶','⡾','⣆','⣎','⣖','⣞','⣦','⣮','⣶','⣾', '⡇','⡏','⡗','⡟','⡧','⡯','⡷','⡿','⣇','⣏','⣗','⣟','⣧','⣯','⣷','⣿') + + unicodeWideOverflowCh = ('≼','≽') \ No newline at end of file diff --git a/TermTk/TTkTheme/theme.py b/TermTk/TTkTheme/theme.py index cf8da832..3477f612 100644 --- a/TermTk/TTkTheme/theme.py +++ b/TermTk/TTkTheme/theme.py @@ -51,6 +51,7 @@ class TTkTheme(): menuBar = draw_utf8.TTkTheme.menuBar tab = draw_utf8.TTkTheme.tab braille = draw_utf8.TTkTheme.braille + unicodeWideOverflowCh = draw_utf8.TTkTheme.unicodeWideOverflowCh fileNameColor = TTkColor.RST # Simil NerdTree purple '''Default to **TTkColor.RST # Simil NerdTree purple**''' @@ -82,6 +83,7 @@ class TTkTheme(): TTkTheme.menuBar = theme['draw'].TTkTheme.menuBar TTkTheme.tab = theme['draw'].TTkTheme.tab TTkTheme.braille = theme['draw'].TTkTheme.braille + TTkTheme.unicodeWideOverflowCh = theme['draw'].TTkTheme.unicodeWideOverflowCh TTkTheme.fileIcon = theme['file'].FileIcon TTkHelper.updateAll() @@ -236,4 +238,7 @@ class TTkTheme(): textEditLineNumberWrapcharColor = TTkColor.fg("#888888")+TTkColor.bg("#333333") '''Default to **TTkColor.fg("#aaaaaa")+TTkColor.bg("#333333")**''' textEditLineNumberSeparatorColor = TTkColor.fg("#444444") - '''Default to **TTkColor.fg("#444444")**''' \ No newline at end of file + '''Default to **TTkColor.fg("#444444")**''' + + unicodeWideOverflowColor = TTkColor.fg("#888888")+TTkColor.bg("#000088") + '''Default to **TTkColor.fg("#888888")**+**TTkColor.bg("#000088")**''' diff --git a/TermTk/TTkWidgets/Fancy/tableview.py b/TermTk/TTkWidgets/Fancy/tableview.py index a4f231a4..8f259aac 100644 --- a/TermTk/TTkWidgets/Fancy/tableview.py +++ b/TermTk/TTkWidgets/Fancy/tableview.py @@ -25,6 +25,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView @@ -35,7 +36,7 @@ class _TTkFancyTableViewHeader(TTkWidget): TTkWidget.__init__(self, *args, **kwargs) self._name = kwargs.get('name' , '_TTkFancyTableViewHeader' ) self._columns = kwargs.get('columns' , [-1] ) - self._header = [""]*len(self._columns) + self._header = [TTkString()]*len(self._columns) self._alignments = [TTkK.NONE]*len(self._columns) self._headerColor = kwargs.get('headerColor' , TTkColor.BOLD ) self.setMaximumHeight(1) @@ -49,11 +50,12 @@ class _TTkFancyTableViewHeader(TTkWidget): def setHeader(self, header): if len(header) != len(self._columns): return - self._header = header + self._header = [TTkString(i) if isinstance(i,str) else i if issubclass(type(i), TTkString) else TTkString() for i in header] + def setColumnSize(self, columns): self._columns = columns - self._header += [""]*(len(self._columns)-len(self._header)) + self._header += [TTkString()]*(len(self._columns)-len(self._header)) self._alignments = [TTkK.NONE]*len(self._columns) def paintEvent(self): @@ -198,7 +200,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): def appendItem(self, item, id=None): if len(item) != len(self._columns): return - textItem = [i if isinstance(i,str) else "" for i in item] + textItem = [TTkString(i) if isinstance(i,str) else i if issubclass(type(i), TTkString) else TTkString() for i in item] widgetItem = [i if isinstance(i,TTkWidget) else None for i in item] if id is not None: self._tableDataId.append(id) @@ -212,7 +214,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): def insertItem(self, index, item, id=None): if len(item) != len(self._columns): return# - textItem = [i if isinstance(i,str) else "" for i in item] + textItem = [TTkString(i) if isinstance(i,str) else i if issubclass(type(i), TTkString) else TTkString() for i in item] widgetItem = [i if isinstance(i,TTkWidget) else None for i in item] if id is not None: self._tableDataId.insert(index, id) diff --git a/TermTk/TTkWidgets/Fancy/treewidget.py b/TermTk/TTkWidgets/Fancy/treewidget.py index 8d10757a..95e93847 100644 --- a/TermTk/TTkWidgets/Fancy/treewidget.py +++ b/TermTk/TTkWidgets/Fancy/treewidget.py @@ -24,6 +24,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.checkbox import TTkCheckbox @@ -52,7 +53,7 @@ class _TTkDisplayedTreeItem(TTkWidget): self._name = kwargs.get('name' , '_TTkDisplayedTreeItem' ) self._depth = kwargs.get('depth' , 0 ) - self._text = kwargs.get('text' , "" ) + self._text = TTkString(kwargs.get('text' , "" )) self._id = kwargs.get('id' , 0 ) self._treeWidgetItem = kwargs.get('treeWidgetItem', None) self._isLeaf = self._treeWidgetItem.childIndicatorPolicy() == TTkK.DontShowIndicator diff --git a/TermTk/TTkWidgets/TTkModelView/treewidget.py b/TermTk/TTkWidgets/TTkModelView/treewidget.py index 51ce5336..7952e3f2 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -24,6 +24,7 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.string import TTkString from TermTk.TTkWidgets.TTkModelView.treewidgetitem import TTkTreeWidgetItem from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot @@ -250,9 +251,9 @@ class TTkTreeWidget(TTkAbstractScrollView): if _icon: _icon = ' '+_icon+' ' if _il==0: - _data.append(' '*_level+_icon+_child.data(_il)) + _data.append(TTkString(' '*_level+_icon+_child.data(_il))) else: - _data.append(_icon+_child.data(_il)) + _data.append(TTkString(_icon+_child.data(_il))) self._cache.append(TTkTreeWidget._Cache( item = _child, diff --git a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py index a2fa1f55..4425c1f0 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py @@ -24,6 +24,7 @@ from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel @@ -41,7 +42,8 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): # self.refreshData = pyTTkSignal(TTkTreeWidgetItem) super().__init__(*args, **kwargs) self._children = [] - self._data = args[0] if len(args)>0 and type(args[0])==list else [''] + data = args[0] if len(args)>0 and type(args[0])==list else [TTkString()] + self._data = [i if issubclass(type(i), TTkString) else TTkString(i) if isinstance(i,str) else TTkString() for i in data] self._alignment = [TTkK.LEFT_ALIGN]*len(self._data) self._parent = kwargs.get('parent', None) self._childIndicatorPolicy = kwargs.get('childIndicatorPolicy', TTkK.DontShowIndicatorWhenChildless) diff --git a/TermTk/TTkWidgets/combobox.py b/TermTk/TTkWidgets/combobox.py index 870e6a44..45779bde 100644 --- a/TermTk/TTkWidgets/combobox.py +++ b/TermTk/TTkWidgets/combobox.py @@ -27,6 +27,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.string import TTkString from TermTk.TTkLayouts.gridlayout import TTkGridLayout from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkWidgets.list_ import TTkList @@ -43,7 +44,6 @@ class TTkComboBox(TTkWidget): self.currentTextChanged = pyTTkSignal(str) self.editTextChanged = pyTTkSignal(str) TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , 'TTkComboBox' ) # self.checked = pyTTkSignal() self._lineEdit = TTkLineEdit(parent=self) self._list = kwargs.get('list', [] ) @@ -120,7 +120,7 @@ class TTkComboBox(TTkWidget): text = self._list[self._id] w = self.width() - self._canvas.drawText(pos=(1,0), text=text, width=w-2, alignment=self._textAlign, color=color) + self._canvas.drawTTkString(pos=(1,0), text=TTkString(text), width=w-2, alignment=self._textAlign, color=color) self._canvas.drawText(pos=(0,0), text="[", color=borderColor) if self._editable: self._canvas.drawText(pos=(w-3,0), text="[^]", color=borderColor) diff --git a/TermTk/TTkWidgets/lineedit.py b/TermTk/TTkWidgets/lineedit.py index e6b9e931..7420c972 100644 --- a/TermTk/TTkWidgets/lineedit.py +++ b/TermTk/TTkWidgets/lineedit.py @@ -25,10 +25,11 @@ import re from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal -from TermTk.TTkWidgets.widget import * +from TermTk.TTkWidgets.widget import TTkWidget ''' @@ -48,11 +49,10 @@ class TTkLineEdit(TTkWidget): self.textChanged = pyTTkSignal(str) self.textEdited = pyTTkSignal(str) TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , 'TTkLineEdit' ) self._inputType = kwargs.get('inputType' , TTkK.Input_Text ) - self._text = kwargs.get('text' , '' ) + self._text = TTkString(kwargs.get('text' , '' )) if self._inputType & TTkK.Input_Number and\ - not self._text.lstrip('-').isdigit(): self._text = "" + not self._text.lstrip('-').isdigit(): self._text = TTkString() self._color = TTkCfg.theme.lineEditTextColor self._offset = 0 self._cursorPos = 0 @@ -67,7 +67,7 @@ class TTkLineEdit(TTkWidget): def setText(self, text, cursorPos=0x1000): if text != self._text: self.textChanged.emit(text) - self._text = text + self._text = TTkString(text) self._cursorPos = max(0,min(cursorPos, len(text))) self._pushCursor() @@ -82,12 +82,13 @@ class TTkLineEdit(TTkWidget): # Align the text and the offset and the cursor to the current view self._offset = max(0, min(self._offset, len(self._text)-w)) # Scroll to the right if reached the edge - if self._cursorPos - self._offset > w: - self._offset = self._cursorPos-w - if self._cursorPos - self._offset < 0: - self._offset = self._cursorPos + cursorPos = self._text.substring(to=self._cursorPos).termWidth() + if cursorPos - self._offset > w: + self._offset = cursorPos-w + if cursorPos - self._offset < 0: + self._offset = cursorPos - TTkHelper.moveCursor(self,self._cursorPos-self._offset,0) + TTkHelper.moveCursor(self,cursorPos-self._offset,0) if self._replace: TTkHelper.showCursor(TTkK.Cursor_Blinking_Block) else: @@ -116,9 +117,7 @@ class TTkLineEdit(TTkWidget): self._canvas.drawText(pos=(0,0), text=text, color=color, width=w) def mousePressEvent(self, evt): - txtPos = evt.x+self._offset - if txtPos > len(self._text): - txtPos = len(self._text) + txtPos = self._text.tabCharPos(evt.x+self._offset) self._cursorPos = txtPos self._selectionFrom = txtPos self._selectionTo = txtPos @@ -126,7 +125,7 @@ class TTkLineEdit(TTkWidget): return True def mouseDragEvent(self, evt) -> bool: - txtPos = evt.x+self._offset + txtPos = self._text.tabCharPos(evt.x+self._offset) self._selectionFrom = max(0, min(txtPos,self._cursorPos)) self._selectionTo = min(len(self._text),max(txtPos,self._cursorPos)) if self._selectionFrom < self._selectionTo: @@ -135,17 +134,17 @@ class TTkLineEdit(TTkWidget): return True def mouseDoubleClickEvent(self, evt) -> bool: - before = self._text[:self._cursorPos] - after = self._text[self._cursorPos:] + before = self._text.substring(to=self._cursorPos) + after = self._text.substring(fr=self._cursorPos) self._selectionFrom = len(before) self._selectionTo = len(before) - selectRE = '[a-zA-Z0-9:,./]*' + selectRE = '[^ \t\r\n\(\)\[\]\.\,\+\-\*\/]*' - if m := re.search(selectRE+'$',before): + if m := before.search(selectRE+'$'): self._selectionFrom -= len(m.group(0)) - if m := re.search('^'+selectRE,after): + if m := after.search('^'+selectRE): self._selectionTo += len(m.group(0)) # TTkLog.debug("x"*self._selectionFrom) @@ -177,13 +176,11 @@ class TTkLineEdit(TTkWidget): elif evt.key == TTkK.Key_Left: if self._selectionFrom < self._selectionTo: self._cursorPos = self._selectionTo - if self._cursorPos > 0: - self._cursorPos -= 1 + self._cursorPos = self._text.prevPos(self._cursorPos) elif evt.key == TTkK.Key_Right: if self._selectionFrom < self._selectionTo: self._cursorPos = self._selectionTo-1 - if self._cursorPos < len(self._text): - self._cursorPos += 1 + self._cursorPos = self._text.nextPos(self._cursorPos) elif evt.key == TTkK.Key_End: self._cursorPos = len(self._text) elif evt.key == TTkK.Key_Home: @@ -192,17 +189,18 @@ class TTkLineEdit(TTkWidget): self._replace = not self._replace elif evt.key == TTkK.Key_Delete: if self._selectionFrom < self._selectionTo: - self._text = self._text[:self._selectionFrom] + self._text[self._selectionTo:] + self._text = self._text.substring(to=self._selectionFrom) + self._text.substring(fr=self._selectionTo) self._cursorPos = self._selectionFrom else: - self._text = self._text[:self._cursorPos] + self._text[self._cursorPos+1:] + self._text = self._text.substring(to=self._cursorPos) + self._text.substring(fr=self._text.nextPos(self._cursorPos)) elif evt.key == TTkK.Key_Backspace: if self._selectionFrom < self._selectionTo: - self._text = self._text[:self._selectionFrom] + self._text[self._selectionTo:] + self._text = self._text.substring(to=self._selectionFrom) + self._text.substring(fr=self._selectionTo) self._cursorPos = self._selectionFrom elif self._cursorPos > 0: - self._text = self._text[:self._cursorPos-1] + self._text[self._cursorPos:] - self._cursorPos -= 1 + prev = self._text.prevPos(self._cursorPos) + self._text = self._text.substring(to=prev) + self._text.substring(fr=self._cursorPos) + self._cursorPos = prev if self._inputType & TTkK.Input_Number and \ not self._text.lstrip('-').isdigit(): @@ -216,15 +214,15 @@ class TTkLineEdit(TTkWidget): text = self._text if self._selectionFrom < self._selectionTo: - pre = text[:self._selectionFrom] - post = text[self._selectionTo:] + pre = text.substring(to=self._selectionFrom) + post = text.substring(fr=self._selectionTo) self._cursorPos = self._selectionFrom else: - pre = text[:self._cursorPos] + pre = text.substring(to=self._cursorPos) if self._replace: - post = text[self._cursorPos+1:] + post = text.substring(fr=self._cursorPos+1) else: - post = text[self._cursorPos:] + post = text.substring(fr=self._cursorPos) text = pre + evt.key + post if self._inputType & TTkK.Input_Number and \ diff --git a/TermTk/TTkWidgets/menubar.py b/TermTk/TTkWidgets/menubar.py index 9f34a7ec..bd3c4a55 100644 --- a/TermTk/TTkWidgets/menubar.py +++ b/TermTk/TTkWidgets/menubar.py @@ -79,10 +79,10 @@ class TTkMenuButton(TTkAbstractListItem): self._menu = [] while self.text.find('&') != -1: index = self.text.find('&') - shortcut = self.text[index+1] + shortcut = self.text.charAt(index+1) TTkHelper.addShortcut(self, shortcut) self._shortcut.append(index) - self.text = self.text[:index]+self.text[index+1:] + self.text = self.text.substring(to=index)+self.text.substring(fr=index+1) txtlen = len(self.text) self.resize(txtlen,1) self.setMinimumSize(txtlen+2,1) diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index 3a5e606f..505d8e74 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -522,7 +522,7 @@ class TTkTextEditView(TTkAbstractScrollView): for y, l in enumerate(subLines): t = outLines[l[0]-subLines[0][0]] - self._canvas.drawText(pos=(-ox,y), text=t.substring(l[1][0],l[1][1]).tab2spaces(self._textWrap._tabSpaces)) + self._canvas.drawTTkString(pos=(-ox,y), text=t.substring(l[1][0],l[1][1]).tab2spaces(self._textWrap._tabSpaces)) if self._lineWrapMode == TTkK.FixedWidth: self._canvas.drawVLine(pos=(self._textWrap._wrapWidth,0), size=h, color=TTkCfg.theme.treeLineColor) @@ -533,7 +533,7 @@ class TTkTextEdit(TTkAbstractScrollArea): '_textEditView', '_lineNumberView', '_lineNumber', # Forwarded Methods - 'clear', 'setText', 'append', 'isReadOnly', 'setReadOnly' + 'clear', 'setText', 'append', 'isReadOnly', 'setReadOnly', 'document', 'wrapWidth', 'setWrapWidth', 'lineWrapMode', 'setLineWrapMode', 'wordWrapMode', 'setWordWrapMode', @@ -562,6 +562,7 @@ class TTkTextEdit(TTkAbstractScrollArea): self.clear = self._textEditView.clear self.setText = self._textEditView.setText self.append = self._textEditView.append + self.document = self._textEditView.document self.isReadOnly = self._textEditView.isReadOnly self.setReadOnly = self._textEditView.setReadOnly self.textCursor = self._textEditView.textCursor diff --git a/demo/demo.py b/demo/demo.py index 827da973..e5d1c27c 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -52,12 +52,6 @@ from showcase.dragndrop import demoDnD from showcase.dndtabs import demoDnDTabs from showcase.sigmask import demoSigmask -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(): - return random.choice(words) -def getSentence(a,b): - return " ".join([getWord() for i in range(0,random.randint(a,b))]) - def stupidPythonHighlighter(txt): def _colorize(regex, txt, color): ret = txt diff --git a/demo/showcase/_showcasehelper.py b/demo/showcase/_showcasehelper.py new file mode 100755 index 00000000..0c509503 --- /dev/null +++ b/demo/showcase/_showcasehelper.py @@ -0,0 +1,75 @@ +# 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 sys, os, random + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +zc1 = chr(0x07a6) # Zero width chars oަ +zc2 = chr(0x20D7) # Zero width chars o⃗ +zc3 = chr(0x065f) # Zero width chars oٟ +utfwords = [ + f"--Zero{zc1}{zc2}{zc3}-1-", f"--Zero-2{zc1}{zc2}{zc3}-", f"--Ze{zc1}{zc2}{zc3}ro-3-", f"{zc1}{zc2}{zc3}--Zero-4-", + "Lorem", "i🙻sum", "d😮l😱r", "sit", "am😎t,", "c😱nsectetur", "adi🙻iscing", "elit,", "sed", "do", "eiusmod", "t😜mpor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliq😞ip", "ex", "ea", "comm😞do", "cons😿quat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "cul🙻a", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] +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'), + ttk.TTkColor.fg('#0000FF')+ttk.TTkColor.bg('#00FF00'), + ttk.TTkColor.fg('#00FF00')+ttk.TTkColor.UNDERLINE, + ttk.TTkColor.fg('#FF0000')+ttk.TTkColor.STRIKETROUGH, + ][random.randint(0,6)] + +def getWord(): + return random.choice(words) +def getWords(n): + www = [random.choice(words) for _ in range(n)] + return " ".join(www) +def getSentence(a,b): + return " ".join([getWords(random.randint(1,4)) for _ in range(0,random.randint(a,b))]) + +def getUtfWord(): + return random.choice(utfwords) +def getUtfWords(n): + www = [random.choice(utfwords) for _ in range(n)] + return " ".join(www) +def getUtfSentence(a,b): + return " ".join([getUtfWords(random.randint(1,4)) for _ in range(0,random.randint(a,b))]) + +def getUtfColoredWords(n): + www = [random.choice(utfwords) for _ in range(n)] + return ttk.TTkString(" ".join(www), randColor()) +def getUtfColoredSentence(a,b): + return ttk.TTkString(" ").join([getUtfColoredWords(random.randint(1,4)) for _ in range(0,random.randint(a,b))]) + +def main(): + root = ttk.TTk() + ttk.TTkLabel(parent=root, text=getUtfColoredSentence(20,50)) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/demo/showcase/fancytable.py b/demo/showcase/fancytable.py index 37b1af11..81d98388 100755 --- a/demo/showcase/fancytable.py +++ b/demo/showcase/fancytable.py @@ -28,11 +28,8 @@ import random 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 getWord(): - return random.choice(words) -def getSentence(a,b): - return " ".join([getWord() for i in range(0,random.randint(a,b))]) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfSentence, getUtfWord table_ii = 1000 @@ -62,7 +59,7 @@ def demoFancyTable(root=None): )) table1.appendItem((" - ","","You see it's all clear, You were meant to be here, From the beginning","","")) for i in range(0, 5): - table1.appendItem((str(i), getWord(), getSentence(8,30), getWord(), getWord())) + table1.appendItem((str(i), getUtfWord(), getUtfSentence(8,30), getUtfWord(), getUtfWord())) table1.appendItem((" - ","This is the end", "Beautiful friend, This is the end My only friend", "the end", "...")) # Attach the add Event @@ -70,14 +67,14 @@ def demoFancyTable(root=None): def add(): global table_ii table_ii+=1 - table1.appendItem((str(table_ii), getWord(), getSentence(8,30), getWord(), getWord())) + table1.appendItem((str(table_ii), getUtfWord(), getUtfSentence(8,30), getUtfWord(), getUtfWord())) btn1.clicked.connect(add) def addMany(): global table_ii for i in range(0, 500): table_ii+=1 - table1.appendItem((str(table_ii), getWord(), getSentence(8,30), getWord(), getWord())) + table1.appendItem((str(table_ii), getUtfWord(), getUtfSentence(8,30), getUtfWord(), getUtfWord())) table1.appendItem((" - ","This is the end", "Beautiful friend, This is the end My only friend", "the end", "...")) btn2.clicked.connect(addMany) diff --git a/demo/showcase/fancytree.py b/demo/showcase/fancytree.py index 1517e3f5..1c6c717d 100755 --- a/demo/showcase/fancytree.py +++ b/demo/showcase/fancytree.py @@ -33,11 +33,8 @@ 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 getWord(): - return random.choice(words) -def getSentence(a,b): - return " ".join([getWord() for i in range(0,random.randint(a,b))]) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfSentence, getUtfWord def demoFancyTree(root=None): # tw = ttk.TTkFancyTreeWidget(parent=rootTree1) @@ -82,7 +79,7 @@ def demoFancyTree(root=None): def updateChildren(item): if item.children(): return for _ in range(0,random.randint(3,8)): - child = ttk.TTkFancyTreeWidgetItem([getWord(),getWord(),getWord()]) + child = ttk.TTkFancyTreeWidgetItem([getUtfWord(),getUtfWord(),getUtfWord()]) if random.randint(0,10)>5: child.setChildIndicatorPolicy(ttk.TTkK.ShowIndicator) child.refreshData.connect(updateChildren) diff --git a/demo/showcase/formwidgets.py b/demo/showcase/formwidgets.py index eac3024e..9603ec03 100755 --- a/demo/showcase/formwidgets.py +++ b/demo/showcase/formwidgets.py @@ -28,11 +28,8 @@ import random 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 getWord(): - return random.choice(words) -def getSentence(a,b): - return " ".join([getWord() for i in range(0,random.randint(a,b))]) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfSentence def demoFormWidgets(root=None): win_form1_grid_layout = ttk.TTkGridLayout(columnMinWidth=1) @@ -47,11 +44,11 @@ def demoFormWidgets(root=None): row +=1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box'),row,0) win_form1_grid_layout.addWidget(ttk.TTkComboBox(list=['One','Two','Some Long Sentence That Is Not a Written Number','Three']),row,2) row +=1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo long Box'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkComboBox(list=[getSentence(1,4) for i in range(100)]),row,2) + win_form1_grid_layout.addWidget(ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(100)]),row,2) row +=1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box Edit. Bottom Insert'),row,0) - win_form1_grid_layout.addWidget(comboEdit1 := ttk.TTkComboBox(list=[getSentence(1,4) for i in range(10)]),row,2) + win_form1_grid_layout.addWidget(comboEdit1 := ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(10)]),row,2) row +=1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Combo Box Edit. Top Insert'),row,0) - win_form1_grid_layout.addWidget(comboEdit2 := ttk.TTkComboBox(list=[getSentence(1,4) for i in range(10)]),row,2) + win_form1_grid_layout.addWidget(comboEdit2 := ttk.TTkComboBox(list=[getUtfSentence(1,4) for i in range(10)]),row,2) comboEdit1.setEditable(True) comboEdit2.setEditable(True) @@ -60,9 +57,9 @@ def demoFormWidgets(root=None): row +=1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 1'),row,0) win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 1'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 2'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 2'),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 2 😎 -'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 3'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 3'),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 3 oަ -'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 4'),row,0) win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 4'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 5'),row,0) diff --git a/demo/showcase/list.py b/demo/showcase/list.py index 5ff733a6..f5a593ba 100755 --- a/demo/showcase/list.py +++ b/demo/showcase/list.py @@ -24,12 +24,12 @@ import sys, os, argparse, math, random + 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 getWord(): - return random.choice(words) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfWord def demoList(root= None): # Define the main Layout @@ -67,8 +67,8 @@ def demoList(root= None): # populate the lists with random entries for i in range(100): - listWidgetSingle.addItem(f"{i}) {getWord()} {getWord()}") - listWidgetMulti.addItem(f"{getWord()} {getWord()}") + listWidgetSingle.addItem(f"{i}) {getUtfWord()} {getUtfWord()}") + listWidgetMulti.addItem(f"{getUtfWord()} {getUtfWord()}") return splitter diff --git a/demo/showcase/textedit.py b/demo/showcase/textedit.py index 8bed8cb6..75685d21 100755 --- a/demo/showcase/textedit.py +++ b/demo/showcase/textedit.py @@ -30,22 +30,8 @@ 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'), - ttk.TTkColor.fg('#0000FF')+ttk.TTkColor.bg('#00FF00'), - ttk.TTkColor.fg('#00FF00')+ttk.TTkColor.UNDERLINE, - ttk.TTkColor.fg('#FF0000')+ttk.TTkColor.STRIKETROUGH, - ][random.randint(0,6)] -def getWords(n): - www = [random.choice(words) for _ in range(n)] - return ttk.TTkString(" ".join(www), randColor()) -def getSentence(a,b,i): - return ttk.TTkString(" ").join([f"{i} "]+[getWords(random.randint(1,4)) for i in range(0,random.randint(a,b))]) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfColoredSentence class superSimpleHorizontalLine(ttk.TTkWidget): def paintEvent(self): @@ -77,6 +63,28 @@ def demoTextEdit(root=None, document=None): with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),'textedit.ANSI.txt')) as f: te.append(f.read()) + # Test Variable sized chars + te.append(ttk.TTkString("Test Variable sized chars\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + te.append( "Emoticons: -😁😂😍😎----") + te.append( " --😐😁😂😍😎-") + te.append("") + + te.append( " UTF-8: £ @ £ ¬ ` 漢 _ _ あ _ _") + te.append( " |.|.|.|.|.||.|.|.||.|.|.") + te.append("") + + + zc1 = chr(0x07a6) + zc2 = chr(0x20D7) + zc3 = chr(0x065f) + te.append( " - | | | | | -") + te.append(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") + te.append( " - | | | | | -") + te.append("") + + te.append(f"Plus Tabs: -\t😁\t😍\to{zc1}{zc2}{zc3}\t😎\to{zc1}{zc2}{zc3}\t😂-") + te.append("") + # Test Tabs te.append(ttk.TTkString("Tabs Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) te.append("Word\tAnother Word\tYet more words") @@ -91,7 +99,7 @@ def demoTextEdit(root=None, document=None): te.append("-------tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\n") te.append(ttk.TTkString("Random TTkString Input Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) - te.append(ttk.TTkString('\n').join([ getSentence(3,10,i) for i in range(50)])) + te.append(ttk.TTkString('\n').join([ getUtfColoredSentence(3,10) for _ in range(50)])) te.append(ttk.TTkString("-- The Very END --",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) diff --git a/demo/showcase/tree.py b/demo/showcase/tree.py index 7cb2a711..f4c5b271 100755 --- a/demo/showcase/tree.py +++ b/demo/showcase/tree.py @@ -33,11 +33,8 @@ 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 getWord(): - return random.choice(words) -def getSentence(a,b): - return " ".join([getWord() for i in range(0,random.randint(a,b))]) +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfWord def demoTree(root=None): tw = ttk.TTkTree(parent=root) @@ -77,7 +74,7 @@ def demoTree(root=None): def updateChildren(item): if item.children(): return for _ in range(0,random.randint(3,8)): - child = ttk.TTkTreeWidgetItem([getWord(),getWord(),getWord()]) + child = ttk.TTkTreeWidgetItem([getUtfWord(),getUtfWord(),getUtfWord()]) if random.randint(0,10)>5: child.setChildIndicatorPolicy(ttk.TTkK.ShowIndicator) item.addChild(child) diff --git a/docs/MDNotes/TODO.md b/docs/MDNotes/TODO.md index 9af89d71..0fb17999 100644 --- a/docs/MDNotes/TODO.md +++ b/docs/MDNotes/TODO.md @@ -5,10 +5,12 @@ - [ ] Remove Duplicate functionalities (i.e. Widget) - [ ] Use @property/@setter when possible - [ ] Uniform the setter/getter/signal/slots -- [ ] [UTF-8] Handle "Fullwidth" forms characters +- [x] [UTF-8] Handle "Fullwidth" forms characters https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) https://stackoverflow.com/questions/68412744/count-length-of-value-within-a-cell-with-full-width-characters + - [ ] Handle Zero Width Joiner (i.e.👩🔧 -> 👩‍🔧👩🏻‍🔧👩🏻‍🔧👩🏼‍🔧👩🏽‍🔧👩🏾‍🔧👩🏿‍🔧): + https://github.com/luchr/WidthInTerminals - [ ] Support Hyperlink: (gnome-terminal) https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda - [x] Process child events before parent @@ -45,7 +47,7 @@ - [ ] Allow dynamic depth change - [x] Define a gradient feature ## Canvas Class -- [ ] Have a look to the Unicode chartable: https://www.utf8-chartable.de/unicode-utf8-table.pl +- [x] Have a look to the Unicode chartable: https://www.utf8-chartable.de/unicode-utf8-table.pl ## Signal/Slots - [x] Implement Signal/Slots diff --git a/tests/test.draw.006.py b/tests/test.draw.006.py new file mode 100755 index 00000000..e2387ce3 --- /dev/null +++ b/tests/test.draw.006.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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 sys, os +import logging +import time + +sys.path.append(os.path.join(sys.path[0],'..')) +from TermTk import TTkLog +from TermTk.TTkCore import TTkColor +from TermTk.TTkCore import TTkHelper +from TermTk.TTkCore import TTkString +from TermTk.TTkCore import TTkTerm + + +# TTkLog.use_default_file_logging() +TTkLog.use_default_stdout_logging() + +# TTkTerm.init(mouse=False) +TTkLog.info("Starting") + +s1 = TTkString("-😁😂😍😎----") +s2 = TTkString("--😐😁😂😍😎-") + +zc1 = chr(0x07a6) +zc2 = chr(0x20D7) +zc3 = chr(0x065f) +s3 = TTkString(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") + +s4 = TTkString("This is a normal string") + +s5 = TTkString(f"-😁😂😍😎- o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") +s6 = TTkString(f"{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") +print(f"o{zc1}{zc2}{zc3} - Zero") +print(f"{zc1}{zc2}{zc3} - Zero") + + +# Examples from: +# https://github.com/luchr/WidthInTerminals +print( "🔧 = \U0001F527 ") +print(f"👩 = {chr(0x1F469)} ") +print(f"👩🔧 = {chr(0x1F469)}{chr(0x1F527)} ") +print(f"👩‍🔧 = {chr(0x1F469)}{chr(0x0200D)}{chr(0x1F527)} ") +print(f"👩🏻‍🔧 = {chr(0x1F469)}{chr(0x1F3FB)}{chr(0x0200D)}{chr(0x1F527)}") +print(f"👩🏻‍🔧 = {chr(0x1F469)}{chr(0x1F3FB)}{chr(0x0200D)}{chr(0x1F527)}") +print(f"👩🏼‍🔧 = {chr(0x1F469)}{chr(0x1F3FC)}{chr(0x0200D)}{chr(0x1F527)}") +print(f"👩🏽‍🔧 = {chr(0x1F469)}{chr(0x1F3FD)}{chr(0x0200D)}{chr(0x1F527)}") +print(f"👩🏾‍🔧 = {chr(0x1F469)}{chr(0x1F3FE)}{chr(0x0200D)}{chr(0x1F527)}") +print(f"👩🏿‍🔧 = {chr(0x1F469)}{chr(0x1F3FF)}{chr(0x0200D)}{chr(0x1F527)}") + +s01 = TTkString(f"🔧 = {chr(0x1F527)} ") # 1F527 : 🔧 +s01 = TTkString(f"👩 = {chr(0x1F469)} ") # 1F469 : 👩 +s01 = TTkString(f"👩🔧 = {chr(0x1F469)}{chr(0x1F527)} ") # 1F469 1F527 : 👩🔧 +s01 = TTkString(f"👩‍🔧 = {chr(0x1F469)}{chr(0x0200D)}{chr(0x1F527)} ") # 1F469 200D 1F527 : 👩‍🔧 +s01 = TTkString(f"👩🏻‍🔧 = {chr(0x1F469)}{chr(0x1F3FB)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FB 200D 1F527 : 👩🏻‍🔧 +s01 = TTkString(f"👩🏻‍🔧 = {chr(0x1F469)}{chr(0x1F3FB)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FB 200D 1F527 : 👩🏻‍🔧 +s01 = TTkString(f"👩🏼‍🔧 = {chr(0x1F469)}{chr(0x1F3FC)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FC 200D 1F527 : 👩🏼‍🔧 +s01 = TTkString(f"👩🏽‍🔧 = {chr(0x1F469)}{chr(0x1F3FD)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FD 200D 1F527 : 👩🏽‍🔧 +s01 = TTkString(f"👩🏾‍🔧 = {chr(0x1F469)}{chr(0x1F3FE)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FE 200D 1F527 : 👩🏾‍🔧 +s01 = TTkString(f"👩🏿‍🔧 = {chr(0x1F469)}{chr(0x1F3FF)}{chr(0x0200D)}{chr(0x1F527)}") # 1F469 1F3FF 200D 1F527 : 👩🏿‍🔧 + +print(s1.getData()[0]) +print(s2.getData()[0]) +print(s3.getData()[0]) +print(s4.getData()[0]) +print(s5.getData()[0]) +print(s6.getData()[0]) + +s5.tabCharPos(1,tabSpaces=4) +s5.tabCharPos(5,tabSpaces=4) +s5.tabCharPos(9,tabSpaces=4) + +# time.sleep(5) + +TTkLog.info("Ending") + +# TTkTerm.exit() \ No newline at end of file diff --git a/tests/test.draw.007.py b/tests/test.draw.007.py new file mode 100755 index 00000000..60785ca8 --- /dev/null +++ b/tests/test.draw.007.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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 sys, os +import logging +import time + +sys.path.append(os.path.join(sys.path[0],'..')) +from TermTk import TTkLog +from TermTk.TTkCore import TTkColor +from TermTk.TTkCore import TTkHelper +from TermTk.TTkCore import TTkString +from TermTk.TTkCore import TTkTerm +from TermTk.TTkCore import TTkCanvas + + +# TTkLog.use_default_file_logging() +TTkLog.use_default_stdout_logging() + +# TTkTerm.init(mouse=False) +TTkLog.info("Starting") + +s1 = TTkString("-😁😂😍😎----") +s2 = TTkString("--😐😁😂😍😎-") + +zc1 = chr(0x07a6) +zc2 = chr(0x20D7) +zc3 = chr(0x065f) +s3 = TTkString(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") + +s4 = TTkString("This is a normal string") + +s5 = TTkString(f"-😁- o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") +s6 = TTkString(f"{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") +TTkLog.debug(f"o{zc1}{zc2}{zc3} - Zero") +TTkLog.debug(f"{zc1}{zc2}{zc3} - Zero") + +canvas = TTkCanvas(width=100,height=100) +canvas.drawTTkString(pos=(0,0),text=s1,width=4) +canvas.drawTTkString(pos=(0,0),text=s1,width=11) +canvas.drawTTkString(pos=(0,0),text=s5,width=8) +canvas.drawTTkString(pos=(0,0),text=s6,width=4) + + +TTkLog.info("Ending") diff --git a/tests/test.metaclass.001.py b/tests/test.metaclass.001.py index 09137aa8..1e823673 100755 --- a/tests/test.metaclass.001.py +++ b/tests/test.metaclass.001.py @@ -21,7 +21,6 @@ # 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. - class A_Meta(type): def __new__(mcs, name, bases, d): print(f"{mcs=}") @@ -40,7 +39,7 @@ print(f"{A=}") a = A(1,2,3,4) -print(f"{a=}\n") +print(f"A ---> {a=}\n") class B(A_Meta): def __init__(self, *args, **kwargs): @@ -50,7 +49,7 @@ class B(A_Meta): b = B("NB",(),{}) -print(f"{b=}\n") +print(f"B ---> {b=}\n") class C(): def __init__(self) -> None: @@ -68,4 +67,4 @@ class E(C,D): e = E() e.pippo() -print(f"{issubclass(E,D)=} {issubclass(E,C)=}") \ No newline at end of file +print(f"E,D ---> {issubclass(E,D)=} {issubclass(E,C)=}") \ No newline at end of file diff --git a/tests/test.metaclass.002.py b/tests/test.metaclass.002.py new file mode 100755 index 00000000..b90d885e --- /dev/null +++ b/tests/test.metaclass.002.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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. + +class A(): + test = True + def __new__(cls, *args, **kwargs): + print(f"New: {args=} {kwargs=} {cls=}") + if kwargs.get('mod'): + return super().__new__(A_Mod) + return super().__new__(cls) + + def __init__(self, *args, **kwargs): + print(f"Init: {args=} {kwargs=}") + +class A_Mod(A): + test = False + def __init__(self, *args, **kwargs): + print(f"Init: Mod {args=} {kwargs=}") + +a = A(1,2,3,aa=1,bb=2,cc=3) +print(f"A ---> {a=} {a.test=}") +b = A(1,2,3,aa=1,bb=2,cc=3,mod=4) +print(f"B ---> {b=} {b.test=}") + +def test(x): + print(f"Test {x=}") + return x == 5 + +print(f"{any(test(x) for x in range(10))=}") \ No newline at end of file diff --git a/tests/timeit/.gitignore b/tests/timeit/.gitignore new file mode 100644 index 00000000..d1a9d95b --- /dev/null +++ b/tests/timeit/.gitignore @@ -0,0 +1 @@ +wcwidth \ No newline at end of file diff --git a/tests/timeit/00.template.py b/tests/timeit/00.template.py new file mode 100644 index 00000000..b4b6103b --- /dev/null +++ b/tests/timeit/00.template.py @@ -0,0 +1,88 @@ +#!/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 sys, os + +import timeit +import random + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + + +def test1(): + return 1 +def test2(): + return 1 +def test3(): + return 1 +def test4(): + return 1 +def test5(): + return 1 +def test6(): + return 1 +def test7(): + return 1 +def test8(): + return 1 +def test9(): + return 1 +def test10(): + return 1 +def test11(): + return 1 +def test12(): + return 1 + +loop = 100 + +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"1 {result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"2 {result / loop:.10f} - {result / loop} {test2()}") +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"3 {result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"4 {result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"5 {result / loop:.10f} - {result / loop} {test5()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"6 {result / loop:.10f} - {result / loop} {test6()}") +result = timeit.timeit('test7()', globals=globals(), number=loop) +print(f"7 {result / loop:.10f} - {result / loop} {test7()}") +result = timeit.timeit('test8()', globals=globals(), number=loop) +print(f"8 {result / loop:.10f} - {result / loop} {test8()}") +result = timeit.timeit('test9()', globals=globals(), number=loop) +print(f"9 {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test10()', globals=globals(), number=loop) +print(f"10 {result / loop:.10f} - {result / loop} {test10()}") +result = timeit.timeit('test11()', globals=globals(), number=loop) +print(f"11 {result / loop:.10f} - {result / loop} {test11()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + + diff --git a/tests/timeit/04.wcwidth.bisearch.py b/tests/timeit/04.wcwidth.bisearch.py new file mode 100644 index 00000000..7b6ffe54 --- /dev/null +++ b/tests/timeit/04.wcwidth.bisearch.py @@ -0,0 +1,158 @@ +#!/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 sys, os + +import timeit +import random + +from wcwidth import * +from functools import lru_cache + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +# Try to create a table with ~200 entries +print(f"Create Table...") +table = [] +base = 0x1000 +for _ in range(200): + incr = random.randint(0x10,0x200) + table.append((base,base+incr)) + base += incr + random.randint(0x10,0x100) +table = tuple(table) +print(f"Create Done!!!") + +for a,b in table: + print(f"0x{a:06x}, 0x{b:06x}") + +print(f"Create Set...") +tset = [] +for a,b in table: + for v in range(a,b+1): + tset.append(v) +tset = set(tset) +print(f"Create Set DONE!!!") +print(f"len tset 0x{len(tset):04x}") + +print(f"Create CharSetStringTest...") +cstr = "" +for _ in range(0x4000): + cstr += chr(random.randint(0xA0,0x40000)) +print(f"Create CharSetStringTest DONE!!!") + +@lru_cache(maxsize=3) +def ttt(val): + return random.randint(10,100) + +print(f"{ttt(1)=}") +print(f"{ttt(2)=}") +print(f"{ttt(3)=}") +print(f"{ttt(1)=}") +print(f"{ttt(2)=}") +print(f"{ttt(3)=}") +print(f"{ttt(4)=}") +print(f"{ttt(1)=}") +print(f"{ttt(3)=}") +print(f"{ttt(2)=}") + +def _bisearch(ucs, table): + lbound = 0 + ubound = len(table) - 1 + + if ucs < table[0][0] or ucs > table[ubound][1]: + return 0 + while ubound >= lbound: + mid = (lbound + ubound) // 2 + if ucs > table[mid][1]: + lbound = mid + 1 + elif ucs < table[mid][0]: + ubound = mid - 1 + else: + return 1 + + return 0 + +@lru_cache(maxsize=1000) +def _bicache(ucs, table): + lbound = 0 + ubound = len(table) - 1 + + if ucs < table[0][0] or ucs > table[ubound][1]: + return 0 + while ubound >= lbound: + mid = (lbound + ubound) // 2 + if ucs > table[mid][1]: + lbound = mid + 1 + elif ucs < table[mid][0]: + ubound = mid - 1 + else: + return 1 + + return 0 + +def test1(): + cw = 0 + for ch in cstr: + cw += _bisearch(ord(ch), table) + return cw + +def test2(): + cw = 0 + for ch in cstr: + cw += _bicache(ord(ch), table) + return cw + +def test3(): + return wcswidth(cstr) + +def test4(): + cw = 0 + for ch in cstr: + cw += 1 if ord(ch) in tset else 0 + return cw + +def test5(): + cw = sum([1 if ord(ch) in tset else 0 for ch in cstr]) + return cw + +def test6(): + return sum([ord(ch) in tset for ch in cstr]) + +loop = 100 + +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test4()}") + +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"{result / loop:.10f} - {result / loop} {test2()}") diff --git a/tests/timeit/05.wcwidth.bisearch.py b/tests/timeit/05.wcwidth.bisearch.py new file mode 100644 index 00000000..e2962c85 --- /dev/null +++ b/tests/timeit/05.wcwidth.bisearch.py @@ -0,0 +1,249 @@ +#!/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 sys, os + +import timeit +import random +import unicodedata + +import wcwidth +from functools import lru_cache + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + + +_unicode_version = "13.0.0" +zw = wcwidth.ZERO_WIDTH[_unicode_version] +# zwcf = wcwidth.ZERO_WIDTH_CF +we = wcwidth.WIDE_EASTASIAN[_unicode_version] +zwcf = [ + 0, # Null (Cc) + 0x034F, # Combining grapheme joiner (Mn) + 0x200B, # Zero width space + 0x200C, # Zero width non-joiner + 0x200D, # Zero width joiner + 0x200E, # Left-to-right mark + 0x200F, # Right-to-left mark + 0x2028, # Line separator (Zl) + 0x2029, # Paragraph separator (Zp) + 0x202A, # Left-to-right embedding + 0x202B, # Right-to-left embedding + 0x202C, # Pop directional formatting + 0x202D, # Left-to-right override + 0x202E, # Right-to-left override + 0x2060, # Word joiner + 0x2061, # Function application + 0x2062, # Invisible times + 0x2063, # Invisible separator +] + + +def set2binmask(s): + ret = [] + for v in s: + id = v >> 5 + mask = v & 0x1F + bit = 1 << mask + if id >= len(ret): + ret += [0]*(id-len(ret)+2) + ret[id] |= bit + return ret + + +print(f"Create Set...") +zset = [] +for a,b in zw: + for v in range(a,b+1): + zset.append(v) +for v in zwcf: + zset.append(v) +zset = set(zset) + +wset = [] +for a,b in we: + for v in range(a,b+1): + wset.append(v) +wset = set(wset) + +print(f"Create Set DONE!!!") + +print(f"Create CharSetStringTest...") +cstr = "" +for _ in range(0x4000): + cstr += chr(random.randint(0x100,0x20000)) +print(f"Create CharSetStringTest DONE!!!") + +# print(f"{set2binmask(zset)}") + +bzset = set2binmask(zset) +bwset = set2binmask(wset) + +print(f"len zset 0x{len(zset):04x}") +print(f"len zset 0x{len(bzset):04x}") +print(f"len wset 0x{len(wset):04x}") +print(f"len wset 0x{len(bwset):04x}") +print(f"len cstr 0x{len(cstr):04x}") + +print([f"'{ch}':{unicodedata.east_asian_width(ch)}:{unicodedata.category(ch)}" for ch in cstr]) + +# @lru_cache(maxsize=3) +# def ttt(val): +# return random.randint(10,100) +# +# print(f"{ttt(1)=}") +# print(f"{ttt(2)=}") +# print(f"{ttt(3)=}") +# print(f"{ttt(1)=}")unicodedata.category +# print(f"{ttt(1)=}") +# print(f"{ttt(3)=}") +# print(f"{ttt(2)=}") + +def _bisearch(ucs, table): + lbound = 0 + ubound = len(table) - 1 + + if ucs < table[0][0] or ucs > table[ubound][1]: + return 0 + while ubound >= lbound: + mid = (lbound + ubound) // 2 + if ucs > table[mid][1]: + lbound = mid + 1 + elif ucs < table[mid][0]: + ubound = mid - 1 + else: + return 1 + + return 0 + +@lru_cache(maxsize=1000) +def _bicache(ucs, table): + lbound = 0 + ubound = len(table) - 1 + + if ucs < table[0][0] or ucs > table[ubound][1]: + return 0 + while ubound >= lbound: + mid = (lbound + ubound) // 2 + if ucs > table[mid][1]: + lbound = mid + 1 + elif ucs < table[mid][0]: + ubound = mid - 1 + else: + return 1 + + return 0 + +def test1(): + cw = 0 + for ch in cstr: + cw += _bisearch(ord(ch), zw) + return cw + +def test2(): + cw = 0 + for ch in cstr: + cw += _bicache(ord(ch), zw) + return cw + +def test3(): + return wcwidth.wcswidth(cstr) + +def test4(): + cw = 0 + for ch in cstr: + cw += 1 if ord(ch) in wset else 0 + return cw + +def test5(): + cw = sum([1 if ord(ch) in wset else 0 for ch in cstr]) + return cw + +def test6(): + return len(cstr) + sum([ord(ch) in wset for ch in cstr]) - sum([ord(ch) in zset for ch in cstr]) + +def test7(): + return len(cstr) + sum([bwset[ord(ch)>>5]>>(ord(ch)&0x1F)&1 for ch in cstr]) - sum([bzset[ord(ch)>>5]>>(ord(ch)&0x1F)&1 for ch in cstr]) + +def test8(): + return len(cstr) + sum([bwset[ord(ch)>>5]>>(ord(ch)&0x1F)&1 for ch in cstr]) - sum([ord(ch) in zset for ch in cstr]) + +def test9(): + return len(cstr) + sum([0!=(bwset[ord(ch)>>5]&(1<<(ord(ch)&0x1F))) for ch in cstr]) - sum([ord(ch) in zset for ch in cstr]) + +def test10(): + return ( len(cstr) + + sum(['W'==unicodedata.east_asian_width(ch) for ch in cstr]) - + sum(['Me'==(c:=unicodedata.category(ch)) or 'Mn'==c for ch in cstr]) ) +def test11(): + return ( len(cstr) + + sum([unicodedata.east_asian_width(ch) == 'W' for ch in cstr]) - + sum([unicodedata.category(ch) in ('Me','Mn') for ch in cstr]) ) + +def test12(): + retTxt = [] + retCol = [] + for i,ch in enumerate(cstr): + if unicodedata.east_asian_width(ch) == 'W': + retTxt += (ch,'') + retCol += (ch,ch) + if unicodedata.category(ch) in ('Me','Mn'): + retTxt[-1]+=ch + else: + retTxt.append(ch) + retCol.append(ch) + return (len(retTxt), len(retCol)) + + +loop = 100 + +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"4 {result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"5 {result / loop:.10f} - {result / loop} {test5()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"6 {result / loop:.10f} - {result / loop} {test6()}") +result = timeit.timeit('test10()', globals=globals(), number=loop) +print(f"10 {result / loop:.10f} - {result / loop} {test10()}") +result = timeit.timeit('test11()', globals=globals(), number=loop) +print(f"11 {result / loop:.10f} - {result / loop} {test11()}") +result = timeit.timeit('test7()', globals=globals(), number=loop) +print(f"7 {result / loop:.10f} - {result / loop} {test7()}") +result = timeit.timeit('test8()', globals=globals(), number=loop) +print(f"8 {result / loop:.10f} - {result / loop} {test8()}") +result = timeit.timeit('test9()', globals=globals(), number=loop) +print(f"9 {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + + +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"3w {result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"1w {result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"2w {result / loop:.10f} - {result / loop} {test2()}") diff --git a/tests/timeit/06.functionPointer.py b/tests/timeit/06.functionPointer.py new file mode 100644 index 00000000..20e5d894 --- /dev/null +++ b/tests/timeit/06.functionPointer.py @@ -0,0 +1,143 @@ +#!/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 sys, os + +import timeit +import random + +sys.path.append(os.path.join(sys.path[0],'../..')) +sys.path.append(os.path.join(sys.path[0],'.')) +import TermTk as ttk + +class A(): + def test(self): + return 1 + +class B(A): + def test(self): + return 2 + +class C(B): + def test(self): + return 3 + +class D(): + __slots__ = ('test') + def __init__(self, sw=True): + if sw: + self.test = self._testA + else: + self.test = self._testB + def _testA(self): + return 11 + def _testB(self): + return 12 + +class E(): + __slots__ = ('_sw') + def __init__(self, sw=True): + self._sw = sw + def test(self): + if self._sw: + return 21 + else: + return 22 + +class F(): + __slots__ = ('_sw') + def __init__(self, sw=True): + self._sw = sw + def test(self): + if self._sw: + return self._testA() + else: + return self._testB() + def _testA(self): + return 31 + def _testB(self): + return 32 + +a = A() +b = B() +c = C() +da = D(sw=True) +db = D(sw=False) +ea = E(sw=True) +eb = E(sw=False) +fa = F(sw=True) +fb = F(sw=False) + +def test1(): + return a.test() +def test2(): + return b.test() +def test3(): + return c.test() +def test4(): + return da.test() +def test5(): + return db.test() +def test6(): + return ea.test() +def test7(): + return eb.test() +def test8(): + return fa.test() +def test9(): + return fb.test() + +def test10(): return None +def test11(): return None +def test12(): return None + +loop = 100000 + +result = timeit.timeit('test1()', globals=globals(), number=loop) +print(f"1a {result / loop:.10f} - {result / loop} {test1()}") +result = timeit.timeit('test2()', globals=globals(), number=loop) +print(f"2b {result / loop:.10f} - {result / loop} {test2()}") +result = timeit.timeit('test3()', globals=globals(), number=loop) +print(f"3c {result / loop:.10f} - {result / loop} {test3()}") +result = timeit.timeit('test4()', globals=globals(), number=loop) +print(f"4da {result / loop:.10f} - {result / loop} {test4()}") +result = timeit.timeit('test5()', globals=globals(), number=loop) +print(f"5db {result / loop:.10f} - {result / loop} {test5()}") +result = timeit.timeit('test6()', globals=globals(), number=loop) +print(f"6ea {result / loop:.10f} - {result / loop} {test6()}") +result = timeit.timeit('test7()', globals=globals(), number=loop) +print(f"7eb {result / loop:.10f} - {result / loop} {test7()}") +result = timeit.timeit('test8()', globals=globals(), number=loop) +print(f"8fa {result / loop:.10f} - {result / loop} {test8()}") +result = timeit.timeit('test9()', globals=globals(), number=loop) +print(f"9fb {result / loop:.10f} - {result / loop} {test9()}") +result = timeit.timeit('test10()', globals=globals(), number=loop) +print(f"10 {result / loop:.10f} - {result / loop} {test10()}") +result = timeit.timeit('test11()', globals=globals(), number=loop) +print(f"11 {result / loop:.10f} - {result / loop} {test11()}") +result = timeit.timeit('test12()', globals=globals(), number=loop) +print(f"12 {result / loop:.10f} - {result / loop} {test12()}") + + + diff --git a/tools/check.import.sh b/tools/check.import.sh index 966188c6..f0b68e4a 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -29,8 +29,9 @@ __check(){ -e "ttk.py:import platform" \ -e "clipboard.py:import importlib.util" \ -e "filebuffer.py:import threading" \ - -e "texedit.py:from math import log10, ceil" - + -e "texedit.py:from math import log10, ceil" \ + -e "canvas.py:import unicodedata" \ + -e "string.py:import unicodedata" } ; if __check ; then diff --git a/tools/gen.utf8.sizeMap.py b/tools/gen.utf8.sizeMap.py new file mode 100644 index 00000000..46fd2454 --- /dev/null +++ b/tools/gen.utf8.sizeMap.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2022 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 urllib.request +import re + +URI='http://www.unicode.org/Public/UCD/latest/ucd/DerivedAge.txt' + +response = urllib.request.urlopen(URI) + +# Matching: +# "FE24..FE26 ; 5.1 # [3] COMBINING MACRON LEFT HALF..COMBINING CONJOINING MACRON" +# "10190..1019B ; 5.1 # [12] ROMAN SEXTANS SIGN..ROMAN CENTURIAL SIGN" +rangeMatch = re.compile(r'^([0-9A-F]+)\.\.([0-9A-F]+) *; ([0-9\.]+) (#.*)$') + +# Matching: +# "A95F ; 5.1 # REJANG SECTION MARK" +# "1093F ; 5.1 # LYDIAN TRIANGULAR MARK" +singleMatch = re.compile(r'^([0-9A-F]+) *; ([0-9\.]+) (#.*)$') + + + +for line in response.readlines(): + # print(line.decode('utf-8')) + if m := rangeMatch.match(line.decode('utf-8')): + rfr = m.group(1) + rto = m.group(2) + rver = m.group(3) + rdesc = m.group(4) + print(f"{rver=} {rfr=} {rto=} {rdesc=}") + elif m := singleMatch.match(line.decode('utf-8')): + rm = m.group(1) + rver = m.group(2) + rdesc = m.group(3) + print(f"{rver=} {rm=} {rdesc=}")