SXXXXXXX_GitUtility/gitsync_tool/gui/tooltip.py
2025-05-05 10:11:21 +02:00

147 lines
5.6 KiB
Python

# --- FILE: gitsync_tool/gui/tooltip.py ---
import tkinter as tk
from typing import Optional
# No other imports from the application are needed here.
class Tooltip:
"""
Simple tooltip implementation for Tkinter widgets.
Shows a label with text when the mouse enters a widget,
hides it on leave or click.
"""
def __init__(self, widget: tk.Widget, text: str):
"""
Initialize the Tooltip.
Args:
widget (tk.Widget): The widget this tooltip is associated with.
text (str): The text to display in the tooltip.
"""
self.widget: tk.Widget = widget
self.text: str = text
self.tooltip_window: Optional[tk.Toplevel] = None
self.show_id: Optional[str] = None # Timer ID for showing the tooltip
self.hide_id: Optional[str] = None # Timer ID for hiding (less common use)
# Bind events only if the widget exists
if self.widget and self.widget.winfo_exists():
# Use add='+' to not overwrite other potential bindings
self.widget.bind("<Enter>", self.schedule_show, add="+")
self.widget.bind("<Leave>", self.schedule_hide, add="+")
# Hide tooltip immediately if the widget is clicked
self.widget.bind("<ButtonPress>", self.hide_tip_now, add="+")
def schedule_show(self, event: Optional[tk.Event] = None) -> None:
"""Schedules the tooltip to appear after a delay."""
self.cancel_timers()
# Schedule showtip to be called after 500ms
# Check if widget still exists before setting timer
if self.widget and self.widget.winfo_exists():
self.show_id = self.widget.after(500, self.show_tip)
def schedule_hide(self, event: Optional[tk.Event] = None) -> None:
"""Schedules the tooltip to disappear after a short delay."""
self.cancel_timers()
# Schedule hide_tip_now after a shorter delay (e.g., 100ms)
# This prevents flickering if mouse moves quickly over/out
if self.widget and self.widget.winfo_exists():
self.hide_id = self.widget.after(100, self.hide_tip_now)
def cancel_timers(self) -> None:
"""Cancels any pending show or hide timers."""
# Cancel show timer
timer_id = self.show_id
self.show_id = None
if timer_id:
try:
if self.widget and self.widget.winfo_exists():
self.widget.after_cancel(timer_id)
except Exception:
# Ignore errors if widget/timer is already gone
pass
# Cancel hide timer
timer_id = self.hide_id
self.hide_id = None
if timer_id:
try:
if self.widget and self.widget.winfo_exists():
self.widget.after_cancel(timer_id)
except Exception:
pass
def show_tip(self) -> None:
"""Creates and displays the tooltip window."""
# Don't show if widget no longer exists or another timer is active
if not self.widget or not self.widget.winfo_exists() or self.hide_id:
return
# Hide any previous tooltip window just in case
self.hide_tip_now()
# Calculate tooltip position near the cursor or widget
x_cursor: int = 0
y_cursor: int = 0
try:
# Try to get cursor position relative to the screen
x_cursor = self.widget.winfo_pointerx() + 15
y_cursor = self.widget.winfo_pointery() + 10
except Exception:
# Fallback: position relative to the widget's top-left corner
try:
x_root: int = self.widget.winfo_rootx()
y_root: int = self.widget.winfo_rooty()
widget_height: int = self.widget.winfo_height()
x_cursor = x_root + 5 # Small offset from widget corner
y_cursor = y_root + widget_height + 5
except Exception:
# Cannot determine position if widget info fails
return
# Create the Toplevel window for the tooltip
self.tooltip_window = tw = tk.Toplevel(self.widget)
# Remove standard window decorations (title bar, borders)
tw.wm_overrideredirect(True)
try:
# Position the window using screen coordinates
tw.wm_geometry(f"+{int(x_cursor)}+{int(y_cursor)}")
except tk.TclError:
# Geometry might fail if coordinates are off-screen
if tw.winfo_exists():
tw.destroy()
self.tooltip_window = None
return
# Create the Label inside the Toplevel window
label = tk.Label(
tw,
text=self.text,
justify=tk.LEFT,
background="#ffffe0", # Standard light yellow background
relief=tk.SOLID,
borderwidth=1,
font=("tahoma", "8", "normal"), # Standard small font
wraplength=350, # Max width before wrapping text
)
# Add some internal padding to the label
label.pack(ipadx=3, ipady=3)
def hide_tip_now(self, event: Optional[tk.Event] = None) -> None:
"""Immediately hides the tooltip window if it exists."""
self.cancel_timers() # Cancel any pending show timers
tw = self.tooltip_window
self.tooltip_window = None
if tw:
try:
# Check if the Toplevel window still exists before destroying
if tw.winfo_exists():
tw.destroy()
except Exception:
# Ignore errors if the window is already destroyed
pass
# --- END OF FILE gitsync_tool/gui/tooltip.py ---