147 lines
5.6 KiB
Python
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 ---
|