Coverage for calltip_w.py: 15%

121 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-11 13:22 -0700

1"""A call-tip window class for Tkinter/IDLE. 

2 

3After tooltip.py, which uses ideas gleaned from PySol. 

4Used by calltip.py. 

5""" 

6from tkinter import Label, LEFT, SOLID, TclError 

7 

8from idlelib.tooltip import TooltipBase 

9 

10HIDE_EVENT = "<<calltipwindow-hide>>" 

11HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>") 

12CHECKHIDE_EVENT = "<<calltipwindow-checkhide>>" 

13CHECKHIDE_SEQUENCES = ("<KeyRelease>", "<ButtonRelease>") 

14CHECKHIDE_TIME = 100 # milliseconds 

15 

16MARK_RIGHT = "calltipwindowregion_right" 

17 

18 

19class CalltipWindow(TooltipBase): 

20 """A call-tip widget for tkinter text widgets.""" 

21 

22 def __init__(self, text_widget): 

23 """Create a call-tip; shown by showtip(). 

24 

25 text_widget: a Text widget with code for which call-tips are desired 

26 """ 

27 # Note: The Text widget will be accessible as self.anchor_widget 

28 super(CalltipWindow, self).__init__(text_widget) 

29 

30 self.label = self.text = None 

31 self.parenline = self.parencol = self.lastline = None 

32 self.hideid = self.checkhideid = None 

33 self.checkhide_after_id = None 

34 

35 def get_position(self): 

36 """Choose the position of the call-tip.""" 

37 curline = int(self.anchor_widget.index("insert").split('.')[0]) 

38 if curline == self.parenline: 

39 anchor_index = (self.parenline, self.parencol) 

40 else: 

41 anchor_index = (curline, 0) 

42 box = self.anchor_widget.bbox("%d.%d" % anchor_index) 

43 if not box: 

44 box = list(self.anchor_widget.bbox("insert")) 

45 # align to left of window 

46 box[0] = 0 

47 box[2] = 0 

48 return box[0] + 2, box[1] + box[3] 

49 

50 def position_window(self): 

51 "Reposition the window if needed." 

52 curline = int(self.anchor_widget.index("insert").split('.')[0]) 

53 if curline == self.lastline: 

54 return 

55 self.lastline = curline 

56 self.anchor_widget.see("insert") 

57 super(CalltipWindow, self).position_window() 

58 

59 def showtip(self, text, parenleft, parenright): 

60 """Show the call-tip, bind events which will close it and reposition it. 

61 

62 text: the text to display in the call-tip 

63 parenleft: index of the opening parenthesis in the text widget 

64 parenright: index of the closing parenthesis in the text widget, 

65 or the end of the line if there is no closing parenthesis 

66 """ 

67 # Only called in calltip.Calltip, where lines are truncated 

68 self.text = text 

69 if self.tipwindow or not self.text: 

70 return 

71 

72 self.anchor_widget.mark_set(MARK_RIGHT, parenright) 

73 self.parenline, self.parencol = map( 

74 int, self.anchor_widget.index(parenleft).split(".")) 

75 

76 super(CalltipWindow, self).showtip() 

77 

78 self._bind_events() 

79 

80 def showcontents(self): 

81 """Create the call-tip widget.""" 

82 self.label = Label(self.tipwindow, text=self.text, justify=LEFT, 

83 background="#ffffd0", foreground="black", 

84 relief=SOLID, borderwidth=1, 

85 font=self.anchor_widget['font']) 

86 self.label.pack() 

87 

88 def checkhide_event(self, event=None): 

89 """Handle CHECK_HIDE_EVENT: call hidetip or reschedule.""" 

90 if not self.tipwindow: 

91 # If the event was triggered by the same event that unbound 

92 # this function, the function will be called nevertheless, 

93 # so do nothing in this case. 

94 return None 

95 

96 # Hide the call-tip if the insertion cursor moves outside of the 

97 # parenthesis. 

98 curline, curcol = map(int, self.anchor_widget.index("insert").split('.')) 

99 if curline < self.parenline or \ 

