First commit
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user