1 """A CallTip window class for Tkinter/IDLE. 2 3 After ToolTip.py, which uses ideas gleaned from PySol 4 Used by the CallTips IDLE extension. 5 6 """ 7 from Tkinter import * 8 9 HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>" 10 HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") 11 CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>" 12 CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") 13 CHECKHIDE_TIME = 100 # miliseconds 14 15 MARK_RIGHT = "calltipwindowregion_right" 16 17 class CallTip: 18 19 def __init__(self, widget): 20 self.widget = widget 21 self.tipwindow = self.label = None 22 self.parenline = self.parencol = None 23 self.lastline = None 24 self.hideid = self.checkhideid = None 25 self.checkhide_after_id = None 26 27 def position_window(self): 28 """Check if needs to reposition the window, and if so - do it.""" 29 curline = int(self.widget.index("insert").split('.')[0]) 30 if curline == self.lastline: 31 return 32 self.lastline = curline 33 self.widget.see("insert") 34 if curline == self.parenline: 35 box = self.widget.bbox("%d.%d" % (self.parenline, 36 self.parencol)) 37 else: 38 box = self.widget.bbox("%d.0" % curline) 39 if not box: 40 box = list(self.widget.bbox("insert")) 41 # align to left of window 42 box[0] = 0 43 box[2] = 0 44 x = box[0] + self.widget.winfo_rootx() + 2 45 y = box[1] + box[3] + self.widget.winfo_rooty() 46 self.tipwindow.wm_geometry("+%d+%d" % (x, y)) 47 48 def showtip(self, text, parenleft, parenright): 49 """Show the calltip, bind events which will close it and reposition it. 50 """ 51 # truncate overly long calltip 52 if len(text) >= 79: 53 textlines = text.splitlines() 54 for i, line in enumerate(textlines): 55 if len(line) > 79: 56 textlines[i] = line[:75] + ' ...' 57 text = '\n'.join(textlines) 58 self.text = text 59 if self.tipwindow or not self.text: 60 return 61 62 self.widget.mark_set(MARK_RIGHT, parenright) 63 self.parenline, self.parencol = map( 64 int, self.widget.index(parenleft).split(".")) 65 66 self.tipwindow = tw = Toplevel(self.widget) 67 self.position_window() 68 # remove border on calltip window 69 tw.wm_overrideredirect(1) 70 try: 71 # This command is only needed and available on Tk >= 8.4.0 for OSX 72 # Without it, call tips intrude on the typing process by grabbing 73 # the focus. 74 tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, 75 "help", "noActivates") 76 except TclError: 77 pass 78 self.label = Label(tw, text=self.text, justify=LEFT, 79 background="#ffffe0", relief=SOLID, borderwidth=1, 80 font = self.widget['font']) 81 self.label.pack() 82 83 self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME, 84 self.checkhide_event) 85 for seq in CHECKHIDE_SEQUENCES: 86 self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) 87 self.widget.after(CHECKHIDE_TIME, self.checkhide_event) 88 self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, 89 self.hide_event) 90 for seq in HIDE_SEQUENCES: 91 self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) 92 93 def checkhide_event(self, event=None): 94 if not self.tipwindow: 95 # If the event was triggered by the same event that unbinded 96 # this function, the function will be called nevertheless, 97 # so do nothing in this case. 98 return 99 curline, curcol = map(int, self.widget.index("insert").split('.')) 100 if curline < self.parenline or \ 101 (curline == self.parenline and curcol <= self.parencol) or \ 102 self.widget.compare("insert", ">", MARK_RIGHT): 103 self.hidetip() 104 else: 105 self.position_window() 106 if self.checkhide_after_id is not None: 107 self.widget.after_cancel(self.checkhide_after_id) 108 self.checkhide_after_id = \ 109 self.widget.after(CHECKHIDE_TIME, self.checkhide_event) 110 111 def hide_event(self, event): 112 if not self.tipwindow: 113 # See the explanation in checkhide_event. 114 return 115 self.hidetip() 116 117 def hidetip(self): 118 if not self.tipwindow: 119 return 120 121 for seq in CHECKHIDE_SEQUENCES: 122 self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) 123 self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid) 124 self.checkhideid = None 125 for seq in HIDE_SEQUENCES: 126 self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) 127 self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) 128 self.hideid = None 129 130 self.label.destroy() 131 self.label = None 132 self.tipwindow.destroy() 133 self.tipwindow = None 134 135 self.widget.mark_unset(MARK_RIGHT) 136 self.parenline = self.parencol = self.lastline = None 137 138 def is_active(self): 139 return bool(self.tipwindow) 140 141 142 143 ############################### 144 # 145 # Test Code 146 # 147 class container: # Conceptually an editor_window 148 def __init__(self): 149 root = Tk() 150 text = self.text = Text(root) 151 text.pack(side=LEFT, fill=BOTH, expand=1) 152 text.insert("insert", "string.split") 153 root.update() 154 self.calltip = CallTip(text) 155 156 text.event_add("<<calltip-show>>", "(") 157 text.event_add("<<calltip-hide>>", ")") 158 text.bind("<<calltip-show>>", self.calltip_show) 159 text.bind("<<calltip-hide>>", self.calltip_hide) 160 161 text.focus_set() 162 root.mainloop() 163 164 def calltip_show(self, event): 165 self.calltip.showtip("Hello world") 166 167 def calltip_hide(self, event): 168 self.calltip.hidetip() 169 170 def main(): 171 # Test code 172 c=container() 173 174 if __name__=='__main__': 175 main() 176