|
|
"""Enable wxPython to be used interactively in prompt_toolkit |
|
|
""" |
|
|
|
|
|
import sys |
|
|
import signal |
|
|
import time |
|
|
from timeit import default_timer as clock |
|
|
import wx |
|
|
|
|
|
|
|
|
def ignore_keyboardinterrupts(func): |
|
|
"""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. |
|
|
""" |
|
|
def wrapper(*args, **kwargs): |
|
|
try: |
|
|
func(*args, **kwargs) |
|
|
except KeyboardInterrupt: |
|
|
pass |
|
|
return wrapper |
|
|
|
|
|
|
|
|
@ignore_keyboardinterrupts |
|
|
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. |
|
|
""" |
|
|
app = wx.GetApp() |
|
|
if app is not None: |
|
|
assert wx.Thread_IsMain() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
evtloop = wx.EventLoop() |
|
|
ea = wx.EventLoopActivator(evtloop) |
|
|
while evtloop.Pending(): |
|
|
evtloop.Dispatch() |
|
|
app.ProcessIdle() |
|
|
del ea |
|
|
return 0 |
|
|
|
|
|
|
|
|
class EventLoopTimer(wx.Timer): |
|
|
|
|
|
def __init__(self, func): |
|
|
self.func = func |
|
|
wx.Timer.__init__(self) |
|
|
|
|
|
def Notify(self): |
|
|
self.func() |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
@ignore_keyboardinterrupts |
|
|
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. |
|
|
""" |
|
|
app = wx.GetApp() |
|
|
if app is not None: |
|
|
assert wx.Thread_IsMain() |
|
|
elr = EventLoopRunner() |
|
|
|
|
|
|
|
|
elr.Run(time=10, |
|
|
input_is_ready=context.input_is_ready) |
|
|
return 0 |
|
|
|
|
|
|
|
|
@ignore_keyboardinterrupts |
|
|
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. |
|
|
""" |
|
|
app = wx.GetApp() |
|
|
if app is not None: |
|
|
assert wx.Thread_IsMain() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
used_time = clock() - t |
|
|
if used_time > 10.0: |
|
|
|
|
|
time.sleep(1.0) |
|
|
elif used_time > 0.1: |
|
|
|
|
|
|
|
|
time.sleep(0.05) |
|
|
else: |
|
|
|
|
|
time.sleep(0.001) |
|
|
del ea |
|
|
return 0 |
|
|
|
|
|
|
|
|
@ignore_keyboardinterrupts |
|
|
def inputhook_wxphoenix(context): |
|
|
"""Run the wx event loop until the user provides more input. |
|
|
|
|
|
This input hook is suitable for use with wxPython >= 4 (a.k.a. Phoenix). |
|
|
|
|
|
It uses the same approach to that used in |
|
|
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. |
|
|
""" |
|
|
|
|
|
app = wx.GetApp() |
|
|
|
|
|
if app is None: |
|
|
return |
|
|
|
|
|
if context.input_is_ready(): |
|
|
return |
|
|
|
|
|
assert wx.IsMainThread() |
|
|
|
|
|
|
|
|
poll_interval = 100 |
|
|
|
|
|
|
|
|
|
|
|
timer = wx.Timer() |
|
|
|
|
|
def poll(ev): |
|
|
if context.input_is_ready(): |
|
|
timer.Stop() |
|
|
app.ExitMainLoop() |
|
|
|
|
|
timer.Start(poll_interval) |
|
|
timer.Bind(wx.EVT_TIMER, poll) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not callable(signal.getsignal(signal.SIGINT)): |
|
|
signal.signal(signal.SIGINT, signal.default_int_handler) |
|
|
|
|
|
|
|
|
|
|
|
app.SetExitOnFrameDelete(False) |
|
|
app.MainLoop() |
|
|
|
|
|
|
|
|
|
|
|
major_version = 3 |
|
|
|
|
|
try: |
|
|
major_version = int(wx.__version__[0]) |
|
|
except Exception: |
|
|
pass |
|
|
|
|
|
|
|
|
if major_version >= 4: |
|
|
inputhook = inputhook_wxphoenix |
|
|
|
|
|
|
|
|
|
|
|
elif sys.platform == 'darwin': |
|
|
inputhook = inputhook_wx2 |
|
|
else: |
|
|
inputhook = inputhook_wx3 |
|
|
|