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 | """ |
Georg Brandl | 14fc427 | 2008-05-17 18:39:55 +0000 | [diff] [blame] | 7 | from tkinter import * |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 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 |
Martin v. Löwis | ee381a0 | 2012-03-13 14:18:36 -0700 | [diff] [blame^] | 25 | self.checkhide_after_id = None |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 26 | |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 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 | """ |
Kurt B. Kaiser | e54710b | 2002-12-12 19:15:39 +0000 | [diff] [blame] | 51 | # truncate overly long calltip |
Kurt B. Kaiser | e72f05d | 2002-09-15 21:43:13 +0000 | [diff] [blame] | 52 | if len(text) >= 79: |
Thomas Wouters | 0e3f591 | 2006-08-11 14:57:12 +0000 | [diff] [blame] | 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) |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 58 | self.text = text |
| 59 | if self.tipwindow or not self.text: |
| 60 | return |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 61 | |
| 62 | self.widget.mark_set(MARK_RIGHT, parenright) |
| 63 | self.parenline, self.parencol = map( |
| 64 | int, self.widget.index(parenleft).split(".")) |
| 65 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 66 | self.tipwindow = tw = Toplevel(self.widget) |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 67 | self.position_window() |
Kurt B. Kaiser | 3b148ca | 2005-11-22 02:17:10 +0000 | [diff] [blame] | 68 | # remove border on calltip window |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 69 | tw.wm_overrideredirect(1) |
Tony Lownds | 8b1b8d6 | 2002-09-23 01:04:05 +0000 | [diff] [blame] | 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. |
Kurt B. Kaiser | 6655e4b | 2002-12-31 16:03:23 +0000 | [diff] [blame] | 74 | tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, |
Tony Lownds | 8b1b8d6 | 2002-09-23 01:04:05 +0000 | [diff] [blame] | 75 | "help", "noActivates") |
| 76 | except TclError: |
| 77 | pass |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 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() |
Martin v. Löwis | ee381a0 | 2012-03-13 14:18:36 -0700 | [diff] [blame^] | 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) |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 110 | |
| 111 | def hide_event(self, event): |
| 112 | if not self.tipwindow: |
| 113 | # See the explanation in checkhide_event. |
| 114 | return |
| 115 | self.hidetip() |
Kurt B. Kaiser | 9a1ae1a | 2001-07-12 22:26:44 +0000 | [diff] [blame] | 116 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 117 | def hidetip(self): |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 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() |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 133 | self.tipwindow = None |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 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 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 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) |
Kurt B. Kaiser | 9a1ae1a | 2001-07-12 22:26:44 +0000 | [diff] [blame] | 160 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 161 | text.focus_set() |
Kurt B. Kaiser | e54710b | 2002-12-12 19:15:39 +0000 | [diff] [blame] | 162 | root.mainloop() |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 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() |