Browse Source

TTkTextEdit: transition pointers to TTkTextCursor

pull/48/head
Eugenio Parodi 4 years ago
parent
commit
be8b2ab006
  1. 115
      TermTk/TTkGui/textcursor.py
  2. 7
      TermTk/TTkGui/textdocument.py
  3. 118
      TermTk/TTkWidgets/texedit.py

115
TermTk/TTkGui/textcursor.py

@ -22,6 +22,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkGui.textdocument import TTkTextDocument
class TTkTextCursor():
@ -47,6 +48,83 @@ class TTkTextCursor():
LineUnderCursor = SelectionType.LineUnderCursor
WordUnderCursor = SelectionType.WordUnderCursor
class MoveOperation():
NoMove = 0
'''Keep the cursor where it is'''
Start = 1
'''Move to the start of the document.'''
StartOfLine = 3
'''Move to the start of the current line.'''
StartOfBlock = 4
'''Move to the start of the current block.'''
StartOfWord = 5
'''Move to the start of the current word.'''
PreviousBlock = 6
'''Move to the start of the previous block.'''
PreviousCharacter = 7
'''Move to the previous character.'''
PreviousWord = 8
'''Move to the beginning of the previous word.'''
Up = 2
'''Move up one line.'''
Left = 9
'''Move left one character.'''
WordLeft = 10
'''Move left one word.'''
End = 11
'''Move to the end of the document.'''
EndOfLine = 13
'''Move to the end of the current line.'''
EndOfWord = 14
'''Move to the end of the current word.'''
EndOfBlock = 15
'''Move to the end of the current block.'''
NextBlock = 16
'''Move to the beginning of the next block.'''
NextCharacter = 17
'''Move to the next character.'''
NextWord = 18
'''Move to the next word.'''
Down = 12
'''Move down one line.'''
Right = 19
'''Move right one character.'''
WordRight = 20
'''Move right one word.'''
NextCell = 21
'''Move to the beginning of the next table cell inside the current table. If the current cell is the last cell in the row, the cursor will move to the first cell in the next row.'''
PreviousCell = 22
'''Move to the beginning of the previous table cell inside the current table. If the current cell is the first cell in the row, the cursor will move to the last cell in the previous row.'''
NextRow = 23
'''Move to the first new cell of the next row in the current table.'''
PreviousRow = 24
'''Move to the last cell of the previous row in the current table.'''
NoMove = MoveOperation.NoMove
Start = MoveOperation.Start
StartOfLine = MoveOperation.StartOfLine
StartOfBlock = MoveOperation.StartOfBlock
StartOfWord = MoveOperation.StartOfWord
PreviousBlock = MoveOperation.PreviousBlock
PreviousCharacter = MoveOperation.PreviousCharacter
PreviousWord = MoveOperation.PreviousWord
Up = MoveOperation.Up
Left = MoveOperation.Left
WordLeft = MoveOperation.WordLeft
End = MoveOperation.End
EndOfLine = MoveOperation.EndOfLine
EndOfWord = MoveOperation.EndOfWord
EndOfBlock = MoveOperation.EndOfBlock
NextBlock = MoveOperation.NextBlock
NextCharacter = MoveOperation.NextCharacter
NextWord = MoveOperation.NextWord
Down = MoveOperation.Down
Right = MoveOperation.Right
WordRight = MoveOperation.WordRight
NextCell = MoveOperation.NextCell
PreviousCell = MoveOperation.PreviousCell
NextRow = MoveOperation.NextRow
PreviousRow = MoveOperation.PreviousRow
class _prop():
__slots__ = ('anchor', 'position')
def __init__(self, anchor, position):
@ -87,13 +165,12 @@ class TTkTextCursor():
self.pos = p
self.line = l
__slots__ = ('_document', '_properties', '_docData')
__slots__ = ('_document', '_properties')
def __init__(self, *args, **kwargs):
self._properties = [TTkTextCursor._prop(
TTkTextCursor._CP(),
TTkTextCursor._CP())]
self._document = kwargs.get('document',TTkTextDocument())
self._docData = self._document._dataLines
def anchor(self):
return self._properties[0].anchor
@ -102,12 +179,24 @@ class TTkTextCursor():
return self._properties[0].position
def setPosition(self, line, pos, moveMode=MoveMode.MoveAnchor ):
TTkLog.debug(f"{line=}, {pos=}, {moveMode=}")
self._properties[0].position.set(line,pos)
if moveMode==TTkTextCursor.MoveAnchor:
self._properties[0].anchor.set(line,pos)
#def movePosition(self, operation, moveMode=MoveMode.MoveAnchor, n=1 ):
# pass
def movePosition(self, operation, moveMode=MoveMode.MoveAnchor, n=1 ):
if operation == TTkTextCursor.Right:
p = self.position()
if p.pos < len(self._document._dataLines[p.line]):
self.setPosition(p.line, p.pos+1, moveMode)
elif p.line < len(self._document._dataLines)-1:
self.setPosition(p.line+1, 0, moveMode)
elif operation == TTkTextCursor.Left:
p = self.position()
if p.pos > 0:
self.setPosition(p.line, p.pos-1, moveMode)
elif p.line > 0:
self.setPosition(p.line-1, len(self._document._dataLines[p.line-1]) , moveMode)
def document(self):
return self._document
@ -130,15 +219,15 @@ class TTkTextCursor():
elif selection == TTkTextCursor.SelectionType.LineUnderCursor:
line = self._properties[0].position.line
self._properties[0].position.pos = 0
self._properties[0].anchor.pos = len(self._docData[line])
self._properties[0].anchor.pos = len(self._document._dataLines[line])
elif selection == TTkTextCursor.SelectionType.WordUnderCursor:
line = self._properties[0].position.line
pos = self._properties[0].position.pos
# Split the current line from the current cursor position
# search the leftmost(on the right slice)/rightmost(on the left slice) word
# in order to match the full word under the cursor
splitBefore = self._docData[line].substring(to=pos)
splitAfter = self._docData[line].substring(fr=pos)
splitBefore = self._document._dataLines[line].substring(to=pos)
splitAfter = self._document._dataLines[line].substring(fr=pos)
xFrom = pos
xTo = pos
selectRE = '[a-zA-Z0-9:,./]*'
@ -161,7 +250,13 @@ class TTkTextCursor():
if not self.hasSelection(): return
selSt = self.selectionStart()
selEn = self.selectionEnd()
self._docData[selSt.line] = self._docData[selSt.line].substring(to=selSt.pos) + \
self._docData[selEn.line].substring(fr=selEn.pos)
self._document._dataLines = self._docData[:selSt.line+1] + self._docData[selEn.line+1:]
self._document._dataLines[selSt.line] = self._document._dataLines[selSt.line].substring(to=selSt.pos) + \
self._document._dataLines[selEn.line].substring(fr=selEn.pos)
self._document._dataLines = self._document._dataLines[:selSt.line+1] + self._document._dataLines[selEn.line+1:]
self.setPosition(selSt.line, selSt.pos)
self._document.contentsChanged.emit()
self._document.contentsChange.emit(selSt.line, selEn.line-selSt.line, 1)
def getHighlightedLine(self, line, color):
return self._document._dataLines[line]

