inputhookqt4.py
180 lines
| 6.6 KiB
| text/x-python
|
PythonLexer
Christian Boos
|
r4931 | # -*- coding: utf-8 -*- | ||
""" | ||||
Qt4's inputhook support function | ||||
Author: Christian Boos | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
# Copyright (C) 2011 The IPython Development Team | ||||
# | ||||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#----------------------------------------------------------------------------- | ||||
#----------------------------------------------------------------------------- | ||||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
Siyu Zhang
|
r10352 | import os | ||
import signal | ||||
import threading | ||||
Christian Boos
|
r5132 | from IPython.core.interactiveshell import InteractiveShell | ||
Christian Boos
|
r4931 | from IPython.external.qt_for_kernel import QtCore, QtGui | ||
Christian Boos
|
r4944 | from IPython.lib.inputhook import allow_CTRL_C, ignore_CTRL_C, stdin_ready | ||
Christian Boos
|
r4931 | |||
#----------------------------------------------------------------------------- | ||||
Siyu Zhang
|
r10405 | # Module Globals | ||
#----------------------------------------------------------------------------- | ||||
got_kbdint = False | ||||
sigint_timer = None | ||||
#----------------------------------------------------------------------------- | ||||
Christian Boos
|
r4931 | # Code | ||
#----------------------------------------------------------------------------- | ||||
def create_inputhook_qt4(mgr, app=None): | ||||
"""Create an input hook for running the Qt4 application event loop. | ||||
Parameters | ||||
---------- | ||||
mgr : an InputHookManager | ||||
app : Qt Application, optional. | ||||
Running application to use. If not given, we probe Qt for an | ||||
existing application object, and create a new one if none is found. | ||||
Returns | ||||
------- | ||||
A pair consisting of a Qt Application (either the one given or the | ||||
one found or created) and a inputhook. | ||||
Notes | ||||
----- | ||||
Christian Boos
|
r4936 | We use a custom input hook instead of PyQt4's default one, as it | ||
interacts better with the readline packages (issue #481). | ||||
Christian Boos
|
r4931 | The inputhook function works in tandem with a 'pre_prompt_hook' | ||
which automatically restores the hook as an inputhook in case the | ||||
latter has been temporarily disabled after having intercepted a | ||||
KeyboardInterrupt. | ||||
""" | ||||
Christian Boos
|
r4936 | |||
Christian Boos
|
r4931 | if app is None: | ||
app = QtCore.QCoreApplication.instance() | ||||
if app is None: | ||||
app = QtGui.QApplication([" "]) | ||||
Christian Boos
|
r4936 | # Re-use previously created inputhook if any | ||
Christian Boos
|
r5132 | ip = InteractiveShell.instance() | ||
Christian Boos
|
r4936 | if hasattr(ip, '_inputhook_qt4'): | ||
return app, ip._inputhook_qt4 | ||||
Christian Boos
|
r4931 | |||
Christian Boos
|
r4936 | # Otherwise create the inputhook_qt4/preprompthook_qt4 pair of | ||
# hooks (they both share the got_kbdint flag) | ||||
Christian Boos
|
r4931 | |||
def inputhook_qt4(): | ||||
Christian Boos
|
r4936 | """PyOS_InputHook python hook for Qt4. | ||
Process pending Qt events and if there's no pending keyboard | ||||
input, spend a short slice of time (50ms) running the Qt event | ||||
loop. | ||||
As a Python ctypes callback can't raise an exception, we catch | ||||
the KeyboardInterrupt and temporarily deactivate the hook, | ||||
which will let a *second* CTRL+C be processed normally and go | ||||
back to a clean prompt line. | ||||
""" | ||||
Christian Boos
|
r4931 | try: | ||
Christian Boos
|
r4944 | allow_CTRL_C() | ||
Christian Boos
|
r4936 | app = QtCore.QCoreApplication.instance() | ||
Christian Boos
|
r5180 | if not app: # shouldn't happen, but safer if it happens anyway... | ||
return 0 | ||||
Christian Boos
|
r4931 | app.processEvents(QtCore.QEventLoop.AllEvents, 300) | ||
if not stdin_ready(): | ||||
Bradley M. Froehle
|
r8402 | # Generally a program would run QCoreApplication::exec() | ||
# from main() to enter and process the Qt event loop until | ||||
# quit() or exit() is called and the program terminates. | ||||
# | ||||
# For our input hook integration, we need to repeatedly | ||||
# enter and process the Qt event loop for only a short | ||||
# amount of time (say 50ms) to ensure that Python stays | ||||
# responsive to other user inputs. | ||||
# | ||||
# A naive approach would be to repeatedly call | ||||
# QCoreApplication::exec(), using a timer to quit after a | ||||
# short amount of time. Unfortunately, QCoreApplication | ||||
# emits an aboutToQuit signal before stopping, which has | ||||
# the undesirable effect of closing all modal windows. | ||||
# | ||||
# To work around this problem, we instead create a | ||||
# QEventLoop and call QEventLoop::exec(). Other than | ||||
# setting some state variables which do not seem to be | ||||
# used anywhere, the only thing QCoreApplication adds is | ||||
# the aboutToQuit signal which is precisely what we are | ||||
# trying to avoid. | ||||
Christian Boos
|
r4931 | timer = QtCore.QTimer() | ||
Bradley M. Froehle
|
r8402 | event_loop = QtCore.QEventLoop() | ||
timer.timeout.connect(event_loop.quit) | ||||
Christian Boos
|
r4931 | while not stdin_ready(): | ||
timer.start(50) | ||||
Bradley M. Froehle
|
r8402 | event_loop.exec_() | ||
Christian Boos
|
r4931 | timer.stop() | ||
except KeyboardInterrupt: | ||||
Siyu Zhang
|
r10405 | global got_kbdint, sigint_timer | ||
Christian Boos
|
r4944 | ignore_CTRL_C() | ||
Siyu Zhang
|
r10405 | got_kbdint = True | ||
Christian Boos
|
r4944 | mgr.clear_inputhook() | ||
Siyu Zhang
|
r10387 | |||
# This generates a second SIGINT so the user doesn't have to | ||||
# press CTRL+C twice to get a clean prompt. | ||||
# | ||||
# Since we can't catch the resulting KeyboardInterrupt here | ||||
# (because this is a ctypes callback), we use a timer to | ||||
# generate the SIGINT after we leave this callback. | ||||
# | ||||
# Unfortunately this doesn't work on Windows (SIGINT kills | ||||
# Python and CTRL_C_EVENT doesn't work). | ||||
Siyu Zhang
|
r10350 | if(os.name == 'posix'): | ||
pid = os.getpid() | ||||
Siyu Zhang
|
r10405 | if(not sigint_timer): | ||
sigint_timer = threading.Timer(.01, os.kill, | ||||
Siyu Zhang
|
r10387 | args=[pid, signal.SIGINT] ) | ||
Siyu Zhang
|
r10405 | sigint_timer.start() | ||
Siyu Zhang
|
r10350 | else: | ||
print("\nKeyboardInterrupt - Ctrl-C again for new prompt") | ||||
Christian Boos
|
r5180 | except: # NO exceptions are allowed to escape from a ctypes callback | ||
MinRK
|
r5741 | ignore_CTRL_C() | ||
Christian Boos
|
r5180 | from traceback import print_exc | ||
print_exc() | ||||
print("Got exception from inputhook_qt4, unregistering.") | ||||
Fernando Perez
|
r5761 | mgr.clear_inputhook() | ||
MinRK
|
r5741 | finally: | ||
allow_CTRL_C() | ||||
Christian Boos
|
r4931 | return 0 | ||
def preprompthook_qt4(ishell): | ||||
Christian Boos
|
r4936 | """'pre_prompt_hook' used to restore the Qt4 input hook | ||
(in case the latter was temporarily deactivated after a | ||||
CTRL+C) | ||||
""" | ||||
Siyu Zhang
|
r10405 | global got_kbdint, sigint_timer | ||
if(sigint_timer): | ||||
sigint_timer.cancel() | ||||
sigint_timer = None | ||||
Siyu Zhang
|
r10383 | |||
Siyu Zhang
|
r10405 | if got_kbdint: | ||
Christian Boos
|
r4931 | mgr.set_inputhook(inputhook_qt4) | ||
Siyu Zhang
|
r10405 | got_kbdint = False | ||
Christian Boos
|
r4936 | |||
ip._inputhook_qt4 = inputhook_qt4 | ||||
ip.set_hook('pre_prompt_hook', preprompthook_qt4) | ||||
Christian Boos
|
r4931 | |||
return app, inputhook_qt4 | ||||