(root)/
Python-3.11.7/
Lib/
idlelib/
tooltip.py
       1  """Tools for displaying tool-tips.
       2  
       3  This includes:
       4   * an abstract base-class for different kinds of tooltips
       5   * a simple text-only Tooltip class
       6  """
       7  from tkinter import *
       8  
       9  
      10  class ESC[4;38;5;81mTooltipBase:
      11      """abstract base class for tooltips"""
      12  
      13      def __init__(self, anchor_widget):
      14          """Create a tooltip.
      15  
      16          anchor_widget: the widget next to which the tooltip will be shown
      17  
      18          Note that a widget will only be shown when showtip() is called.
      19          """
      20          self.anchor_widget = anchor_widget
      21          self.tipwindow = None
      22  
      23      def __del__(self):
      24          self.hidetip()
      25  
      26      def showtip(self):
      27          """display the tooltip"""
      28          if self.tipwindow:
      29              return
      30          self.tipwindow = tw = Toplevel(self.anchor_widget)
      31          # show no border on the top level window
      32          tw.wm_overrideredirect(1)
      33          try:
      34              # This command is only needed and available on Tk >= 8.4.0 for OSX.
      35              # Without it, call tips intrude on the typing process by grabbing
      36              # the focus.
      37              tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
      38                         "help", "noActivates")
      39          except TclError:
      40              pass
      41  
      42          self.position_window()
      43          self.showcontents()
      44          self.tipwindow.update_idletasks()  # Needed on MacOS -- see #34275.
      45          self.tipwindow.lift()  # work around bug in Tk 8.5.18+ (issue #24570)
      46  
      47      def position_window(self):
      48          """(re)-set the tooltip's screen position"""
      49          x, y = self.get_position()
      50          root_x = self.anchor_widget.winfo_rootx() + x
      51          root_y = self.anchor_widget.winfo_rooty() + y
      52          self.tipwindow.wm_geometry("+%d+%d" % (root_x, root_y))
      53  
      54      def get_position(self):
      55          """choose a screen position for the tooltip"""
      56          # The tip window must be completely outside the anchor widget;
      57          # otherwise when the mouse enters the tip window we get
      58          # a leave event and it disappears, and then we get an enter
      59          # event and it reappears, and so on forever :-(
      60          #
      61          # Note: This is a simplistic implementation; sub-classes will likely
      62          # want to override this.
      63          return 20, self.anchor_widget.winfo_height() + 1
      64  
      65      def showcontents(self):
      66          """content display hook for sub-classes"""
      67          # See ToolTip for an example
      68          raise NotImplementedError
      69  
      70      def hidetip(self):
      71          """hide the tooltip"""
      72          # Note: This is called by __del__, so careful when overriding/extending
      73          tw = self.tipwindow
      74          self.tipwindow = None
      75          if tw:
      76              try:
      77                  tw.destroy()
      78              except TclError:  # pragma: no cover
      79                  pass
      80  
      81  
      82  class ESC[4;38;5;81mOnHoverTooltipBase(ESC[4;38;5;149mTooltipBase):
      83      """abstract base class for tooltips, with delayed on-hover display"""
      84  
      85      def __init__(self, anchor_widget, hover_delay=1000):
      86          """Create a tooltip with a mouse hover delay.
      87  
      88          anchor_widget: the widget next to which the tooltip will be shown
      89          hover_delay: time to delay before showing the tooltip, in milliseconds
      90  
      91          Note that a widget will only be shown when showtip() is called,
      92          e.g. after hovering over the anchor widget with the mouse for enough
      93          time.
      94          """
      95          super().__init__(anchor_widget)
      96          self.hover_delay = hover_delay
      97  
      98          self._after_id = None
      99          self._id1 = self.anchor_widget.bind("<Enter>", self._show_event)
     100          self._id2 = self.anchor_widget.bind("<Leave>", self._hide_event)
     101          self._id3 = self.anchor_widget.bind("<Button>", self._hide_event)
     102  
     103      def __del__(self):
     104          try:
     105              self.anchor_widget.unbind("<Enter>", self._id1)
     106              self.anchor_widget.unbind("<Leave>", self._id2)  # pragma: no cover
     107              self.anchor_widget.unbind("<Button>", self._id3) # pragma: no cover
     108          except TclError:
     109              pass
     110          super().__del__()
     111  
     112      def _show_event(self, event=None):
     113          """event handler to display the tooltip"""
     114          if self.hover_delay:
     115              self.schedule()
     116          else:
     117              self.showtip()
     118  
     119      def _hide_event(self, event=None):
     120          """event handler to hide the tooltip"""
     121          self.hidetip()
     122  
     123      def schedule(self):
     124          """schedule the future display of the tooltip"""
     125          self.unschedule()
     126          self._after_id = self.anchor_widget.after(self.hover_delay,
     127                                                    self.showtip)
     128  
     129      def unschedule(self):
     130          """cancel the future display of the tooltip"""
     131          after_id = self._after_id
     132          self._after_id = None
     133          if after_id:
     134              self.anchor_widget.after_cancel(after_id)
     135  
     136      def hidetip(self):
     137          """hide the tooltip"""
     138          try:
     139              self.unschedule()
     140          except TclError:  # pragma: no cover
     141              pass
     142          super().hidetip()
     143  
     144  
     145  class ESC[4;38;5;81mHovertip(ESC[4;38;5;149mOnHoverTooltipBase):
     146      "A tooltip that pops up when a mouse hovers over an anchor widget."
     147      def __init__(self, anchor_widget, text, hover_delay=1000):
     148          """Create a text tooltip with a mouse hover delay.
     149  
     150          anchor_widget: the widget next to which the tooltip will be shown
     151          hover_delay: time to delay before showing the tooltip, in milliseconds
     152  
     153          Note that a widget will only be shown when showtip() is called,
     154          e.g. after hovering over the anchor widget with the mouse for enough
     155          time.
     156          """
     157          super().__init__(anchor_widget, hover_delay=hover_delay)
     158          self.text = text
     159  
     160      def showcontents(self):
     161          label = Label(self.tipwindow, text=self.text, justify=LEFT,
     162                        background="#ffffe0", relief=SOLID, borderwidth=1)
     163          label.pack()
     164  
     165  
     166  def _tooltip(parent):  # htest #
     167      top = Toplevel(parent)
     168      top.title("Test tooltip")
     169      x, y = map(int, parent.geometry().split('+')[1:])
     170      top.geometry("+%d+%d" % (x, y + 150))
     171      label = Label(top, text="Place your mouse over buttons")
     172      label.pack()
     173      button1 = Button(top, text="Button 1 -- 1/2 second hover delay")
     174      button1.pack()
     175      Hovertip(button1, "This is tooltip text for button1.", hover_delay=500)
     176      button2 = Button(top, text="Button 2 -- no hover delay")
     177      button2.pack()
     178      Hovertip(button2, "This is tooltip\ntext for button2.", hover_delay=None)
     179  
     180  
     181  if __name__ == '__main__':
     182      from unittest import main
     183      main('idlelib.idle_test.test_tooltip', verbosity=2, exit=False)
     184  
     185      from idlelib.idle_test.htest import run
     186      run(_tooltip)