##// END OF EJS Templates
RF: Use SetExitOnFrameDelete instead of creating a frame
Paul McCarthy -
Show More
@@ -1,221 +1,217
1 1 """Enable wxPython to be used interactively in prompt_toolkit
2 2 """
3 3
4 4 import sys
5 5 import signal
6 6 import time
7 7 from timeit import default_timer as clock
8 8 import wx
9 9
10 10
11 11 def ignore_keyboardinterrupts(func):
12 12 """Decorator which causes KeyboardInterrupt exceptions to be ignored during
13 13 execution of the decorated function.
14 14
15 15 This is used by the inputhook functions to handle the event where the user
16 16 presses CTRL+C while IPython is idle, and the inputhook loop is running. In
17 17 this case, we want to ignore interrupts.
18 18 """
19 19 def wrapper(*args, **kwargs):
20 20 try:
21 21 func(*args, **kwargs)
22 22 except KeyboardInterrupt:
23 23 pass
24 24 return wrapper
25 25
26 26
27 27 @ignore_keyboardinterrupts
28 28 def inputhook_wx1(context):
29 29 """Run the wx event loop by processing pending events only.
30 30
31 31 This approach seems to work, but its performance is not great as it
32 32 relies on having PyOS_InputHook called regularly.
33 33 """
34 34 app = wx.GetApp()
35 35 if app is not None:
36 36 assert wx.Thread_IsMain()
37 37
38 38 # Make a temporary event loop and process system events until
39 39 # there are no more waiting, then allow idle events (which
40 40 # will also deal with pending or posted wx events.)
41 41 evtloop = wx.EventLoop()
42 42 ea = wx.EventLoopActivator(evtloop)
43 43 while evtloop.Pending():
44 44 evtloop.Dispatch()
45 45 app.ProcessIdle()
46 46 del ea
47 47 return 0
48 48
49 49
50 50 class EventLoopTimer(wx.Timer):
51 51
52 52 def __init__(self, func):
53 53 self.func = func
54 54 wx.Timer.__init__(self)
55 55
56 56 def Notify(self):
57 57 self.func()
58 58
59 59
60 60 class EventLoopRunner(object):
61 61
62 62 def Run(self, time, input_is_ready):
63 63 self.input_is_ready = input_is_ready
64 64 self.evtloop = wx.EventLoop()
65 65 self.timer = EventLoopTimer(self.check_stdin)
66 66 self.timer.Start(time)
67 67 self.evtloop.Run()
68 68
69 69 def check_stdin(self):
70 70 if self.input_is_ready():
71 71 self.timer.Stop()
72 72 self.evtloop.Exit()
73 73
74 74
75 75 @ignore_keyboardinterrupts
76 76 def inputhook_wx2(context):
77 77 """Run the wx event loop, polling for stdin.
78 78
79 79 This version runs the wx eventloop for an undetermined amount of time,
80 80 during which it periodically checks to see if anything is ready on
81 81 stdin. If anything is ready on stdin, the event loop exits.
82 82
83 83 The argument to elr.Run controls how often the event loop looks at stdin.
84 84 This determines the responsiveness at the keyboard. A setting of 1000
85 85 enables a user to type at most 1 char per second. I have found that a
86 86 setting of 10 gives good keyboard response. We can shorten it further,
87 87 but eventually performance would suffer from calling select/kbhit too
88 88 often.
89 89 """
90 90 app = wx.GetApp()
91 91 if app is not None:
92 92 assert wx.Thread_IsMain()
93 93 elr = EventLoopRunner()
94 94 # As this time is made shorter, keyboard response improves, but idle
95 95 # CPU load goes up. 10 ms seems like a good compromise.
96 96 elr.Run(time=10, # CHANGE time here to control polling interval
97 97 input_is_ready=context.input_is_ready)
98 98 return 0
99 99
100 100
101 101 @ignore_keyboardinterrupts
102 102 def inputhook_wx3(context):
103 103 """Run the wx event loop by processing pending events only.
104 104
105 105 This is like inputhook_wx1, but it keeps processing pending events
106 106 until stdin is ready. After processing all pending events, a call to
107 107 time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%.
108 108 This sleep time should be tuned though for best performance.
109 109 """
110 110 app = wx.GetApp()
111 111 if app is not None:
112 112 assert wx.Thread_IsMain()
113 113
114 114 # The import of wx on Linux sets the handler for signal.SIGINT
115 115 # to 0. This is a bug in wx or gtk. We fix by just setting it
116 116 # back to the Python default.
117 117 if not callable(signal.getsignal(signal.SIGINT)):
118 118 signal.signal(signal.SIGINT, signal.default_int_handler)
119 119
120 120 evtloop = wx.EventLoop()
121 121 ea = wx.EventLoopActivator(evtloop)
122 122 t = clock()
123 123 while not context.input_is_ready():
124 124 while evtloop.Pending():
125 125 t = clock()
126 126 evtloop.Dispatch()
127 127 app.ProcessIdle()
128 128 # We need to sleep at this point to keep the idle CPU load
129 129 # low. However, if sleep to long, GUI response is poor. As
130 130 # a compromise, we watch how often GUI events are being processed
131 131 # and switch between a short and long sleep time. Here are some
132 132 # stats useful in helping to tune this.
133 133 # time CPU load
134 134 # 0.001 13%
135 135 # 0.005 3%
136 136 # 0.01 1.5%
137 137 # 0.05 0.5%
138 138 used_time = clock() - t
139 139 if used_time > 10.0:
140 140 # print 'Sleep for 1 s' # dbg
141 141 time.sleep(1.0)
142 142 elif used_time > 0.1:
143 143 # Few GUI events coming in, so we can sleep longer
144 144 # print 'Sleep for 0.05 s' # dbg
145 145 time.sleep(0.05)
146 146 else:
147 147 # Many GUI events coming in, so sleep only very little
148 148 time.sleep(0.001)
149 149 del ea
150 150 return 0
151 151
152 152
153 153 @ignore_keyboardinterrupts
154 154 def inputhook_wxphoenix(context):
155 155 """Run the wx event loop until the user provides more input.
156 156
157 157 This input hook is suitable for use with wxPython >= 4 (a.k.a. Phoenix).
158 158
159 159 It uses the same approach to that used in
160 160 ipykernel.eventloops.loop_wx. The wx.MainLoop is executed, and a wx.Timer
161 161 is used to periodically poll the context for input. As soon as input is
162 162 ready, the wx.MainLoop is stopped.
163 163 """
164 164
165 165 app = wx.GetApp()
166 166
167 167 if app is None:
168 168 return
169 169
170 170 if context.input_is_ready():
171 171 return
172 172
173 173 assert wx.IsMainThread()
174 174
175 175 # Wx uses milliseconds
176 176 poll_interval = 100
177 177
178 # We have to create a dummy wx.Frame, otherwise wx.App.MainLoop will know
179 # that it has nothing to do, and will return immediately.
180 frame = getattr(inputhook_wxphoenix, '_frame', None)
181 if frame is None:
182 inputhook_wxphoenix._frame = frame = wx.Frame(None)
183 frame.Show(False)
184
185 178 # Use a wx.Timer to periodically check whether input is ready - as soon as
186 179 # it is, we exit the main loop
187 180 def poll(ev):
188 181 if context.input_is_ready():
189 182 app.ExitMainLoop()
190 183
191 184 timer = wx.Timer()
192 185 timer.Start(poll_interval)
193 186 timer.Bind(wx.EVT_TIMER, poll)
194 187
195 188 # The import of wx on Linux sets the handler for signal.SIGINT to 0. This
196 189 # is a bug in wx or gtk. We fix by just setting it back to the Python
197 190 # default.
198 191 if not callable(signal.getsignal(signal.SIGINT)):
199 192 signal.signal(signal.SIGINT, signal.default_int_handler)
200 193
194 # The SetExitOnFrameDelete call allows us to run the wx mainloop without
195 # having a frame open.
196 app.SetExitOnFrameDelete(False)
201 197 app.MainLoop()
202 198
203 199
204 200 # Get the major wx version number to figure out what input hook we should use.
205 201 major_version = 3
206 202
207 203 try:
208 204 major_version = int(wx.__version__[0])
209 205 except Exception:
210 206 pass
211 207
212 208 # Use the phoenix hook on all platforms for wxpython >= 4
213 209 if major_version >= 4:
214 210 inputhook = inputhook_wxphoenix
215 211 # On OSX, evtloop.Pending() always returns True, regardless of there being
216 212 # any events pending. As such we can't use implementations 1 or 3 of the
217 213 # inputhook as those depend on a pending/dispatch loop.
218 214 elif sys.platform == 'darwin':
219 215 inputhook = inputhook_wx2
220 216 else:
221 217 inputhook = inputhook_wx3
General Comments 0
You need to be logged in to leave comments. Login now