| """A CallTip window class for Tkinter/IDLE. | 
 |  | 
 | After ToolTip.py, which uses ideas gleaned from PySol | 
 | Used by the CallTips IDLE extension. | 
 |  | 
 | """ | 
 | from Tkinter import * | 
 |  | 
 | HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>" | 
 | HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") | 
 | CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>" | 
 | CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") | 
 | CHECKHIDE_TIME = 100 # miliseconds | 
 |  | 
 | MARK_RIGHT = "calltipwindowregion_right" | 
 |  | 
 | class CallTip: | 
 |  | 
 |     def __init__(self, widget): | 
 |         self.widget = widget | 
 |         self.tipwindow = self.label = None | 
 |         self.parenline = self.parencol = None | 
 |         self.lastline = None | 
 |         self.hideid = self.checkhideid = None | 
 |  | 
 |     def position_window(self): | 
 |         """Check if needs to reposition the window, and if so - do it.""" | 
 |         curline = int(self.widget.index("insert").split('.')[0]) | 
 |         if curline == self.lastline: | 
 |             return | 
 |         self.lastline = curline | 
 |         self.widget.see("insert") | 
 |         if curline == self.parenline: | 
 |             box = self.widget.bbox("%d.%d" % (self.parenline, | 
 |                                               self.parencol)) | 
 |         else: | 
 |             box = self.widget.bbox("%d.0" % curline) | 
 |         if not box: | 
 |             box = list(self.widget.bbox("insert")) | 
 |             # align to left of window | 
 |             box[0] = 0 | 
 |             box[2] = 0 | 
 |         x = box[0] + self.widget.winfo_rootx() + 2 | 
 |         y = box[1] + box[3] + self.widget.winfo_rooty() | 
 |         self.tipwindow.wm_geometry("+%d+%d" % (x, y)) | 
 |  | 
 |     def showtip(self, text, parenleft, parenright): | 
 |         """Show the calltip, bind events which will close it and reposition it. | 
 |         """ | 
 |         # truncate overly long calltip | 
 |         if len(text) >= 79: | 
 |             textlines = text.splitlines() | 
 |             for i, line in enumerate(textlines): | 
 |                 if len(line) > 79: | 
 |                     textlines[i] = line[:75] + ' ...' | 
 |             text = '\n'.join(textlines) | 
 |         self.text = text | 
 |         if self.tipwindow or not self.text: | 
 |             return | 
 |  | 
 |         self.widget.mark_set(MARK_RIGHT, parenright) | 
 |         self.parenline, self.parencol = map( | 
 |             int, self.widget.index(parenleft).split(".")) | 
 |  | 
 |         self.tipwindow = tw = Toplevel(self.widget) | 
 |         self.position_window() | 
 |         # remove border on calltip window | 
 |         tw.wm_overrideredirect(1) | 
 |         try: | 
 |             # This command is only needed and available on Tk >= 8.4.0 for OSX | 
 |             # Without it, call tips intrude on the typing process by grabbing | 
 |             # the focus. | 
 |             tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, | 
 |                        "help", "noActivates") | 
 |         except TclError: | 
 |             pass | 
 |         self.label = Label(tw, text=self.text, justify=LEFT, | 
 |                            background="#ffffe0", relief=SOLID, borderwidth=1, | 
 |                            font = self.widget['font']) | 
 |         self.label.pack() | 
 |  | 
 |         self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME, | 
 |                                             self.checkhide_event) | 
 |         for seq in CHECKHIDE_SEQUENCES: | 
 |             self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) | 
 |         self.widget.after(CHECKHIDE_TIME, self.checkhide_event) | 
 |         self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, | 
 |                                        self.hide_event) | 
 |         for seq in HIDE_SEQUENCES: | 
 |             self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) | 
 |  | 
 |     def checkhide_event(self, event=None): | 
 |         if not self.tipwindow: | 
 |             # If the event was triggered by the same event that unbinded | 
 |             # this function, the function will be called nevertheless, | 
 |             # so do nothing in this case. | 
 |             return | 
 |         curline, curcol = map(int, self.widget.index("insert").split('.')) | 
 |         if curline < self.parenline or \ | 
 |            (curline == self.parenline and curcol <= self.parencol) or \ | 
 |            self.widget.compare("insert", ">", MARK_RIGHT): | 
 |             self.hidetip() | 
 |         else: | 
 |             self.position_window() | 
 |             self.widget.after(CHECKHIDE_TIME, self.checkhide_event) | 
 |  | 
 |     def hide_event(self, event): | 
 |         if not self.tipwindow: | 
 |             # See the explanation in checkhide_event. | 
 |             return | 
 |         self.hidetip() | 
 |  | 
 |     def hidetip(self): | 
 |         if not self.tipwindow: | 
 |             return | 
 |  | 
 |         for seq in CHECKHIDE_SEQUENCES: | 
 |             self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) | 
 |         self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid) | 
 |         self.checkhideid = None | 
 |         for seq in HIDE_SEQUENCES: | 
 |             self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) | 
 |         self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) | 
 |         self.hideid = None | 
 |  | 
 |         self.label.destroy() | 
 |         self.label = None | 
 |         self.tipwindow.destroy() | 
 |         self.tipwindow = None | 
 |  | 
 |         self.widget.mark_unset(MARK_RIGHT) | 
 |         self.parenline = self.parencol = self.lastline = None | 
 |  | 
 |     def is_active(self): | 
 |         return bool(self.tipwindow) | 
 |  | 
 |  | 
 |  | 
 | ############################### | 
 | # | 
 | # Test Code | 
 | # | 
 | class container: # Conceptually an editor_window | 
 |     def __init__(self): | 
 |         root = Tk() | 
 |         text = self.text = Text(root) | 
 |         text.pack(side=LEFT, fill=BOTH, expand=1) | 
 |         text.insert("insert", "string.split") | 
 |         root.update() | 
 |         self.calltip = CallTip(text) | 
 |  | 
 |         text.event_add("<<calltip-show>>", "(") | 
 |         text.event_add("<<calltip-hide>>", ")") | 
 |         text.bind("<<calltip-show>>", self.calltip_show) | 
 |         text.bind("<<calltip-hide>>", self.calltip_hide) | 
 |  | 
 |         text.focus_set() | 
 |         root.mainloop() | 
 |  | 
 |     def calltip_show(self, event): | 
 |         self.calltip.showtip("Hello world") | 
 |  | 
 |     def calltip_hide(self, event): | 
 |         self.calltip.hidetip() | 
 |  | 
 | def main(): | 
 |     # Test code | 
 |     c=container() | 
 |  | 
 | if __name__=='__main__': | 
 |     main() |