7
TermTk/TTkGui/textdocument.py

@ -32,7 +32,7 @@ class TTkTextDocument():
'contentsChange', 'contentsChanged',
)
def __init__(self, *args, **kwargs):
self.contentsChange = pyTTkSignal(int,int,int,int) # int,int position, int charsRemoved, int charsAdded
self.contentsChange = pyTTkSignal(int,int,int) # int line, int linesRemoved, int linesAdded
self.contentsChanged = pyTTkSignal()
text = kwargs.get('text',"")
self._dataLines = [TTkString(t) for t in text.split('\n')]
@ -46,15 +46,14 @@ class TTkTextDocument():
def setText(self, text):
self._dataLines = [TTkString(t) for t in text.split('\n')]
self.contentsChanged.emit()
self.contentsChange.emit(0,0,0,len(text))
self.contentsChange.emit(0,0,len(self._dataLines))
def appendText(self, text):
if type(text) == str:
text = TTkString() + text
oldLines = len(self._dataLines)
oldPos = len(self._dataLines[-1])
self._dataLines += text.split('\n')
self.contentsChanged.emit()
self.contentsChange.emit(oldLines,oldPos,0,len(text))
self.contentsChange.emit(oldLines,0,len(self._dataLines)-oldLines)

118
TermTk/TTkWidgets/texedit.py

