(root)/
Python-3.11.7/
Lib/
idlelib/
calltip_w.py
       1  """A call-tip window class for Tkinter/IDLE.
       2  
       3  After tooltip.py, which uses ideas gleaned from PySol.
       4  Used by calltip.py.
       5  """
       6  from tkinter import Label, LEFT, SOLID, TclError
       7  
       8  from idlelib.tooltip import TooltipBase
       9  
      10  HIDE_EVENT = "<<calltipwindow-hide>>"
      11  HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
      12  CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>"
      13  CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
      14  CHECKHIDE_TIME = 100  # milliseconds
      15  
      16  MARK_RIGHT = "calltipwindowregion_right"
      17  
      18  
      19  class ESC[4;38;5;81mCalltipWindow(ESC[4;38;5;149mTooltipBase):
      20      """A call-tip widget for tkinter text widgets."""
      21  
      22      def __init__(self, text_widget):
      23          """Create a call-tip; shown by showtip().
      24  
      25          text_widget: a Text widget with code for which call-tips are desired
      26          """
      27          # Note: The Text widget will be accessible as self.anchor_widget
      28          super().__init__(text_widget)
      29  
      30          self.label = self.text = None
      31          self.parenline = self.parencol = self.lastline = None
      32          self.hideid = self.checkhideid = None
      33          self.checkhide_after_id = None
      34  
      35      def get_position(self):
      36          """Choose the position of the call-tip."""
      37          curline = int(self.anchor_widget.index("insert").split('.')[0])
      38          if curline == self.parenline:
      39              anchor_index = (self.parenline, self.parencol)
      40          else:
      41              anchor_index = (curline, 0)
      42          box = self.anchor_widget.bbox("%d.%d" % anchor_index)
      43          if not box:
      44              box = list(self.anchor_widget.bbox("insert"))
      45              # align to left of window
      46              box[0] = 0
      47              box[2] = 0
      48          return box[0] + 2, box[1] + box[3]
      49  
      50      def position_window(self):
      51          "Reposition the window if needed."
      52          curline = int(self.anchor_widget.index("insert").split('.')[0])
      53          if curline == self.lastline:
      54              return
      55          self.lastline = curline
      56          self.anchor_widget.see("insert")
      57          super().position_window()
      58  
      59      def showtip(self, text, parenleft, parenright):
      60          """Show the call-tip, bind events which will close it and reposition it.
      61  
      62          text: the text to display in the call-tip
      63          parenleft: index of the opening parenthesis in the text widget
      64          parenright: index of the closing parenthesis in the text widget,
      65                      or the end of the line if there is no closing parenthesis
      66          """
      67          # Only called in calltip.Calltip, where lines are truncated
      68          self.text = text
      69          if self.tipwindow or not self.text:
      70              return
      71  
      72          self.anchor_widget.mark_set(MARK_RIGHT, parenright)
      73          self.parenline, self.parencol = map(
      74              int, self.anchor_widget.index(parenleft).split("."))
      75  
      76          super().showtip()
      77  
      78          self._bind_events()
      79  
      80      def showcontents(self):
      81          """Create the call-tip widget."""
      82          self.label = Label(self.tipwindow, text=self.text, justify=LEFT,
      83                             background="#ffffd0", foreground="black",
      84                             relief=SOLID, borderwidth=1,
      85                             font=self.anchor_widget['font'])
      86          self.label.pack()
      87  
      88      def checkhide_event(self, event=None):
      89          """Handle CHECK_HIDE_EVENT: call hidetip or reschedule."""
      90          if not self.tipwindow:
      91              # If the event was triggered by the same event that unbound
      92              # this function, the function will be called nevertheless,
      93              # so do nothing in this case.
      94              return None
      95  
      96          # Hide the call-tip if the insertion cursor moves outside of the
      97          # parenthesis.
      98          curline, curcol = map(int, self.anchor_widget.index("insert").split('.'))
      99          if curline < self.parenline or \
     100             (curline == self.parenline and curcol <= self.parencol) or \
     101             self.anchor_widget.compare("insert", ">", MARK_RIGHT):
     102              self.hidetip()
     103              return "break"
     104  
     105          # Not hiding the call-tip.
     106  
     107          self.position_window()
     108          # Re-schedule this function to be called again in a short while.
     109          if self.checkhide_after_id is not None:
     110              self.anchor_widget.after_cancel(self.checkhide_after_id)
     111          self.checkhide_after_id = \
     112              self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event)
     113          return None
     114  
     115      def hide_event(self, event):
     116          """Handle HIDE_EVENT by calling hidetip."""
     117          if not self.tipwindow:
     118              # See the explanation in checkhide_event.
     119              return None
     120          self.hidetip()
     121          return "break"
     122  
     123      def hidetip(self):
     124          """Hide the call-tip."""
     125          if not self.tipwindow:
     126              return
     127  
     128          try:
     129              self.label.destroy()
     130          except TclError:
     131              pass
     132          self.label = None
     133  
     134          self.parenline = self.parencol = self.lastline = None
     135          try:
     136              self.anchor_widget.mark_unset(MARK_RIGHT)
     137          except TclError:
     138              pass
     139  
     140          try:
     141              self._unbind_events()
     142          except (TclError, ValueError):
     143              # ValueError may be raised by MultiCall
     144              pass
     145  
     146          super().hidetip()
     147  
     148      def _bind_events(self):
     149          """Bind event handlers."""
     150          self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT,
     151                                                     self.checkhide_event)
     152          for seq in CHECKHIDE_SEQUENCES:
     153              self.anchor_widget.event_add(CHECKHIDE_EVENT, seq)
     154          self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event)
     155          self.hideid = self.anchor_widget.bind(HIDE_EVENT,
     156                                                self.hide_event)
     157          for seq in HIDE_SEQUENCES:
     158              self.anchor_widget.event_add(HIDE_EVENT, seq)
     159  
     160      def _unbind_events(self):
     161          """Unbind event handlers."""
     162          for seq in CHECKHIDE_SEQUENCES:
     163              self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq)
     164          self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid)
     165          self.checkhideid = None
     166          for seq in HIDE_SEQUENCES:
     167              self.anchor_widget.event_delete(HIDE_EVENT, seq)
     168          self.anchor_widget.unbind(HIDE_EVENT, self.hideid)
     169          self.hideid = None
     170  
     171  
     172  def _calltip_window(parent):  # htest #
     173      from tkinter import Toplevel, Text, LEFT, BOTH
     174  
     175      top = Toplevel(parent)
     176      top.title("Test call-tips")
     177      x, y = map(int, parent.geometry().split('+')[1:])
     178      top.geometry("250x100+%d+%d" % (x + 175, y + 150))
     179      text = Text(top)
     180      text.pack(side=LEFT, fill=BOTH, expand=1)
     181      text.insert("insert", "string.split")
     182      top.update()
     183  
     184      calltip = CalltipWindow(text)
     185      def calltip_show(event):
     186          calltip.showtip("(s='Hello world')", "insert", "end")
     187      def calltip_hide(event):
     188          calltip.hidetip()
     189      text.event_add("<<calltip-show>>", "(")
     190      text.event_add("<<calltip-hide>>", ")")
     191      text.bind("<<calltip-show>>", calltip_show)
     192      text.bind("<<calltip-hide>>", calltip_hide)
     193  
     194      text.focus_set()
     195  
     196  
     197  if __name__ == '__main__':
     198      from unittest import main
     199      main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False)
     200  
     201      from idlelib.idle_test.htest import run
     202      run(_calltip_window)