Browse Source

Basic wrap implementation in the text edit

pull/34/head
Eugenio Parodi 4 years ago
parent
commit
9ef1bb1d88
  1. 3
      TermTk/TTkCore/canvas.py
  2. 31
      TermTk/TTkCore/constant.py
  3. 32
      TermTk/TTkCore/string.py
  4. 100
      TermTk/TTkWidgets/texedit.py
  5. 7
      demo/showcase/textedit.py

3
TermTk/TTkCore/canvas.py

@ -209,7 +209,7 @@ class TTkCanvas:
if isinstance(text, TTkString):
text = text.align(width=width, alignment=alignment, color=color)
txt, colors = text.getData()
txt, colors = text.tab2spaces().getData()
if forceColor:
colors=[color]*len(colors)
for i in range(max(0,-x), min(len(txt),self._width-x)):
@ -217,6 +217,7 @@ class TTkCanvas:
self._data[y][x+i] = txt[i]
self._colors[y][x+i] = colors[i].mod(x+i,y)
else:
text = text.replace('\t',' ')
if lentxt < width:
pad = width-lentxt
if alignment in [TTkK.NONE, TTkK.LEFT_ALIGN]:

31
TermTk/TTkCore/constant.py

@ -142,7 +142,7 @@ class TTkConstant:
Events reported by :class:`~TermTk.TTkCore.TTkTerm.inputmouse.TTkMouseEvent` -> :class:`~TermTk.TTkCore.TTkTerm.inputmouse.TTkMouseEvent.key`
'''
NoButton = 0x00000000
'''The button state does not refer to any button (see QMouseEvent::button()).'''
'''The button state does not refer to any button.'''
AllButtons = 0x07ffffff
'''This value corresponds to a mask of all possible mouse buttons. Use to set the 'acceptedButtons' property of a MouseArea to accept ALL mouse buttons.'''
LeftButton = 0x00000001
@ -164,6 +164,35 @@ class TTkConstant:
MiddleButton = MouseKey.MiddleButton
Wheel = MouseKey.Wheel
class WrapMode():
'''Those constants describes how text is wrapped in a document.'''
# NoWrap = 0x00
# '''Text is not wrapped at all.'''
WordWrap = 0x01
'''Text is wrapped at word boundaries.'''
# ManualWrap = 0x02
# '''Same as :class:`~TermTk.TTkCore.constant.TTkConstant.WrapMode.NoWrap`'''
WrapAnywhere = 0x03
'''Text can be wrapped at any point on a line, even if it occurs in the middle of a word.'''
WrapAtWordBoundaryOrAnywhere = 0x04
'''If possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.'''
# NoWrap = WrapMode.NoWrap
WordWrap = WrapMode.WordWrap
# ManualWrap = WrapMode.ManualWrap
WrapAnywhere = WrapMode.WrapAnywhere
WrapAtWordBoundaryOrAnywhere = WrapMode.WrapAtWordBoundaryOrAnywhere
class LineWrapMode():
NoWrap = 0x00
WidgetWidth = 0x01
FixedWidth = 0x03
NoWrap = LineWrapMode.NoWrap
WidgetWidth = LineWrapMode.WidgetWidth
FixedWidth = LineWrapMode.FixedWidth
# Events
class MouseEvent():
'''Input Mouse Event

32
TermTk/TTkCore/string.py

@ -130,7 +130,8 @@ class TTkString():
def __gt__(self, other): return self._text > other._text
def __ge__(self, other): return self._text >= other._text
def tab2spaces(self, tabSpaces):
def tab2spaces(self, tabSpaces=4):
'''Return the string representation with the tabs (converted in spaces) trimmed and aligned'''
ret = TTkString()
slices = self._text.split("\t")
ret._text += slices[0]
@ -145,6 +146,35 @@ class TTkString():
pos+=len(s)+1
return ret
def tabCharPos(self, pos, tabSpaces=4):
'''Return the char position in the string from the position in its representation with the tab solved
i.e.
::
pos X = 11
tab2Spaces |----------|---------------------|
Tabs |--| | |-| |-| |
_text Lorem ipsum dolor sit amet,
chars .....t .....t .....t ...t.....
ret x = 8 (tab is a char)
'''
slices = self._text.split("\t")
postxt = 0 # position of the text
lentxt = 0 # length of the text with resolved tabs
for s in slices:
lens = len(s)
lentxt += lens
postxt += lens
if pos<postxt:
return pos
spaces = tabSpaces - (lentxt+tabSpaces)%tabSpaces
lentxt += spaces
postxt += 1
pos -= spaces-1
return len(self._text)
def toAscii(self):
''' Return the ascii representation of the string '''
return self._text

100
TermTk/TTkWidgets/texedit.py

