qtconsoleapp.py
382 lines
| 13.0 KiB
| text/x-python
|
PythonLexer
|
r2801 | """ A minimal application using the Qt console-style IPython frontend. | ||
|
r4021 | |||
This is not a complete console app, as subprocess will not be able to receive | ||||
input, there is no real readline support, among other limitations. | ||||
Authors: | ||||
* Evan Patterson | ||||
* Min RK | ||||
* Erik Tollerud | ||||
* Fernando Perez | ||||
|
r5136 | * Bussonnier Matthias | ||
* Thomas Kluyver | ||||
|
r5603 | * Paul Ivanov | ||
|
r4021 | |||
|
r2758 | """ | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
|
r3971 | # stdlib imports | ||
import os | ||||
import signal | ||||
import sys | ||||
|
r6168 | # If run on Windows, install an exception hook which pops up a | ||
# message box. Pythonw.exe hides the console, so without this | ||||
# the application silently fails to load. | ||||
# | ||||
# We always install this handler, because the expectation is for | ||||
# qtconsole to bring up a GUI even if called from the console. | ||||
# The old handler is called, so the exception is printed as well. | ||||
# If desired, check for pythonw with an additional condition | ||||
# (sys.executable.lower().find('pythonw.exe') >= 0). | ||||
if os.name == 'nt': | ||||
old_excepthook = sys.excepthook | ||||
def gui_excepthook(exctype, value, tb): | ||||
try: | ||||
import ctypes, traceback | ||||
MB_ICONERROR = 0x00000010L | ||||
title = u'Error starting IPython QtConsole' | ||||
msg = u''.join(traceback.format_exception(exctype, value, tb)) | ||||
ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR) | ||||
finally: | ||||
# Also call the old exception hook to let it do | ||||
# its thing too. | ||||
old_excepthook(exctype, value, tb) | ||||
sys.excepthook = gui_excepthook | ||||
|
r3971 | # System library imports | ||
|
r5609 | from IPython.external.qt import QtCore, QtGui | ||
|
r3304 | |||
|
r2758 | # Local imports | ||
|
r11131 | from IPython.config.application import catch_config_error | ||
|
r4024 | from IPython.core.application import BaseIPythonApplication | ||
|
r11022 | from IPython.qt.console.ipython_widget import IPythonWidget | ||
from IPython.qt.console.rich_ipython_widget import RichIPythonWidget | ||||
from IPython.qt.console import styles | ||||
from IPython.qt.console.mainwindow import MainWindow | ||||
from IPython.qt.client import QtKernelClient | ||||
from IPython.qt.manager import QtKernelManager | ||||
|
r3971 | from IPython.utils.traitlets import ( | ||
|
r11131 | Dict, Unicode, CBool, Any | ||
|
r3971 | ) | ||
|
r5604 | |||
|
r11022 | from IPython.consoleapp import ( | ||
|
r5618 | IPythonConsoleApp, app_aliases, app_flags, flags, aliases | ||
|
r5603 | ) | ||
|
r3971 | |||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r3144 | # Network Constants | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r3144 | from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS | ||
|
r2823 | |||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r4216 | # Globals | ||
#----------------------------------------------------------------------------- | ||||
_examples = """ | ||||
|
r11784 | ipython qtconsole # start the qtconsole | ||
ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode | ||||
|
r4216 | """ | ||
#----------------------------------------------------------------------------- | ||||
|
r3971 | # Aliases and Flags | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r2758 | |||
|
r5610 | # start with copy of flags | ||
flags = dict(flags) | ||||
|
r4247 | qt_flags = { | ||
|
r6806 | 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}}, | ||
|
r3971 | "Disable rich text support."), | ||
|
r4247 | } | ||
|
r7389 | |||
|
r5610 | # and app_flags from the Console Mixin | ||
qt_flags.update(app_flags) | ||||
# add frontend flags to the full set | ||||
|
r4247 | flags.update(qt_flags) | ||
|
r3971 | |||
|
r5610 | # start with copy of front&backend aliases list | ||
aliases = dict(aliases) | ||||
|
r4247 | qt_aliases = dict( | ||
|
r3971 | style = 'IPythonWidget.syntax_style', | ||
stylesheet = 'IPythonQtConsoleApp.stylesheet', | ||||
colors = 'ZMQInteractiveShell.colors', | ||||
editor = 'IPythonWidget.editor', | ||||
|
r4222 | paging = 'ConsoleWidget.paging', | ||
|
r4247 | ) | ||
|
r5610 | # and app_aliases from the Console Mixin | ||
qt_aliases.update(app_aliases) | ||||
|
r7389 | qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'}) | ||
|
r5610 | # add frontend aliases to the full set | ||
|
r4247 | aliases.update(qt_aliases) | ||
|
r4222 | |||
|
r5610 | # get flags&aliases into sets, and remove a couple that | ||
# shouldn't be scrubbed from backend flags: | ||||
qt_aliases = set(qt_aliases.keys()) | ||||
qt_aliases.remove('colors') | ||||
qt_flags = set(qt_flags.keys()) | ||||
|
r5136 | #----------------------------------------------------------------------------- | ||
# Classes | ||||
#----------------------------------------------------------------------------- | ||||
|
r3971 | |||
#----------------------------------------------------------------------------- | ||||
# IPythonQtConsole | ||||
#----------------------------------------------------------------------------- | ||||
|
r4215 | |||
|
r5618 | class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp): | ||
|
r3971 | name = 'ipython-qtconsole' | ||
|
r5060 | |||
|
r4021 | description = """ | ||
The IPython QtConsole. | ||||
This launches a Console-style application using Qt. It is not a full | ||||
|
r4222 | console, in that launched terminal subprocesses will not be able to accept | ||
input. | ||||
The QtConsole supports various extra features beyond the Terminal IPython | ||||
shell, such as inline plotting with matplotlib, via: | ||||
|
r11784 | ipython qtconsole --matplotlib=inline | ||
|
r4021 | |||
|
r4222 | as well as saving your session as HTML, and printing the output. | ||
|
r4021 | |||
""" | ||||
|
r4216 | examples = _examples | ||
|
r4215 | |||
|
r7291 | classes = [IPythonWidget] + IPythonConsoleApp.classes | ||
|
r3971 | flags = Dict(flags) | ||
aliases = Dict(aliases) | ||||
|
r5620 | frontend_flags = Any(qt_flags) | ||
frontend_aliases = Any(qt_aliases) | ||||
|
r10288 | kernel_client_class = QtKernelClient | ||
|
r10312 | kernel_manager_class = QtKernelManager | ||
|
r3971 | |||
stylesheet = Unicode('', config=True, | ||||
help="path to a custom CSS stylesheet") | ||||
|
r10960 | hide_menubar = CBool(False, config=True, | ||
help="Start the console window with the menu bar hidden.") | ||||
maximize = CBool(False, config=True, | ||||
help="Start the console window maximized.") | ||||
|
r3983 | plain = CBool(False, config=True, | ||
|
r3976 | help="Use a plaintext widget instead of rich text (plain can't print/save).") | ||
|
r3971 | |||
|
r6806 | def _plain_changed(self, name, old, new): | ||
kind = 'plain' if new else 'rich' | ||||
|
r3971 | self.config.ConsoleWidget.kind = kind | ||
|
r6806 | if new: | ||
|
r3971 | self.widget_factory = IPythonWidget | ||
|
r3173 | else: | ||
|
r3971 | self.widget_factory = RichIPythonWidget | ||
# the factory for creating a widget | ||||
widget_factory = Any(RichIPythonWidget) | ||||
def parse_command_line(self, argv=None): | ||||
super(IPythonQtConsoleApp, self).parse_command_line(argv) | ||||
|
r5620 | self.build_kernel_argv(argv) | ||
|
r5604 | |||
|
r3971 | |||
|
r5135 | def new_frontend_master(self): | ||
""" Create and return new frontend attached to new kernel, launched on localhost. | ||||
|
r5039 | """ | ||
|
r5849 | kernel_manager = self.kernel_manager_class( | ||
|
r5073 | connection_file=self._new_connection_file(), | ||
|
r11064 | parent=self, | ||
|
r10312 | autorestart=True, | ||
|
r5035 | ) | ||
# start the kernel | ||||
|
r6806 | kwargs = dict() | ||
|
r5039 | kwargs['extra_arguments'] = self.kernel_argv | ||
kernel_manager.start_kernel(**kwargs) | ||||
|
r10312 | kernel_manager.client_factory = self.kernel_client_class | ||
kernel_client = kernel_manager.client() | ||||
kernel_client.start_channels(shell=True, iopub=True) | ||||
|
r5035 | widget = self.widget_factory(config=self.config, | ||
|
r5073 | local_kernel=True) | ||
|
r6056 | self.init_colors(widget) | ||
|
r5035 | widget.kernel_manager = kernel_manager | ||
|
r10289 | widget.kernel_client = kernel_client | ||
|
r5138 | widget._existing = False | ||
widget._may_close = True | ||||
widget._confirm_exit = self.confirm_exit | ||||
|
r5135 | return widget | ||
def new_frontend_slave(self, current_widget): | ||||
"""Create and return a new frontend attached to an existing kernel. | ||||
Parameters | ||||
---------- | ||||
current_widget : IPythonWidget | ||||
The IPythonWidget whose kernel this frontend is to share | ||||
""" | ||||
|
r10288 | kernel_client = self.kernel_client_class( | ||
connection_file=current_widget.kernel_client.connection_file, | ||||
|
r5073 | config = self.config, | ||
|
r5035 | ) | ||
|
r10288 | kernel_client.load_connection_file() | ||
kernel_client.start_channels() | ||||
|
r5035 | widget = self.widget_factory(config=self.config, | ||
|
r5073 | local_kernel=False) | ||
|
r6056 | self.init_colors(widget) | ||
|
r5138 | widget._existing = True | ||
widget._may_close = False | ||||
widget._confirm_exit = False | ||||
|
r10288 | widget.kernel_client = kernel_client | ||
|
r10312 | widget.kernel_manager = current_widget.kernel_manager | ||
|
r5135 | return widget | ||
|
r3971 | |||
|
r10312 | def init_qt_app(self): | ||
# separate from qt_elements, because it must run first | ||||
self.app = QtGui.QApplication([]) | ||||
|
r3971 | def init_qt_elements(self): | ||
# Create the widget. | ||||
|
r5053 | |||
base_path = os.path.abspath(os.path.dirname(__file__)) | ||||
icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg') | ||||
|
r5055 | self.app.icon = QtGui.QIcon(icon_path) | ||
QtGui.QApplication.setWindowIcon(self.app.icon) | ||||
|
r5028 | |||
|
r12125 | ip = self.ip | ||
|
r9177 | local_kernel = (not self.existing) or ip in LOCAL_IPS | ||
|
r3971 | self.widget = self.widget_factory(config=self.config, | ||
local_kernel=local_kernel) | ||||
|
r6056 | self.init_colors(self.widget) | ||
|
r5138 | self.widget._existing = self.existing | ||
self.widget._may_close = not self.existing | ||||
self.widget._confirm_exit = self.confirm_exit | ||||
|
r5038 | |||
|
r3971 | self.widget.kernel_manager = self.kernel_manager | ||
|
r10288 | self.widget.kernel_client = self.kernel_client | ||
|
r5138 | self.window = MainWindow(self.app, | ||
|
r5135 | confirm_exit=self.confirm_exit, | ||
new_frontend_factory=self.new_frontend_master, | ||||
slave_frontend_factory=self.new_frontend_slave, | ||||
) | ||||
|
r5038 | self.window.log = self.log | ||
|
r5051 | self.window.add_tab_with_frontend(self.widget) | ||
|
r5052 | self.window.init_menu_bar() | ||
|
r5393 | |||
|
r10960 | # Ignore on OSX, where there is always a menu bar | ||
if sys.platform != 'darwin' and self.hide_menubar: | ||||
self.window.menuBar().setVisible(False) | ||||
|
r6806 | self.window.setWindowTitle('IPython') | ||
|
r3971 | |||
|
r6056 | def init_colors(self, widget): | ||
|
r3971 | """Configure the coloring of the widget""" | ||
# Note: This will be dramatically simplified when colors | ||||
# are removed from the backend. | ||||
# parse the colors arg down to current known labels | ||||
try: | ||||
colors = self.config.ZMQInteractiveShell.colors | ||||
except AttributeError: | ||||
colors = None | ||||
try: | ||||
|
r5127 | style = self.config.IPythonWidget.syntax_style | ||
|
r3971 | except AttributeError: | ||
style = None | ||||
|
r6056 | try: | ||
sheet = self.config.IPythonWidget.style_sheet | ||||
except AttributeError: | ||||
sheet = None | ||||
|
r3971 | |||
# find the value for colors: | ||||
if colors: | ||||
colors=colors.lower() | ||||
if colors in ('lightbg', 'light'): | ||||
colors='lightbg' | ||||
elif colors in ('dark', 'linux'): | ||||
colors='linux' | ||||
else: | ||||
colors='nocolor' | ||||
elif style: | ||||
if style=='bw': | ||||
colors='nocolor' | ||||
elif styles.dark_style(style): | ||||
colors='linux' | ||||
else: | ||||
colors='lightbg' | ||||
|
r3173 | else: | ||
|
r3971 | colors=None | ||
|
r6056 | # Configure the style | ||
|
r3971 | if style: | ||
widget.style_sheet = styles.sheet_from_template(style, colors) | ||||
widget.syntax_style = style | ||||
|
r3170 | widget._syntax_style_changed() | ||
widget._style_sheet_changed() | ||||
|
r3171 | elif colors: | ||
|
r6056 | # use a default dark/light/bw style | ||
|
r3171 | widget.set_default_style(colors=colors) | ||
|
r3170 | |||
|
r3971 | if self.stylesheet: | ||
|
r6056 | # we got an explicit stylesheet | ||
|
r3971 | if os.path.isfile(self.stylesheet): | ||
with open(self.stylesheet) as f: | ||||
|
r3170 | sheet = f.read() | ||
else: | ||||
|
r6056 | raise IOError("Stylesheet %r not found." % self.stylesheet) | ||
if sheet: | ||||
widget.style_sheet = sheet | ||||
widget._style_sheet_changed() | ||||
|
r3971 | |||
|
r5609 | def init_signal(self): | ||
"""allow clean shutdown on sigint""" | ||||
signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2)) | ||||
# need a timer, so that QApplication doesn't block until a real | ||||
# Qt event fires (can require mouse movement) | ||||
# timer trick from http://stackoverflow.com/q/4938723/938949 | ||||
timer = QtCore.QTimer() | ||||
# Let the interpreter run each 200 ms: | ||||
timer.timeout.connect(lambda: None) | ||||
timer.start(200) | ||||
# hold onto ref, so the timer doesn't get cleaned up | ||||
self._sigint_timer = timer | ||||
|
r5214 | @catch_config_error | ||
|
r3971 | def initialize(self, argv=None): | ||
|
r10312 | self.init_qt_app() | ||
|
r3971 | super(IPythonQtConsoleApp, self).initialize(argv) | ||
|
r5618 | IPythonConsoleApp.initialize(self,argv) | ||
|
r3971 | self.init_qt_elements() | ||
|
r5609 | self.init_signal() | ||
|
r3971 | |||
def start(self): | ||||
# draw the window | ||||
|
r10960 | if self.maximize: | ||
self.window.showMaximized() | ||||
else: | ||||
self.window.show() | ||||
|
r5712 | self.window.raise_() | ||
|
r3170 | |||
|
r3971 | # Start the application main loop. | ||
self.app.exec_() | ||||
|
r2841 | |||
|
r3971 | #----------------------------------------------------------------------------- | ||
# Main entry point | ||||
#----------------------------------------------------------------------------- | ||||
def main(): | ||||
app = IPythonQtConsoleApp() | ||||
app.initialize() | ||||
app.start() | ||||
|
r2758 | |||
if __name__ == '__main__': | ||||
main() | ||||