Browse Source

Merge branch 'main' into 295-support-for-gpm

pull/296/head
Eugenio Parodi 🌶️ 1 year ago
parent
commit
9825be0022
  1. 1
      .gitignore
  2. 9
      .vscode/launch.json
  3. 7
      TermTk/TTkGui/__init__.py
  4. 8
      TermTk/TTkGui/textcursor.py
  5. 121
      TermTk/TTkGui/textdocument.py
  6. 57
      TermTk/TTkGui/textdocument_highlight.py
  7. 303
      TermTk/TTkGui/textdocument_highlight_pygments.py
  8. 5
      TermTk/TTkWidgets/combobox.py
  9. 4
      TermTk/TTkWidgets/listwidget.py
  10. 101
      TermTk/TTkWidgets/texedit.py
  11. 0
      tests/t.generic/test.argspec.001.py
  12. 0
      tests/t.generic/test.classes.001.slots.py
  13. 0
      tests/t.generic/test.classes.001.slots.typing.py
  14. 0
      tests/t.generic/test.dataclass.001.py
  15. 0
      tests/t.generic/test.generic.005.fnctl.py
  16. 0
      tests/t.ui/test.ui.018.TextEdit.01.Pygments.py
  17. 118
      tests/t.ui/test.ui.018.TextEdit.02.Pygments.py
  18. 4
      tools/check.import.sh

1
.gitignore vendored

@ -4,6 +4,7 @@ __pycache__/
*$py.class
# other
pygments
pyTermTk-Docs
tests/test.dummy.py
*.swp

9
.vscode/launch.json vendored