@ -33,19 +33,33 @@ from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView
class _TTkTextEditView(TTkAbstractScrollView):
__slots__ = (
'_lines', '_hsize',
'_lines', '_dataLines', '_hsize',
'_cursorPos', '_cursorParams', '_selectionFrom', '_selectionTo',
'_tabSpaces',
'_lineWrapMode', '_wordWrapMode', '_wrapWidth', '_lastWrapUsed',
'_replace',
'_readOnly'
)
'''
in order to support the line wrap, I need to divide the full data text in;
_dataLines = the entire text divided in lines, easy to add/remove/append lines
_lines = an array of tuples for each displayed line with a pointer to a
specific line and its slice to be shown at this coordinate;
[ (line, (posFrom, posTo)), ... ]
This is required to support the wrap feature
'''
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self._name = kwargs.get('name' , '_TTkTextEditView' )
self._readOnly = True
self._hsize = 0
self._lines = ['']
self._lines = [(0,(0,0))]
self._dataLines = ['']
self._tabSpaces = 4
self._wrapWidth = 30
self._lastWrapUsed = 0
self._lineWrapMode = TTkK.NoWrap
self._wordWrapMode = TTkK.NoWrap
self._replace = False
self._cursorPos = (0,0)
self._selectionFrom = (0,0)
@ -59,26 +73,86 @@ class _TTkTextEditView(TTkAbstractScrollView):
def setReadOnly(self, ro):
self._readOnly = ro
def wrapWidth(self):
return self._wrapWidth
def setWrapWidth(self, width):
self._wrapWidth = width
self._rewrap()
def lineWrapMode(self):
return self._lineWrapMode
def setLineWrapMode(self, mode):
self._lineWrapMode = mode
self._rewrap()
def wordWrapMode(self):
return self._wordWrapMode
def setWordWrapMode(self, mode):
self._wordWrapMode = mode
self._rewrap()
@pyTTkSlot(str)
def setText(self, text):
self.viewMoveTo(0, 0)
self._lines = []
self._dataLines = []
self.append(text)
@pyTTkSlot(str)
def append(self, text):
if type(text) == str:
text = TTkString() + text
self._lines += text.split('\n')
self._dataLines += text.split('\n')
self._updateSize()
self.viewChanged.emit()
self._rewrap()
def _rewrap(self):
self._lines = []
if self._lineWrapMode == TTkK.NoWrap:
def _process(i,l):
self._lines.append((i,(0,len(l))))
else:
if self._lineWrapMode == TTkK.WidgetWidth:
w = self.width()
if not w: return
elif self._lineWrapMode == TTkK.FixedWidth:
w = self._wrapWidth
def _process(i,l):
fr = 0
to = 0
while len(l):
fl = l.tab2spaces(self._tabSpaces)
if len(fl) <= w:
self._lines.append((i,(fr,fr+len(l))))
l=[]
else:
to = l.tabCharPos(w,self._tabSpaces)
self._lines.append((i,(fr,fr+to)))
l = l.substring(to)
fr += to
self.update()
for i,l in enumerate(self._dataLines):
_process(i,l)
def resizeEvent(self, w, h):
if w != self._lastWrapUsed and w>self._tabSpaces:
self._lastWrapUsed = w
self._rewrap()
def _updateSize(self):
self._hsize = max( [ len(l) for l in self._lines ] )
self._hsize = max( [ len(l) for l in self._dataLines ] )
def viewFullAreaSize(self) -> (int, int):
return self._hsize, len(self._lines)
if self._lineWrapMode == TTkK.NoWrap:
return self._hsize, len(self._lines)
elif self._lineWrapMode == TTkK.WidgetWidth:
return self.width(), len(self._lines)
elif self._lineWrapMode == TTkK.FixedWidth:
return self._wrapWidth, len(self._lines)
def viewDisplayedSize(self) -> (int, int):
return self.size()
@ -314,8 +388,8 @@ class _TTkTextEditView(TTkAbstractScrollView):
selectColor = TTkCfg.theme.lineEditTextColorSelected
h = self.height()
for y, t in enumerate(self._lines[oy:oy+h]):
t = t.tab2spaces(self._tabSpaces)
for y, l in enumerate(self._lines[oy:oy+h]):
t = self._dataLines[l[0]].substring(l[1][0],l[1][1]).tab2spaces(self._tabSpaces)
if self._selectionFrom[1] <= y+oy <= self._selectionTo[1]:
pf = 0 if y+oy > self._selectionFrom[1] else self._selectionFrom[0]
pt = len(t) if y+oy < self._selectionTo[1] else self._selectionTo[0]
@ -328,6 +402,9 @@ class TTkTextEdit(TTkAbstractScrollArea):
'_textEditView',
# Forwarded Methods
'setText', 'append', 'isReadOnly', 'setReadOnly'
'wrapWidth', 'setWrapWidth',
'lineWrapMode', 'setLineWrapMode',
'wordWrapMode', 'setWordWrapMode',
)
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
@ -338,4 +415,11 @@ class TTkTextEdit(TTkAbstractScrollArea):
self.append = self._textEditView.append
self.isReadOnly = self._textEditView.isReadOnly
self.setReadOnly = self._textEditView.setReadOnly
# Forward Wrap Methods
self.wrapWidth = self._textEditView.wrapWidth
self.setWrapWidth = self._textEditView.setWrapWidth
self.lineWrapMode = self._textEditView.lineWrapMode
self.setLineWrapMode = self._textEditView.setLineWrapMode
self.wordWrapMode = self._textEditView.wordWrapMode
self.setWordWrapMode = self._textEditView.setWordWrapMode

7
demo/showcase/textedit.py

@ -69,6 +69,13 @@ def demoTextEdit(root=None):
te.append(ttk.TTkString("Random TTkString Input Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD))
te.append(ttk.TTkString('\n').join([ getSentence(5,25,i) for i in range(50)]))
# use the widget size to wrap
# te.setLineWrapMode(ttk.TTkK.WidgetWidth)
# Use a fixed wrap size
te.setLineWrapMode(ttk.TTkK.FixedWidth)
te.setWrapWidth(40)
return te
def main():

Loading…
Cancel
Save