100 (curline == self.parenline and curcol <= self.parencol) or \ 

101 self.anchor_widget.compare("insert", ">", MARK_RIGHT): 

102 self.hidetip() 

103 return "break" 

104 

105 # Not hiding the call-tip. 

106 

107 self.position_window() 

108 # Re-schedule this function to be called again in a short while. 

109 if self.checkhide_after_id is not None: 

110 self.anchor_widget.after_cancel(self.checkhide_after_id) 

111 self.checkhide_after_id = \ 

112 self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) 

113 return None 

114 

115 def hide_event(self, event): 

116 """Handle HIDE_EVENT by calling hidetip.""" 

117 if not self.tipwindow: 

118 # See the explanation in checkhide_event. 

119 return None 

120 self.hidetip() 

121 return "break" 

122 

123 def hidetip(self): 

124 """Hide the call-tip.""" 

125 if not self.tipwindow: 

126 return 

127 

128 try: 

129 self.label.destroy() 

130 except TclError: 

131 pass 

132 self.label = None 

133 

134 self.parenline = self.parencol = self.lastline = None 

135 try: 

136 self.anchor_widget.mark_unset(MARK_RIGHT) 

137 except TclError: 

138 pass 

139 

140 try: 

141 self._unbind_events() 

142 except (TclError, ValueError): 

143 # ValueError may be raised by MultiCall 

144 pass 

145 

146 super(CalltipWindow, self).hidetip() 

147 

148 def _bind_events(self): 

149 """Bind event handlers.""" 

150 self.checkhideid = self.anchor_widget.bind(CHECKHIDE_EVENT, 

151 self.checkhide_event) 

152 for seq in CHECKHIDE_SEQUENCES: 

153 self.anchor_widget.event_add(CHECKHIDE_EVENT, seq) 

154 self.anchor_widget.after(CHECKHIDE_TIME, self.checkhide_event) 

155 self.hideid = self.anchor_widget.bind(HIDE_EVENT, 

156 self.hide_event) 

157 for seq in HIDE_SEQUENCES: 

158 self.anchor_widget.event_add(HIDE_EVENT, seq) 

159 

160 def _unbind_events(self): 

161 """Unbind event handlers.""" 

162 for seq in CHECKHIDE_SEQUENCES: 

163 self.anchor_widget.event_delete(CHECKHIDE_EVENT, seq) 

164 self.anchor_widget.unbind(CHECKHIDE_EVENT, self.checkhideid) 

165 self.checkhideid = None 

166 for seq in HIDE_SEQUENCES: 

167 self.anchor_widget.event_delete(HIDE_EVENT, seq) 

168 self.anchor_widget.unbind(HIDE_EVENT, self.hideid) 

169 self.hideid = None 

170 

171 

172def _calltip_window(parent): # htest # 

173 from tkinter import Toplevel, Text, LEFT, BOTH 

174 

175 top = Toplevel(parent) 

176 top.title("Test call-tips") 

177 x, y = map(int, parent.geometry().split('+')[1:]) 

178 top.geometry("250x100+%d+%d" % (x + 175, y + 150)) 

179 text = Text(top) 

180 text.pack(side=LEFT, fill=BOTH, expand=1) 

181 text.insert("insert", "string.split") 

182 top.update() 

183 

184 calltip = CalltipWindow(text) 

185 def calltip_show(event): 

186 calltip.showtip("(s='Hello world')", "insert", "end") 

187 def calltip_hide(event): 

188 calltip.hidetip() 

189 text.event_add("<<calltip-show>>", "(") 

190 text.event_add("<<calltip-hide>>", ")") 

191 text.bind("<<calltip-show>>", calltip_show) 

192 text.bind("<<calltip-hide>>", calltip_hide) 

193 

194 text.focus_set() 

195 

196if __name__ == '__main__': 196 ↛ 197line 196 didn't jump to line 197, because the condition on line 196 was never true

197 from unittest import main 

198 main('idlelib.idle_test.test_calltip_w', verbosity=2, exit=False) 

199 

200 from idlelib.idle_test.htest import run 

201 run(_calltip_window)