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. |
Kurt B. Kaiser | e54710b | 2002-12-12 19:15:39 +0000 | [diff] [blame] | 5 | """ |
Terry Jan Reedy | 44f09eb | 2014-07-01 18:52:37 -0400 | [diff] [blame] | 6 | from tkinter import Toplevel, Label, LEFT, SOLID, TclError |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 7 | |
Kurt B. Kaiser | 3b148ca | 2005-11-22 02:17:10 +0000 | [diff] [blame] | 8 | HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>" |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 9 | HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") |
| 10 | CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>" |
| 11 | CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") |
| 12 | CHECKHIDE_TIME = 100 # miliseconds |
| 13 | |
| 14 | MARK_RIGHT = "calltipwindowregion_right" |
| 15 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 16 | class CallTip: |
| 17 | |
| 18 | def __init__(self, widget): |
| 19 | self.widget = widget |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 20 | self.tipwindow = self.label = None |
| 21 | self.parenline = self.parencol = None |
| 22 | self.lastline = None |
| 23 | self.hideid = self.checkhideid = None |
Martin v. Löwis | ee381a0 | 2012-03-13 14:18:36 -0700 | [diff] [blame] | 24 | self.checkhide_after_id = 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 | """ |
Terry Jan Reedy | 0e3b0e3 | 2014-01-21 21:12:24 -0500 | [diff] [blame] | 50 | # Only called in CallTips, where lines are truncated |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 51 | self.text = text |
| 52 | if self.tipwindow or not self.text: |
| 53 | return |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 54 | |
| 55 | self.widget.mark_set(MARK_RIGHT, parenright) |
| 56 | self.parenline, self.parencol = map( |
| 57 | int, self.widget.index(parenleft).split(".")) |
| 58 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 59 | self.tipwindow = tw = Toplevel(self.widget) |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 60 | self.position_window() |
Kurt B. Kaiser | 3b148ca | 2005-11-22 02:17:10 +0000 | [diff] [blame] | 61 | # remove border on calltip window |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 62 | tw.wm_overrideredirect(1) |
Tony Lownds | 8b1b8d6 | 2002-09-23 01:04:05 +0000 | [diff] [blame] | 63 | try: |
| 64 | # This command is only needed and available on Tk >= 8.4.0 for OSX |
| 65 | # Without it, call tips intrude on the typing process by grabbing |
| 66 | # the focus. |
Kurt B. Kaiser | 6655e4b | 2002-12-31 16:03:23 +0000 | [diff] [blame] | 67 | tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w, |
Tony Lownds | 8b1b8d6 | 2002-09-23 01:04:05 +0000 | [diff] [blame] | 68 | "help", "noActivates") |
| 69 | except TclError: |
| 70 | pass |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 71 | self.label = Label(tw, text=self.text, justify=LEFT, |
| 72 | background="#ffffe0", relief=SOLID, borderwidth=1, |
| 73 | font = self.widget['font']) |
| 74 | self.label.pack() |
| 75 | |
| 76 | self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME, |
| 77 | self.checkhide_event) |
| 78 | for seq in CHECKHIDE_SEQUENCES: |
| 79 | self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) |
| 80 | self.widget.after(CHECKHIDE_TIME, self.checkhide_event) |
| 81 | self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME, |
| 82 | self.hide_event) |
| 83 | for seq in HIDE_SEQUENCES: |
| 84 | self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq) |
| 85 | |
| 86 | def checkhide_event(self, event=None): |
| 87 | if not self.tipwindow: |
| 88 | # If the event was triggered by the same event that unbinded |
| 89 | # this function, the function will be called nevertheless, |
| 90 | # so do nothing in this case. |
| 91 | return |
| 92 | curline, curcol = map(int, self.widget.index("insert").split('.')) |
| 93 | if curline < self.parenline or \ |
| 94 | (curline == self.parenline and curcol <= self.parencol) or \ |
| 95 | self.widget.compare("insert", ">", MARK_RIGHT): |
| 96 | self.hidetip() |
| 97 | else: |
| 98 | self.position_window() |
Martin v. Löwis | ee381a0 | 2012-03-13 14:18:36 -0700 | [diff] [blame] | 99 | if self.checkhide_after_id is not None: |
| 100 | self.widget.after_cancel(self.checkhide_after_id) |
| 101 | self.checkhide_after_id = \ |
| 102 | self.widget.after(CHECKHIDE_TIME, self.checkhide_event) |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 103 | |
| 104 | def hide_event(self, event): |
| 105 | if not self.tipwindow: |
| 106 | # See the explanation in checkhide_event. |
| 107 | return |
| 108 | self.hidetip() |
Kurt B. Kaiser | 9a1ae1a | 2001-07-12 22:26:44 +0000 | [diff] [blame] | 109 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 110 | def hidetip(self): |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 111 | if not self.tipwindow: |
| 112 | return |
| 113 | |
| 114 | for seq in CHECKHIDE_SEQUENCES: |
| 115 | self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq) |
| 116 | self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid) |
| 117 | self.checkhideid = None |
| 118 | for seq in HIDE_SEQUENCES: |
| 119 | self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq) |
| 120 | self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid) |
| 121 | self.hideid = None |
| 122 | |
| 123 | self.label.destroy() |
| 124 | self.label = None |
| 125 | self.tipwindow.destroy() |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 126 | self.tipwindow = None |
Kurt B. Kaiser | b175445 | 2005-11-18 22:05:48 +0000 | [diff] [blame] | 127 | |
| 128 | self.widget.mark_unset(MARK_RIGHT) |
| 129 | self.parenline = self.parencol = self.lastline = None |
| 130 | |
| 131 | def is_active(self): |
| 132 | return bool(self.tipwindow) |
| 133 | |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 134 | |
Terry Jan Reedy | 44f09eb | 2014-07-01 18:52:37 -0400 | [diff] [blame] | 135 | def _calltip_window(parent): # htest # |
Terry Jan Reedy | cd56736 | 2014-10-17 01:31:35 -0400 | [diff] [blame^] | 136 | from tkinter import Toplevel, Text, LEFT, BOTH |
Terry Jan Reedy | 44f09eb | 2014-07-01 18:52:37 -0400 | [diff] [blame] | 137 | |
Terry Jan Reedy | cd56736 | 2014-10-17 01:31:35 -0400 | [diff] [blame^] | 138 | top = Toplevel(parent) |
| 139 | top.title("Test calltips") |
| 140 | top.geometry("200x100+%d+%d" % (parent.winfo_rootx() + 200, |
| 141 | parent.winfo_rooty() + 150)) |
| 142 | text = Text(top) |
| 143 | text.pack(side=LEFT, fill=BOTH, expand=1) |
| 144 | text.insert("insert", "string.split") |
| 145 | top.update() |
| 146 | calltip = CallTip(text) |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 147 | |
Terry Jan Reedy | cd56736 | 2014-10-17 01:31:35 -0400 | [diff] [blame^] | 148 | def calltip_show(event): |
| 149 | calltip.showtip("(s=Hello world)", "insert", "end") |
| 150 | def calltip_hide(event): |
| 151 | calltip.hidetip() |
| 152 | text.event_add("<<calltip-show>>", "(") |
| 153 | text.event_add("<<calltip-hide>>", ")") |
| 154 | text.bind("<<calltip-show>>", calltip_show) |
| 155 | text.bind("<<calltip-hide>>", calltip_hide) |
| 156 | text.focus_set() |
David Scherer | 7aced17 | 2000-08-15 01:13:23 +0000 | [diff] [blame] | 157 | |
| 158 | if __name__=='__main__': |
Terry Jan Reedy | 1b392ff | 2014-05-24 18:48:18 -0400 | [diff] [blame] | 159 | from idlelib.idle_test.htest import run |
| 160 | run(_calltip_window) |