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