Spaces:
Running
Running
"""A call-tip window class for Tkinter/IDLE. | |
After tooltip.py, which uses ideas gleaned from PySol. | |
Used by calltip.py. | |
""" | |
from tkinter import Label, LEFT, SOLID, TclError | |
from idlelib.tooltip import TooltipBase | |
HIDE_EVENT = "<<calltipwindow-hide>>" | |
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") | |
CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>" | |
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") | |
CHECKHIDE_TIME = 100 # milliseconds | |
MARK_RIGHT = "calltipwindowregion_right" | |
class CalltipWindow(TooltipBase): | |
"""A call-tip widget for tkinter text widgets.""" | |
def __init__(self, text_widget): | |
"""Create a call-tip; shown by showtip(). | |
text_widget: a Text widget with code for which call-tips are desired | |
""" | |
# Note: The Text widget will be accessible as self.anchor_widget | |
super(CalltipWindow, self).__init__(text_widget) | |
self.label = self.text = None | |
self.parenline = self.parencol = self.lastline = None | |
self.hideid = self.checkhideid = None | |
self.checkhide_after_id = None | |
def get_position(self): | |
"""Choose the position of the call-tip.""" | |
curline = int(self.anchor_widget.index("insert").split('.')[0]) | |
if curline == self.parenline: | |
anchor_index = (self.parenline, self.parencol) | |
else: | |
anchor_index = (curline, 0) | |
box = self.anchor_widget.bbox("%d.%d" % anchor_index) | |
if not box: | |
box = list(self.anchor_widget.bbox("insert")) | |
# align to left of window | |
box[0] = 0 | |
box[2] = 0 | |
return box[0] + 2, box[1] + box[3] | |
def position_window(self): | |
"Reposition the window if needed." | |
curline = int(self.anchor_widget.index("insert").split('.')[0]) | |
if curline == self.lastline: | |
return | |
self.lastline = curline | |
self.anchor_widget.see("insert") | |
super(CalltipWindow, self).position_window() | |
def showtip(self, text, parenleft, parenright): | |
"""Show the call-tip, bind events which will close it and reposition it. | |
text: the text to display in the call-tip | |
parenleft: index of the opening parenthesis in the text widget | |
parenright: index of the closing parenthesis in the text widget, | |
or the end of the line if there is no closing parenthesis | |
""" | |
# Only called in calltip.Calltip, where lines are truncated | |
self.text = text | |
if self.tipwindow or not self.text: | |
return | |
self.anchor_widget.mark_set(MARK_RIGHT, parenright) | |
self.parenline, self.parencol = map( | |
int, self.anchor_widget.index(parenleft).split(".")) | |
super(CalltipWindow, self).showtip() | |
self._bind_events() | |
def showcontents(self): | |
"""Create the call-tip widget.""" | |
self.label = Label(self.tipwindow, text=self.text, justify=LEFT, | |
background="#ffffd0", foreground="black", | |
relief=SOLID, borderwidth=1, | |
font=self.anchor_widget['font']) | |
self.label.pack() | |
def checkhide_event(self, event=None): | |
"""Handle CHECK_HIDE_EVENT: call hidetip or reschedule.""" | |
if not self.tipwindow: | |
# If the event was triggered by the same event that unbound | |
# this function, the function will be called nevertheless, | |
# so do nothing in this case. | |
return None | |
# Hide the call-tip if the insertion cursor moves outside of the | |
# parenthesis. | |
curline, curcol = map(int, self.anchor_widget.index("insert").split('.')) | |
if curline < self.parenline or \ | |
(curline == self.parenline and curcol <= self.parencol) or \ | |
self.anchor_widget.compare("insert", ">", MARK_RIGHT): | |
self.hidetip() | |
return "break" | |
# Not hiding the call-tip. | |
self.position_window() | |
# Re-schedule this function to be called again in a short while. | |
if self.checkhide_after_id is not None: | |
self.anchor_widget.after_cancel(self.checkhide_after_id) | |
self.checkhide_after_id = \ | |
self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) | |
return None | |
def hide_event(self, event): | |
"""Handle HIDE_EVENT by calling hidetip.""" | |
if not self.tipwindow: | |
# See the explanation in checkhide_event. | |
return None | |
self.hidetip() | |
return "break" | |
def hidetip(self): | |
"""Hide the call-tip.""" | |
if not self.tipwindow: | |
return | |
try: | |
self.label.destroy() | |
except TclError: | |
pass | |
self.label = None | |
self.parenline = self.parencol = self.lastline = None | |
try: | |
self.anchor_widget.mark_unset(MARK_RIGHT) | |
except TclError: | |
pass | |
try: | |
self._unbind_events() | |
except (TclError, ValueError): | |
# ValueError may be raised by MultiCall | |
pass | |
super(CalltipWindow, self).hidetip() | |
def _bind_events(self): | |
"""Bind event handlers.""" | |
self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT, | |
self.checkhide_event) | |
for seq in CHECKHIDE_SEQUENCES: | |
self.anchor_widget.event_add(CHECKHIDE_EVENT, seq) | |
self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) | |
self.hideid = self.anchor_widget.bind(HIDE_EVENT, | |
self.hide_event) | |
for seq in HIDE_SEQUENCES: | |
self.anchor_widget.event_add(HIDE_EVENT, seq) | |
def _unbind_events(self): | |
"""Unbind event handlers.""" | |
for seq in CHECKHIDE_SEQUENCES: | |
self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq) | |
self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid) | |
self.checkhideid = None | |
for seq in HIDE_SEQUENCES: | |
self.anchor_widget.event_delete(HIDE_EVENT, seq) | |
self.anchor_widget.unbind(HIDE_EVENT, self.hideid) | |
self.hideid = None | |
def _calltip_window(parent): # htest # | |
from tkinter import Toplevel, Text, LEFT, BOTH | |
top = Toplevel(parent) | |
top.title("Test call-tips") | |
x, y = map(int, parent.geometry().split('+')[1:]) | |
top.geometry("250x100+%d+%d" % (x + 175, y + 150)) | |
text = Text(top) | |
text.pack(side=LEFT, fill=BOTH, expand=1) | |
text.insert("insert", "string.split") | |
top.update() | |
calltip = CalltipWindow(text) | |
def calltip_show(event): | |
calltip.showtip("(s='Hello world')", "insert", "end") | |
def calltip_hide(event): | |
calltip.hidetip() | |
text.event_add("<<calltip-show>>", "(") | |
text.event_add("<<calltip-hide>>", ")") | |
text.bind("<<calltip-show>>", calltip_show) | |
text.bind("<<calltip-hide>>", calltip_hide) | |
text.focus_set() | |
if __name__ == '__main__': | |
from unittest import main | |
main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False) | |
from idlelib.idle_test.htest import run | |
run(_calltip_window) | |