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