Source code for ahkpy.tooltip
import dataclasses as dc
import queue
from typing import Optional
from .flow import ahk_call, global_ahk_lock
from .settings import COORD_MODES, _set_coord_mode
from .timer import Timer, set_countdown
from .unset import UNSET
__all__ = [
"ToolTip",
]
[docs]@dc.dataclass
class ToolTip:
"""The tooltip object.
No more than 20 tooltips can be shown simultaneously.
Example usage::
tt = ToolTip(text="hello") # Doesn't show the tooltip yet
tt.text = "hello from attribute"
tt.show()
# ^^ Shows a tooltip with the text "hello from attribute" near the mouse
# cursor
tt.show(text="hello from keyword argument")
# ^^ Hides the previous tooltip and shows a new one with the text "hello
# from keyword argument" near the mouse cursor
"""
text: Optional[str] = None
x: Optional[int] = None
y: Optional[int] = None
relative_to: str = "window"
timeout: Optional[float] = None
_pool = queue.LifoQueue(maxsize=20)
for tooltip_id in range(20, 0, -1):
_pool.put(tooltip_id)
del tooltip_id
def __init__(self, text=None, *, x=None, y=None, relative_to="window", timeout=None):
self.text = text
self.x = x
self.y = y
if relative_to not in COORD_MODES:
raise ValueError(f"{relative_to!r} is not a valid coord mode")
self.relative_to = relative_to
self._id: Optional[int] = None
self._timer: Optional[Timer] = None
[docs] def show(self, text=None, *, x=UNSET, y=UNSET, relative_to=None, timeout=UNSET):
"""Show the tooltip.
If the method is called without the *x* and *y* arguments, the menu is
shown at the mouse cursor.
Otherwise, the tooltip's position depends on the *relative_to* argument.
Valid *relative_to* values are:
- ``"screen"`` – coordinates are relative to the desktop (entire
screen).
- ``"window"`` – coordinates are relative to the active window.
- ``"client"`` – coordinates are relative to the active window's client
area, excluding title bar, menu and borders.
The optional *x* and *y* arguments set the tooltip's position relative
to the area specified by the *relative_to* argument. The default
*relative_to* value is ``"window"``. So if you call
``tooltip.show("hello", x=42)``, the *y* coordinate will be the mouse
cursor's *y* coordinate, and the *x* coordinate will be 42 pixels to the
right of the active window.
The *text* argument is required to either be set as the instance
attribute, or passed as an argument.
If the optional *timeout* argument is given, the tooltip will be hidden
after this many seconds.
:command: `ToolTip
<https://www.autohotkey.com/docs/commands/ToolTip.htm>`_
"""
if not text and not self.text:
raise ValueError("text must not be empty")
elif not text:
text = self.text
if x is UNSET:
x = self.x
if y is UNSET:
y = self.y
x = x if x is not None else ""
y = y if y is not None else ""
if relative_to is None:
relative_to = self.relative_to
tooltip_id = self._acquire()
with global_ahk_lock:
_set_coord_mode("tooltip", relative_to)
ahk_call("ToolTip", str(text), x, y, tooltip_id)
if timeout is UNSET:
timeout = self.timeout
if timeout is not None:
if self._timer:
self._timer.start(timeout)
else:
self._timer = set_countdown(timeout, self.hide)
elif self._timer:
self._timer.stop()
self._timer = None
[docs] def hide(self):
"""Hide the tooltip."""
if self._id is None:
return
ahk_call("ToolTip", "", "", "", self._id)
if self._timer:
self._timer.stop()
self._timer = None
self._release()
def _acquire(self):
if self._id is None:
try:
self._id = ToolTip._pool.get_nowait()
except queue.Empty:
raise RuntimeError("cannot show more than 20 tooltips simultaneously") from None
return self._id
def _release(self):
if self._id is None:
return
try:
ToolTip._pool.put_nowait(self._id)
except queue.Full:
raise RuntimeError("tooltip pool is corrupted") from None
self._id = None