mirror of
https://github.com/python/cpython.git
synced 2025-07-12 13:55:34 +00:00

Some MacOS-tk combinations need .update_idletasks(). The call is both unneeded and innocuous on Linux and Windows. Patch by Kevin Waltzer.
168 lines
6.1 KiB
Python
168 lines
6.1 KiB
Python
"""A calltip window class for Tkinter/IDLE.
|
|
|
|
After tooltip.py, which uses ideas gleaned from PySol
|
|
Used by calltip.
|
|
"""
|
|
from tkinter import Toplevel, Label, LEFT, SOLID, TclError
|
|
|
|
HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
|
|
HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
|
|
CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
|
|
CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
|
|
CHECKHIDE_TIME = 100 # milliseconds
|
|
|
|
MARK_RIGHT = "calltipwindowregion_right"
|
|
|
|
class CalltipWindow:
|
|
|
|
def __init__(self, widget):
|
|
self.widget = widget
|
|
self.tipwindow = self.label = None
|
|
self.parenline = self.parencol = None
|
|
self.lastline = None
|
|
self.hideid = self.checkhideid = None
|
|
self.checkhide_after_id = None
|
|
|
|
def position_window(self):
|
|
"""Check if needs to reposition the window, and if so - do it."""
|
|
curline = int(self.widget.index("insert").split('.')[0])
|
|
if curline == self.lastline:
|
|
return
|
|
self.lastline = curline
|
|
self.widget.see("insert")
|
|
if curline == self.parenline:
|
|
box = self.widget.bbox("%d.%d" % (self.parenline,
|
|
self.parencol))
|
|
else:
|
|
box = self.widget.bbox("%d.0" % curline)
|
|
if not box:
|
|
box = list(self.widget.bbox("insert"))
|
|
# align to left of window
|
|
box[0] = 0
|
|
box[2] = 0
|
|
x = box[0] + self.widget.winfo_rootx() + 2
|
|
y = box[1] + box[3] + self.widget.winfo_rooty()
|
|
self.tipwindow.wm_geometry("+%d+%d" % (x, y))
|
|
|
|
def showtip(self, text, parenleft, parenright):
|
|
"""Show the calltip, bind events which will close it and reposition it.
|
|
"""
|
|
# Only called in calltip.Calltip, where lines are truncated
|
|
self.text = text
|
|
if self.tipwindow or not self.text:
|
|
return
|
|
|
|
self.widget.mark_set(MARK_RIGHT, parenright)
|
|
self.parenline, self.parencol = map(
|
|
int, self.widget.index(parenleft).split("."))
|
|
|
|
self.tipwindow = tw = Toplevel(self.widget)
|
|
self.position_window()
|
|
# remove border on calltip window
|
|
tw.wm_overrideredirect(1)
|
|
try:
|
|
# This command is only needed and available on Tk >= 8.4.0 for OSX
|
|
# Without it, call tips intrude on the typing process by grabbing
|
|
# the focus.
|
|
tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
|
|
"help", "noActivates")
|
|
except TclError:
|
|
pass
|
|
self.label = Label(tw, text=self.text, justify=LEFT,
|
|
background="#ffffe0", relief=SOLID, borderwidth=1,
|
|
font = self.widget['font'])
|
|
self.label.pack()
|
|
tw.update_idletasks()
|
|
tw.lift() # work around bug in Tk 8.5.18+ (issue #24570)
|
|
|
|
self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
|
|
self.checkhide_event)
|
|
for seq in CHECKHIDE_SEQUENCES:
|
|
self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
|
|
self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
|
|
self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
|
|
self.hide_event)
|
|
for seq in HIDE_SEQUENCES:
|
|
self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
|
|
|
|
def checkhide_event(self, event=None):
|
|
if not self.tipwindow:
|
|
# If the event was triggered by the same event that unbinded
|
|
# this function, the function will be called nevertheless,
|
|
# so do nothing in this case.
|
|
return None
|
|
curline, curcol = map(int, self.widget.index("insert").split('.'))
|
|
if curline < self.parenline or \
|
|
(curline == self.parenline and curcol <= self.parencol) or \
|
|
self.widget.compare("insert", ">", MARK_RIGHT):
|
|
self.hidetip()
|
|
return "break"
|
|
else:
|
|
self.position_window()
|
|
if self.checkhide_after_id is not None:
|
|
self.widget.after_cancel(self.checkhide_after_id)
|
|
self.checkhide_after_id = \
|
|
self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
|
|
return None
|
|
|
|
def hide_event(self, event):
|
|
if not self.tipwindow:
|
|
# See the explanation in checkhide_event.
|
|
return None
|
|
self.hidetip()
|
|
return "break"
|
|
|
|
def hidetip(self):
|
|
if not self.tipwindow:
|
|
return
|
|
|
|
for seq in CHECKHIDE_SEQUENCES:
|
|
self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
|
|
self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid)
|
|
self.checkhideid = None
|
|
for seq in HIDE_SEQUENCES:
|
|
self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
|
|
self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
|
|
self.hideid = None
|
|
|
|
self.label.destroy()
|
|
self.label = None
|
|
self.tipwindow.destroy()
|
|
self.tipwindow = None
|
|
|
|
self.widget.mark_unset(MARK_RIGHT)
|
|
self.parenline = self.parencol = self.lastline = None
|
|
|
|
def is_active(self):
|
|
return bool(self.tipwindow)
|
|
|
|
|
|
def _calltip_window(parent): # htest #
|
|
from tkinter import Toplevel, Text, LEFT, BOTH
|
|
|
|
top = Toplevel(parent)
|
|
top.title("Test calltips")
|
|
x, y = map(int, parent.geometry().split('+')[1:])
|
|
top.geometry("200x100+%d+%d" % (x + 250, y + 175))
|
|
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)
|