Show More
qtconsoleapp.py
1078 lines
| 42.9 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 | ||||
|
r2758 | """ | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
|
r3971 | # stdlib imports | ||
|
r4986 | import json | ||
|
r3971 | import os | ||
import signal | ||||
import sys | ||||
# System library imports | ||||
|
r5027 | from IPython.external.qt import QtGui,QtCore | ||
|
r3170 | from pygments.styles import get_all_styles | ||
|
r3304 | |||
|
r2758 | # Local imports | ||
|
r3983 | from IPython.config.application import boolean_flag | ||
|
r4024 | from IPython.core.application import BaseIPythonApplication | ||
from IPython.core.profiledir import ProfileDir | ||||
|
r4972 | from IPython.lib.kernel import tunnel_to_kernel, find_connection_file | ||
|
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 | ||||
|
r3170 | from IPython.frontend.qt.console import styles | ||
|
r2758 | from IPython.frontend.qt.kernelmanager import QtKernelManager | ||
|
r4958 | from IPython.utils.path import filefind | ||
|
r4967 | from IPython.utils.py3compat import str_to_bytes | ||
|
r3971 | from IPython.utils.traitlets import ( | ||
|
r3983 | Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any | ||
|
r3971 | ) | ||
from IPython.zmq.ipkernel import ( | ||||
flags as ipkernel_flags, | ||||
aliases as ipkernel_aliases, | ||||
IPKernelApp | ||||
) | ||||
|
r4962 | from IPython.zmq.session import Session, default_secure | ||
|
r3971 | from IPython.zmq.zmqshell import ZMQInteractiveShell | ||
|
r5028 | import application_rc | ||
|
r2758 | |||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r3144 | # Network Constants | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r3144 | from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS | ||
|
r2823 | |||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r4216 | # Globals | ||
#----------------------------------------------------------------------------- | ||||
_examples = """ | ||||
ipython qtconsole # start the qtconsole | ||||
ipython qtconsole --pylab=inline # start with pylab in inline plotting mode | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
|
r2961 | # Classes | ||
#----------------------------------------------------------------------------- | ||||
class MainWindow(QtGui.QMainWindow): | ||||
#--------------------------------------------------------------------------- | ||||
# 'object' interface | ||||
#--------------------------------------------------------------------------- | ||||
|
r5027 | |||
|
r3983 | def __init__(self, app, frontend, existing=False, may_close=True, | ||
confirm_exit=True): | ||||
|
r2961 | """ Create a MainWindow for the specified FrontendWidget. | ||
|
r3100 | |||
|
r3105 | The app is passed as an argument to allow for different | ||
closing behavior depending on whether we are the Kernel's parent. | ||||
|
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 | ||||
|
r2961 | """ | ||
|
r5037 | # ** current atributes, for window: | ||
# _existing, (kernel) passed by constructor. | ||||
# _may_close (the kernel) passed by constructor | ||||
# _confirm_exit | ||||
# | ||||
# ** for the frontend | ||||
# _keep_kernel_on_exit (may be set by %exit) | ||||
|
r2961 | super(MainWindow, self).__init__() | ||
|
r3104 | self._app = app | ||
|
r5037 | #self._frontend = frontend | ||
#self._existing = existing | ||||
#if existing: | ||||
# self._may_close = may_close | ||||
#else: | ||||
# self._may_close = True | ||||
#self._frontend.exit_requested.connect(self.close) | ||||
#self._confirm_exit = confirm_exit | ||||
|
r4817 | |||
|
r5035 | self.tabWidget = QtGui.QTabWidget(self) | ||
self.tabWidget.setDocumentMode(True) | ||||
self.tabWidget.setTabsClosable(True) | ||||
|
r5037 | #self.tabWidget.addTab(frontend,"QtConsole1") | ||
|
r5035 | self.tabWidget.tabCloseRequested[int].connect(self.closetab) | ||
self.setCentralWidget(self.tabWidget) | ||||
self.updateTabBarVisibility() | ||||
def updateTabBarVisibility(self): | ||||
""" update visibility of the tabBar depending of the number of tab | ||||
0 or 1 tab, tabBar hiddent | ||||
2+ tabs, tabbarHidden | ||||
need to be called explicitely, or be connected to tabInserted/tabRemoved | ||||
""" | ||||
if self.tabWidget.count() <= 1: | ||||
self.tabWidget.tabBar().setVisible(False) | ||||
else: | ||||
self.tabWidget.tabBar().setVisible(True) | ||||
|
r5037 | @property | ||
|
r5035 | def activeFrontend(self): | ||
return self.tabWidget.currentWidget() | ||||
|
r5037 | def closetab(self,tab,force=None): | ||
|
r5035 | """ Called when a user try to close a tab | ||
It takes the number of the tab to be closed as argument, (does not for | ||||
now, but should) take care of whether or not shuting down the kernel | ||||
attached to the frontend | ||||
""" | ||||
|
r5037 | print "trying to closing tab",tab | ||
closing_widget=self.tabWidget.widget(tab) | ||||
keepkernel = None #Use the prompt by default | ||||
if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic | ||||
keepkernel = closing_widget._keep_kernel_on_exit | ||||
kernel_manager = closing_widget.kernel_manager | ||||
if keepkernel is None and not closing_widget._confirm_exit: | ||||
# don't prompt, just terminate the kernel if we own it | ||||
# or leave it alone if we don't | ||||
keepkernel = not closing_widget._existing | ||||
if keepkernel is None: #show prompt | ||||
if kernel_manager and kernel_manager.channels_running: | ||||
title = self.window().windowTitle() | ||||
cancel = QtGui.QMessageBox.Cancel | ||||
okay = QtGui.QMessageBox.Ok | ||||
if closing_widget._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') | ||||
box = QtGui.QMessageBox(QtGui.QMessageBox.Question, | ||||
title, msg) | ||||
box.setInformativeText(info) | ||||
box.addButton(cancel) | ||||
box.addButton(justthis, QtGui.QMessageBox.NoRole) | ||||
box.addButton(closeall, QtGui.QMessageBox.YesRole) | ||||
box.setDefaultButton(closeall) | ||||
box.setEscapeButton(cancel) | ||||
pixmap = QtGui.QPixmap(':/icon/IPythonConsole.png') | ||||
scaledpixmap = pixmap.scaledToWidth(64,mode=QtCore.Qt.SmoothTransformation) | ||||
box.setIconPixmap(scaledpixmap) | ||||
reply = box.exec_() | ||||
if reply == 1: # close All | ||||
kernel_manager.shutdown_kernel() | ||||
closing_widget.pasteMagic("exit") | ||||
self.tabWidget.removeTab(tab) | ||||
#kernel_manager.stop_channels() | ||||
#event.accept() | ||||
elif reply == 0: # close Console | ||||
if not closing_widget._existing: | ||||
# Have kernel: don't quit, just close the window | ||||
self._app.setQuitOnLastWindowClosed(False) | ||||
self.deleteLater() | ||||
#event.accept() | ||||
#else: | ||||
#event.ignore() | ||||
else: | ||||
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: | ||||
self.tabWidget.removeTab(tab) | ||||
else: | ||||
event.ignore() | ||||
elif keepkernel: #close console but leave kernel running (no prompt) | ||||
if kernel_manager and kernel_manager.channels_running: | ||||
if not currentWidget._existing: | ||||
# I have the kernel: don't quit, just close the window | ||||
self._app.setQuitOnLastWindowClosed(False) | ||||
#event.accept() | ||||
else: #close console and kernel (no prompt) | ||||
if kernel_manager and kernel_manager.channels_running: | ||||
kernel_manager.shutdown_kernel() | ||||
#event.accept() | ||||
#try: | ||||
# if closing_widget._local_kernel and not keepkernel: | ||||
# kernel_manager = self.tabWidget.widget(tab).kernel_manager.shutdown_kernel() | ||||
# else: | ||||
# print "not owning the kernel/asked not to shut it down" | ||||
#except: | ||||
# print "can't ask the kernel to shutdown" | ||||
|
r5035 | #if self.tabWidget.count() == 1: | ||
#self.close() | ||||
|
r5037 | #self.tabWidget.removeTab(tab) | ||
|
r5035 | self.updateTabBarVisibility() | ||
def addTabWithFrontend(self,frontend,name=None): | ||||
""" insert a tab with a given frontend in the tab bar, and give it a name | ||||
""" | ||||
if not name: | ||||
name=str('no Name '+str(self.tabWidget.count())) | ||||
self.tabWidget.addTab(frontend,name) | ||||
self.updateTabBarVisibility() | ||||
frontend.exit_requested.connect(self.irequest) | ||||
def irequest(self,obj): | ||||
print "I request to exit",obj | ||||
print "which is tab:",self.tabWidget.indexOf(obj) | ||||
self.closetab(self.tabWidget.indexOf(obj)) | ||||
|
r5034 | # MenuBar is always present on Mac Os, so let's populate it with possible | ||
# action, don't do it on other platform as some user might not want the | ||||
# menu bar, or give them an option to remove it | ||||
|
r5027 | |||
|
r5031 | def initMenuBar(self): | ||
#create menu in the order they should appear in the menu bar | ||||
self.fileMenu = self.menuBar().addMenu("File") | ||||
self.editMenu = self.menuBar().addMenu("Edit") | ||||
self.fontMenu = self.menuBar().addMenu("Font") | ||||
self.windowMenu = self.menuBar().addMenu("Window") | ||||
self.magicMenu = self.menuBar().addMenu("Magic") | ||||
# please keep the Help menu in Mac Os even if empty. It will | ||||
# automatically contain a search field to search inside menus and | ||||
# please keep it spelled in English, as long as Qt Doesn't support | ||||
# a QAction.MenuRole like HelpMenuRole otherwise it will loose | ||||
# this search field fonctionnality | ||||
self.helpMenu = self.menuBar().addMenu("Help") | ||||
# sould wrap every line of the following block into a try/except, | ||||
# as we are not sure of instanciating a _frontend which support all | ||||
# theses actions, but there might be a better way | ||||
try: | ||||
|
r5035 | self.print_action = QtGui.QAction("Print", | ||
self, | ||||
shortcut="Ctrl+P", | ||||
|
r5036 | triggered=self.print_action_active_frontend) | ||
|
r5035 | self.fileMenu.addAction(self.print_action) | ||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (print), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5035 | self.export_action=QtGui.QAction("Export", | ||
self, | ||||
shortcut="Ctrl+S", | ||||
triggered=self.export_action_active_frontend | ||||
) | ||||
self.fileMenu.addAction(self.export_action) | ||||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (Export), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5036 | self.select_all_action = QtGui.QAction("Select All", | ||
self, | ||||
shortcut="Ctrl+A", | ||||
triggered=self.select_all_active_frontend | ||||
) | ||||
self.fileMenu.addAction(self.select_all_action) | ||||
|
r5031 | except AttributeError: | ||
|
r5036 | print "trying to add unexisting action (select all), skipping" | ||
|
r5027 | |||
|
r5031 | try: | ||
|
r5032 | self.undo_action = QtGui.QAction("Undo", | ||
|
r5036 | self, | ||
shortcut="Ctrl+Z", | ||||
statusTip="Undo last action if possible", | ||||
triggered=self.undo_active_frontend | ||||
) | ||||
|
r5032 | self.editMenu.addAction(self.undo_action) | ||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (undo), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5032 | self.redo_action = QtGui.QAction("Redo", | ||
self, | ||||
shortcut="Ctrl+Shift+Z", | ||||
statusTip="Redo last action if possible", | ||||
|
r5035 | triggered=self.redo_active_frontend) | ||
|
r5032 | self.editMenu.addAction(self.redo_action) | ||
|
r5031 | except AttributeError: | ||
|
r5036 | print "trying to add unexisting action (redo), skipping" | ||
|
r5027 | |||
|
r5031 | try: | ||
|
r5036 | self.increase_font_size = QtGui.QAction("Increase Font Size", | ||
self, | ||||
shortcut="Ctrl++", | ||||
triggered=self.increase_font_size_active_frontend | ||||
) | ||||
self.fontMenu.addAction(self.increase_font_size) | ||||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (increase font size), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5036 | self.decrease_font_size = QtGui.QAction("Decrease Font Size", | ||
self, | ||||
shortcut="Ctrl+-", | ||||
triggered=self.decrease_font_size_active_frontend | ||||
) | ||||
self.fontMenu.addAction(self.decrease_font_size) | ||||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (decrease font size), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5036 | self.reset_font_size = QtGui.QAction("Reset Font Size", | ||
self, | ||||
shortcut="Ctrl+0", | ||||
triggered=self.reset_font_size_active_frontend | ||||
) | ||||
self.fontMenu.addAction(self.reset_font_size) | ||||
|
r5031 | except AttributeError: | ||
|
r5035 | print "trying to add unexisting action (reset font size), skipping" | ||
|
r5027 | |||
|
r5031 | try: | ||
|
r5032 | self.reset_action = QtGui.QAction("Reset", | ||
self, | ||||
statusTip="Clear all varible from workspace", | ||||
|
r5035 | triggered=self.reset_magic_active_frontend) | ||
|
r5032 | self.magicMenu.addAction(self.reset_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (reset), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5033 | self.history_action = QtGui.QAction("History", | ||
self, | ||||
statusTip="show command history", | ||||
|
r5035 | triggered=self.history_magic_active_frontend) | ||
|
r5033 | self.magicMenu.addAction(self.history_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (history), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5033 | self.save_action = QtGui.QAction("Export History ", | ||
self, | ||||
statusTip="Export History as Python File", | ||||
|
r5035 | triggered=self.save_magic_active_frontend) | ||
|
r5033 | self.magicMenu.addAction(self.save_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (save), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5032 | self.clear_action = QtGui.QAction("Clear", | ||
self, | ||||
statusTip="Clear the console", | ||||
|
r5035 | triggered=self.clear_magic_active_frontend) | ||
|
r5032 | self.magicMenu.addAction(self.clear_action) | ||
|
r5031 | except AttributeError: | ||
print "trying to add unexisting action, skipping" | ||||
try: | ||||
|
r5032 | self.who_action = QtGui.QAction("Who", | ||
self, | ||||
statusTip="List interactive variable", | ||||
|
r5035 | triggered=self.who_magic_active_frontend) | ||
|
r5032 | self.magicMenu.addAction(self.who_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (who), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5032 | self.who_ls_action = QtGui.QAction("Who ls", | ||
self, | ||||
statusTip="Return a list of interactive variable", | ||||
|
r5035 | triggered=self.who_ls_magic_active_frontend) | ||
|
r5032 | self.magicMenu.addAction(self.who_ls_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (who_ls), skipping" | ||
|
r5031 | |||
try: | ||||
|
r5032 | self.whos_action = QtGui.QAction("Whos", | ||
self, | ||||
statusTip="List interactive variable with detail", | ||||
|
r5035 | triggered=self.whos_magic_active_frontend) | ||
|
r5032 | self.magicMenu.addAction(self.whos_action) | ||
|
r5031 | except AttributeError: | ||
|
r5032 | print "trying to add unexisting action (whos), skipping" | ||
|
r5031 | |||
|
r5035 | def undo_active_frontend(self): | ||
|
r5037 | self.activeFrontend.undo() | ||
|
r5035 | |||
def redo_active_frontend(self): | ||||
|
r5037 | self.activeFrontend.redo() | ||
|
r5035 | def reset_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.reset_magic() | ||
|
r5035 | def history_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.history_magic() | ||
|
r5035 | def save_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.save_magic() | ||
|
r5035 | def clear_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.clear_magic() | ||
|
r5035 | def who_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.who_magic() | ||
|
r5035 | def who_ls_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.who_ls_magic() | ||
|
r5035 | def whos_magic_active_frontend(self): | ||
|
r5037 | self.activeFrontend.whos_magic() | ||
|
r5035 | |||
def print_action_active_frontend(self): | ||||
|
r5037 | self.activeFrontend.print_action.trigger() | ||
|
r5035 | |||
def export_action_active_frontend(self): | ||||
|
r5037 | self.activeFrontend.export_action.trigger() | ||
|
r5035 | |||
|
r5036 | def select_all_active_frontend(self): | ||
|
r5037 | self.activeFrontend.select_all_action.trigger() | ||
|
r5035 | |||
def increase_font_size_active_frontend(self): | ||||
|
r5037 | self.activeFrontend.increase_font_size.trigger() | ||
|
r5035 | def decrease_font_size_active_frontend(self): | ||
|
r5037 | self.activeFrontend.decrease_font_size.trigger() | ||
|
r5035 | def reset_font_size_active_frontend(self): | ||
|
r5037 | self.activeFrontend.reset_font_size.trigger() | ||
|
r2961 | #--------------------------------------------------------------------------- | ||
# QWidget interface | ||||
#--------------------------------------------------------------------------- | ||||
def closeEvent(self, event): | ||||
|
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. | ||||
|
r2961 | """ | ||
|
r5037 | # Should change the logic | ||
# put this code iin a per tab bases, with each atributes specified on the tab. | ||||
# otherwise, we should ask the user: | ||||
# ==================================== | ||||
# close all the tabs and kernel ? | ||||
# Yes | No | Cancel | Keep the kernel runing | Ask for each | ||||
# ==================================== | ||||
# We might also add an action "close all `non local kernel` console" | ||||
# Closing widget is based on the tab index, so we have to build the | ||||
# list of widget before actually looping throught it. By Sending the %exit magic | ||||
# you are sure to avoid question about restarting the kernel. Otherwise you'll have to | ||||
# close the 'non local kernel' tabs before | ||||
#allWidget = [ self.tabWidget.widget(n) for n in range(self.tabWidget.count())] | ||||
#for w in allWidget: | ||||
# w.exit_magic() | ||||
#return | ||||
#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 | ||||
|
r3185 | |||
|
r5037 | #kernel_manager = self._frontend.kernel_manager | ||
|
r3983 | |||
|
r5037 | #if keepkernel is None and not self._confirm_exit: | ||
# # don't prompt, just terminate the kernel if we own it | ||||
# # or leave it alone if we don't | ||||
# keepkernel = not self._existing | ||||
return | ||||
|
r3185 | if keepkernel is None: #show prompt | ||
|
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') | ||||
|
r3304 | box = QtGui.QMessageBox(QtGui.QMessageBox.Question, | ||
title, msg) | ||||
|
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) | ||||
|
r5028 | pixmap = QtGui.QPixmap(':/icon/IPythonConsole.png') | ||
scaledpixmap = pixmap.scaledToWidth(64,mode=QtCore.Qt.SmoothTransformation) | ||||
box.setIconPixmap(scaledpixmap) | ||||
|
r3183 | 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() | ||||
|
r3142 | else: | ||
|
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() | ||||
|
r3185 | elif keepkernel: #close console but leave kernel running (no prompt) | ||
|
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() | ||||
|
r3185 | else: #close console and kernel (no prompt) | ||
|
r3183 | if kernel_manager and kernel_manager.channels_running: | ||
kernel_manager.shutdown_kernel() | ||||
event.accept() | ||||
|
r2961 | |||
#----------------------------------------------------------------------------- | ||||
|
r3971 | # Aliases and Flags | ||
|
r2961 | #----------------------------------------------------------------------------- | ||
|
r2758 | |||
|
r3971 | flags = dict(ipkernel_flags) | ||
|
r4247 | qt_flags = { | ||
|
r4958 | 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}}, | ||
"Connect to an existing kernel. If no argument specified, guess most recent"), | ||||
|
r3971 | 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}}, | ||
"Use a pure Python kernel instead of an IPython kernel."), | ||||
'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}}, | ||||
"Disable rich text support."), | ||||
|
r4247 | } | ||
qt_flags.update(boolean_flag( | ||||
|
r3983 | 'gui-completion', 'ConsoleWidget.gui_completion', | ||
"use a GUI widget for tab completion", | ||||
"use plaintext output for completion" | ||||
)) | ||||
|
r4247 | qt_flags.update(boolean_flag( | ||
|
r3983 | 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit', | ||
"""Set to display confirmation dialog on exit. You can always use 'exit' or 'quit', | ||||
to force a direct exit without any confirmation. | ||||
""", | ||||
"""Don't prompt the user when exiting. This will terminate the kernel | ||||
if it is owned by the frontend, and leave it alive if it is external. | ||||
""" | ||||
)) | ||||
|
r4247 | flags.update(qt_flags) | ||
|
r3971 | |||
aliases = dict(ipkernel_aliases) | ||||
|
r4247 | qt_aliases = dict( | ||
|
r3971 | hb = 'IPythonQtConsoleApp.hb_port', | ||
shell = 'IPythonQtConsoleApp.shell_port', | ||||
iopub = 'IPythonQtConsoleApp.iopub_port', | ||||
stdin = 'IPythonQtConsoleApp.stdin_port', | ||||
ip = 'IPythonQtConsoleApp.ip', | ||||
|
r4958 | existing = 'IPythonQtConsoleApp.existing', | ||
f = 'IPythonQtConsoleApp.connection_file', | ||||
|
r3971 | |||
style = 'IPythonWidget.syntax_style', | ||||
stylesheet = 'IPythonQtConsoleApp.stylesheet', | ||||
colors = 'ZMQInteractiveShell.colors', | ||||
editor = 'IPythonWidget.editor', | ||||
|
r4222 | paging = 'ConsoleWidget.paging', | ||
|
r4594 | ssh = 'IPythonQtConsoleApp.sshserver', | ||
|
r4247 | ) | ||
aliases.update(qt_aliases) | ||||
|
r4222 | |||
|
r3971 | |||
#----------------------------------------------------------------------------- | ||||
# IPythonQtConsole | ||||
#----------------------------------------------------------------------------- | ||||
|
r4215 | |||
|
r3971 | class IPythonQtConsoleApp(BaseIPythonApplication): | ||
name = 'ipython-qtconsole' | ||||
default_config_file_name='ipython_config.py' | ||||
|
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: | ||||
ipython qtconsole --pylab=inline | ||||
|
r4021 | |||
|
r4222 | as well as saving your session as HTML, and printing the output. | ||
|
r4021 | |||
""" | ||||
|
r4216 | examples = _examples | ||
|
r4215 | |||
|
r4015 | classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session] | ||
|
r3971 | flags = Dict(flags) | ||
aliases = Dict(aliases) | ||||
kernel_argv = List(Unicode) | ||||
|
r4265 | # create requested profiles by default, if they don't exist: | ||
auto_create = CBool(True) | ||||
|
r3971 | # connection info: | ||
ip = Unicode(LOCALHOST, config=True, | ||||
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!""" | ||||
) | ||||
|
r4594 | |||
sshserver = Unicode('', config=True, | ||||
help="""The SSH server to use to connect to the kernel.""") | ||||
sshkey = Unicode('', config=True, | ||||
help="""Path to the ssh key to use for logging in to the ssh server.""") | ||||
|
r3971 | hb_port = Int(0, config=True, | ||
help="set the heartbeat port [default: random]") | ||||
shell_port = Int(0, config=True, | ||||
help="set the shell (XREP) port [default: random]") | ||||
iopub_port = Int(0, config=True, | ||||
help="set the iopub (PUB) port [default: random]") | ||||
stdin_port = Int(0, config=True, | ||||
help="set the stdin (XREQ) port [default: random]") | ||||
|
r4958 | connection_file = Unicode('', config=True, | ||
help="""JSON file in which to store connection info [default: kernel-<pid>.json] | ||||
|
r3971 | |||
|
r4958 | This file will contain the IP, ports, and authentication key needed to connect | ||
clients to this kernel. By default, this file will be created in the security-dir | ||||
of the current profile, but can be specified by absolute path. | ||||
""") | ||||
def _connection_file_default(self): | ||||
return 'kernel-%i.json' % os.getpid() | ||||
existing = Unicode('', config=True, | ||||
help="""Connect to an already running kernel""") | ||||
|
r3971 | |||
stylesheet = Unicode('', config=True, | ||||
help="path to a custom CSS stylesheet") | ||||
|
r3983 | pure = CBool(False, config=True, | ||
|
r3971 | help="Use a pure Python kernel instead of an IPython kernel.") | ||
|
r3983 | plain = CBool(False, config=True, | ||
|
r3976 | help="Use a plaintext widget instead of rich text (plain can't print/save).") | ||
|
r3971 | |||
def _pure_changed(self, name, old, new): | ||||
kind = 'plain' if self.plain else 'rich' | ||||
self.config.ConsoleWidget.kind = kind | ||||
if self.pure: | ||||
self.widget_factory = FrontendWidget | ||||
elif self.plain: | ||||
self.widget_factory = IPythonWidget | ||||
|
r3173 | else: | ||
|
r3971 | self.widget_factory = RichIPythonWidget | ||
_plain_changed = _pure_changed | ||||
|
r3983 | confirm_exit = CBool(True, config=True, | ||
help=""" | ||||
Set to display confirmation dialog on exit. You can always use 'exit' or 'quit', | ||||
to force a direct exit without any confirmation.""", | ||||
) | ||||
|
r3971 | # the factory for creating a widget | ||
widget_factory = Any(RichIPythonWidget) | ||||
def parse_command_line(self, argv=None): | ||||
super(IPythonQtConsoleApp, self).parse_command_line(argv) | ||||
if argv is None: | ||||
argv = sys.argv[1:] | ||||
self.kernel_argv = list(argv) # copy | ||||
|
r4118 | # kernel should inherit default config file from frontend | ||
|
r4197 | self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name) | ||
|
r4957 | # Scrub frontend-specific flags | ||
|
r3971 | for a in argv: | ||
|
r4957 | if a.startswith('-') and a.lstrip('-') in qt_flags: | ||
self.kernel_argv.remove(a) | ||||
swallow_next = False | ||||
for a in argv: | ||||
if swallow_next: | ||||
self.kernel_argv.remove(a) | ||||
swallow_next = False | ||||
continue | ||||
|
r4247 | if a.startswith('-'): | ||
|
r5015 | split = a.lstrip('-').split('=') | ||
|
r4957 | alias = split[0] | ||
if alias in qt_aliases: | ||||
|
r4247 | self.kernel_argv.remove(a) | ||
|
r4957 | if len(split) == 1: | ||
# alias passed with arg via space | ||||
swallow_next = True | ||||
|
r4594 | |||
|
r4958 | def init_connection_file(self): | ||
|
r4969 | """find the connection file, and load the info if found. | ||
The current working directory and the current profile's security | ||||
directory will be searched for the file if it is not given by | ||||
absolute path. | ||||
When attempting to connect to an existing kernel and the `--existing` | ||||
argument does not match an existing file, it will be interpreted as a | ||||
fileglob, and the matching file in the current profile's security dir | ||||
with the latest access time will be used. | ||||
""" | ||||
|
r4958 | if self.existing: | ||
try: | ||||
|
r4972 | cf = find_connection_file(self.existing) | ||
except Exception: | ||||
self.log.critical("Could not find existing kernel connection file %s", self.existing) | ||||
self.exit(1) | ||||
|
r4958 | self.log.info("Connecting to existing kernel: %s" % cf) | ||
self.connection_file = cf | ||||
# should load_connection_file only be used for existing? | ||||
# as it is now, this allows reusing ports if an existing | ||||
# file is requested | ||||
|
r4986 | try: | ||
self.load_connection_file() | ||||
except Exception: | ||||
self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True) | ||||
self.exit(1) | ||||
|
r4958 | |||
def load_connection_file(self): | ||||
"""load ip/port/hmac config from JSON connection file""" | ||||
# this is identical to KernelApp.load_connection_file | ||||
# perhaps it can be centralized somewhere? | ||||
try: | ||||
fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir]) | ||||
except IOError: | ||||
self.log.debug("Connection File not found: %s", self.connection_file) | ||||
return | ||||
self.log.debug(u"Loading connection file %s", fname) | ||||
with open(fname) as f: | ||||
s = f.read() | ||||
cfg = json.loads(s) | ||||
if self.ip == LOCALHOST and 'ip' in cfg: | ||||
# not overridden by config or cl_args | ||||
self.ip = cfg['ip'] | ||||
for channel in ('hb', 'shell', 'iopub', 'stdin'): | ||||
name = channel + '_port' | ||||
if getattr(self, name) == 0 and name in cfg: | ||||
# not overridden by config or cl_args | ||||
setattr(self, name, cfg[name]) | ||||
if 'key' in cfg: | ||||
|
r4967 | self.config.Session.key = str_to_bytes(cfg['key']) | ||
|
r4958 | |||
|
r4594 | def init_ssh(self): | ||
|
r4595 | """set up ssh tunnels, if needed.""" | ||
|
r4594 | if not self.sshserver and not self.sshkey: | ||
return | ||||
if self.sshkey and not self.sshserver: | ||||
|
r4971 | # specifying just the key implies that we are connecting directly | ||
|
r4594 | self.sshserver = self.ip | ||
|
r4971 | self.ip = LOCALHOST | ||
|
r4594 | |||
|
r4971 | # build connection dict for tunnels: | ||
info = dict(ip=self.ip, | ||||
shell_port=self.shell_port, | ||||
iopub_port=self.iopub_port, | ||||
stdin_port=self.stdin_port, | ||||
hb_port=self.hb_port | ||||
) | ||||
|
r4594 | |||
|
r4971 | self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver)) | ||
|
r4594 | |||
|
r4971 | # tunnels return a new set of ports, which will be on localhost: | ||
self.ip = LOCALHOST | ||||
try: | ||||
newports = tunnel_to_kernel(info, self.sshserver, self.sshkey) | ||||
except: | ||||
# even catch KeyboardInterrupt | ||||
self.log.error("Could not setup tunnels", exc_info=True) | ||||
self.exit(1) | ||||
|
r4594 | |||
|
r4971 | self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports | ||
|
r4594 | |||
|
r4961 | cf = self.connection_file | ||
base,ext = os.path.splitext(cf) | ||||
base = os.path.basename(base) | ||||
self.connection_file = os.path.basename(base)+'-ssh'+ext | ||||
self.log.critical("To connect another client via this tunnel, use:") | ||||
self.log.critical("--existing %s" % self.connection_file) | ||||
|
r4846 | |||
|
r3971 | def init_kernel_manager(self): | ||
# Don't let Qt or ZMQ swallow KeyboardInterupts. | ||||
signal.signal(signal.SIGINT, signal.SIG_DFL) | ||||
|
r4958 | sec = self.profile_dir.security_dir | ||
try: | ||||
cf = filefind(self.connection_file, ['.', sec]) | ||||
except IOError: | ||||
|
r4969 | # file might not exist | ||
|
r4958 | if self.connection_file == os.path.basename(self.connection_file): | ||
# just shortname, put it in security dir | ||||
cf = os.path.join(sec, self.connection_file) | ||||
else: | ||||
cf = self.connection_file | ||||
|
r3971 | |||
# Create a KernelManager and start a kernel. | ||||
|
r4958 | self.kernel_manager = QtKernelManager( | ||
ip=self.ip, | ||||
|
r4956 | shell_port=self.shell_port, | ||
|
r4959 | iopub_port=self.iopub_port, | ||
|
r4956 | stdin_port=self.stdin_port, | ||
hb_port=self.hb_port, | ||||
|
r4958 | connection_file=cf, | ||
|
r4956 | config=self.config, | ||
|
r3971 | ) | ||
# start the kernel | ||||
if not self.existing: | ||||
|
r4958 | kwargs = dict(ipython=not self.pure) | ||
|
r3971 | kwargs['extra_arguments'] = self.kernel_argv | ||
self.kernel_manager.start_kernel(**kwargs) | ||||
|
r4961 | elif self.sshserver: | ||
# ssh, write new connection file | ||||
self.kernel_manager.write_connection_file() | ||||
|
r3971 | self.kernel_manager.start_channels() | ||
|
r5035 | def createTabWithNewFrontend(self): | ||
kernel_manager = QtKernelManager( | ||||
shell_address=(self.ip, self.shell_port), | ||||
sub_address=(self.ip, self.iopub_port), | ||||
stdin_address=(self.ip, self.stdin_port), | ||||
hb_address=(self.ip, self.hb_port), | ||||
config=self.config | ||||
) | ||||
# start the kernel | ||||
if not self.existing: | ||||
kwargs = dict(ip=self.ip, ipython=not self.pure) | ||||
kwargs['extra_arguments'] = self.kernel_argv | ||||
kernel_manager.start_kernel(**kwargs) | ||||
kernel_manager.start_channels() | ||||
local_kernel = (not self.existing) or self.ip in LOCAL_IPS | ||||
widget = self.widget_factory(config=self.config, | ||||
local_kernel=local_kernel) | ||||
widget.kernel_manager = kernel_manager | ||||
|
r5037 | widget._confirm_exit=True; | ||
widget._may_close=True; | ||||
|
r5035 | self.window.addTabWithFrontend(widget) | ||
def createTabAttachedToCurrentTabKernel(self): | ||||
currentWidget=self.window.tabWidget.currentWidget() | ||||
currentWidgetIndex=self.window.tabWidget.indexOf(currentWidget) | ||||
ckm=currentWidget.kernel_manager; | ||||
cwname=self.window.tabWidget.tabText(currentWidgetIndex); | ||||
kernel_manager = QtKernelManager( | ||||
shell_address=ckm.shell_address, | ||||
sub_address=ckm.sub_address, | ||||
stdin_address=ckm.stdin_address, | ||||
hb_address=ckm.hb_address, | ||||
config=self.config | ||||
) | ||||
kernel_manager.start_channels() | ||||
local_kernel = (not self.existing) or self.ip in LOCAL_IPS | ||||
widget = self.widget_factory(config=self.config, | ||||
local_kernel=False) | ||||
|
r5037 | widget._confirm_exit=True; | ||
widget._may_close=False; | ||||
|
r5035 | widget.kernel_manager = kernel_manager | ||
self.window.addTabWithFrontend(widget,name=str('('+cwname+') slave')) | ||||
|
r3971 | |||
def init_qt_elements(self): | ||||
# Create the widget. | ||||
self.app = QtGui.QApplication([]) | ||||
|
r5028 | pixmap=QtGui.QPixmap(':/icon/IPythonConsole.png') | ||
icon=QtGui.QIcon(pixmap) | ||||
QtGui.QApplication.setWindowIcon(icon) | ||||
|
r3971 | local_kernel = (not self.existing) or self.ip in LOCAL_IPS | ||
self.widget = self.widget_factory(config=self.config, | ||||
local_kernel=local_kernel) | ||||
self.widget.kernel_manager = self.kernel_manager | ||||
self.window = MainWindow(self.app, self.widget, self.existing, | ||||
|
r3983 | may_close=local_kernel, | ||
confirm_exit=self.confirm_exit) | ||||
|
r5037 | self.window.addTabWithFrontend(self.widget) | ||
|
r5031 | self.window.initMenuBar() | ||
|
r3971 | self.window.setWindowTitle('Python' if self.pure else 'IPython') | ||
def init_colors(self): | ||||
"""Configure the coloring of the widget""" | ||||
# Note: This will be dramatically simplified when colors | ||||
# are removed from the backend. | ||||
if self.pure: | ||||
# only IPythonWidget supports styling | ||||
return | ||||
# parse the colors arg down to current known labels | ||||
try: | ||||
colors = self.config.ZMQInteractiveShell.colors | ||||
except AttributeError: | ||||
colors = None | ||||
try: | ||||
style = self.config.IPythonWidget.colors | ||||
except AttributeError: | ||||
style = None | ||||
# 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 | ||
# Configure the style. | ||||
widget = self.widget | ||||
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: | ||
# use a default style | ||||
widget.set_default_style(colors=colors) | ||||
|
r3170 | else: | ||
# this is redundant for now, but allows the widget's | ||||
# defaults to change | ||||
|
r3171 | widget.set_default_style() | ||
|
r3170 | |||
|
r3971 | if self.stylesheet: | ||
|
r3170 | # we got an expicit stylesheet | ||
|
r3971 | if os.path.isfile(self.stylesheet): | ||
with open(self.stylesheet) as f: | ||||
|
r3170 | sheet = f.read() | ||
widget.style_sheet = sheet | ||||
widget._style_sheet_changed() | ||||
else: | ||||
|
r3971 | raise IOError("Stylesheet %r not found."%self.stylesheet) | ||
def initialize(self, argv=None): | ||||
super(IPythonQtConsoleApp, self).initialize(argv) | ||||
|
r4958 | self.init_connection_file() | ||
|
r4962 | default_secure(self.config) | ||
|
r4961 | self.init_ssh() | ||
|
r3971 | self.init_kernel_manager() | ||
self.init_qt_elements() | ||||
self.init_colors() | ||||
|
r4817 | self.init_window_shortcut() | ||
def init_window_shortcut(self): | ||||
|
r5027 | self.fullScreenAct = QtGui.QAction("Full Screen", | ||
self.window, | ||||
shortcut="Ctrl+Meta+Space", | ||||
statusTip="Toggle between Fullscreen and Normal Size", | ||||
triggered=self.toggleFullScreen) | ||||
|
r5035 | self.tabAndNewKernelAct =QtGui.QAction("Tab with New kernel", | ||
self.window, | ||||
shortcut="Ctrl+T", | ||||
triggered=self.createTabWithNewFrontend) | ||||
self.window.windowMenu.addAction(self.tabAndNewKernelAct) | ||||
self.tabSameKernalAct =QtGui.QAction("Tab with Same kernel", | ||||
self.window, | ||||
shortcut="Ctrl+Shift+T", | ||||
triggered=self.createTabAttachedToCurrentTabKernel) | ||||
self.window.windowMenu.addAction(self.tabSameKernalAct) | ||||
self.window.windowMenu.addSeparator() | ||||
|
r5027 | |||
# creating shortcut in menubar only for Mac OS as I don't | ||||
# know the shortcut or if the windows manager assign it in | ||||
# other platform. | ||||
if sys.platform == 'darwin': | ||||
self.minimizeAct = QtGui.QAction("Minimize", | ||||
self.window, | ||||
shortcut="Ctrl+m", | ||||
statusTip="Minimize the window/Restore Normal Size", | ||||
triggered=self.toggleMinimized) | ||||
self.maximizeAct = QtGui.QAction("Maximize", | ||||
self.window, | ||||
shortcut="Ctrl+Shift+M", | ||||
statusTip="Maximize the window/Restore Normal Size", | ||||
triggered=self.toggleMaximized) | ||||
self.onlineHelpAct = QtGui.QAction("Open Online Help", | ||||
self.window, | ||||
triggered=self._open_online_help) | ||||
self.windowMenu = self.window.windowMenu | ||||
self.windowMenu.addAction(self.minimizeAct) | ||||
self.windowMenu.addAction(self.maximizeAct) | ||||
self.windowMenu.addSeparator() | ||||
self.windowMenu.addAction(self.fullScreenAct) | ||||
self.window.helpMenu.addAction(self.onlineHelpAct) | ||||
else: | ||||
# if we don't put it in a menu, we add it to the window so | ||||
# that it can still be triggerd by shortcut | ||||
self.window.addAction(self.fullScreenAct) | ||||
def toggleMinimized(self): | ||||
if not self.window.isMinimized(): | ||||
self.window.showMinimized() | ||||
else: | ||||
self.window.showNormal() | ||||
def _open_online_help(self): | ||||
QtGui.QDesktopServices.openUrl( | ||||
QtCore.QUrl("http://ipython.org/documentation.html", | ||||
QtCore.QUrl.TolerantMode) | ||||
) | ||||
def toggleMaximized(self): | ||||
if not self.window.isMaximized(): | ||||
self.window.showMaximized() | ||||
else: | ||||
self.window.showNormal() | ||||
# Min/Max imizing while in full screen give a bug | ||||
# when going out of full screen, at least on OSX | ||||
|
r4817 | def toggleFullScreen(self): | ||
if not self.window.isFullScreen(): | ||||
self.window.showFullScreen() | ||||
|
r5027 | if sys.platform == 'darwin': | ||
self.maximizeAct.setEnabled(False) | ||||
self.minimizeAct.setEnabled(False) | ||||
|
r4817 | else: | ||
self.window.showNormal() | ||||
|
r5027 | if sys.platform == 'darwin': | ||
self.maximizeAct.setEnabled(True) | ||||
self.minimizeAct.setEnabled(True) | ||||
|
r3971 | |||
def start(self): | ||||
# draw the window | ||||
self.window.show() | ||||
|
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() | ||||