/Lib/idlelib/CallTipWindow.py

http://unladen-swallow.googlecode.com/ · Python · 171 lines · 122 code · 25 blank · 24 comment · 24 complexity · 5f672dbf9067bb98b35dd75a367b8498 MD5 · raw file

  1. """A CallTip window class for Tkinter/IDLE.
  2. After ToolTip.py, which uses ideas gleaned from PySol
  3. Used by the CallTips IDLE extension.
  4. """
  5. from Tkinter import *
  6. HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
  7. HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
  8. CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
  9. CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>")
  10. CHECKHIDE_TIME = 100 # miliseconds
  11. MARK_RIGHT = "calltipwindowregion_right"
  12. class CallTip:
  13. def __init__(self, widget):
  14. self.widget = widget
  15. self.tipwindow = self.label = None
  16. self.parenline = self.parencol = None
  17. self.lastline = None
  18. self.hideid = self.checkhideid = None
  19. def position_window(self):
  20. """Check if needs to reposition the window, and if so - do it."""
  21. curline = int(self.widget.index("insert").split('.')[0])
  22. if curline == self.lastline:
  23. return
  24. self.lastline = curline
  25. self.widget.see("insert")
  26. if curline == self.parenline:
  27. box = self.widget.bbox("%d.%d" % (self.parenline,
  28. self.parencol))
  29. else:
  30. box = self.widget.bbox("%d.0" % curline)
  31. if not box:
  32. box = list(self.widget.bbox("insert"))
  33. # align to left of window
  34. box[0] = 0
  35. box[2] = 0
  36. x = box[0] + self.widget.winfo_rootx() + 2
  37. y = box[1] + box[3] + self.widget.winfo_rooty()
  38. self.tipwindow.wm_geometry("+%d+%d" % (x, y))
  39. def showtip(self, text, parenleft, parenright):
  40. """Show the calltip, bind events which will close it and reposition it.
  41. """
  42. # truncate overly long calltip
  43. if len(text) >= 79:
  44. textlines = text.splitlines()
  45. for i, line in enumerate(textlines):
  46. if len(line) > 79:
  47. textlines[i] = line[:75] + ' ...'
  48. text = '\n'.join(textlines)
  49. self.text = text
  50. if self.tipwindow or not self.text:
  51. return
  52. self.widget.mark_set(MARK_RIGHT, parenright)
  53. self.parenline, self.parencol = map(
  54. int, self.widget.index(parenleft).split("."))
  55. self.tipwindow = tw = Toplevel(self.widget)
  56. self.position_window()
  57. # remove border on calltip window
  58. tw.wm_overrideredirect(1)
  59. try:
  60. # This command is only needed and available on Tk >= 8.4.0 for OSX
  61. # Without it, call tips intrude on the typing process by grabbing
  62. # the focus.
  63. tw.tk.call("::tk::unsupported::MacWindowStyle", "style", tw._w,
  64. "help", "noActivates")
  65. except TclError:
  66. pass
  67. self.label = Label(tw, text=self.text, justify=LEFT,
  68. background="#ffffe0", relief=SOLID, borderwidth=1,
  69. font = self.widget['font'])
  70. self.label.pack()
  71. self.checkhideid = self.widget.bind(CHECKHIDE_VIRTUAL_EVENT_NAME,
  72. self.checkhide_event)
  73. for seq in CHECKHIDE_SEQUENCES:
  74. self.widget.event_add(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
  75. self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
  76. self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
  77. self.hide_event)
  78. for seq in HIDE_SEQUENCES:
  79. self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
  80. def checkhide_event(self, event=None):
  81. if not self.tipwindow:
  82. # If the event was triggered by the same event that unbinded
  83. # this function, the function will be called nevertheless,
  84. # so do nothing in this case.
  85. return
  86. curline, curcol = map(int, self.widget.index("insert").split('.'))
  87. if curline < self.parenline or \
  88. (curline == self.parenline and curcol <= self.parencol) or \
  89. self.widget.compare("insert", ">", MARK_RIGHT):
  90. self.hidetip()
  91. else:
  92. self.position_window()
  93. self.widget.after(CHECKHIDE_TIME, self.checkhide_event)
  94. def hide_event(self, event):
  95. if not self.tipwindow:
  96. # See the explanation in checkhide_event.
  97. return
  98. self.hidetip()
  99. def hidetip(self):
  100. if not self.tipwindow:
  101. return
  102. for seq in CHECKHIDE_SEQUENCES:
  103. self.widget.event_delete(CHECKHIDE_VIRTUAL_EVENT_NAME, seq)
  104. self.widget.unbind(CHECKHIDE_VIRTUAL_EVENT_NAME, self.checkhideid)
  105. self.checkhideid = None
  106. for seq in HIDE_SEQUENCES:
  107. self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
  108. self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
  109. self.hideid = None
  110. self.label.destroy()
  111. self.label = None
  112. self.tipwindow.destroy()
  113. self.tipwindow = None
  114. self.widget.mark_unset(MARK_RIGHT)
  115. self.parenline = self.parencol = self.lastline = None
  116. def is_active(self):
  117. return bool(self.tipwindow)
  118. ###############################
  119. #
  120. # Test Code
  121. #
  122. class container: # Conceptually an editor_window
  123. def __init__(self):
  124. root = Tk()
  125. text = self.text = Text(root)
  126. text.pack(side=LEFT, fill=BOTH, expand=1)
  127. text.insert("insert", "string.split")
  128. root.update()
  129. self.calltip = CallTip(text)
  130. text.event_add("<<calltip-show>>", "(")
  131. text.event_add("<<calltip-hide>>", ")")
  132. text.bind("<<calltip-show>>", self.calltip_show)
  133. text.bind("<<calltip-hide>>", self.calltip_hide)
  134. text.focus_set()
  135. root.mainloop()
  136. def calltip_show(self, event):
  137. self.calltip.showtip("Hello world")
  138. def calltip_hide(self, event):
  139. self.calltip.hidetip()
  140. def main():
  141. # Test code
  142. c=container()
  143. if __name__=='__main__':
  144. main()