17db96d56Sopenharmony_ci"""A call-tip window class for Tkinter/IDLE. 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ciAfter tooltip.py, which uses ideas gleaned from PySol. 47db96d56Sopenharmony_ciUsed by calltip.py. 57db96d56Sopenharmony_ci""" 67db96d56Sopenharmony_cifrom tkinter import Label, LEFT, SOLID, TclError 77db96d56Sopenharmony_ci 87db96d56Sopenharmony_cifrom idlelib.tooltip import TooltipBase 97db96d56Sopenharmony_ci 107db96d56Sopenharmony_ciHIDE_EVENT = "<<calltipwindow-hide>>" 117db96d56Sopenharmony_ciHIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") 127db96d56Sopenharmony_ciCHECKHIDE_EVENT = "<<calltipwindow-checkhide>>" 137db96d56Sopenharmony_ciCHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") 147db96d56Sopenharmony_ciCHECKHIDE_TIME = 100 # milliseconds 157db96d56Sopenharmony_ci 167db96d56Sopenharmony_ciMARK_RIGHT = "calltipwindowregion_right" 177db96d56Sopenharmony_ci 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ciclass CalltipWindow(TooltipBase): 207db96d56Sopenharmony_ci """A call-tip widget for tkinter text widgets.""" 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci def __init__(self, text_widget): 237db96d56Sopenharmony_ci """Create a call-tip; shown by showtip(). 247db96d56Sopenharmony_ci 257db96d56Sopenharmony_ci text_widget: a Text widget with code for which call-tips are desired 267db96d56Sopenharmony_ci """ 277db96d56Sopenharmony_ci # Note: The Text widget will be accessible as self.anchor_widget 287db96d56Sopenharmony_ci super().__init__(text_widget) 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ci self.label = self.text = None 317db96d56Sopenharmony_ci self.parenline = self.parencol = self.lastline = None 327db96d56Sopenharmony_ci self.hideid = self.checkhideid = None 337db96d56Sopenharmony_ci self.checkhide_after_id = None 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ci def get_position(self): 367db96d56Sopenharmony_ci """Choose the position of the call-tip.""" 377db96d56Sopenharmony_ci curline = int(self.anchor_widget.index("insert").split('.')[0]) 387db96d56Sopenharmony_ci if curline == self.parenline: 397db96d56Sopenharmony_ci anchor_index = (self.parenline, self.parencol) 407db96d56Sopenharmony_ci else: 417db96d56Sopenharmony_ci anchor_index = (curline, 0) 427db96d56Sopenharmony_ci box = self.anchor_widget.bbox("%d.%d" % anchor_index) 437db96d56Sopenharmony_ci if not box: 447db96d56Sopenharmony_ci box = list(self.anchor_widget.bbox("insert")) 457db96d56Sopenharmony_ci # align to left of window 467db96d56Sopenharmony_ci box[0] = 0 477db96d56Sopenharmony_ci box[2] = 0 487db96d56Sopenharmony_ci return box[0] + 2, box[1] + box[3] 497db96d56Sopenharmony_ci 507db96d56Sopenharmony_ci def position_window(self): 517db96d56Sopenharmony_ci "Reposition the window if needed." 527db96d56Sopenharmony_ci curline = int(self.anchor_widget.index("insert").split('.')[0]) 537db96d56Sopenharmony_ci if curline == self.lastline: 547db96d56Sopenharmony_ci return 557db96d56Sopenharmony_ci self.lastline = curline 567db96d56Sopenharmony_ci self.anchor_widget.see("insert") 577db96d56Sopenharmony_ci super().position_window() 587db96d56Sopenharmony_ci 597db96d56Sopenharmony_ci def showtip(self, text, parenleft, parenright): 607db96d56Sopenharmony_ci """Show the call-tip, bind events which will close it and reposition it. 617db96d56Sopenharmony_ci 627db96d56Sopenharmony_ci text: the text to display in the call-tip 637db96d56Sopenharmony_ci parenleft: index of the opening parenthesis in the text widget 647db96d56Sopenharmony_ci parenright: index of the closing parenthesis in the text widget, 657db96d56Sopenharmony_ci or the end of the line if there is no closing parenthesis 667db96d56Sopenharmony_ci """ 677db96d56Sopenharmony_ci # Only called in calltip.Calltip, where lines are truncated 687db96d56Sopenharmony_ci self.text = text 697db96d56Sopenharmony_ci if self.tipwindow or not self.text: 707db96d56Sopenharmony_ci return 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ci self.anchor_widget.mark_set(MARK_RIGHT, parenright) 737db96d56Sopenharmony_ci self.parenline, self.parencol = map( 747db96d56Sopenharmony_ci int, self.anchor_widget.index(parenleft).split(".")) 757db96d56Sopenharmony_ci 767db96d56Sopenharmony_ci super().showtip() 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci self._bind_events() 797db96d56Sopenharmony_ci 807db96d56Sopenharmony_ci def showcontents(self): 817db96d56Sopenharmony_ci """Create the call-tip widget.""" 827db96d56Sopenharmony_ci self.label = Label(self.tipwindow, text=self.text, justify=LEFT, 837db96d56Sopenharmony_ci background="#ffffd0", foreground="black", 847db96d56Sopenharmony_ci relief=SOLID, borderwidth=1, 857db96d56Sopenharmony_ci font=self.anchor_widget['font']) 867db96d56Sopenharmony_ci self.label.pack() 877db96d56Sopenharmony_ci 887db96d56Sopenharmony_ci def checkhide_event(self, event=None): 897db96d56Sopenharmony_ci """Handle CHECK_HIDE_EVENT: call hidetip or reschedule.""" 907db96d56Sopenharmony_ci if not self.tipwindow: 917db96d56Sopenharmony_ci # If the event was triggered by the same event that unbound 927db96d56Sopenharmony_ci # this function, the function will be called nevertheless, 937db96d56Sopenharmony_ci # so do nothing in this case. 947db96d56Sopenharmony_ci return None 957db96d56Sopenharmony_ci 967db96d56Sopenharmony_ci # Hide the call-tip if the insertion cursor moves outside of the 977db96d56Sopenharmony_ci # parenthesis. 987db96d56Sopenharmony_ci curline, curcol = map(int, self.anchor_widget.index("insert").split('.')) 997db96d56Sopenharmony_ci if curline < self.parenline or \ 1007db96d56Sopenharmony_ci (curline == self.parenline and curcol <= self.parencol) or \ 1017db96d56Sopenharmony_ci self.anchor_widget.compare("insert", ">", MARK_RIGHT): 1027db96d56Sopenharmony_ci self.hidetip() 1037db96d56Sopenharmony_ci return "break" 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ci # Not hiding the call-tip. 1067db96d56Sopenharmony_ci 1077db96d56Sopenharmony_ci self.position_window() 1087db96d56Sopenharmony_ci # Re-schedule this function to be called again in a short while. 1097db96d56Sopenharmony_ci if self.checkhide_after_id is not None: 1107db96d56Sopenharmony_ci self.anchor_widget.after_cancel(self.checkhide_after_id) 1117db96d56Sopenharmony_ci self.checkhide_after_id = \ 1127db96d56Sopenharmony_ci self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) 1137db96d56Sopenharmony_ci return None 1147db96d56Sopenharmony_ci 1157db96d56Sopenharmony_ci def hide_event(self, event): 1167db96d56Sopenharmony_ci """Handle HIDE_EVENT by calling hidetip.""" 1177db96d56Sopenharmony_ci if not self.tipwindow: 1187db96d56Sopenharmony_ci # See the explanation in checkhide_event. 1197db96d56Sopenharmony_ci return None 1207db96d56Sopenharmony_ci self.hidetip() 1217db96d56Sopenharmony_ci return "break" 1227db96d56Sopenharmony_ci 1237db96d56Sopenharmony_ci def hidetip(self): 1247db96d56Sopenharmony_ci """Hide the call-tip.""" 1257db96d56Sopenharmony_ci if not self.tipwindow: 1267db96d56Sopenharmony_ci return 1277db96d56Sopenharmony_ci 1287db96d56Sopenharmony_ci try: 1297db96d56Sopenharmony_ci self.label.destroy() 1307db96d56Sopenharmony_ci except TclError: 1317db96d56Sopenharmony_ci pass 1327db96d56Sopenharmony_ci self.label = None 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci self.parenline = self.parencol = self.lastline = None 1357db96d56Sopenharmony_ci try: 1367db96d56Sopenharmony_ci self.anchor_widget.mark_unset(MARK_RIGHT) 1377db96d56Sopenharmony_ci except TclError: 1387db96d56Sopenharmony_ci pass 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci try: 1417db96d56Sopenharmony_ci self._unbind_events() 1427db96d56Sopenharmony_ci except (TclError, ValueError): 1437db96d56Sopenharmony_ci # ValueError may be raised by MultiCall 1447db96d56Sopenharmony_ci pass 1457db96d56Sopenharmony_ci 1467db96d56Sopenharmony_ci super().hidetip() 1477db96d56Sopenharmony_ci 1487db96d56Sopenharmony_ci def _bind_events(self): 1497db96d56Sopenharmony_ci """Bind event handlers.""" 1507db96d56Sopenharmony_ci self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT, 1517db96d56Sopenharmony_ci self.checkhide_event) 1527db96d56Sopenharmony_ci for seq in CHECKHIDE_SEQUENCES: 1537db96d56Sopenharmony_ci self.anchor_widget.event_add(CHECKHIDE_EVENT, seq) 1547db96d56Sopenharmony_ci self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) 1557db96d56Sopenharmony_ci self.hideid = self.anchor_widget.bind(HIDE_EVENT, 1567db96d56Sopenharmony_ci self.hide_event) 1577db96d56Sopenharmony_ci for seq in HIDE_SEQUENCES: 1587db96d56Sopenharmony_ci self.anchor_widget.event_add(HIDE_EVENT, seq) 1597db96d56Sopenharmony_ci 1607db96d56Sopenharmony_ci def _unbind_events(self): 1617db96d56Sopenharmony_ci """Unbind event handlers.""" 1627db96d56Sopenharmony_ci for seq in CHECKHIDE_SEQUENCES: 1637db96d56Sopenharmony_ci self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq) 1647db96d56Sopenharmony_ci self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid) 1657db96d56Sopenharmony_ci self.checkhideid = None 1667db96d56Sopenharmony_ci for seq in HIDE_SEQUENCES: 1677db96d56Sopenharmony_ci self.anchor_widget.event_delete(HIDE_EVENT, seq) 1687db96d56Sopenharmony_ci self.anchor_widget.unbind(HIDE_EVENT, self.hideid) 1697db96d56Sopenharmony_ci self.hideid = None 1707db96d56Sopenharmony_ci 1717db96d56Sopenharmony_ci 1727db96d56Sopenharmony_cidef _calltip_window(parent): # htest # 1737db96d56Sopenharmony_ci from tkinter import Toplevel, Text, LEFT, BOTH 1747db96d56Sopenharmony_ci 1757db96d56Sopenharmony_ci top = Toplevel(parent) 1767db96d56Sopenharmony_ci top.title("Test call-tips") 1777db96d56Sopenharmony_ci x, y = map(int, parent.geometry().split('+')[1:]) 1787db96d56Sopenharmony_ci top.geometry("250x100+%d+%d" % (x + 175, y + 150)) 1797db96d56Sopenharmony_ci text = Text(top) 1807db96d56Sopenharmony_ci text.pack(side=LEFT, fill=BOTH, expand=1) 1817db96d56Sopenharmony_ci text.insert("insert", "string.split") 1827db96d56Sopenharmony_ci top.update() 1837db96d56Sopenharmony_ci 1847db96d56Sopenharmony_ci calltip = CalltipWindow(text) 1857db96d56Sopenharmony_ci def calltip_show(event): 1867db96d56Sopenharmony_ci calltip.showtip("(s='Hello world')", "insert", "end") 1877db96d56Sopenharmony_ci def calltip_hide(event): 1887db96d56Sopenharmony_ci calltip.hidetip() 1897db96d56Sopenharmony_ci text.event_add("<<calltip-show>>", "(") 1907db96d56Sopenharmony_ci text.event_add("<<calltip-hide>>", ")") 1917db96d56Sopenharmony_ci text.bind("<<calltip-show>>", calltip_show) 1927db96d56Sopenharmony_ci text.bind("<<calltip-hide>>", calltip_hide) 1937db96d56Sopenharmony_ci 1947db96d56Sopenharmony_ci text.focus_set() 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ciif __name__ == '__main__': 1977db96d56Sopenharmony_ci from unittest import main 1987db96d56Sopenharmony_ci main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False) 1997db96d56Sopenharmony_ci 2007db96d56Sopenharmony_ci from idlelib.idle_test.htest import run 2017db96d56Sopenharmony_ci run(_calltip_window) 202