""" 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("", self._on_enter, add="+") self.widget.bind("", self._on_leave, add="+") self.widget.bind("", 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("") self.widget.unbind("") self.widget.unbind("") except: pass