@ -36,7 +36,7 @@ from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView
class _TTkTextEditView(TTkAbstractScrollView):
__slots__ = (
'_textDocument', '_hsize', '_lines',
'_textCursor', '_cursorPos', '_cursorParams', '_selectionFrom', '_selectionTo',
'_textCursor', '_cursorParams',
'_tabSpaces',
'_lineWrapMode', '_wordWrapMode', '_wrapWidth', '_lastWrapUsed',
'_replace',
@ -56,6 +56,7 @@ class _TTkTextEditView(TTkAbstractScrollView):
self._readOnly = True
self._textDocument = TTkTextDocument()
self._textCursor = TTkTextCursor(document=self._textDocument)
self._textDocument.contentsChanged.connect(self._rewrap)
self._hsize = 0
self._lines = [(0,(0,0))]
self._tabSpaces = 4
@ -64,9 +65,6 @@ class _TTkTextEditView(TTkAbstractScrollView):
self._lineWrapMode = TTkK.NoWrap
self._wordWrapMode = TTkK.WrapAnywhere
self._replace = False
self._cursorPos = (0,0)
self._selectionFrom = (0,0)
self._selectionTo = (0,0)
self._cursorParams = None
self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus)
@ -103,16 +101,13 @@ class _TTkTextEditView(TTkAbstractScrollView):
@pyTTkSlot(str)
def setText(self, text):
self.viewMoveTo(0, 0)
self._textDocument = TTkTextDocument(text)
self._textCursor = TTkTextCursor(document=self._textDocument)
self._textDocument.setText(text)
self._updateSize()
self._rewrap()
@pyTTkSlot(str)
def append(self, text):
self._textDocument.appendText(text)
self._updateSize()
self._rewrap()
def _rewrap(self):
self._lines = []
@ -179,8 +174,9 @@ class _TTkTextEditView(TTkAbstractScrollView):
return
ox, oy = self.getViewOffsets()
y = self._textCursor.position().line
x,_ = self._cursorFromLinePos(y, self._textCursor.position().pos)
x,y = self._cursorFromDataPos(
self._textCursor.position().line,
self._textCursor.position().pos)
y -= oy
x -= ox
@ -204,38 +200,15 @@ class _TTkTextEditView(TTkAbstractScrollView):
self.update()
def _setCursorPos(self, x, y, alignRightTab=False):
x,y = self._cursorAlign(x,y, alignRightTab)
self._cursorPos = (x,y)
self._selectionFrom = (x,y)
self._selectionTo = (x,y)
self._setCursorPosNEW(x,y)
self._scrolToInclude(x,y)
self._setCursorPosNEW(x,y,alignRightTab)
def _setCursorPosNEW(self, x, y, alignRightTab=False, moveAnchor=True):
x,y = self._cursorAlign(x,y, alignRightTab)
_, pos = self._linePosFromCursor(x,y)
self._textCursor.setPosition(y, pos,
self._textCursor.setPosition(self._lines[y][0], pos,
moveMode=TTkTextCursor.MoveAnchor if moveAnchor else TTkTextCursor.KeepAnchor)
TTkLog.debug(f"{self._cursorPos=} - {pos=}")
self._scrolToInclude(x,y)
def _moveHCursor(self, x,y, hoff):
l, dx = self._linePosFromCursor(x,y)
dt, _ = self._lines[y]
# Due to the internal usage I assume hoff 1 or -1
dx += hoff
if hoff > 0 and dx>len(l) and dt<self._textDocument.lineCount():
dx = 0
dt += 1
elif dx<0:
if dt == 0: # Beginning of the file
dx = 0
else:
dt -= 1
dx = len(self._textDocument._dataLines[dt])
cx, cy = self._cursorFromDataPos(dt,dx)
self._setCursorPos(cx, cy, hoff>0)
def _scrolToInclude(self, x, y):
# Scroll the area (if required) to include the position x,y
_,_,w,h = self.geometry()
@ -250,7 +223,6 @@ class _TTkTextEditView(TTkAbstractScrollView):
def _eraseSelection(self):
if not self._textCursor.hasSelection(): return
self._textCursor.removeSelectedText()
self._rewrap()
def _cursorAlign(self, x, y, alignRightTab = False):
'''
@ -281,6 +253,12 @@ class _TTkTextEditView(TTkAbstractScrollView):
dt, (fr, to) = self._lines[y]
return self._textDocument._dataLines[dt], fr+self._textDocument._dataLines[dt].substring(fr,to).tabCharPos(x,self._tabSpaces)
def _widgetPositionFromTextCursor(self, line, pos):
for i,l in enumerate(self._lines):
if l[0] == line and l[1][0] <= pos < l[1][1]:
return pos-l[1][0], i
return 0,0
def _cursorFromLinePos(self,liney,p):
'''
return the x,y cursor position relative to the widget from the
@ -325,94 +303,40 @@ class _TTkTextEditView(TTkAbstractScrollView):
ox, oy = self.getViewOffsets()
x,y = self._cursorAlign(evt.x + ox, evt.y + oy)
self._setCursorPosNEW(x,y,moveAnchor=False)
cx = self._cursorPos[0]
cy = self._cursorPos[1]
if y < cy: # Mouse Dragged above the cursor
self._selectionFrom = ( x, y )
self._selectionTo = ( cx, cy )
elif y > cy: # Mouse Dragged below the cursor
self._selectionFrom = ( cx, cy )
self._selectionTo = ( x, y )
else: # Mouse on the same line of the cursor
self._selectionFrom = ( min(cx,x), y )
self._selectionTo = ( max(cx,x), y )
self._scrolToInclude(x,y)
self.update()
return True
def mouseDoubleClickEvent(self, evt) -> bool:
if self._readOnly:
return super().mouseDoubleClickEvent(evt)
ox, oy = self.getViewOffsets()
x,y = self._cursorAlign(evt.x + ox, evt.y + oy)
l,p = self._linePosFromCursor(x,y)
before = l.substring(to=p)
after = l.substring(fr=p)
xFrom = len(before)
xTo = len(before)
selectRE = '[a-zA-Z0-9:,./]*'
if m := before.search(selectRE+'$'):
xFrom -= len(m.group(0))
if m := after.search('^'+selectRE):
xTo += len(m.group(0))
self._selectionFrom = self._cursorFromLinePos(y,xFrom)
self._selectionTo = self._cursorFromLinePos(y,xTo)
self._cursorPos = self._selectionFrom
self._textCursor.select(TTkTextCursor.WordUnderCursor)
self.update()
return True
def mouseTapEvent(self, evt) -> bool:
if self._readOnly:
return super().mouseTapEvent(evt)
ox, oy = self.getViewOffsets()
x,y = self._cursorAlign(evt.x + ox, evt.y + oy)
self._cursorPos = (x,y)
l,_ = self._linePosFromCursor(x,y)
self._selectionFrom = self._cursorFromLinePos(y,0)
self._selectionTo = self._cursorFromLinePos(y,len(l))
self._cursorPos = self._selectionFrom
self._textCursor.select(TTkTextCursor.LineUnderCursor)
self.update()
return True
#def mouseReleaseEvent(self, evt) -> bool:
# if self._readOnly:
# return super().mouseReleaseEvent(evt)
# ox, oy = self.getViewOffsets()
# self._cursorPos = self._selectionFrom
# self.update()
# return True
def keyEvent(self, evt):
if self._readOnly:
return super().keyEvent(evt)
if evt.type == TTkK.SpecialKey:
_,_,w,h = self.geometry()
cx, cy = self._cursorPos
p = self._textCursor.position()
cx, cy = self._cursorFromDataPos(p.line, p.pos)
dt, (fr, to) = self._lines[cy]
# Don't Handle the special tab key, for now
if evt.key == TTkK.Key_Tab:
return False
if evt.key == TTkK.Key_Up: self._setCursorPos(cx , cy-1)
elif evt.key == TTkK.Key_Down: self._setCursorPos(cx , cy+1)
elif evt.key == TTkK.Key_Left: self._moveHCursor( cx , cy , -1 )
elif evt.key == TTkK.Key_Right: self._moveHCursor( cx , cy , +1 )
elif evt.key == TTkK.Key_Left: self._textCursor.movePosition(TTkTextCursor.Left)
elif evt.key == TTkK.Key_Right: self._textCursor.movePosition(TTkTextCursor.Right)
elif evt.key == TTkK.Key_End: self._setCursorPos(w , cy )
elif evt.key == TTkK.Key_Home: self._setCursorPos(0 , cy )
elif evt.key == TTkK.Key_PageUp: self._setCursorPos(cx , cy - h)
@ -493,9 +417,9 @@ class _TTkTextEditView(TTkAbstractScrollView):
for y, l in enumerate(self._lines[oy:oy+h]):
t = self._textDocument._dataLines[l[0]]
# apply the selection color if required
if selSt.line <= y+oy <= selEn.line:
pf = 0 if y+oy > selSt.line else selSt.pos
pt = len(t) if y+oy < selEn.line else selEn.pos
if selSt.line <= l[0] <= selEn.line:
pf = 0 if l[0] > selSt.line else selSt.pos
pt = len(t) if l[0] < selEn.line else selEn.pos
t = t.setColor(color=selectColor, posFrom=pf, posTo=pt )
self._canvas.drawText(pos=(-ox,y), text=t.substring(l[1][0],l[1][1]).tab2spaces(self._tabSpaces))
if self._lineWrapMode == TTkK.FixedWidth:

Loading…
Cancel
Save