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