140 lines
4.0 KiB
Python
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
|