@ -12,6 +12,14 @@
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
},{
"name": "Python: Current File with Args",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["tools/webExporter/js/ttkproxy.js"]
},{
"name": "Python: Test Player",
"type": "debugpy",
@ -30,7 +38,6 @@
"env": {
"PYTHONPATH": "./tools"
}
},{
"name": "Python: TTk Designer Quick",
"type": "debugpy",

7
TermTk/TTkGui/__init__.py

@ -1,6 +1,13 @@
import importlib.util
from .drag import *
from .textwrap1 import *
from .textcursor import *
from .textdocument import *
from .clipboard import *
from .tooltip import *
if importlib.util.find_spec('pygments'):
from .textdocument_highlight_pygments import *
else:
from .textdocument_highlight import *

8
TermTk/TTkGui/textcursor.py

@ -348,6 +348,7 @@ class TTkTextCursor():
def replaceText(self, text:TTkString, moveCursor:bool=False) -> None:
# if there is no selection, just select the next n chars till the end of the line
# the newline is not replaced
self._document._acquire()
for p in self._properties:
if not p.hasSelection():
line = p.position.line
@ -358,9 +359,11 @@ class TTkTextCursor():
pos = self._document._dataLines[line].nextPos(pos)
pos = min(size,pos)
p.anchor.set(line,pos)
self._document._release()
return self.insertText(text, moveCursor)
def insertText(self, text:TTkString, moveCursor:bool=False) -> None:
self._document._acquire()
_lineFirst = -1
if self.hasSelection():
_lineFirst, _lineRem, _lineAdd = self._removeSelectedText()
@ -446,6 +449,7 @@ class TTkTextCursor():
pp.anchor.line += diffLine
self._autoChanged = True
self._document.setChanged(True)
self._document._release()
self._document.contentsChanged.emit()
self._document.contentsChange.emit(lineFirst, lineRem, lineAdd)
self._autoChanged = False
@ -552,14 +556,17 @@ class TTkTextCursor():
def removeSelectedText(self) -> None:
if not self.hasSelection(): return
self._document._acquire()
a,b,c = self._removeSelectedText()
self._autoChanged = True
self._document.setChanged(True)
self._document._release()
self._document.contentsChanged.emit()
self._document.contentsChange.emit(a,b,c)
self._autoChanged = False
def applyColor(self, color:TTkColor) -> None:
self._document._acquire()
for p in self._properties:
selSt = p.selectionStart()
selEn = p.selectionEnd()
@ -570,6 +577,7 @@ class TTkTextCursor():
self._document._dataLines[l] = line.setColor(color=color, posFrom=pf, posTo=pt)
self._autoChanged = True
self._document.setChanged(True)
self._document._acquire()
self._document.contentsChanged.emit()
# self._document.contentsChange.emit(0,0,0)
self._autoChanged = True

121
TermTk/TTkGui/textdocument.py

@ -22,61 +22,64 @@
__all__ = ['TTkTextDocument']
from threading import Lock
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.color import TTkColor
class TTkTextDocument():
'''
Undo,Redo Logic
Old:
::
_snapshotId: = last saved/undo/redo state
3 = doc4
_snapshots:
[doc1, doc2, doc3, doc4, doc5, doc6, . . .]
New:
::
SnapshotId:
2
Snapshots: _lastSnap _dataLines (unstaged)
0 1 2 3 4 5
Cursors:
c0, c1, c2, c3, c4 = _lastCursor
Diffs:
[ d01, d12, d23, d34 ] = Forward Diffs
[ d10, d21, d32, d43 ] = Backward Diffs
Slices: = common txt slices between snapshots
[ s01, s12, s23, s34 ]
::
Data Structure
Snapshot B > Snapshot C
_nextDiff _nextDiff > Next snapshot
_prevDiff _prevDiff or Null if at the end
V A V V
Diff B->A Diff B->C Diff C->B
slice = txtBA slice = txtBC slice = txtBA
snap snap snap
'''
# '''
# Undo,Redo Logic
#
# Old:
#
# ::
#
# _snapshotId: = last saved/undo/redo state
# 3 = doc4
# _snapshots:
# [doc1, doc2, doc3, doc4, doc5, doc6, . . .]
#
# New:
#
# ::
#
# SnapshotId:
# 2
# Snapshots: _lastSnap _dataLines (unstaged)
# ╒═══╕ ╒═══╕ ╒═══╕ ╒═══╕ ╒═══╕ ╒═══
# │ 0 │ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5
# └───┘ └───┘ └───┘ └───┘ └───┘ └───
# Cursors:
# c0, c1, c2, c3, c4 = _lastCursor
# Diffs:
# [ d01, d12, d23, d34 ] = Forward Diffs
# [ d10, d21, d32, d43 ] = Backward Diffs
# Slices: = common txt slices between snapshots
# [ s01, s12, s23, s34 ]
#
# ::
#
# Data Structure
# ╔═══════════════╗ ╔═══════════════
# Snapshot B ║ ┌─────────────>║ Snapshot C ║
# ╟───────────────╢ │ ╟───────────────
# _nextDiff ║──────┐ │ ║ _nextDiff ║───> Next snapshot
# ┌───║ _prevDiff ║ │ │ ┌───║ _prevDiff ║ or Null if at the end
# │ ╚═══════════════╝ │ │ │ ╚═══════════════
# V A V │ V
# ╔═══════════════╗ │ ╔═══════════════╗ ╔═══════════════
# ║ Diff B->A ║ │ ║ Diff B->C ║ ║ Diff C->B
# ╟───────────────╢ │ ╟───────────────╢ ╟───────────────
# ║ slice = txtBA ║ │ ║ slice = txtBC ║ ║ slice = txtBA
# ║ snap ║ │ ║ snap ║ ║ snap
# ╚═══════════════╝ │ ╚═══════════════╝ ╚═══════════════
# │ │
# └─────────────────────────────
#
# '''
class _snapDiff():
'''
Doc:
@ -122,21 +125,27 @@ class TTkTextDocument():
'_dataLines', '_modified',
'_snap', '_snapChanged',
'_lastSnap', '_lastCursor',
'_backgroundColor',
'_docMutex',
# Signals
'contentsChange', 'contentsChanged',
'formatChanged',
'cursorPositionChanged',
'undoAvailable', 'redoAvailable', 'undoCommandAdded',
'modificationChanged'
)
def __init__(self, *, text:TTkString=" ") -> None:
from TermTk.TTkGui.textcursor import TTkTextCursor
self._docMutex = Lock()
self.cursorPositionChanged = pyTTkSignal(TTkTextCursor)
self.contentsChange = pyTTkSignal(int,int,int) # int line, int linesRemoved, int linesAdded
self.contentsChanged = pyTTkSignal()
self.formatChanged = pyTTkSignal()
self.undoAvailable = pyTTkSignal(bool)
self.redoAvailable = pyTTkSignal(bool)
self.undoCommandAdded = pyTTkSignal()
self.modificationChanged = pyTTkSignal(bool)
self._backgroundColor = TTkColor.RST
text = text
self._dataLines = [TTkString(t) for t in text.split('\n')]
self._modified = False
@ -170,6 +179,12 @@ class TTkTextDocument():
# z1 = l1+a1 + (a2-r2)
# z2 = l2+a2
def _acquire(self) -> None:
self._docMutex.acquire()
def _release(self) -> None:
self._docMutex.release()
@staticmethod
def _mergeChangesSlices(ch1,ch2):
l1,r1,a1 = ch1
@ -221,10 +236,12 @@ class TTkTextDocument():
remLines = len(self._dataLines)
if not isinstance(text, str) and not isinstance(text,TTkString):
text=str(text)
self._acquire()
self._dataLines = [TTkString(t) for t in text.split('\n')]
self._modified = False
self._lastSnap = self._dataLines.copy()
self._snap = TTkTextDocument._snapshot(self._lastCursor, None, None)
self._release()
self.contentsChanged.emit()
self.contentsChange.emit(0,remLines,len(self._dataLines))
self._snapChanged = None
@ -232,11 +249,13 @@ class TTkTextDocument():
def appendText(self, text):
if type(text) == str:
text = TTkString() + text
self._acquire()
oldLines = len(self._dataLines)
self._dataLines += text.split('\n')
self._modified = False
self._lastSnap = self._dataLines.copy()
self._snap = TTkTextDocument._snapshot(self._lastCursor, None, None)
self._release()
self.contentsChanged.emit()
self.contentsChange.emit(oldLines,0,len(self._dataLines)-oldLines)
self._snapChanged = None
@ -292,10 +311,12 @@ class TTkTextDocument():
(not next and not self._snap._prevDiff) ):
return None
self._acquire()
if next:
self._snap = self._snap.getNextSnap(self._dataLines)
else:
self._snap = self._snap.getPrevSnap(self._dataLines)
self._release()
self._lastSnap = self._dataLines.copy()
self._lastCursor = self._snap._cursor.copy()

57
TermTk/TTkGui/textdocument_highlight.py

@ -0,0 +1,57 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
__all__ = ['TextDocumentHighlight']
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkGui import TTkTextDocument
class TextDocumentHighlight(TTkTextDocument):
__slots__ = (
#Signals
'highlightUpdate')
def __init__(self, *args, **kwargs):
self.highlightUpdate = pyTTkSignal()
super().__init__(*args, **kwargs)
TTkLog.warn("Pygments not found!!!")
@staticmethod
def getStyles() -> list[str]:
return []
@staticmethod
def getLexers() -> list[str]:
return []
@pyTTkSlot(str)
def setStyle(self, alias:str) -> None:
pass
@pyTTkSlot(str)
def setLexer(self, alias:str) -> None:
pass
@pyTTkSlot(str)
def guessLexerFromFilename(self, fileName:str) -> None:
pass

303
TermTk/TTkGui/textdocument_highlight_pygments.py

@ -0,0 +1,303 @@
# MIT License
#
# Copyright (c) 2024 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
__all__ = ['TextDocumentHighlight']
from pygments import highlight
from pygments.util import ClassNotFound
from pygments.styles import get_all_styles
from pygments.lexers import guess_lexer, guess_lexer_for_filename, get_lexer_by_name, special, get_all_lexers
from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter
from pygments.formatter import Formatter
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Token, Whitespace
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkCore.timer import TTkTimer
from TermTk.TTkGui.textdocument import TTkTextDocument
class _TTkFormatter(Formatter):
class Data():
__slots__=('lines', 'block', 'error', 'multiline')
def __init__(self, lines, block):
self.lines = lines
self.block = block
self.error = None
self.multiline = False
__slots__ = ('_dl', '_blockNum', '_highlightStyles', '_defaultColor')
def __init__(self, *args, **kwargs):
self._defaultColor = TTkColor.RST
super().__init__(*args, **kwargs)
self._highlightStyles = {}
self._blockNum = 1
for token, style in self.style:
# Token = Token.Comment.PreprocFile
# style = {
# 'color': '6272a4',
# 'bgcolor': None,
# 'bold': False, 'italic': False, 'underline': False,
# 'border': None,
# 'roman': None, 'sans': None, 'mono': None,
# 'ansicolor': None, 'bgansicolor': None}
# TTkLog.debug(f"{token=} {style=}")
color = TTkColor.RST
if style['color']:
color += TTkColor.fg(f"#{style['color']}")
if style['bgcolor']:
color += TTkColor.bg(f"#{style['bgcolor']}")
if style['bold']:
color += TTkColor.BOLD
if style['italic']:
color += TTkColor.ITALIC
if style['underline']:
color += TTkColor.UNDERLINE
self._highlightStyles[token] = color
def setDl(self,dl):
self._dl = dl
def setDefaultColor(self, color:TTkColor) -> None:
self._defaultColor = color
def format(self, tokensource, _):
multiline = False
multilineId = 0
for ttype, value in tokensource:
if ttype == Error and self._dl.error is None:
self._dl.error = len(self._dl.lines)-1
# self._dl.multiline = ttype == Comment.Multiline
multiline = ttype == Comment.Multiline
while ttype not in self._highlightStyles:
ttype = ttype.parent
# TTkLog.debug (f"{ttype=}")
# TTkLog.debug (f"{value=}")
color:TTkColor = self._highlightStyles[ttype]
if not color.hasForeground():
color += self._defaultColor
values = value.split('\n')
self._dl.lines[-1] += TTkString(values[0],color)
self._dl.lines += [TTkString(t,color) for t in values[1:]]
self._dl.block[-1] = self._blockNum
self._dl.block += [self._blockNum]*(len(values)-1)
# self._dl.lines += [TTkString(t) for t in value.split('\n')]
# multiline = len(values)>1 if self._dl.lines[-1]._text == values[-1] else self._dl.multiline
# if self._dl.lines[-1]._text == '' or not multiline:
# self._blockNum += 1
# multilineId = len(self._dl.lines)
if multiline:
multilineId += len(values)
else:
multilineId = 0
self._blockNum += 1
if multiline:
self._dl.multiline = multilineId
class TextDocumentHighlight(TTkTextDocument):
_linesRefreshed:int = 30
__slots__ = (
'_timerRefresh',
'_blocks', '_changedContent', '_refreshContent',
'_lexer', '_formatter',
'_defaultForegroundColor',
#Signals
'highlightUpdate')
def __init__(self, **kwargs):
self.highlightUpdate = pyTTkSignal()
self._lexer = None
self._blocks = []
self._defaultForegroundColor = TTkColor.RST
# self._formatter = _TTkFormatter(style='dracula')
self._formatter = _TTkFormatter(style='gruvbox-dark')
super().__init__(**kwargs)
self._timerRefresh = TTkTimer()
self._timerRefresh.timeout.connect(self._refreshEvent)
self._changedContent = (0,0,len(self._dataLines))
self._refreshContent = (0,TextDocumentHighlight._linesRefreshed)
# self.contentsChange.connect(lambda a,b,c: TTkLog.debug(f"{a=} {b=} {c=}"))
self.contentsChange.connect(self._saveChangedContent)
try:
self._lexer = guess_lexer(self.toPlainText())
TTkLog.debug(f"Using Lexer: {self._lexer.name}")
except ClassNotFound:
self._lexer = special.TextLexer()
self._timerRefresh.start(0.3)
@staticmethod
def getStyles() -> list[str]:
return sorted(get_all_styles())
@staticmethod
def getLexers() -> list[str]:
return sorted(list(set(b for a in get_all_lexers() for b in a[1])))
@pyTTkSlot(str)
def setStyle(self, alias:str) -> None:
self._formatter = formatter = _TTkFormatter(style=alias)
if (color:=formatter.style.background_color) and color != "#000000":
self._backgroundColor = TTkColor.bg(color)
else:
self._backgroundColor = TTkColor.RST
if self._backgroundColor == TTkColor.RST:
self._defaultForegroundColor = TTkColor.RST
else:
r,g,b = self._backgroundColor.bgToRGB()
if r+g+b < 127*3:
self._defaultForegroundColor = TTkColor.WHITE
else:
self._defaultForegroundColor = TTkColor.BLACK
TTkLog.debug(f"{color=} {alias=} {formatter.style}")
self._changedContent = (0,0,len(self._dataLines))
self._refreshContent = (0,TextDocumentHighlight._linesRefreshed)
self._timerRefresh.start(0.3)
@pyTTkSlot(str)
def setLexer(self, alias:str) -> None:
try:
self._lexer = get_lexer_by_name(alias)
self._changedContent = (0,0,len(self._dataLines))
self._refreshContent = (0,TextDocumentHighlight._linesRefreshed)
self._timerRefresh.start(0.3)
TTkLog.debug(f"Using Lexer: {self._lexer.name}")
except ClassNotFound:
self._lexer = special.TextLexer()
@pyTTkSlot(str)
def guessLexerFromFilename(self, fileName:str) -> None:
with open(fileName, 'r') as f:
content = f.read()
try:
self._lexer = guess_lexer_for_filename(fileName, content)
TTkLog.debug(f"Using Lexer: {self._lexer.name}")
except ClassNotFound:
self._lexer = special.TextLexer()
@pyTTkSlot(int,int,int)
def _saveChangedContent(self,a,b,c):
if self._changedContent:
self._changedContent = TTkTextDocument._mergeChangesSlices(self._changedContent,(a,b,c))
else:
self._changedContent = (a,b,c)
if not self._refreshContent:
self._refreshContent = (self._changedContent[0], TextDocumentHighlight._linesRefreshed)
self._timerRefresh.start(0.1)
@pyTTkSlot()
def _refreshEvent(self):
if not self._refreshContent: return
self._acquire()
ra,rb = self._refreshContent
if self._changedContent:
ca,cb,cc = self._changedContent
self._changedContent = None
self._blocks[ca:ca+cb] = [0]*cc
ra = min(ra,ca)
# find the beginning of the current block
# TTkLog.debug(self._blocks)
if ra and self._blocks:
blockId = self._blocks[ra]
for i,v in enumerate(reversed(self._blocks[:ra])):
# TTkLog.debug(f"{i=}:{v=} {blockId=}")
if v == blockId or not blockId:
blockId = v
ra -= 1
rb += 1
else:
break
# TTkLog.debug(f"{ra=} {rb=}")
eof = False
if (ra+rb) >= len(self._dataLines):
rb = len(self._dataLines)-ra
eof=True
tsl = self._dataLines[ra:ra+rb]
# Find the offset from the first not empty line
# because pygments autostrip the heading empty lines
offset = 0
for i,l in enumerate(tsl):
if l != '':
offset = i
break
# TTkLog.debug(f"Refresh {self._lexer.name} {ra=} {rb=}")
tsl1 = [TTkString()]*(offset+1)
block = [0]*(offset+1)
kfd = _TTkFormatter.Data(tsl1, block)
self._formatter.setDl(kfd)
self._formatter.setDefaultColor(self._defaultForegroundColor)
rawl = [l._text for l in tsl[offset:]]
rawt = '\n'.join(rawl)
highlight(rawt, self._lexer, self._formatter)
# for ll in tsl:
# TTkLog.debug(f"1: -{ll}-")
# for ll in tsl1:
# TTkLog.debug(f"2: -{ll}-")
tsl1 = tsl1[:rb]
block = block[:rb]
self._dataLines[ra:ra+rb] = tsl1 + tsl[len(tsl1):]
self._blocks[ra:ra+rb] = block + [-1]*(rb-len(block))
# TTkLog.debug(self._blocks)
if kfd.error is not None:
self._refreshContent = (ra+kfd.error,rb<<1)
# TTkLog.debug(f"Error: {self._refreshContent=}")
elif kfd.multiline is not None:
self._refreshContent = (ra+kfd.multiline,rb<<1)
elif (ra+rb) < len(self._dataLines):
self._refreshContent = (ra+rb,TextDocumentHighlight._linesRefreshed)
else:
self._refreshContent = None
# TTkLog.debug(f"{self._refreshContent=}")
if not eof:
self._timerRefresh.start(0.03)
else:
TTkLog.debug(f"Refresh {self._lexer.name} DONE!!!")
self._release()
self.highlightUpdate.emit()
self.formatChanged.emit()

5
TermTk/TTkWidgets/combobox.py

@ -336,9 +336,8 @@ class TTkComboBox(TTkContainer):
self._popupFrame = TTkResizableFrame(layout=TTkGridLayout(), size=(frameWidth,frameHeight))
TTkHelper.overlay(self, self._popupFrame, 0, 0)
listw = TTkList(parent=self._popupFrame)
TTkLog.debug(f"{self._list}")
for item in self._list:
listw.addItem(item)
# TTkLog.debug(f"{self._list}")
listw.addItems(self._list)
if self._id != -1:
listw.setCurrentRow(self._id)
listw.textClicked.connect(self._callback)

4
TermTk/TTkWidgets/listwidget.py

@ -272,8 +272,7 @@ class TTkListWidget(TTkAbstractScrollView):
def addItems(self, items):
'''addItems'''
for item in items:
self.addItem(item)
self.addItemsAt(items=items, pos=len(self._items))
def _placeItems(self):
minw = self.width()
@ -292,6 +291,7 @@ class TTkListWidget(TTkAbstractScrollView):
def addItemsAt(self, items, pos):
'''addItemsAt'''
items = [TTkAbstractListItem(text=i) if isinstance(i, str) or isinstance(i, TTkString) else i for i in items]
for item in items:
if not issubclass(type(item),TTkAbstractListItem):
TTkLog.error(f"{item=} is not an TTkAbstractListItem")

101
TermTk/TTkWidgets/texedit.py

@ -24,13 +24,15 @@ __all__ = ['TTkTextEditView', 'TTkTextEdit']
from math import log10, floor
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.color import TTkColor
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.canvas import TTkCanvas
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent
from TermTk.TTkGui.clipboard import TTkClipboard
from TermTk.TTkGui.textwrap1 import TTkTextWrap
from TermTk.TTkGui.textcursor import TTkTextCursor
@ -59,14 +61,14 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView):
super().__init__(**kwargs)
self.setMaximumWidth(2)
def _wrapChanged(self):
def _wrapChanged(self) -> None:
dt = max(1,self._textWrap._lines[-1][0])
off = self._startingNumber
width = 1+max(len(str(int(dt+off))),len(str(int(off))))
self.setMaximumWidth(width)
self.update()
def setTextWrap(self, tw):
def setTextWrap(self, tw) -> None:
self._textWrap = tw
tw.wrapChanged.connect(self._wrapChanged)
self._wrapChanged()
@ -77,7 +79,7 @@ class _TTkTextEditViewLineNumber(TTkAbstractScrollView):
else:
return self.size()
def paintEvent(self, canvas):
def paintEvent(self, canvas: TTkCanvas) -> None:
if not self._textWrap: return
_, oy = self.getViewOffsets()
w, h = self.size()
@ -244,11 +246,11 @@ class TTkTextEditView(TTkAbstractScrollView):
return self._multiLine
@pyTTkSlot(bool)
def _undoAvailable(self, available):
def _undoAvailable(self, available) -> None:
self.undoAvailable.emit(available)
@pyTTkSlot(bool)
def _redoAvailable(self, available):
def _redoAvailable(self, available) -> None:
self.redoAvailable.emit(available)
# def toHtml(self, encoding): pass
@ -261,31 +263,31 @@ class TTkTextEditView(TTkAbstractScrollView):
# return self._textDocument.toMarkdown()
# return ""
def toAnsi(self):
def toAnsi(self) -> str:
'''toAnsi'''
if self._textDocument:
return self._textDocument.toAnsi()
return ""
def toPlainText(self):
def toPlainText(self) ->str:
'''toPlainText'''
if self._textDocument:
return self._textDocument.toPlainText()
return ""
def toRawText(self):
def toRawText(self) -> TTkString:
'''toRawText'''
if self._textDocument:
return self._textDocument.toRawText()
return TTkString()
def isUndoAvailable(self):
def isUndoAvailable(self) -> bool:
'''isUndoAvailable'''
if self._textDocument:
return self._textDocument.isUndoAvailable()
return False
def isRedoAvailable(self):
def isRedoAvailable(self) -> bool:
'''isRedoAvailable'''
if self._textDocument:
return self._textDocument.isRedoAvailable()
@ -295,13 +297,14 @@ class TTkTextEditView(TTkAbstractScrollView):
'''document'''
return self._textDocument
def setDocument(self, document:TTkTextDocument):
def setDocument(self, document:TTkTextDocument) -> None:
'''setDocument'''
if self._textDocument:
self._textDocument.contentsChanged.disconnect(self._documentChanged)
self._textDocument.cursorPositionChanged.disconnect(self._cursorPositionChanged)
self._textDocument.undoAvailable.disconnect(self._undoAvailable)
self._textDocument.redoAvailable.disconnect(self._redoAvailable)
self._textDocument.formatChanged.disconnect(self.update)
self._textWrap.wrapChanged.disconnect(self.update)
if not document:
document = TTkTextDocument()
@ -312,6 +315,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self._textDocument.cursorPositionChanged.connect(self._cursorPositionChanged)
self._textDocument.undoAvailable.connect(self._undoAvailable)
self._textDocument.redoAvailable.connect(self._redoAvailable)
self._textDocument.formatChanged.connect(self.update)
# Trigger an update when the rewrap happen
self._textWrap.wrapChanged.connect(self.update)
@ -327,53 +331,53 @@ class TTkTextEditView(TTkAbstractScrollView):
def isReadOnly(self) -> bool :
return self._readOnly
def setReadOnly(self, ro):
def setReadOnly(self, ro) -> None:
self._readOnly = ro
self.disableWidgetCursor(ro)
def clear(self):
def clear(self) -> None:
self.setText(TTkString())
def lineWrapMode(self):
def lineWrapMode(self) -> TTkK.LineWrapMode:
return self._lineWrapMode
def setLineWrapMode(self, mode):
def setLineWrapMode(self, mode:TTkK.LineWrapMode):
self._lineWrapMode = mode
if mode == TTkK.NoWrap:
if mode == TTkK.LineWrapMode.NoWrap:
self._textWrap.disable()
else:
self._textWrap.enable()
if mode == TTkK.WidgetWidth:
if mode == TTkK.LineWrapMode.WidgetWidth:
self._textWrap.setWrapWidth(self.width())
self._textWrap.rewrap()
@pyTTkSlot(str)
def setText(self, text):
def setText(self, text) -> None:
self.viewMoveTo(0, 0)
self._textDocument.setText(text)
self._updateSize()
@pyTTkSlot(str)
def append(self, text):
def append(self, text) -> None:
self._textDocument.appendText(text)
self._updateSize()
@pyTTkSlot()
def undo(self):
def undo(self) -> None:
if c := self._textDocument.restoreSnapshotPrev():
self._textCursor.restore(c)
@pyTTkSlot()
def redo(self):
def redo(self) -> None:
if c := self._textDocument.restoreSnapshotNext():
self._textCursor.restore(c)
@pyTTkSlot()
def clear(self):
def clear(self) -> None:
pass
@pyTTkSlot()
def copy(self):
def copy(self) -> None:
if not self._textCursor.hasSelection():
txt = TTkString('\n').join(self._textCursor.getLinesUnderCursor())
else:
@ -381,7 +385,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self._clipboard.setText(txt)
@pyTTkSlot()
def cut(self):
def cut(self) -> None:
if not self._textCursor.hasSelection():
self._textCursor.movePosition(moveMode=TTkTextCursor.MoveAnchor, operation=TTkTextCursor.StartOfLine)
self._textCursor.movePosition(moveMode=TTkTextCursor.KeepAnchor, operation=TTkTextCursor.EndOfLine)
@ -390,31 +394,31 @@ class TTkTextEditView(TTkAbstractScrollView):
self._textCursor.removeSelectedText()
@pyTTkSlot()
def paste(self):
def paste(self) -> None:
txt = self._clipboard.text()
self.pasteEvent(txt)
@pyTTkSlot()
def _documentChanged(self):
def _documentChanged(self) -> None:
self._rewrap()
self.textChanged.emit()
def _rewrap(self):
def _rewrap(self) -> None:
self._textWrap.rewrap()
self.viewChanged.emit()
self.update()
@pyTTkSlot(TTkColor)
def setColor(self, color):
def setColor(self, color:TTkColor) -> None:
self.textCursor().setColor(color)
@pyTTkSlot(TTkTextCursor)
def _cursorPositionChanged(self, cursor):
def _cursorPositionChanged(self, cursor:TTkTextCursor) -> None:
if cursor == self._textCursor:
self.currentColorChanged.emit(cursor.positionColor())
self._pushCursor()
def resizeEvent(self, w, h):
def resizeEvent(self, w:int, h:int) -> None:
if ( self.lineWrapMode() == TTkK.WidgetWidth and
w != self._lastWrapUsed and
w > self._textWrap._tabSpaces ):
@ -423,7 +427,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self._rewrap()
return super().resizeEvent(w,h)
def _updateSize(self):
def _updateSize(self) -> None:
self._hsize = max( len(l) for l in self._textDocument._dataLines ) + 1
def viewFullAreaSize(self) -> tuple[int,int]:
@ -434,7 +438,7 @@ class TTkTextEditView(TTkAbstractScrollView):
elif self.lineWrapMode() == TTkK.FixedWidth:
return self.wrapWidth(), self._textWrap.size()
def _pushCursor(self):
def _pushCursor(self) -> None:
ox, oy = self.getViewOffsets()
x,y = self._textWrap.dataToScreenPosition(
@ -452,7 +456,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
def _setCursorPos(self, x, y, moveAnchor=True, addCursor=False):
def _setCursorPos(self, x, y, moveAnchor=True, addCursor=False) -> tuple[int,int]:
x,y = self._textWrap.normalizeScreenPosition(x,y)
line, pos = self._textWrap.screenToDataPosition(x,y)
if addCursor:
@ -464,7 +468,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self._scrolToInclude(x,y)
return x, y
def _scrolToInclude(self, x, y):
def _scrolToInclude(self, x, y) -> None:
# Scroll the area (if required) to include the position x,y
_,_,w,h = self.geometry()
offx, offy = self.getViewOffsets()
@ -472,7 +476,7 @@ class TTkTextEditView(TTkAbstractScrollView):
offy = max(min(offy, y),y-h+1)
self.viewMoveTo(offx, offy)
def mousePressEvent(self, evt) -> bool:
def mousePressEvent(self, evt: TTkMouseEvent) -> bool:
if self._readOnly:
return super().mousePressEvent(evt)
ox, oy = self.getViewOffsets()
@ -482,12 +486,12 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
return True
def mouseReleaseEvent(self, evt) -> bool:
def mouseReleaseEvent(self, evt: TTkMouseEvent) -> bool:
if self._textCursor.hasSelection():
self.copy()
return True
def mouseDragEvent(self, evt) -> bool:
def mouseDragEvent(self, evt: TTkMouseEvent) -> bool:
if self._readOnly:
return super().mouseDragEvent(evt)
ox, oy = self.getViewOffsets()
@ -498,7 +502,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
return True
def mouseDoubleClickEvent(self, evt) -> bool:
def mouseDoubleClickEvent(self, evt: TTkMouseEvent) -> bool:
if self._readOnly:
return super().mouseDoubleClickEvent(evt)
self._textCursor.select(TTkTextCursor.WordUnderCursor)
@ -509,7 +513,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
return True
def mouseTapEvent(self, evt) -> bool:
def mouseTapEvent(self, evt: TTkMouseEvent) -> bool:
if self._readOnly:
return super().mouseTapEvent(evt)
self._textCursor.select(TTkTextCursor.LineUnderCursor)
@ -520,7 +524,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
return True
def pasteEvent(self, txt:str):
def pasteEvent(self, txt:str) -> bool:
txt = TTkString(txt)
if not self._multiLine:
txt = TTkString().join(txt.split('\n'))
@ -537,7 +541,7 @@ class TTkTextEditView(TTkAbstractScrollView):
self.update()
return True
def keyEvent(self, evt):
def keyEvent(self, evt: TTkKeyEvent) -> bool:
if self._readOnly:
return super().keyEvent(evt)
@ -669,13 +673,17 @@ class TTkTextEditView(TTkAbstractScrollView):
return super().keyEvent(evt)
def paintEvent(self, canvas):
def paintEvent(self, canvas: TTkCanvas) -> None:
ox, oy = self.getViewOffsets()
style = self.currentStyle()
color = style['color']
selectColor = style['selectedColor']
lineColor = style['lineColor']
backgroundColor = self._textDocument._backgroundColor
if backgroundColor != TTkColor.RST:
canvas.fill(color=backgroundColor)
h = self.height()
subLines = self._textWrap._lines[oy:oy+h]
@ -684,7 +692,10 @@ class TTkTextEditView(TTkAbstractScrollView):
for y, l in enumerate(subLines):
t = outLines[l[0]-subLines[0][0]]
canvas.drawTTkString(pos=(-ox,y), text=t.substring(l[1][0],l[1][1]).tab2spaces(self._textWrap._tabSpaces))
text:TTkString = t.substring(l[1][0],l[1][1]).tab2spaces(self._textWrap._tabSpaces)
if backgroundColor != TTkColor.RST:
text = text.completeColor(backgroundColor)
canvas.drawTTkString(pos=(-ox,y), text=text)
if self._lineWrapMode == TTkK.FixedWidth:
canvas.drawVLine(pos=(self._textWrap._wrapWidth,0), size=h, color=lineColor)

0
tests/t.generic/test.argspec.001.py

0
tests/t.generic/test.classes.001.slots.py

0
tests/t.generic/test.classes.001.slots.typing.py

0
tests/t.generic/test.dataclass.001.py

0
tests/t.generic/test.generic.005.fnctl.py

0
tests/t.ui/test.ui.018.TextEdit.Pygments.py → tests/t.ui/test.ui.018.TextEdit.01.Pygments.py

118
tests/t.ui/test.ui.018.TextEdit.02.Pygments.py

@ -0,0 +1,118 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import os
import sys
import random
import argparse
sys.path.append(os.path.join(sys.path[0],'../..'))
import TermTk as ttk
def demoTextEdit(root, filenames):
frame = ttk.TTkFrame(parent=root, border=False, layout=ttk.TTkGridLayout())
te = ttk.TTkTextEdit()
te.setReadOnly(False)
file = filenames[0]
with open(file, 'r') as f:
content = f.read()
doc = ttk.TextDocumentHighlight(text=content)
te.setDocument(doc)
# use the widget size to wrap
# te.setLineWrapMode(ttk.TTkK.WidgetWidth)
# te.setWordWrapMode(ttk.TTkK.WordWrap)
# Use a fixed wrap size
# te.setLineWrapMode(ttk.TTkK.FixedWidth)
# te.setWrapWidth(100)
frame.layout().addWidget(te,1,0,1,9)
frame.layout().addWidget(ttk.TTkLabel(text="Wrap: ", maxWidth=6),0,0)
frame.layout().addWidget(lineWrap := ttk.TTkComboBox(list=['NoWrap','WidgetWidth','FixedWidth']),0,1)
frame.layout().addWidget(ttk.TTkLabel(text=" Type: ",maxWidth=7),0,2)
frame.layout().addWidget(wordWrap := ttk.TTkComboBox(list=['WordWrap','WrapAnywhere'], enabled=False),0,3)
frame.layout().addWidget(ttk.TTkLabel(text=" FixW: ",maxWidth=7),0,4)
frame.layout().addWidget(fixWidth := ttk.TTkSpinBox(value=te.wrapWidth(), maximum=500, minimum=10, enabled=False),0,5)
frame.layout().addWidget(ttk.TTkLabel(text=" Lexer: ",maxWidth=8),0,5)
frame.layout().addWidget(lexers := ttk.TTkComboBox(list=ttk.TextDocumentHighlight.getLexers()),0,6)
frame.layout().addWidget(ttk.TTkLabel(text=" Style: ",maxWidth=8),0,7)
frame.layout().addWidget(styles := ttk.TTkComboBox(list=ttk.TextDocumentHighlight.getStyles()),0,8)
lineWrap.setCurrentIndex(0)
wordWrap.setCurrentIndex(1)
fixWidth.valueChanged.connect(te.setWrapWidth)
lexers.currentTextChanged.connect(doc.setLexer)
styles.currentTextChanged.connect(doc.setStyle)
@ttk.pyTTkSlot(int)
def _lineWrapCallback(index):
if index == 0:
te.setLineWrapMode(ttk.TTkK.NoWrap)
wordWrap.setDisabled()
fixWidth.setDisabled()
elif index == 1:
te.setLineWrapMode(ttk.TTkK.WidgetWidth)
wordWrap.setEnabled()
fixWidth.setDisabled()
else:
te.setLineWrapMode(ttk.TTkK.FixedWidth)
wordWrap.setEnabled()
fixWidth.setEnabled()
lineWrap.currentIndexChanged.connect(_lineWrapCallback)
@ttk.pyTTkSlot(int)
def _wordWrapCallback(index):
if index == 0:
te.setWordWrapMode(ttk.TTkK.WordWrap)
else:
te.setWordWrapMode(ttk.TTkK.WrapAnywhere)
wordWrap.currentIndexChanged.connect(_wordWrapCallback)
return frame
def main():
parser = argparse.ArgumentParser()
parser.add_argument('-f', help='Full Screen', action='store_true')
parser.add_argument('filename', type=str, nargs='+',
help='the filename/s')
args = parser.parse_args()
root = ttk.TTk()
if args.f:
rootTree = root
root.setLayout(ttk.TTkGridLayout())
else:
rootTree = ttk.TTkWindow(parent=root,pos = (0,0), size=(100,40), title="Test Text Edit", layout=ttk.TTkGridLayout(), border=True)
demoTextEdit(rootTree, args.filename)
root.mainloop()
if __name__ == "__main__":
main()

4
tools/check.import.sh

@ -48,6 +48,10 @@ __check(){
-e "TTkTerm/input_thread.py:import threading, queue" \
-e "TTkTerm/input_thread.py:from ..drivers import TTkInputDriver" \
-e "TTkTerm/input.py:from .input_thread import *" |
grep -v \
-e "TTkGui/__init__.py:import importlib.util" \
-e "TTkGui/textdocument.py:from threading import Lock" \
-e "TTkGui/textdocument_highlight_pygments.py:from pygments" |
grep -v \
-e "TTkTerm/term.py:from ..drivers import *" \
-e "drivers/unix_thread.py:import sys, os" \

Loading…
Cancel
Save