Browse Source

Added ToolTip feature

pull/114/head
Eugenio Parodi 3 years ago
parent
commit
a112bde7a7
  1. 27
      TermTk/TTkCore/helper.py
  2. 104
      TermTk/TTkCore/timer.py
  3. 1
      TermTk/TTkCore/ttk.py
  4. 3
      TermTk/TTkGui/__init__.py
  5. 1
      TermTk/TTkGui/drag.py
  6. 79
      TermTk/TTkGui/tooltip.py
  7. 35
      TermTk/TTkWidgets/widget.py
  8. 47
      tests/test.ui.025.toolTip.py

27
TermTk/TTkCore/helper.py

@ -440,3 +440,30 @@ class TTkHelper:
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()

104
TermTk/TTkCore/timer.py

@ -81,24 +81,15 @@ if importlib.util.find_spec('pyodideProxy'):
def stop(self):
pass
else:
class TTkTimer(threading.Thread):
# from .log import TTkLog
class TTkTimer():
_timers = []
__slots__ = (
'timeout', '_timerEvent',
'_delay', '_delayLock', '_quit',
'_stopTime')
__slots__ = ('timeout', '_timer')
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._timer = None
TTkTimer._timers.append(self)
threading.Thread.start(self)
@staticmethod
def quitAll():
@ -106,29 +97,76 @@ else:
timer.quit()
def quit(self):
self._quit.set()
self._delay=1
self._timerEvent.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()
if self._timer:
self._timer.cancel()
@pyTTkSlot(int)
def start(self, sec=0):
self._lastTime = time.time()
self._delay = sec
self._timerEvent.set()
if self._timer:
self._timer.cancel()
self._timer = threading.Timer(sec, self.timeout.emit)
self._timer.start()
@pyTTkSlot()
def stop(self):
# TODO: Timer.stop()
self._stopTime = time.time()
if self._timer:
self._timer.cancel()
# class TTkTimer(threading.Thread):
# _timers = []
# __slots__ = (
# 'timeout', '_timerEvent',
# '_delay', '_delayLock', '_quit',
# '_stopTime')
# 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)
# TTkTimer._timers.append(self)
# threading.Thread.start(self)
#
# @staticmethod
# def quitAll():
# for timer in TTkTimer._timers:
# timer.quit()
#
# def quit(self):
# self._quit.set()
# self._delay=1
# self._timerEvent.set()
#
# def run(self):
# while self._timerEvent.wait():
# self._timerEvent.clear()
# self._delayLock.acquire()
# if not self._delay:
# self._delayLock.release()
# continue
# 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()
# self._delayLock.acquire()
# self._delay = sec
# self._delayLock.release()
# self._timerEvent.set()
#
# @pyTTkSlot()
# def stop(self):
# # TODO: Timer.stop()
# self._stopTime = time.time()
#

1
TermTk/TTkCore/ttk.py

@ -180,6 +180,7 @@ class TTk(TTkWidget):
# 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

3
TermTk/TTkGui/__init__.py

@ -2,4 +2,5 @@ from .drag import TTkDrag, TTkDropEvent
from .textwrap1 import TTkTextWrap
from .textcursor import TTkTextCursor
from .textdocument import TTkTextDocument
from .clipboard import TTkClipboard
from .clipboard import TTkClipboard
from .tooltip import TTkToolTip

1
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):

79
TermTk/TTkGui/tooltip.py

@ -0,0 +1,79 @@
# MIT License
#
# Copyright (c) 2023 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.
# 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

35
TermTk/TTkWidgets/widget.py

@ -26,6 +26,7 @@ from TermTk.TTkCore.cfg import TTkCfg, TTkGlbl
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 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
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
@ -354,6 +361,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
_mouseOver = None
_mouseOverTmp = None
_mouseOverProcessed = False
def mouseEvent(self, evt):
''' .. caution:: Don't touch this! '''
if not self._enabled: return True
@ -402,15 +410,24 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
# handle Enter/Leave Events
# _mouseOverTmp hold the top widget under the mouse
# if different than self it means that it is a child
if ( TTkWidget._mouseOver != TTkWidget._mouseOverTmp == self ):
if TTkWidget._mouseOver:
TTkWidget._mouseOver.leaveEvent(evt)
TTkWidget._mouseOver = self
TTkWidget._mouseOver.enterEvent(evt)
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
@ -673,6 +690,12 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents):
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 } ,

47
tests/test.ui.025.toolTip.py

@ -0,0 +1,47 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2023 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 sys, os, argparse, math, random
sys.path.append(os.path.join(sys.path[0],'..'))
import TermTk as ttk
from math import sin, cos
ttk.TTkLog.use_default_file_logging()
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!!!"))
rf = 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=rf)
root.mainloop()
Loading…
Cancel
Save