Coverage for /Users/Newville/Codes/xraylarch/larch/wxlib/inputhook.py: 31%

154 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1#!/usr/bin/env python 

2""" 

3Enable wxPython to be used interacive by setting PyOS_InputHook. 

4 

5based on inputhook.py and inputhookwx.py from IPython, 

6which has several authors, including 

7 Robin Dunn, Brian Granger, Ondrej Certik 

8 

9tweaked by M Newville for larch 

10""" 

11 

12import sys 

13import time 

14import signal 

15from select import select 

16from ctypes import c_void_p, c_int, cast, CFUNCTYPE, pythonapi 

17 

18import wx 

19 

20POLLTIME = 10 # milliseconds 

21ON_INTERRUPT = None 

22WXLARCH_SYM = None 

23WXLARCH_INP = None 

24UPDATE_GROUPNAME = '_sys.wx' 

25UPDATE_GROUP = None 

26UPDATE_VAR = 'force_wxupdate' 

27IN_MODAL_DIALOG = False 

28 

29def update_requested(): 

30 "check if update has been requested" 

31 global WXLARCH_SYM, UPDATE_VAR, UPDATE_GROUP, UPDATE_GROUPNAME 

32 if WXLARCH_SYM is not None: 

33 if UPDATE_GROUP is None: 

34 UPDATE_GROUP = WXLARCH_SYM.get_symbol(UPDATE_GROUPNAME) 

35 return getattr(UPDATE_GROUP, UPDATE_VAR, False) 

36 return False 

37 

38def clear_update_request(): 

39 "clear update request" 

40 global WXLARCH_SYM, UPDATE_VAR, UPDATE_GROUP, UPDATE_GROUPNAME 

41 if UPDATE_GROUP is not None: 

42 setattr(UPDATE_GROUP, UPDATE_VAR, False) 

43 

44clock = time.time 

45sleep = time.sleep 

46 

47def onCtrlC(*args, **kws): 

48 global WXLARCH_SYM 

49 try: 

50 WXLARCH_SYM.set_symbol('_sys.wx.keyboard_interrupt', True) 

51 except AttributeError: 

52 pass 

53 raise KeyboardInterrupt 

54 return 0 

55 

56def capture_CtrlC(): 

57 signal.signal(signal.SIGINT, onCtrlC) 

58 

59def ignore_CtrlC(): 

60 signal.signal(signal.SIGINT, signal.SIG_IGN) 

61 

62def allow_idle(): 

63 # allow idle (needed for Mac OS X) 

64 pass 

65 

66def stdin_ready(): 

67 inp, out, err = select([sys.stdin],[],[],0) 

68 return bool(inp) 

69 

70if sys.platform == 'win32': 

71 from msvcrt import kbhit as stdin_ready 

72 clock = time.monotonic 

73 def ignore_CtrlC(): 

74 pass 

75 

76 

77class EnteredModalDialogHook(wx.ModalDialogHook): 

78 """ 

79 set Global flag IN_MODAL_DIALOG when in a Modal Dialog. 

80 

81 this will allow the event loop to *not* try to read stdina 

82 when a modal dialog is blocking, which causes problems on MacOS 

83 """ 

84 def __init__(self): 

85 wx.ModalDialogHook.__init__(self) 

86 

87 def Enter(self, dialog): 

88 global IN_MODAL_DIALOG 

89 IN_MODAL_DIALOG = True 

90 return wx.ID_NONE 

91 

92 def Exit(self, dialog): 

93 global IN_MODAL_DIALOG 

94 IN_MODAL_DIALOG = False 

95 

96 

97class EventLoopRunner(object): 

98 def __init__(self, parent): 

99 self.parent = parent 

100 self.timer = wx.Timer() 

101 self.evtloop = wx.GUIEventLoop() 

102 

103 def run(self, poll_time=None): 

104 if poll_time is None: 

105 poll_time = POLLTIME 

106 self.t0 = clock() 

107 self.evtloop = wx.GUIEventLoop() 

108 self.timer = wx.Timer() 

109 self.parent.Bind(wx.EVT_TIMER, self.check_stdin) 

110 self.timer.Start(poll_time) 

111 self.evtloop.Run() 

112 

113 def check_stdin(self, event=None): 

114 try: 

115 if (not IN_MODAL_DIALOG and (stdin_ready() or 

116 update_requested() or 

117 (clock() - self.t0) > 15)): 

118 self.timer.Stop() 

119 self.evtloop.Exit() 

120 del self.timer, self.evtloop 

121 clear_update_request() 

122 

123 except KeyboardInterrupt: 

124 print('Captured Ctrl-C!') 

125 except: 

