ipythonqt.py
277 lines
| 11.8 KiB
| text/x-python
|
PythonLexer
epatters
|
r2801 | """ A minimal application using the Qt console-style IPython frontend. | ||
epatters
|
r2758 | """ | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
epatters
|
r2758 | # Systemm library imports | ||
Evan Patterson
|
r3304 | from IPython.external.qt import QtGui | ||
MinRK
|
r3170 | from pygments.styles import get_all_styles | ||
Evan Patterson
|
r3304 | |||
epatters
|
r2758 | # Local imports | ||
from IPython.external.argparse import ArgumentParser | ||||
epatters
|
r2801 | from IPython.frontend.qt.console.frontend_widget import FrontendWidget | ||
from IPython.frontend.qt.console.ipython_widget import IPythonWidget | ||||
from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget | ||||
MinRK
|
r3170 | from IPython.frontend.qt.console import styles | ||
epatters
|
r2758 | from IPython.frontend.qt.kernelmanager import QtKernelManager | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
MinRK
|
r3144 | # Network Constants | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
MinRK
|
r3144 | from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS | ||
epatters
|
r2823 | |||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
# Classes | ||||
#----------------------------------------------------------------------------- | ||||
class MainWindow(QtGui.QMainWindow): | ||||
#--------------------------------------------------------------------------- | ||||
# 'object' interface | ||||
#--------------------------------------------------------------------------- | ||||
MinRK
|
r3142 | def __init__(self, app, frontend, existing=False, may_close=True): | ||
epatters
|
r2961 | """ Create a MainWindow for the specified FrontendWidget. | ||
MinRK
|
r3100 | |||
MinRK
|
r3105 | The app is passed as an argument to allow for different | ||
closing behavior depending on whether we are the Kernel's parent. | ||||
MinRK
|
r3142 | If existing is True, then this Console does not own the Kernel. | ||
If may_close is True, then this Console is permitted to close the kernel | ||||
epatters
|
r2961 | """ | ||
super(MainWindow, self).__init__() | ||||
MinRK
|
r3104 | self._app = app | ||
epatters
|
r2961 | self._frontend = frontend | ||
MinRK
|
r3090 | self._existing = existing | ||
MinRK
|
r3144 | if existing: | ||
MinRK
|
r3142 | self._may_close = may_close | ||
else: | ||||
self._may_close = True | ||||
epatters
|
r2961 | self._frontend.exit_requested.connect(self.close) | ||
self.setCentralWidget(frontend) | ||||
#--------------------------------------------------------------------------- | ||||
# QWidget interface | ||||
#--------------------------------------------------------------------------- | ||||
def closeEvent(self, event): | ||||
Erik Tollerud
|
r3190 | """ Close the window and the kernel (if necessary). | ||
This will prompt the user if they are finished with the kernel, and if | ||||
so, closes the kernel cleanly. Alternatively, if the exit magic is used, | ||||
it closes without prompt. | ||||
epatters
|
r2961 | """ | ||
Erik Tollerud
|
r3185 | keepkernel = None #Use the prompt by default | ||
if hasattr(self._frontend,'_keep_kernel_on_exit'): #set by exit magic | ||||
keepkernel = self._frontend._keep_kernel_on_exit | ||||
epatters
|
r2982 | kernel_manager = self._frontend.kernel_manager | ||
Erik Tollerud
|
r3183 | |||
Erik Tollerud
|
r3185 | if keepkernel is None: #show prompt | ||
Erik Tollerud
|
r3183 | if kernel_manager and kernel_manager.channels_running: | ||
title = self.window().windowTitle() | ||||
cancel = QtGui.QMessageBox.Cancel | ||||
okay = QtGui.QMessageBox.Ok | ||||
if self._may_close: | ||||
msg = "You are closing this Console window." | ||||
info = "Would you like to quit the Kernel and all attached Consoles as well?" | ||||
justthis = QtGui.QPushButton("&No, just this Console", self) | ||||
justthis.setShortcut('N') | ||||
closeall = QtGui.QPushButton("&Yes, quit everything", self) | ||||
closeall.setShortcut('Y') | ||||
Evan Patterson
|
r3304 | box = QtGui.QMessageBox(QtGui.QMessageBox.Question, | ||
title, msg) | ||||
Erik Tollerud
|
r3183 | box.setInformativeText(info) | ||
box.addButton(cancel) | ||||
box.addButton(justthis, QtGui.QMessageBox.NoRole) | ||||
box.addButton(closeall, QtGui.QMessageBox.YesRole) | ||||
box.setDefaultButton(closeall) | ||||
box.setEscapeButton(cancel) | ||||
reply = box.exec_() | ||||
if reply == 1: # close All | ||||
kernel_manager.shutdown_kernel() | ||||
#kernel_manager.stop_channels() | ||||
event.accept() | ||||
elif reply == 0: # close Console | ||||
if not self._existing: | ||||
# Have kernel: don't quit, just close the window | ||||
self._app.setQuitOnLastWindowClosed(False) | ||||
self.deleteLater() | ||||
event.accept() | ||||
else: | ||||
event.ignore() | ||||
MinRK
|
r3142 | else: | ||
Erik Tollerud
|
r3183 | reply = QtGui.QMessageBox.question(self, title, | ||
"Are you sure you want to close this Console?"+ | ||||
"\nThe Kernel and other Consoles will remain active.", | ||||
okay|cancel, | ||||
defaultButton=okay | ||||
) | ||||
if reply == okay: | ||||
event.accept() | ||||
else: | ||||
event.ignore() | ||||
Erik Tollerud
|
r3185 | elif keepkernel: #close console but leave kernel running (no prompt) | ||
Erik Tollerud
|
r3183 | if kernel_manager and kernel_manager.channels_running: | ||
if not self._existing: | ||||
# I have the kernel: don't quit, just close the window | ||||
self._app.setQuitOnLastWindowClosed(False) | ||||
event.accept() | ||||
Erik Tollerud
|
r3185 | else: #close console and kernel (no prompt) | ||
Erik Tollerud
|
r3183 | if kernel_manager and kernel_manager.channels_running: | ||
kernel_manager.shutdown_kernel() | ||||
event.accept() | ||||
epatters
|
r2961 | |||
#----------------------------------------------------------------------------- | ||||
# Main entry point | ||||
#----------------------------------------------------------------------------- | ||||
epatters
|
r2758 | |||
def main(): | ||||
epatters
|
r2801 | """ Entry point for application. | ||
epatters
|
r2758 | """ | ||
# Parse command line arguments. | ||||
parser = ArgumentParser() | ||||
epatters
|
r2871 | kgroup = parser.add_argument_group('kernel options') | ||
kgroup.add_argument('-e', '--existing', action='store_true', | ||||
epatters
|
r2823 | help='connect to an existing kernel') | ||
epatters
|
r2871 | kgroup.add_argument('--ip', type=str, default=LOCALHOST, | ||
MinRK
|
r3152 | help=\ | ||
"set the kernel\'s IP address [default localhost].\ | ||||
If the IP address is something other than localhost, then \ | ||||
Consoles on other machines will be able to connect\ | ||||
to the Kernel, so be careful!") | ||||
epatters
|
r2871 | kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0, | ||
epatters
|
r2823 | help='set the XREQ channel port [default random]') | ||
epatters
|
r2871 | kgroup.add_argument('--sub', type=int, metavar='PORT', default=0, | ||
epatters
|
r2823 | help='set the SUB channel port [default random]') | ||
epatters
|
r2871 | kgroup.add_argument('--rep', type=int, metavar='PORT', default=0, | ||
epatters
|
r2823 | help='set the REP channel port [default random]') | ||
Brian Granger
|
r2910 | kgroup.add_argument('--hb', type=int, metavar='PORT', default=0, | ||
MinRK
|
r3170 | help='set the heartbeat port [default random]') | ||
epatters
|
r2866 | |||
epatters
|
r2871 | egroup = kgroup.add_mutually_exclusive_group() | ||
egroup.add_argument('--pure', action='store_true', help = \ | ||||
'use a pure Python kernel instead of an IPython kernel') | ||||
Brian Granger
|
r2873 | egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?', | ||
Brian Granger
|
r2872 | const='auto', help = \ | ||
epatters
|
r2880 | "Pre-load matplotlib and numpy for interactive use. If GUI is not \ | ||
given, the GUI backend is matplotlib's, otherwise use one of: \ | ||||
Fernando Perez
|
r2991 | ['tk', 'gtk', 'qt', 'wx', 'inline'].") | ||
epatters
|
r2871 | |||
wgroup = parser.add_argument_group('widget options') | ||||
wgroup.add_argument('--paging', type=str, default='inside', | ||||
choices = ['inside', 'hsplit', 'vsplit', 'none'], | ||||
help='set the paging style [default inside]') | ||||
epatters
|
r3766 | wgroup.add_argument('--plain', action='store_true', | ||
help='disable rich text support') | ||||
epatters
|
r2917 | wgroup.add_argument('--gui-completion', action='store_true', | ||
help='use a GUI widget for tab completion') | ||||
MinRK
|
r3170 | wgroup.add_argument('--style', type=str, | ||
MinRK
|
r3171 | choices = list(get_all_styles()), | ||
epatters
|
r3766 | help='specify a pygments style for by name') | ||
MinRK
|
r3170 | wgroup.add_argument('--stylesheet', type=str, | ||
epatters
|
r3766 | help='path to a custom CSS stylesheet') | ||
wgroup.add_argument('--colors', type=str, help = \ | ||||
"Set the color scheme (LightBG,Linux,NoColor). This is guessed \ | ||||
based on the pygments style if not set.") | ||||
Brian Granger
|
r2873 | |||
epatters
|
r2823 | args = parser.parse_args() | ||
Brian Granger
|
r2873 | |||
MinRK
|
r3173 | # parse the colors arg down to current known labels | ||
if args.colors: | ||||
colors=args.colors.lower() | ||||
if colors in ('lightbg', 'light'): | ||||
colors='lightbg' | ||||
elif colors in ('dark', 'linux'): | ||||
colors='linux' | ||||
else: | ||||
colors='nocolor' | ||||
elif args.style: | ||||
if args.style=='bw': | ||||
colors='nocolor' | ||||
elif styles.dark_style(args.style): | ||||
colors='linux' | ||||
else: | ||||
colors='lightbg' | ||||
else: | ||||
colors=None | ||||
epatters
|
r2758 | # Don't let Qt or ZMQ swallow KeyboardInterupts. | ||
import signal | ||||
signal.signal(signal.SIGINT, signal.SIG_DFL) | ||||
# Create a KernelManager and start a kernel. | ||||
epatters
|
r2823 | kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq), | ||
sub_address=(args.ip, args.sub), | ||||
Brian Granger
|
r2910 | rep_address=(args.ip, args.rep), | ||
hb_address=(args.ip, args.hb)) | ||||
MinRK
|
r3142 | if not args.existing: | ||
MinRK
|
r3144 | # if not args.ip in LOCAL_IPS+ALL_ALIAS: | ||
# raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS) | ||||
MinRK
|
r3173 | |||
MinRK
|
r3144 | kwargs = dict(ip=args.ip) | ||
epatters
|
r2823 | if args.pure: | ||
MinRK
|
r3144 | kwargs['ipython']=False | ||
MinRK
|
r3173 | else: | ||
kwargs['colors']=colors | ||||
if args.pylab: | ||||
kwargs['pylab']=args.pylab | ||||
MinRK
|
r3144 | kernel_manager.start_kernel(**kwargs) | ||
epatters
|
r2758 | kernel_manager.start_channels() | ||
epatters
|
r2841 | # Create the widget. | ||
epatters
|
r2758 | app = QtGui.QApplication([]) | ||
epatters
|
r3766 | local_kernel = (not args.existing) or args.ip in LOCAL_IPS | ||
epatters
|
r2823 | if args.pure: | ||
epatters
|
r3766 | kind = 'plain' if args.plain else 'rich' | ||
widget = FrontendWidget(kind=kind, paging=args.paging, | ||||
local_kernel=local_kernel) | ||||
elif args.plain: | ||||
MinRK
|
r3129 | widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel) | ||
epatters
|
r3766 | else: | ||
widget = RichIPythonWidget(paging=args.paging, | ||||
local_kernel=local_kernel) | ||||
epatters
|
r2917 | widget.gui_completion = args.gui_completion | ||
epatters
|
r2758 | widget.kernel_manager = kernel_manager | ||
epatters
|
r2961 | |||
epatters
|
r3766 | # Configure the style. | ||
MinRK
|
r3170 | if not args.pure: # only IPythonWidget supports styles | ||
if args.style: | ||||
widget.syntax_style = args.style | ||||
MinRK
|
r3173 | widget.style_sheet = styles.sheet_from_template(args.style, colors) | ||
MinRK
|
r3170 | widget._syntax_style_changed() | ||
widget._style_sheet_changed() | ||||
MinRK
|
r3171 | elif colors: | ||
# use a default style | ||||
widget.set_default_style(colors=colors) | ||||
MinRK
|
r3170 | else: | ||
# this is redundant for now, but allows the widget's | ||||
# defaults to change | ||||
MinRK
|
r3171 | widget.set_default_style() | ||
MinRK
|
r3170 | |||
if args.stylesheet: | ||||
# we got an expicit stylesheet | ||||
if os.path.isfile(args.stylesheet): | ||||
with open(args.stylesheet) as f: | ||||
sheet = f.read() | ||||
widget.style_sheet = sheet | ||||
widget._style_sheet_changed() | ||||
else: | ||||
raise IOError("Stylesheet %r not found."%args.stylesheet) | ||||
epatters
|
r2961 | # Create the main window. | ||
MinRK
|
r3142 | window = MainWindow(app, widget, args.existing, may_close=local_kernel) | ||
epatters
|
r2961 | window.setWindowTitle('Python' if args.pure else 'IPython') | ||
window.show() | ||||
epatters
|
r2841 | |||
# Start the application main loop. | ||||
epatters
|
r2758 | app.exec_() | ||
if __name__ == '__main__': | ||||
main() | ||||