diff --git a/TermTk/TTkCore/TTkTerm/input.py b/TermTk/TTkCore/TTkTerm/input.py index 82d2324e..a2e71730 100644 --- a/TermTk/TTkCore/TTkTerm/input.py +++ b/TermTk/TTkCore/TTkTerm/input.py @@ -96,7 +96,7 @@ def key_process(self, stdinRead): y = int(m.group(3))-1 state = m.group(4) key = TTkMouseEvent.NoButton - evt = TTkMouseEvent.NoEvent + evt = TTkMouseEvent.Move tap = 0 def _checkTap(lastTime, tap): diff --git a/TermTk/TTkCore/TTkTerm/term_base.py b/TermTk/TTkCore/TTkTerm/term_base.py index 5c90f6a2..342f4b12 100644 --- a/TermTk/TTkCore/TTkTerm/term_base.py +++ b/TermTk/TTkCore/TTkTerm/term_base.py @@ -82,19 +82,23 @@ class Sigmask(): CTRL_Q = 0x0008 title: str = "TermTk" - mouse: bool = True width: int = 0 height: int = 0 + mouse: bool = True + directMouse: bool = False _sigWinChCb = None @staticmethod - def init(mouse: bool = True, title: str = "TermTk", sigmask=0): + def init(mouse: bool = True, directMouse: bool = False, title: str = "TermTk", sigmask=0): TTkTermBase.title = title - TTkTermBase.mouse = mouse + TTkTermBase.mouse = mouse | directMouse + TTkTermBase.directMouse = directMouse TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title)) if TTkTermBase.mouse: TTkTermBase.push(TTkTermBase.Mouse.ON) + if TTkTermBase.directMouse: + TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON) TTkTermBase.setEcho(False) TTkTermBase.CRNL(False) TTkTermBase.setSigmask(sigmask, False) @@ -118,6 +122,8 @@ def cont(): TTkTermBase.push(TTkTermBase.ALT_SCREEN + TTkTermBase.CLEAR + TTkTermBase.Cursor.HIDE + TTkTermBase.escTitle(TTkTermBase.title)) if TTkTermBase.mouse: TTkTermBase.push(TTkTermBase.Mouse.ON) + if TTkTermBase.directMouse: + TTkTermBase.push(TTkTermBase.Mouse.DIRECT_ON) TTkTermBase.setEcho(False) TTkTermBase.CRNL(False) diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 221a71a6..6726a201 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -440,3 +440,30 @@ def dndEnd(): TTkHelper._rootWidget.rootLayout().removeWidget(TTkHelper._dnd['d'].pixmap()) TTkHelper._dnd = None TTkHelper._rootWidget.update() + + # ToolTip Helper Methods + toolTipWidget = None + toolTipTrigger = lambda _: True + toolTipReset = lambda : True + + @staticmethod + def toolTipShow(tt): + TTkHelper.toolTipClose() + if not TTkHelper._rootWidget: return + TTkHelper.toolTipWidget = tt + rw,rh = TTkHelper._rootWidget.size() + tw,th = tt.size() + mx,my = TTkHelper._mousePos + x = max(0, min(mx-(tw//2),rw-tw)) + if my <= th: # Draw below the Mouse + y = my+1 + else: # Draw above the Mouse + y = max(0,my-th) + tt.move(x,y) + TTkHelper._rootWidget.rootLayout().addWidget(tt) + tt.raiseWidget() + + def toolTipClose(): + TTkHelper.toolTipReset() + if TTkHelper.toolTipWidget: + TTkHelper.toolTipWidget.close() diff --git a/TermTk/TTkCore/timer.py b/TermTk/TTkCore/timer.py index d73ce55e..60a203af 100644 --- a/TermTk/TTkCore/timer.py +++ b/TermTk/TTkCore/timer.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - # MIT License # # Copyright (c) 2021 Eugenio Parodi @@ -35,7 +33,7 @@ class TTkTimer(): _uid = 0 __slots__ = ( - '_id', '_running', + '_id', '_running', '_timer', 'timeout', '_timerEvent', '_delay', '_delayLock', '_quit', '_stopTime') @@ -44,6 +42,7 @@ def __init__(self): # Define Signals self.timeout = pyTTkSignal() self._running = True + self._timer = None self._id = TTkTimer._uid TTkTimer._uid +=1 @@ -69,36 +68,33 @@ def pyodideQuit(): def quit(self): pass - def run(self): - pass - - @pyTTkSlot(int) - def start(self, sec=0): + @pyTTkSlot(float) + def start(self, sec=0.0): + self.stop() if self._running: - pyodideProxy.setTimeout(int(sec*1000), self._id) + self._timer = pyodideProxy.setTimeout(int(sec*1000), self._id) + # pyodideProxy.consoleLog(f"Timer {self._timer}") @pyTTkSlot() def stop(self): - pass + # pyodideProxy.consoleLog(f"Timer {self._timer}") + if self._timer: + pyodideProxy.stopTimeout(self._timer) + self._timer = None else: class TTkTimer(threading.Thread): _timers = [] __slots__ = ( - 'timeout', '_timerEvent', - '_delay', '_delayLock', '_quit', - '_stopTime') + 'timeout', '_delay', + '_timer', '_quit', '_start') def __init__(self): - # Define Signals self.timeout = pyTTkSignal() - - self._timerEvent = threading.Event() - self._quit = threading.Event() - self._stopTime = 0 - self._delay=0 - self._delayLock = threading.Lock() - threading.Thread.__init__(self) + self._delay = 0 + self._quit = threading.Event() + self._start = threading.Event() + self._timer = threading.Event() + super().__init__() TTkTimer._timers.append(self) - threading.Thread.start(self) @staticmethod def quitAll(): @@ -107,28 +103,26 @@ def quitAll(): def quit(self): self._quit.set() - self._delay=1 - self._timerEvent.set() + self._timer.set() + self._start.set() def run(self): - while self._timerEvent.wait(): - self._timerEvent.clear() - while self._delay > 0: - # self._delayLock.acquire() - delay = self._delay - self._delay = 0 - # self._delayLock.release() - if self._quit.wait(delay): - return - self.timeout.emit() - - @pyTTkSlot(int) - def start(self, sec=0): - self._lastTime = time.time() + while not self._quit.is_set(): + self._start.wait() + self._start.clear() + if not self._timer.wait(self._delay): + self.timeout.emit() + + @pyTTkSlot(float) + def start(self, sec=0.0): self._delay = sec - self._timerEvent.set() + self._timer.set() + self._timer.clear() + self._start.set() + if not self.native_id: + super().start() @pyTTkSlot() def stop(self): - # TODO: Timer.stop() - self._stopTime = time.time() + self._timer.set() + diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index 4d28d8fc..c60c9214 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -54,7 +54,7 @@ def __init__(self, input): self.resize(1,1) input.inputEvent.connect(self._mouseInput) @pyTTkSlot(TTkKeyEvent, TTkMouseEvent) - def _mouseInput(self, kevt, mevt): + def _mouseInput(self, _, mevt): if mevt is not None: self._cursor = '✠' self._color = TTkColor.RST @@ -77,7 +77,7 @@ def paintEvent(self): #self._canvas.drawChar((0,0),'✜') __slots__ = ( - '_input', + '_input', '_termMouse', '_termDirectMouse', '_title', '_showMouseCursor', '_sigmask', @@ -86,6 +86,8 @@ def paintEvent(self): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + self._termMouse = True + self._termDirectMouse = kwargs.get('mouseTrack',False) self._input = TTkInput() self._input.inputEvent.connect(self._processInput) self._title = kwargs.get('title','TermTk') @@ -143,7 +145,11 @@ def mainloop(self): # Keep track of the multiTap to avoid the extra key release self._lastMultiTap = False - TTkTerm.init(title=self._title, sigmask=self._sigmask) + TTkTerm.init( + title=self._title, + sigmask=self._sigmask, + mouse=self._termMouse, + directMouse=self._termDirectMouse ) if self._showMouseCursor: TTkTerm.push(TTkTerm.Mouse.DIRECT_ON) @@ -174,6 +180,7 @@ def _mouse_event(self, mevt): # Upload the global mouse position # Mainly used by the drag pixmap display TTkHelper.setMousePos((mevt.x,mevt.y)) + TTkWidget._mouseOverProcessed = False # Avoid to broadcast a key release after a multitap event if mevt.evt == TTkK.Release and self._lastMultiTap: return @@ -188,12 +195,11 @@ def _mouse_event(self, mevt): # Mouse Events forwarded straight to the Focus widget: # - Drag - # - Move # - Release focusWidget = TTkHelper.getFocus() if ( focusWidget is not None and - mevt.evt != TTkK.Press and - mevt.key != TTkK.Wheel and + ( mevt.evt == TTkK.Drag or + mevt.evt == TTkK.Release ) and not TTkHelper.isDnD() ) : x,y = TTkHelper.absPos(focusWidget) nmevt = mevt.clone(pos=(mevt.x-x, mevt.y-y)) diff --git a/TermTk/TTkGui/__init__.py b/TermTk/TTkGui/__init__.py index 8ba6988a..33cfe160 100644 --- a/TermTk/TTkGui/__init__.py +++ b/TermTk/TTkGui/__init__.py @@ -2,4 +2,5 @@ from .textwrap1 import TTkTextWrap from .textcursor import TTkTextCursor from .textdocument import TTkTextDocument -from .clipboard import TTkClipboard \ No newline at end of file +from .clipboard import TTkClipboard +from .tooltip import TTkToolTip diff --git a/TermTk/TTkGui/drag.py b/TermTk/TTkGui/drag.py index d4e91b3c..261f6d68 100644 --- a/TermTk/TTkGui/drag.py +++ b/TermTk/TTkGui/drag.py @@ -30,7 +30,6 @@ class _TTkDragDisplayWidget(TTkWidget): __slots__ = ('_pixmap') def __init__(self, *args, **kwargs): TTkWidget.__init__(self, *args, **kwargs) - self._name = kwargs.get('name' , '_TTkDragDisplayWidget' ) self._x, self._y = TTkHelper.mousePos() def setPixmap(self, pixmap): diff --git a/TermTk/TTkGui/tooltip.py b/TermTk/TTkGui/tooltip.py new file mode 100644 index 00000000..a3d1a232 --- /dev/null +++ b/TermTk/TTkGui/tooltip.py @@ -0,0 +1,78 @@ +# MIT License +# +# Copyright (c) 2023 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. + +# from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.log import TTkLog +from TermTk.TTkCore.canvas import TTkCanvas +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.timer import TTkTimer +from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.string import TTkString +from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkCore.signal import pyTTkSlot + +class _TTkToolTipDisplayWidget(TTkWidget): + __slots__ = ('_toolTip', '_x', '_y') + def __init__(self, *args, **kwargs): + TTkWidget.__init__(self, *args, **kwargs) + self._toolTip = kwargs.get('toolTip',TTkString()).split('\n') + w = 2+max([s.termWidth() for s in self._toolTip]) + h = 2+len(self._toolTip) + self.resize(w,h) + + def mouseEvent(self, evt): return False + + def paintEvent(self): + w,h = self.size() + borderColor = TTkColor.fg("#888888") + canvas = self.getCanvas() + canvas.drawBox(pos=(0,0),size=(w,h), color=borderColor) + canvas.drawChar(pos=(0, 0), char='╭', color=borderColor) + canvas.drawChar(pos=(w-1,0), char='╮', color=borderColor) + canvas.drawChar(pos=(w-1,h-1),char='╯', color=borderColor) + canvas.drawChar(pos=(0, h-1),char='╰', color=borderColor) + for i,s in enumerate(self._toolTip,1): + canvas.drawTTkString(pos=(1,i), text=s) + +class TTkToolTip(): + toolTipTimer = TTkTimer() + toolTip = TTkString() + + @pyTTkSlot() + @staticmethod + def _toolTipShow(): + # TTkLog.debug(f"TT:{TTkToolTip.toolTip}") + TTkHelper.toolTipShow(_TTkToolTipDisplayWidget(toolTip=TTkToolTip.toolTip)) + + @staticmethod + def trigger(toolTip): + # TTkToolTip.toolTipTimer.stop() + TTkToolTip.toolTip = toolTip + TTkToolTip.toolTipTimer.start(1) + + @staticmethod + def reset(): + TTkToolTip.toolTipTimer.stop() + +TTkToolTip.toolTipTimer.timeout.connect(TTkToolTip._toolTipShow) +TTkHelper.toolTipTrigger = TTkToolTip.trigger +TTkHelper.toolTipReset = TTkToolTip.reset \ No newline at end of file diff --git a/TermTk/TTkTheme/theme.py b/TermTk/TTkTheme/theme.py index 98106de9..68b76127 100644 --- a/TermTk/TTkTheme/theme.py +++ b/TermTk/TTkTheme/theme.py @@ -133,6 +133,11 @@ def loadTheme(theme): buttonBorderColorFocus = TTkColor.fg("#ffff00") + TTkColor.BOLD '''Default to **TTkColor.fg("#ffff00") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' + buttonTextColorHover = TTkColor.fg("#dddd88")+TTkColor.bg("#000050")+TTkColor.BOLD + '''Default to **TTkColor.fg("#dddd88")+TTkColor.bg("#000066")+** :class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' + buttonBorderColorHover = TTkColor.fg("#ffffcc") + TTkColor.BOLD + '''Default to **TTkColor.fg("#ffff88") + **:class:`~TermTk.TTkCore.color.TTkColor.BOLD`''' + buttonTextColorDisabled = textColorDisabled '''Default to :class:`textColorDisabled`''' buttonBorderColorDisabled= borderColorDisabled diff --git a/TermTk/TTkWidgets/Fancy/treewidget.py b/TermTk/TTkWidgets/Fancy/treewidget.py index 95e93847..3aa7714e 100644 --- a/TermTk/TTkWidgets/Fancy/treewidget.py +++ b/TermTk/TTkWidgets/Fancy/treewidget.py @@ -38,7 +38,7 @@ def __init__(self, *args, **kwargs): self.setMinimumSize(1, 1) def paintEvent(self): - if self._checked: + if self.isChecked(): self._canvas.drawText(pos=(0,0), text="▼") else: self._canvas.drawText(pos=(0,0), text="▶") diff --git a/TermTk/TTkWidgets/button.py b/TermTk/TTkWidgets/button.py index fd9fc2df..32685f14 100644 --- a/TermTk/TTkWidgets/button.py +++ b/TermTk/TTkWidgets/button.py @@ -23,9 +23,10 @@ # SOFTWARE. from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.signal import pyTTkSignal -from TermTk.TTkWidgets.widget import * +from TermTk.TTkWidgets.widget import TTkWidget class TTkButton(TTkWidget): ''' TTkButton: @@ -213,6 +214,16 @@ def keyEvent(self, evt): return True return False + def enterEvent(self, evt) -> bool: + self.update() + + def leaveEvent(self, evt) -> bool: + self.update() + + def mouseMoveEvent(self, evt) -> bool: + self.update() + return super().mouseMoveEvent(evt) + def paintEvent(self): if not self.isEnabled(): borderColor = self._borderColorDisabled @@ -232,15 +243,20 @@ def paintEvent(self): grid = TTkCfg.theme.buttonBoxGridUnchecked borderColor = TTkCfg.theme.buttonBorderColorUnchecked textColor = TTkCfg.theme.buttonTextColorUnchecked + if self.hasFocus(): + borderColor = self._borderColorFocus else: grid = TTkCfg.theme.buttonBoxGrid - borderColor = self._borderColor if self.hasFocus(): textColor = self._textColorFocus + borderColor = self._borderColorFocus + elif self.isEntered(): + textColor = TTkCfg.theme.buttonTextColorHover + borderColor = TTkCfg.theme.buttonBorderColorHover else: textColor = self._textColor - if self.hasFocus(): - borderColor = self._borderColorFocus + borderColor = self._borderColor + text = self._text w = self.width()-2 h = self.height() diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index ba09144c..cde8fc04 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -26,6 +26,7 @@ from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.helper import TTkHelper +from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkTemplates.lookandfeel import TTkLookAndFeel @@ -80,6 +81,9 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): :param int minHeight: the minHeight of the widget, defaults to 0 :param [int,int] minSize: the minSize [width,height] of the widget, optional + :param toolTip: This property holds the widget's tooltip + :type toolTip: :class:`~TermTk.TTkCore.string.TTkString` + :param lookAndFeel: the style helper to be used for any customization :type lookAndFeel: :class:`~TermTk.TTkTemplates.lookandfeel.TTkTTkLookAndFeel` @@ -100,6 +104,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '_pendingMouseRelease', '_enabled', '_lookAndFeel', + '_toolTip', #Signals 'focusChanged') @@ -138,6 +143,8 @@ def __init__(self, *args, **kwargs): self._visible = kwargs.get('visible', True) self._enabled = kwargs.get('enabled', True) + self._toolTip = TTkString(kwargs.get('toolTip','')) + self._focus = False self._focus_policy = TTkK.NoFocus @@ -340,30 +347,30 @@ def _mouseEventLayoutHandle(evt, layout): if item.layoutItemType == TTkK.WidgetItem and not item.isEmpty(): widget = item.widget() if not widget._visible: continue - wevt = None - mouseEvent = False - if isinstance(evt, TTkMouseEvent): - mouseEvent = True - wx,wy,ww,wh = widget.geometry() - # Skip the mouse event if outside this widget - if wx <= x < wx+ww and wy <= y < wy+wh: - wevt = evt.clone(pos=(x-wx, y-wy)) - if mouseEvent: - if wevt is not None: - if widget.mouseEvent(wevt): - return True - continue - + wx,wy,ww,wh = widget.geometry() + # Skip the mouse event if outside this widget + if not (wx <= x < wx+ww and wy <= y < wy+wh): continue + wevt = evt.clone(pos=(x-wx, y-wy)) + if widget.mouseEvent(wevt): + return True elif item.layoutItemType == TTkK.LayoutItem: levt = evt.clone(pos=(x, y)) if TTkWidget._mouseEventLayoutHandle(levt, item): return True return False + _mouseOver = None + _mouseOverTmp = None + _mouseOverProcessed = False def mouseEvent(self, evt): ''' .. caution:: Don't touch this! ''' if not self._enabled: return True + # Saving self in this global variable + # So that after the "_mouseEventLayoutHandle" + # this tmp value will hold the last widget below the mouse + TTkWidget._mouseOverTmp = self + # Mouse Drag has priority because it # should be handled by the focused widget # unless there is a Drag and Drop event ongoing @@ -376,7 +383,7 @@ def mouseEvent(self, evt): return True # If there is an overlay and it is modal, - # return False if this widget id not part of any + # return False if this widget is not part of any # of the widgets above the modal if not TTkHelper.checkModalOverlay(self): return False @@ -400,10 +407,27 @@ def mouseEvent(self, evt): return True return ret - # handle own events + # handle Enter/Leave Events + # _mouseOverTmp hold the top widget under the mouse + # if different than self it means that it is a child if evt.evt == TTkK.Move: + if not TTkWidget._mouseOverProcessed: + if TTkWidget._mouseOver != TTkWidget._mouseOverTmp == self: + if TTkWidget._mouseOver: + # TTkLog.debug(f"Leave: {TTkWidget._mouseOver._name}") + TTkWidget._mouseOver.leaveEvent(evt) + TTkWidget._mouseOver = self + # TTkLog.debug(f"Enter: {TTkWidget._mouseOver._name}") + TTkHelper.toolTipClose() + if self._toolTip and self._toolTip != '': + TTkHelper.toolTipTrigger(self._toolTip) + # TTkHelper.triggerToolTip(self._name) + TTkWidget._mouseOver.enterEvent(evt) + TTkWidget._mouseOverProcessed = True if self.mouseMoveEvent(evt): return True + else: + TTkHelper.toolTipClose() if evt.evt == TTkK.Release: self._pendingMouseRelease = False @@ -639,6 +663,9 @@ def setFocusPolicy(self, policy): def focusInEvent(self): pass def focusOutEvent(self): pass + def isEntered(self): + return self._mouseOver == self + def isEnabled(self): return self._enabled @@ -663,6 +690,12 @@ def setLookAndFeel(self, laf): self._lookAndFeel = laf self._lookAndFeel.modified.connect(self.update) + def toolTip(self): + return self._toolTip + + def setToolTip(self, toolTip): + self._toolTip = toolTip + _ttkProperties = { 'X' : { 'init': {'name':'x', 'type':int } , diff --git a/demo/demo.py b/demo/demo.py index ce4d2371..e1df2994 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -255,12 +255,14 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('-f', help='Full Screen (default)', action='store_true') parser.add_argument('-w', help='Windowed', action='store_true') + parser.add_argument('-t', help='Track Mouse', action='store_true') args = parser.parse_args() windowed = args.w + mouseTrack = args.t ttk.TTkLog.use_default_file_logging() - root = ttk.TTk(title="pyTermTk Demo") + root = ttk.TTk(title="pyTermTk Demo", mouseTrack=mouseTrack) if windowed: winTabbed1 = ttk.TTkWindow(parent=root,pos=(0,0), size=(120,40), title="pyTermTk Showcase", border=True, layout=ttk.TTkGridLayout(), flags=ttk.TTkK.NONE) border = True diff --git a/docs/MDNotes/TODO.md b/docs/MDNotes/TODO.md index 0fb17999..d218867b 100644 --- a/docs/MDNotes/TODO.md +++ b/docs/MDNotes/TODO.md @@ -52,6 +52,10 @@ ## Signal/Slots - [x] Implement Signal/Slots +## WeakRef +- [ ] TTkTimer._timers +- [ ] Signals/Slots + ## Logs - [x] Log Class - [ ] Run Logger on a separate thread (push string to a queue) diff --git a/tests/pytest/mock_term.py b/tests/pytest/mock_term.py index 3e514f2e..003f0506 100644 --- a/tests/pytest/mock_term.py +++ b/tests/pytest/mock_term.py @@ -79,7 +79,7 @@ def registerResizeCb(_): pass @staticmethod def exit(): pass @staticmethod - def init(title,sigmask): pass + def init(title,sigmask,mouse,directMouse): pass @staticmethod def getTerminalSize(): return 250,70 diff --git a/tests/sandbox/Makefile b/tests/sandbox/Makefile index 80f70c67..ee932f08 100644 --- a/tests/sandbox/Makefile +++ b/tests/sandbox/Makefile @@ -39,4 +39,14 @@ buildSandbox: www find ../../tmp/TermTk/ -name "*.py" | sed 's,.*tmp/,,' | sort | xargs tar cvzf bin/TermTk.tgz -C ../../tmp find ../../tutorial -name "*.py" | sort | xargs tar cvzf bin/tutorial.tgz + find ../../demo/paint.py ../../demo/ttkode.py ../../demo/demo.py ../../demo/showcase/*.* | sort | xargs tar cvzf bin/demo.tgz + +buildTestSandbox: www + rm -rf bin + mkdir -p bin + + $( cd ../../ ; tools/prepareBuild.sh release ; ) + + find ../../TermTk/ -name "*.py" | sort | xargs tar cvzf bin/TermTk.tgz + find ../../tutorial -name "*.py" | sort | xargs tar cvzf bin/tutorial.tgz find ../../demo/paint.py ../../demo/ttkode.py ../../demo/demo.py ../../demo/showcase/*.* | sort | xargs tar cvzf bin/demo.tgz \ No newline at end of file diff --git a/tests/sandbox/sandbox.html b/tests/sandbox/sandbox.html index 995124f9..f94fab2a 100644 --- a/tests/sandbox/sandbox.html +++ b/tests/sandbox/sandbox.html @@ -144,8 +144,12 @@ return [term.cols, term.rows] }, setTimeout: function(t, i) { - // console.log("TIME",i,t) - setTimeout(() => ttk_timer(i), t) + // console.log("TIME (Start)",i,t) + return setTimeout(() => ttk_timer(i), t) + }, + stopTimeout: function(t) { + // console.log("TIME (Stop)",t) + clearTimeout(t) }, clearTimeout: function(){ let highestTimeoutId = setTimeout(";"); @@ -179,6 +183,59 @@ term.write('Tutorials - Loaded\n\r') + /* Sidebar + Fetch all the files in the pyodide.FS + And push them in the sidebar + */ + let getAllFiles = function(p){ + let ls = pyodide.FS.readdir(p) + let ret = [] + for(let i=0 ; i { term.reset() @@ -253,59 +313,6 @@ `,{ globals: namespace } ); - /* Sidebar - Fetch all the files in the pyodide.FS - And push them in the sidebar - */ - let getAllFiles = function(p){ - let ls = pyodide.FS.readdir(p) - let ret = [] - for(let i=0 ; i diff --git a/tests/test.generic.001.py b/tests/test.generic.001.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.002.py b/tests/test.generic.002.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.003.py b/tests/test.generic.003.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.004.footprint.py b/tests/test.generic.004.footprint.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.006.weakref.01.py b/tests/test.generic.006.weakref.01.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.006.weakref.02.py b/tests/test.generic.006.weakref.02.py old mode 100644 new mode 100755 diff --git a/tests/test.generic.007.timer.py b/tests/test.generic.007.timer.py new file mode 100755 index 00000000..720af1e9 --- /dev/null +++ b/tests/test.generic.007.timer.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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 threading + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +root = ttk.TTk(title="pyTermTk Timer Test") + +class TimerV2(): + __slots__ = ('timeout', '_timer') + def __init__(self): + # Define Signals + self.timeout = ttk.pyTTkSignal() + self._timer = None + + def quit(self): + if self._timer: + self._timer.cancel() + + @ttk.pyTTkSlot(float) + def start(self, sec=0.0): + if self._timer: + self._timer.cancel() + self._timer = threading.Timer(sec, self.timeout.emit) + self._timer.start() + + @ttk.pyTTkSlot() + def stop(self): + if self._timer: + self._timer.cancel() + +class TimerV3(threading.Thread): + __slots__ = ('timeout', '_timer', '_quit', '_delay') + def __init__(self): + self.timeout = ttk.pyTTkSignal() + self._delay = 0 + self._quit = threading.Event() + self._start = threading.Event() + self._timer = threading.Event() + super().__init__() + super().start() + + def quit(self): + self._quit.set() + self._timer.set() + self._start.set() + + def run(self): + while not self._quit.is_set(): + # ttk.TTkLog.info(f"t3-2 _start.wait") + self._start.wait() + self._start.clear() + + # ttk.TTkLog.info(f"t3-3 _timer.wait {self._delay=}") + if not self._timer.wait(self._delay): + # ttk.TTkLog.info(f"t3-5 (EMIT)") + self.timeout.emit() + #ttk.TTkLog.info(f"t3-6") + self._quit.set() + #ttk.TTkLog.info(f"t3-7") + + # def run(self): + # self.finished.wait(self.interval) + # if not self.finished.is_set(): + # self.function(*self.args, **self.kwargs) + # self.finished.set() + + @ttk.pyTTkSlot(float) + def start(self, sec=0.0): + self._delay = sec + self._timer.set() + self._timer.clear() + self._start.set() + + @ttk.pyTTkSlot() + def stop(self): + self._timer.set() + +sb = ttk.TTkSpinBox(parent=root, pos=(0,1), size=(10,1), value=2) +bStart = ttk.TTkButton(parent=root, text="Start", pos=(0,2), size=(10,3), border=True) +bStop = ttk.TTkButton(parent=root, text="Stop", pos=(0,5), size=(10,3), border=True) +# ttk.TTkButton(parent=root, text="Button 2", pos=(0,2), size=(10,3), toolTip="TT Button 2", border=True) +# ttk.TTkButton(parent=root, text="Button 2", pos=(0,2), size=(10,3), toolTip="TT Button 2", border=True) + +w1 = ttk.TTkWindow(parent=root, title="LOG", pos=(0,10), size=(90,20), layout=ttk.TTkGridLayout(), toolTip="TT Log Window\n With\nLogDump") +ttk.TTkLogViewer(parent=w1) + +t2 = TimerV2() +t2.timeout.connect(lambda : ttk.TTkLog.debug(f"timeout (t2)")) +t3 = TimerV3() +t3.timeout.connect(lambda : ttk.TTkLog.debug(f"timeout (t3)")) + +t3_loop = TimerV3() + +@ttk.pyTTkSlot() +def _loop(): + ttk.TTkLog.debug(f"timeout (t3) LOOP") + t3_loop.start(1) + +t3_loop.timeout.connect(_loop) +t3_loop.start(1) + + +@ttk.pyTTkSlot() +def _start(): + v = sb.value() + ttk.TTkLog.debug(f"start : {v=}") + t2.start(v) + t3.start(v) + +@ttk.pyTTkSlot() +def _stop(): + t2.stop() + t3.stop() + +bStart.clicked.connect(_start) +bStop.clicked.connect(_stop) + + +root.mainloop() \ No newline at end of file diff --git a/tests/test.ui.025.toolTip.py b/tests/test.ui.025.toolTip.py new file mode 100755 index 00000000..0204c7cd --- /dev/null +++ b/tests/test.ui.025.toolTip.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 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, argparse, math, random + +sys.path.append(os.path.join(sys.path[0],'..')) +import TermTk as ttk + +root = ttk.TTk(title="pyTermTk Demo", mouseTrack=True) + +ttk.TTkLabel( parent=root, text="Label 1", pos=(0,0), size=(10,1), toolTip="TT Label 1") +ttk.TTkButton(parent=root, text="Button 1", pos=(0,1), size=(10,1), toolTip="TT Button 1") +ttk.TTkButton(parent=root, text="Button 2", pos=(0,2), size=(10,3), toolTip="TT Button 2", border=True) +ttk.TTkButton(parent=root, text="Button 3", pos=(0,5), size=(20,3), toolTip="TT Button 3\n\nNewline", border=True) +ttk.TTkButton(parent=root, text="Button 3", pos=(21,0), size=(20,10), border=True, + toolTip= + ttk.TTkString(color=ttk.TTkColor.fg("#ff0000") ,text=" L😎rem ipsum\n")+ + ttk.TTkString(color=ttk.TTkColor.fg("#00ff00") ,text="dolor sit amet,\n ⌚ ❤ 💙 🙋'\nYepp!!!")) + +w1 = ttk.TTkWindow(parent=root, title="LOG", pos=(0,10), size=(90,20), layout=ttk.TTkGridLayout(), toolTip="TT Log Window\n With\nLogDump") +ttk.TTkLogViewer(parent=w1) +w2 = ttk.TTkWindow(parent=root, title="KeyLogger", pos=(0,30), size=(60,7), layout=ttk.TTkGridLayout()) +ttk.TTkKeyPressView(parent=w2, toolTip="This is a\nKey Logger Widget") + +root.mainloop() \ No newline at end of file