126 print(sys.exc_info()) 

127 

128 

129def inputhook_wx(): 

130 """Run the wx event loop by processing pending events only. 

131 

132 This keeps processing pending events until stdin is ready. 

133 After processing all pending events, a call to time.sleep is inserted. 

134 This is needed, otherwise, CPU usage is at 100%. 

135 This sleep time should be tuned though for best performance. 

136 """ 

137 # We need to protect against a user pressing Control-C when IPython is 

138 # idle and this is running. We trap KeyboardInterrupt and pass. 

139 try: 

140 app = wx.GetApp() 

141 if app is not None: 

142 assert wx.IsMainThread() 

143 

144 if not callable(signal.getsignal(signal.SIGINT)): 

145 signal.signal(signal.SIGINT, signal.default_int_handler) 

146 evtloop = wx.GUIEventLoop() 

147 ea = wx.EventLoopActivator(evtloop) 

148 t = clock() 

149 while not stdin_ready() and not update_requested(): 

150 while evtloop.Pending(): 

151 t = clock() 

152 evtloop.Dispatch() 

153 

154 if callable(getattr(app, 'ProcessIdle', None)): 

155 app.ProcessIdle() 

156 if callable(getattr(evtloop, 'ProcessIdle', None)): 

157 evtloop.ProcessIdle() 

158 

159 # We need to sleep at this point to keep the idle CPU load 

160 # low. However, if sleep to long, GUI response is poor. 

161 used_time = clock() - t 

162 ptime = 0.001 

163 if used_time > 0.10: ptime = 0.05 

164 if used_time > 3.00: ptime = 0.25 

165 if used_time > 30.00: ptime = 1.00 

166 sleep(ptime) 

167 del ea 

168 clear_update_request() 

169 except KeyboardInterrupt: 

170 if callable(ON_INTERRUPT): 

171 ON_INTERRUPT() 

172 return 0 

173 

174def ping(timeout=0.001): 

175 "ping wx" 

176 try: 

177 t0 = clock() 

178 app = wx.GetApp() 

179 if app is not None: 

180 assert wx.IsMainThread() 

181 # Make a temporary event loop and process system events until 

182 # there are no more waiting, then allow idle events (which 

183 # will also deal with pending or posted wx events.) 

184 evtloop = wx_EventLoop() 

185 ea = wx.EventLoopActivator(evtloop) 

186 t0 = clock() 

187 while clock()-t0 < timeout: 

188 evtloop.Dispatch() 

189 app.ProcessIdle() 

190 del ea 

191 except: 

192 pass 

193 

194def inputhook_darwin(): 

195 """Run the wx event loop, polling for stdin. 

196 

197 This version runs the wx eventloop for an undetermined amount of time, 

198 during which it periodically checks to see if anything is ready on 

199 stdin. If anything is ready on stdin, the event loop exits. 

200 

201 The argument to eloop.run controls how often the event loop looks at stdin. 

202 This determines the responsiveness at the keyboard. A setting of 20ms 

203 gives decent keyboard response. It can be shortened if we know the loop will 

204 exit (as from update_requested()), but CPU usage of the idle process goes up 

205 if we run this too frequently. 

206 """ 

207 global POLLTIME, ON_INTERRUPT 

208 try: 

209 app = wx.GetApp() 

210 if app is not None: 

211 assert wx.IsMainThread() 

212 modal_hook = EnteredModalDialogHook() 

213 modal_hook.Register() 

214 eloop = EventLoopRunner(parent=app) 

215 ptime = POLLTIME 

216 if update_requested(): 

217 ptime /= 10 

218 eloop.run(poll_time=int(ptime+1)) 

219 except KeyboardInterrupt: 

220 if callable(ON_INTERRUPT): 

221 ON_INTERRUPT() 

222 return 0 

223 

224 

225def ping_darwin(timeout=0.001): 

226 inputhook_darwin() 

227 

228if sys.platform == 'darwin': 

229 # On OSX, evtloop.Pending() always returns True, regardless of there being 

230 # any events pending. As such we can't use implementations 1 or 3 of the 

231 # inputhook as those depend on a pending/dispatch loop. 

232 inputhook_wx = inputhook_darwin 

233 

234try: 

235 capture_CtrlC() 

236except: 

237 pass 

238cback = CFUNCTYPE(c_int)(inputhook_wx) 

239py_inphook = c_void_p.in_dll(pythonapi, 'PyOS_InputHook') 

240py_inphook.value = cast(cback, c_void_p).value 

241 

242# import for Darwin! 

243if sys.platform == 'darwin': 

244 from .allow_idle_macosx import allow_idle 

245 allow_idle() 

246# print("no allow idle")