2323TOOLTIP_MAX_OFFSET = 100 # Maximum horizontal offset from widget edge
2424TOOLTIP_VERTICAL_OFFSET = 10 # Vertical offset when positioning above widget
2525TOOLTIP_SHOW_DELAY_MS = 250 # Delay before showing to avoid flicker while moving across dense UIs
26- TOOLTIP_HIDE_DELAY_MS = 75 # Small delay prevents leave/enter jitter from leaving stale tooltips behind
2726
2827
2928class MonitorBounds (NamedTuple ):
@@ -475,35 +474,16 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
475474 self .position_below : bool = position_below
476475 self .toplevel_class = toplevel_class or tk .Toplevel
477476 self .timers : dict [str , Optional [str ]] = {}
477+ self ._is_aqua : bool = widget .tk .call ("tk" , "windowingsystem" ) == "aqua"
478478
479479 # Bind the <Enter> and <Leave> events to show and hide the tooltip
480- if platform_system () == "Darwin" :
481- # On macOS, defer tooltip creation slightly to avoid flashing while
482- # moving through dense tables and controls.
483- if tag_name and isinstance (self .widget , tk .Text ):
484- self .widget .tag_bind (tag_name , "<Enter>" , self .schedule_show , "+" )
485- self .widget .tag_bind (tag_name , "<Leave>" , self .destroy_hide , "+" )
486- else :
487- self .widget .bind ("<Enter>" , self .schedule_show , "+" )
488- self .widget .bind ("<Leave>" , self .destroy_hide , "+" )
480+ # Defer tooltip creation slightly to avoid flashing while moving through dense tables.
481+ if tag_name and isinstance (self .widget , tk .Text ):
482+ self .widget .tag_bind (tag_name , "<Enter>" , self .schedule_show , "+" )
483+ self .widget .tag_bind (tag_name , "<Leave>" , self .destroy_hide , "+" )
489484 else :
490- if tag_name and isinstance (self .widget , tk .Text ):
491- self .widget .tag_bind (tag_name , "<Enter>" , self .show , "+" )
492- self .widget .tag_bind (tag_name , "<Leave>" , self .hide , "+" )
493- else :
494- self .widget .bind ("<Enter>" , self .show , "+" )
495- self .widget .bind ("<Leave>" , self .hide , "+" )
496- # On non-macOS, create the tooltip immediately and show/hide it on events
497- self .tooltip = cast ("tk.Toplevel" , self .toplevel_class (widget ))
498- self .tooltip .wm_overrideredirect (boolean = True )
499- tooltip_label = ttk .Label (
500- self .tooltip , text = text , background = "#ffffe0" , relief = "solid" , borderwidth = 1 , justify = tk .LEFT
501- )
502- tooltip_label .pack ()
503- self .tooltip .withdraw () # Initially hide the tooltip
504- # Bind to tooltip to prevent hiding when mouse is over it
505- self .tooltip .bind ("<Enter>" , self ._cancel_hide )
506- self .tooltip .bind ("<Leave>" , self .hide )
485+ self .widget .bind ("<Enter>" , self .schedule_show , "+" )
486+ self .widget .bind ("<Leave>" , self .destroy_hide , "+" )
507487
508488 self .widget .bind ("<Destroy>" , self ._on_widget_destroy , "+" )
509489
@@ -517,22 +497,9 @@ def _cancel_timer(self, name: str) -> None:
517497 def _cancel_show (self ) -> None :
518498 self ._cancel_timer ("show" )
519499
520- def show (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
521- """On non-macOS, tooltip already exists, show it on events."""
522- self ._cancel_hide ()
523- self ._hide_active_tooltip ()
524- if self .tooltip :
525- self .position_tooltip ()
526- self .tooltip .deiconify ()
527- Tooltip ._active_tooltip = self
528-
529- def _cancel_hide (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
530- self ._cancel_timer ("hide" )
531-
532500 def _on_widget_destroy (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
533501 """Stop any active timers if the widget is destroyed."""
534502 self ._cancel_show ()
535- self ._cancel_hide ()
536503 self ._cancel_timer ("alpha" )
537504
538505 if self .tooltip :
@@ -547,16 +514,14 @@ def _hide_active_tooltip(self) -> None:
547514 Tooltip ._active_tooltip .force_hide ()
548515 Tooltip ._active_tooltip = None
549516
550- def schedule_show (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
517+ def schedule_show (self , _event : Optional [tk .Event ] = None ) -> None :
551518 """Delay tooltip creation slightly to avoid flicker during pointer movement."""
552- self ._cancel_hide ()
553519 self ._cancel_show ()
554520 self .timers ["show" ] = self .widget .after (TOOLTIP_SHOW_DELAY_MS , self .create_show )
555521
556- def create_show (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
557- """On macOS, only create the tooltip when the mouse enters the widget."""
522+ def create_show (self , _event : Optional [tk .Event ] = None ) -> None :
523+ """Create and show the tooltip when the pointer is still over the widget after the delay ."""
558524 self ._cancel_show ()
559- self ._cancel_hide ()
560525
561526 try :
562527 pointed = self .widget .winfo_containing (self .widget .winfo_pointerx (), self .widget .winfo_pointery ())
@@ -577,22 +542,21 @@ def create_show(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002
577542 self .tooltip .wm_overrideredirect (True ) # noqa: FBT003
578543 self .tooltip .withdraw ()
579544
580- if self .widget . tk . call ( "tk" , "windowingsystem" ) == "aqua" :
545+ if self ._is_aqua :
581546 self .tooltip .attributes ("-alpha" , 0.0 )
582547
583- try :
584- self .tooltip .tk .call (
585- "::tk::unsupported::MacWindowStyle" ,
586- "style" ,
587- self .tooltip ._w , # type: ignore[attr-defined] # noqa: SLF001 # pylint: disable=protected-access
588- "help" ,
589- "noActivates" ,
590- )
591- self .tooltip .configure (bg = "#ffffe0" )
592- except AttributeError : # Catches protected member access error
593- self .tooltip .wm_attributes ("-alpha" , 1.0 ) # Ensure opacity
594- self .tooltip .wm_attributes ("-topmost" , True ) # Keep on top # noqa: FBT003
595- self .tooltip .configure (bg = "#ffffe0" )
548+ try :
549+ self .tooltip .tk .call (
550+ "::tk::unsupported::MacWindowStyle" ,
551+ "style" ,
552+ self .tooltip ._w , # type: ignore[attr-defined] # noqa: SLF001 # pylint: disable=protected-access
553+ "help" ,
554+ "noActivates" ,
555+ )
556+ except (AttributeError , tk .TclError ): # Fallback when MacWindowStyle or Tk attribute access is unsupported
557+ with contextlib .suppress (tk .TclError ):
558+ self .tooltip .wm_attributes ("-alpha" , 1.0 ) # Ensure opacity
559+ self .tooltip .wm_attributes ("-topmost" , True ) # Keep on top # noqa: FBT003
596560 tooltip_label = ttk .Label (
597561 self .tooltip , text = self .text , background = "#ffffe0" , relief = "solid" , borderwidth = 1 , justify = tk .LEFT
598562 )
@@ -605,7 +569,7 @@ def create_show(self, event: Optional[tk.Event] = None) -> None: # noqa: ARG002
605569 self .tooltip .update_idletasks () # Force macOS to finish rendering text and colors
606570 self .tooltip .deiconify () # still invisible on Mac
607571
608- if self .widget . tk . call ( "tk" , "windowingsystem" ) == "aqua" :
572+ if self ._is_aqua :
609573
610574 def _activate_alpha () -> None :
611575 self .timers .pop ("alpha" , None )
@@ -652,35 +616,19 @@ def position_tooltip(self) -> None:
652616 # Silently ignore - tooltip will be recreated on next hover if needed
653617 pass
654618
655- def hide (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
656- """Hide the tooltip after a delay on non-macOS."""
657- self ._cancel_hide ()
658- self .timers ["hide" ] = self .widget .after (TOOLTIP_HIDE_DELAY_MS , self ._do_hide )
659-
660- def _do_hide (self ) -> None :
661- """Actually hide or destroy the tooltip depending on platform."""
662- if self .tooltip :
663- self .tooltip .withdraw ()
664- if Tooltip ._active_tooltip is self :
665- Tooltip ._active_tooltip = None
666- self .timers .pop ("hide" , None )
667-
668619 def force_hide (self ) -> None :
669- """Immediately hide or destroy the tooltip, depending on platform ."""
620+ """Immediately destroy the tooltip globally across all OSs ."""
670621 self ._cancel_show ()
671- self ._cancel_hide ()
672622 self ._cancel_timer ("alpha" )
673623 if self .tooltip :
674- if platform_system () == "Darwin" :
624+ with contextlib . suppress ( tk . TclError ) :
675625 self .tooltip .destroy ()
676- self .tooltip = None
677- else :
678- self .tooltip .withdraw ()
626+ self .tooltip = None
679627 if Tooltip ._active_tooltip is self :
680628 Tooltip ._active_tooltip = None
681629
682630 def destroy_hide (self , event : Optional [tk .Event ] = None ) -> None : # noqa: ARG002 # pylint: disable=unused-argument
683- """On macOS, fully destroy the tooltip when the mouse leaves the widget."""
631+ """Immediately destroy the tooltip when the mouse leaves the widget, on all platforms ."""
684632 self .force_hide ()
685633
686634
0 commit comments