wx.py
219 lines
| 7.0 KiB
| text/x-python
|
PythonLexer
Min ho Kim
|
r25167 | """Enable wxPython to be used interactively in prompt_toolkit | ||
Thomas Kluyver
|
r21941 | """ | ||
import sys | ||||
import signal | ||||
import time | ||||
from timeit import default_timer as clock | ||||
import wx | ||||
Paul McCarthy
|
r25213 | def ignore_keyboardinterrupts(func): | ||
Paul McCarthy
|
r25214 | """Decorator which causes KeyboardInterrupt exceptions to be ignored during | ||
execution of the decorated function. | ||||
This is used by the inputhook functions to handle the event where the user | ||||
presses CTRL+C while IPython is idle, and the inputhook loop is running. In | ||||
this case, we want to ignore interrupts. | ||||
Paul McCarthy
|
r25213 | """ | ||
def wrapper(*args, **kwargs): | ||||
try: | ||||
func(*args, **kwargs) | ||||
except KeyboardInterrupt: | ||||
pass | ||||
return wrapper | ||||
@ignore_keyboardinterrupts | ||||
Thomas Kluyver
|
r21941 | def inputhook_wx1(context): | ||
"""Run the wx event loop by processing pending events only. | ||||
This approach seems to work, but its performance is not great as it | ||||
relies on having PyOS_InputHook called regularly. | ||||
""" | ||||
Paul McCarthy
|
r25213 | app = wx.GetApp() | ||
if app is not None: | ||||
assert wx.Thread_IsMain() | ||||
# Make a temporary event loop and process system events until | ||||
# there are no more waiting, then allow idle events (which | ||||
# will also deal with pending or posted wx events.) | ||||
evtloop = wx.EventLoop() | ||||
ea = wx.EventLoopActivator(evtloop) | ||||
while evtloop.Pending(): | ||||
evtloop.Dispatch() | ||||
app.ProcessIdle() | ||||
del ea | ||||
Thomas Kluyver
|
r21941 | return 0 | ||
Paul McCarthy
|
r25213 | |||
Thomas Kluyver
|
r21941 | class EventLoopTimer(wx.Timer): | ||
def __init__(self, func): | ||||
self.func = func | ||||
wx.Timer.__init__(self) | ||||
def Notify(self): | ||||
self.func() | ||||
Paul McCarthy
|
r25213 | |||
Thomas Kluyver
|
r21941 | class EventLoopRunner(object): | ||
def Run(self, time, input_is_ready): | ||||
self.input_is_ready = input_is_ready | ||||
self.evtloop = wx.EventLoop() | ||||
self.timer = EventLoopTimer(self.check_stdin) | ||||
self.timer.Start(time) | ||||
self.evtloop.Run() | ||||
def check_stdin(self): | ||||
if self.input_is_ready(): | ||||
self.timer.Stop() | ||||
self.evtloop.Exit() | ||||
Paul McCarthy
|
r25213 | |||
@ignore_keyboardinterrupts | ||||
Thomas Kluyver
|
r21941 | def inputhook_wx2(context): | ||
"""Run the wx event loop, polling for stdin. | ||||
This version runs the wx eventloop for an undetermined amount of time, | ||||
during which it periodically checks to see if anything is ready on | ||||
stdin. If anything is ready on stdin, the event loop exits. | ||||
The argument to elr.Run controls how often the event loop looks at stdin. | ||||
This determines the responsiveness at the keyboard. A setting of 1000 | ||||
enables a user to type at most 1 char per second. I have found that a | ||||
setting of 10 gives good keyboard response. We can shorten it further, | ||||
but eventually performance would suffer from calling select/kbhit too | ||||
often. | ||||
""" | ||||
Paul McCarthy
|
r25213 | app = wx.GetApp() | ||
if app is not None: | ||||
assert wx.Thread_IsMain() | ||||
elr = EventLoopRunner() | ||||
# As this time is made shorter, keyboard response improves, but idle | ||||
# CPU load goes up. 10 ms seems like a good compromise. | ||||
elr.Run(time=10, # CHANGE time here to control polling interval | ||||
input_is_ready=context.input_is_ready) | ||||
Thomas Kluyver
|
r21941 | return 0 | ||
Paul McCarthy
|
r25213 | |||
@ignore_keyboardinterrupts | ||||
Thomas Kluyver
|
r21941 | def inputhook_wx3(context): | ||
"""Run the wx event loop by processing pending events only. | ||||
This is like inputhook_wx1, but it keeps processing pending events | ||||
until stdin is ready. After processing all pending events, a call to | ||||
time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. | ||||
This sleep time should be tuned though for best performance. | ||||
""" | ||||
Paul McCarthy
|
r25213 | app = wx.GetApp() | ||
if app is not None: | ||||
assert wx.Thread_IsMain() | ||||
# The import of wx on Linux sets the handler for signal.SIGINT | ||||
# to 0. This is a bug in wx or gtk. We fix by just setting it | ||||
# back to the Python default. | ||||
if not callable(signal.getsignal(signal.SIGINT)): | ||||
signal.signal(signal.SIGINT, signal.default_int_handler) | ||||
evtloop = wx.EventLoop() | ||||
ea = wx.EventLoopActivator(evtloop) | ||||
t = clock() | ||||
while not context.input_is_ready(): | ||||
while evtloop.Pending(): | ||||
t = clock() | ||||
evtloop.Dispatch() | ||||
app.ProcessIdle() | ||||
# We need to sleep at this point to keep the idle CPU load | ||||
# low. However, if sleep to long, GUI response is poor. As | ||||
# a compromise, we watch how often GUI events are being processed | ||||
# and switch between a short and long sleep time. Here are some | ||||
# stats useful in helping to tune this. | ||||
# time CPU load | ||||
# 0.001 13% | ||||
# 0.005 3% | ||||
# 0.01 1.5% | ||||
# 0.05 0.5% | ||||
used_time = clock() - t | ||||
if used_time > 10.0: | ||||
Antony Lee
|
r28756 | # print('Sleep for 1 s') # dbg | ||
Paul McCarthy
|
r25213 | time.sleep(1.0) | ||
elif used_time > 0.1: | ||||
# Few GUI events coming in, so we can sleep longer | ||||
Antony Lee
|
r28756 | # print('Sleep for 0.05 s') # dbg | ||
Paul McCarthy
|
r25213 | time.sleep(0.05) | ||
else: | ||||
# Many GUI events coming in, so sleep only very little | ||||
time.sleep(0.001) | ||||
del ea | ||||
Thomas Kluyver
|
r21941 | return 0 | ||
Paul McCarthy
|
r25210 | |||
Paul McCarthy
|
r25213 | @ignore_keyboardinterrupts | ||
Paul McCarthy
|
r25210 | def inputhook_wxphoenix(context): | ||
Paul McCarthy
|
r25212 | """Run the wx event loop until the user provides more input. | ||
Paul McCarthy
|
r25210 | |||
Paul McCarthy
|
r25216 | This input hook is suitable for use with wxPython >= 4 (a.k.a. Phoenix). | ||
It uses the same approach to that used in | ||||
Paul McCarthy
|
r25215 | ipykernel.eventloops.loop_wx. The wx.MainLoop is executed, and a wx.Timer | ||
is used to periodically poll the context for input. As soon as input is | ||||
ready, the wx.MainLoop is stopped. | ||||
Paul McCarthy
|
r25210 | """ | ||
Paul McCarthy
|
r25212 | app = wx.GetApp() | ||
if app is None: | ||||
return | ||||
Paul McCarthy
|
r25214 | if context.input_is_ready(): | ||
return | ||||
assert wx.IsMainThread() | ||||
# Wx uses milliseconds | ||||
Paul McCarthy
|
r25212 | poll_interval = 100 | ||
Paul McCarthy
|
r25214 | # Use a wx.Timer to periodically check whether input is ready - as soon as | ||
# it is, we exit the main loop | ||||
Paul McCarthy
|
r25820 | timer = wx.Timer() | ||
Paul McCarthy
|
r25214 | def poll(ev): | ||
Paul McCarthy
|
r25212 | if context.input_is_ready(): | ||
Paul McCarthy
|
r25820 | timer.Stop() | ||
Paul McCarthy
|
r25212 | app.ExitMainLoop() | ||
Paul McCarthy
|
r25214 | timer.Start(poll_interval) | ||
timer.Bind(wx.EVT_TIMER, poll) | ||||
# The import of wx on Linux sets the handler for signal.SIGINT to 0. This | ||||
# is a bug in wx or gtk. We fix by just setting it back to the Python | ||||
# default. | ||||
Paul McCarthy
|
r25212 | if not callable(signal.getsignal(signal.SIGINT)): | ||
signal.signal(signal.SIGINT, signal.default_int_handler) | ||||
Paul McCarthy
|
r25217 | # The SetExitOnFrameDelete call allows us to run the wx mainloop without | ||
# having a frame open. | ||||
app.SetExitOnFrameDelete(False) | ||||
Paul McCarthy
|
r25212 | app.MainLoop() | ||
Paul McCarthy
|
r25213 | |||
Paul McCarthy
|
r25212 | # Get the major wx version number to figure out what input hook we should use. | ||
major_version = 3 | ||||
Paul McCarthy
|
r25214 | |||
Paul McCarthy
|
r25212 | try: | ||
major_version = int(wx.__version__[0]) | ||||
except Exception: | ||||
pass | ||||
# Use the phoenix hook on all platforms for wxpython >= 4 | ||||
if major_version >= 4: | ||||
inputhook = inputhook_wxphoenix | ||||
# On OSX, evtloop.Pending() always returns True, regardless of there being | ||||
# any events pending. As such we can't use implementations 1 or 3 of the | ||||
# inputhook as those depend on a pending/dispatch loop. | ||||
elif sys.platform == 'darwin': | ||||
Thomas Kluyver
|
r21941 | inputhook = inputhook_wx2 | ||
else: | ||||
Paul McCarthy
|
r25212 | inputhook = inputhook_wx3 | ||