Files
I-SecretUpdate/ui/components/tooltip.py
T
2025-12-19 12:58:58 +01:00

140 lines
4.0 KiB
Python

"""
ToolTip Component
Lightweight tooltip widget that shows on hover with configurable delay.
"""
import tkinter as tk
from typing import Optional
class ToolTip:
"""
Creates a tooltip for a given widget.
Shows full text on hover if the displayed text is truncated.
"""
def __init__(
self,
widget: tk.Widget,
text: str,
delay: int = 500,
wrap_length: int = 300
):
"""
Initialize the tooltip.
Args:
widget: The widget to attach the tooltip to
text: The text to display in the tooltip
delay: Delay in milliseconds before showing tooltip
wrap_length: Maximum width in pixels before wrapping text
"""
self.widget = widget
self.text = text
self.delay = delay
self.wrap_length = wrap_length
self.tooltip_window: Optional[tk.Toplevel] = None
self.after_id: Optional[str] = None
# Bind hover events
self.widget.bind("<Enter>", self._on_enter, add="+")
self.widget.bind("<Leave>", self._on_leave, add="+")
self.widget.bind("<ButtonPress>", self._on_leave, add="+")
def _on_enter(self, event=None):
"""Handle mouse enter event."""
# Schedule tooltip to appear after delay
self._cancel_scheduled()
self.after_id = self.widget.after(self.delay, self._show_tooltip)
def _on_leave(self, event=None):
"""Handle mouse leave event."""
self._cancel_scheduled()
self._hide_tooltip()
def _cancel_scheduled(self):
"""Cancel scheduled tooltip appearance."""
if self.after_id:
self.widget.after_cancel(self.after_id)
self.after_id = None
def _show_tooltip(self):
"""Display the tooltip window."""
if self.tooltip_window or not self.text:
return
# Get widget position
x = self.widget.winfo_rootx() + 20
y = self.widget.winfo_rooty() + self.widget.winfo_height() + 5
# Create tooltip window
self.tooltip_window = tk.Toplevel(self.widget)
self.tooltip_window.wm_overrideredirect(True) # Remove window decorations
# Handle screen edge cases
screen_width = self.widget.winfo_screenwidth()
screen_height = self.widget.winfo_screenheight()
# Create label with text
label = tk.Label(
self.tooltip_window,
text=self.text,
justify=tk.LEFT,
background="#ffffe0", # Light yellow background
foreground="#000000", # Black text
relief=tk.SOLID,
borderwidth=1,
wraplength=self.wrap_length,
font=("TkDefaultFont", 9),
padx=8,
pady=6
)
label.pack()
# Update to get actual size
self.tooltip_window.update_idletasks()
tooltip_width = self.tooltip_window.winfo_width()
tooltip_height = self.tooltip_window.winfo_height()
# Adjust position if near screen edges
if x + tooltip_width > screen_width:
x = screen_width - tooltip_width - 10
if y + tooltip_height > screen_height:
y = self.widget.winfo_rooty() - tooltip_height - 5
# Position the tooltip
self.tooltip_window.wm_geometry(f"+{x}+{y}")
def _hide_tooltip(self):
"""Hide the tooltip window."""
if self.tooltip_window:
self.tooltip_window.destroy()
self.tooltip_window = None
def update_text(self, text: str):
"""
Update the tooltip text.
Args:
text: New text to display
"""
self.text = text
if self.tooltip_window:
self._hide_tooltip()
def destroy(self):
"""Clean up the tooltip."""
self._cancel_scheduled()
self._hide_tooltip()
# Unbind events
try:
self.widget.unbind("<Enter>")
self.widget.unbind("<Leave>")
self.widget.unbind("<ButtonPress>")
except:
pass