# --- 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("", self.schedule_show, add="+") self.widget.bind("", self.schedule_hide, add="+") # Hide tooltip immediately if the widget is clicked self.widget.bind("", 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 ---