diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index a50c38fe..8f9b1737 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -35,9 +35,10 @@ class TTkHelper: _updateWidget = set() _updateBuffer = set() _mousePos = (0,0) - _cursorPos = [0,0] + _cursorPos = (0,0) _cursor = False _cursorType = TTkTerm.Cursor.BLINKING_BLOCK + _cursorWidget = None class _Overlay(): __slots__ = ('_widget','_prevFocus','_x','_y','_modal') def __init__(self,x,y,widget,prevFocus,modal): @@ -345,6 +346,30 @@ class TTkHelper: return TTkHelper.isParent(p, parent) return False + @staticmethod + def widgetAt(x, y, layout=None): + layout = layout if layout else TTkHelper._rootWidget.rootLayout() + lx,ly,lw,lh =layout.geometry() + lox, loy = layout.offset() + lx,ly,lw,lh = lx+lox, ly+loy, lw-lox, lh-loy + if x=lx+lw or y=lh+ly: return None + x-=lx + y-=ly + for item in reversed(layout.zSortedItems): + if item.layoutItemType() == TTkK.WidgetItem and not item.isEmpty(): + widget = item.widget() + if not widget._visible: continue + wx,wy,ww,wh = widget.geometry() + + if wx <= x < wx+ww and wy <= y < wy+wh: + return TTkHelper.widgetAt(x-wx, y-wy, widget.rootLayout()) + continue + + elif item.layoutItemType() == TTkK.LayoutItem: + if (wid:=TTkHelper.widgetAt(x, y, item)): + return wid + return layout.parentWidget() + @staticmethod def absPos(widget) -> (int,int): wx, wy = 0,0 @@ -433,13 +458,22 @@ class TTkHelper: TTkTerm.Cursor.hide() TTkHelper._cursorType = TTkTerm.Cursor.BLINKING_BLOCK TTkHelper._cursor = False + # TTkHelper._cursorWidget = None @staticmethod def moveCursor(widget, x, y): + TTkHelper._cursorWidget = widget xx, yy = TTkHelper.absPos(widget) - TTkHelper._cursorPos = [xx+x,yy+y] + pos = (xx+x,yy+y) + if TTkHelper._cursorPos == pos: + return + TTkHelper._cursorPos = pos TTkTerm.push(TTkTerm.Cursor.moveTo(yy+y+1,xx+x+1)) + @staticmethod + def cursorWidget(): + return TTkHelper._cursorWidget + class Color(TTkTermColor): pass # Drag and Drop related helper routines diff --git a/TermTk/TTkWidgets/lineedit.py b/TermTk/TTkWidgets/lineedit.py index b93a2cdc..a46429ed 100644 --- a/TermTk/TTkWidgets/lineedit.py +++ b/TermTk/TTkWidgets/lineedit.py @@ -63,6 +63,7 @@ class TTkLineEdit(TTkWidget): self.setMaximumHeight(1) self.setMinimumSize(1,1) self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) + self.enableWidgetCursor() @pyTTkSlot(str) def setText(self, text, cursorPos=0x1000): @@ -99,12 +100,11 @@ class TTkLineEdit(TTkWidget): if cursorPos - self._offset < 0: self._offset = cursorPos - TTkHelper.moveCursor(self,cursorPos-self._offset,0) - if self.hasFocus(): - if self._replace: - TTkHelper.showCursor(TTkK.Cursor_Blinking_Block) - else: - TTkHelper.showCursor(TTkK.Cursor_Blinking_Bar) + if self._replace: + self.setWidgetCursor(pos=(cursorPos-self._offset, 0), type=TTkK.Cursor_Blinking_Block) + else: + self.setWidgetCursor(pos=(cursorPos-self._offset, 0), type=TTkK.Cursor_Blinking_Bar) + self.update() def paintEvent(self, canvas): diff --git a/TermTk/TTkWidgets/texedit.py b/TermTk/TTkWidgets/texedit.py index d19b3d5b..a329221a 100644 --- a/TermTk/TTkWidgets/texedit.py +++ b/TermTk/TTkWidgets/texedit.py @@ -30,7 +30,6 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot -from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkGui.clipboard import TTkClipboard from TermTk.TTkGui.textwrap1 import TTkTextWrap from TermTk.TTkGui.textcursor import TTkTextCursor @@ -133,7 +132,9 @@ class TTkTextEditView(TTkAbstractScrollView): self._clipboard = TTkClipboard() self.setFocusPolicy(TTkK.ClickFocus + TTkK.TabFocus) self.setDocument(kwargs.get('document', TTkTextDocument())) + self.disableWidgetCursor(self._readOnly) self._updateSize() + self.viewChanged.connect(self._pushCursor) def multiLine(self) -> bool : '''multiline''' @@ -225,6 +226,7 @@ class TTkTextEditView(TTkAbstractScrollView): def setReadOnly(self, ro): self._readOnly = ro + self.disableWidgetCursor(ro) def clear(self): self.setText(TTkString()) @@ -334,8 +336,6 @@ class TTkTextEditView(TTkAbstractScrollView): return self.size() def _pushCursor(self): - if self._readOnly or not self.hasFocus(): - return ox, oy = self.getViewOffsets() x,y = self._textWrap.dataToScreenPosition( @@ -344,23 +344,13 @@ class TTkTextEditView(TTkAbstractScrollView): y -= oy x -= ox - if x > self.width() or y>=self.height() or \ - x<0 or y<0: - TTkHelper.hideCursor() - return - - # Avoid the show/move cursor to be called again if in the same position - if self._cursorParams and \ - self._cursorParams['pos'] == (x,y) and \ - self._cursorParams['replace'] == self._replace: - return - self._cursorParams = {'pos': (x,y), 'replace': self._replace} - TTkHelper.moveCursor(self,x,y) + if self._replace: - TTkHelper.showCursor(TTkK.Cursor_Blinking_Block) + self.setWidgetCursor(pos=(x,y), type=TTkK.Cursor_Blinking_Block) else: - TTkHelper.showCursor(TTkK.Cursor_Blinking_Bar) + self.setWidgetCursor(pos=(x,y), type=TTkK.Cursor_Blinking_Bar) + self.update() def _setCursorPos(self, x, y, moveAnchor=True, addCursor=False): @@ -389,6 +379,7 @@ class TTkTextEditView(TTkAbstractScrollView): ox, oy = self.getViewOffsets() self._setCursorPos(evt.x + ox, evt.y + oy, addCursor=evt.mod&TTkK.ControlModifier==TTkK.ControlModifier) self._textCursor.clearColor() + self._pushCursor() self.update() return True @@ -404,6 +395,7 @@ class TTkTextEditView(TTkAbstractScrollView): x,y = self._setCursorPos(evt.x + ox, evt.y + oy, moveAnchor=False) self._textCursor.clearColor() self._scrolToInclude(x,y) + self._pushCursor() self.update() return True @@ -414,6 +406,7 @@ class TTkTextEditView(TTkAbstractScrollView): if self._textCursor.hasSelection(): self.copy() self._textCursor.clearColor() + self._pushCursor() self.update() return True @@ -424,6 +417,7 @@ class TTkTextEditView(TTkAbstractScrollView): if self._textCursor.hasSelection(): self.copy() self._textCursor.clearColor() + self._pushCursor() self.update() return True @@ -536,6 +530,7 @@ class TTkTextEditView(TTkAbstractScrollView): cx, cy = self._textWrap.dataToScreenPosition(p.line, p.pos) self._updateSize() self._scrolToInclude(cx,cy) + self._pushCursor() self.update() return True else: # Input char @@ -549,17 +544,12 @@ class TTkTextEditView(TTkAbstractScrollView): cx, cy = self._textWrap.dataToScreenPosition(p.line, p.pos) self._updateSize() self._scrolToInclude(cx,cy) + self._pushCursor() self.update() return True return super().keyEvent(evt) - def focusInEvent(self): - self.update() - - def focusOutEvent(self): - TTkHelper.hideCursor() - def paintEvent(self, canvas): ox, oy = self.getViewOffsets() if self.hasFocus(): @@ -578,7 +568,6 @@ class TTkTextEditView(TTkAbstractScrollView): if self._lineWrapMode == TTkK.FixedWidth: canvas.drawVLine(pos=(self._textWrap._wrapWidth,0), size=h, color=TTkCfg.theme.treeLineColor) - self._pushCursor() class TTkTextEdit(TTkAbstractScrollArea): '''TTkTextEdit diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 5165bff0..c4995767 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -114,6 +114,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '_enabled', '_lookAndFeel', '_style', '_currentStyle', '_toolTip', + '_widgetCursor', '_widgetCursorEnabled', '_widgetCursorType', #Signals 'focusChanged', 'sizeChanged') @@ -123,6 +124,10 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.sizeChanged = pyTTkSignal(int,int) # self.sizeChanged.connect(self.resizeEvent) + self._widgetCursor = (0,0) + self._widgetCursorEnabled = False + self._widgetCursorType = TTkK.Cursor_Blinking_Bar + self._name = kwargs.get('name', self.__class__.__name__) self._parent = kwargs.get('parent', None ) @@ -676,6 +681,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self._focus = True self.focusChanged.emit(self._focus) self.focusInEvent() + self._pushWidgetCursor() def clearFocus(self): # TTkLog.debug(f"clearFocus: {self._name} - {self._focus}") @@ -805,3 +811,31 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.update() return True return False + + # Widget Cursor Helpers + def enableWidgetCursor(self, enable=True): + self._widgetCursorEnabled = enable + self._pushWidgetCursor() + + def disableWidgetCursor(self, disable=True): + self._widgetCursorEnabled = not disable + self._pushWidgetCursor() + + def setWidgetCursor(self, pos=None, type=None): + self._widgetCursor = pos if pos else self._widgetCursor + self._widgetCursorType = type if type else self._widgetCursorType + self._pushWidgetCursor() + + def _pushWidgetCursor(self): + if self._widgetCursorEnabled and self._visible and ( self._focus or self == TTkHelper.cursorWidget() ): + cx,cy = self._widgetCursor + ax, ay = TTkHelper.absPos(self) + if ( self == TTkHelper.widgetAt(cx+ax, cy+ay) or + # Since the blinking bar can be placed also at the left side of the next + # char, it can be displayed also if its position is one char outside the boudaries + ( self._widgetCursorType == TTkK.Cursor_Blinking_Bar and + self == TTkHelper.widgetAt(cx+ax-1, cy+ay) ) ): + TTkHelper.showCursor(self._widgetCursorType) + TTkHelper.moveCursor(self, cx, cy) + else: + TTkHelper.hideCursor()