qtconsoleapp.py
1111 lines
| 44.3 KiB
| text/x-python
|
PythonLexer
epatters
|
r2801 | """ A minimal application using the Qt console-style IPython frontend. | ||
MinRK
|
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 | ||||
epatters
|
r2758 | """ | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
# Imports | ||||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r3971 | # stdlib imports | ||
MinRK
|
r4986 | import json | ||
MinRK
|
r3971 | import os | ||
import signal | ||||
import sys | ||||
Matthias BUSSONNIER
|
r5059 | import webbrowser | ||
from getpass import getpass | ||||
MinRK
|
r3971 | |||
# System library imports | ||||
Matthias BUSSONNIER
|
r5027 | from IPython.external.qt import QtGui,QtCore | ||
MinRK
|
r3170 | from pygments.styles import get_all_styles | ||
Evan Patterson
|
r3304 | |||
epatters
|
r2758 | # Local imports | ||
MinRK
|
r3983 | from IPython.config.application import boolean_flag | ||
MinRK
|
r4024 | from IPython.core.application import BaseIPythonApplication | ||
from IPython.core.profiledir import ProfileDir | ||||
MinRK
|
r4972 | from IPython.lib.kernel import tunnel_to_kernel, find_connection_file | ||
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 | ||
MinRK
|
r4958 | from IPython.utils.path import filefind | ||
MinRK
|
r4967 | from IPython.utils.py3compat import str_to_bytes | ||
MinRK
|
r3971 | from IPython.utils.traitlets import ( | ||
MinRK
|
r3983 | Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any | ||
MinRK
|
r3971 | ) | ||
from IPython.zmq.ipkernel import ( | ||||
flags as ipkernel_flags, | ||||
aliases as ipkernel_aliases, | ||||
IPKernelApp | ||||
) | ||||
MinRK
|
r4962 | from IPython.zmq.session import Session, default_secure | ||
MinRK
|
r3971 | from IPython.zmq.zmqshell import ZMQInteractiveShell | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
MinRK
|
r3144 | # Network Constants | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
MinRK
|
r3144 | from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS | ||
epatters
|
r2823 | |||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
Brian Granger
|
r4216 | # Globals | ||
#----------------------------------------------------------------------------- | ||||
_examples = """ | ||||
ipython qtconsole # start the qtconsole | ||||
ipython qtconsole --pylab=inline # start with pylab in inline plotting mode | ||||
""" | ||||
#----------------------------------------------------------------------------- | ||||
epatters
|
r2961 | # Classes | ||
#----------------------------------------------------------------------------- | ||||
class MainWindow(QtGui.QMainWindow): | ||||
#--------------------------------------------------------------------------- | ||||
# 'object' interface | ||||
#--------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5027 | |||
MinRK
|
r3983 | def __init__(self, app, frontend, existing=False, may_close=True, | ||
confirm_exit=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 | """ | ||
Matthias BUSSONNIER
|
r5037 | |||
epatters
|
r2961 | super(MainWindow, self).__init__() | ||
MinRK
|
r3104 | self._app = app | ||
Matthias BUSSONNIER
|
r4817 | |||
Matthias BUSSONNIER
|
r5047 | self.tab_widget = QtGui.QTabWidget(self) | ||
self.tab_widget.setDocumentMode(True) | ||||
self.tab_widget.setTabsClosable(True) | ||||
Matthias BUSSONNIER
|
r5048 | self.tab_widget.tabCloseRequested[int].connect(self.close_tab) | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5047 | self.setCentralWidget(self.tab_widget) | ||
Matthias BUSSONNIER
|
r5049 | self.update_tab_bar_visibility() | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5049 | def update_tab_bar_visibility(self): | ||
Matthias BUSSONNIER
|
r5035 | """ update visibility of the tabBar depending of the number of tab | ||
Matthias BUSSONNIER
|
r5038 | 0 or 1 tab, tabBar hidden | ||
2+ tabs, tabBar visible | ||||
send a self.close if number of tab ==0 | ||||
Matthias BUSSONNIER
|
r5035 | |||
need to be called explicitely, or be connected to tabInserted/tabRemoved | ||||
""" | ||||
Matthias BUSSONNIER
|
r5047 | if self.tab_widget.count() <= 1: | ||
self.tab_widget.tabBar().setVisible(False) | ||||
Matthias BUSSONNIER
|
r5035 | else: | ||
Matthias BUSSONNIER
|
r5047 | self.tab_widget.tabBar().setVisible(True) | ||
if self.tab_widget.count()==0 : | ||||
Matthias BUSSONNIER
|
r5038 | self.close() | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5037 | @property | ||
Matthias BUSSONNIER
|
r5050 | def active_frontend(self): | ||
Matthias BUSSONNIER
|
r5047 | return self.tab_widget.currentWidget() | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5051 | def close_tab(self,current_tab): | ||
Matthias BUSSONNIER
|
r5038 | """ Called when you need to try to close a tab. | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5038 | It takes the number of the tab to be closed as argument, or a referece | ||
to the wiget insite this tab | ||||
Matthias BUSSONNIER
|
r5035 | """ | ||
Matthias BUSSONNIER
|
r5038 | |||
# let's be sure "tab" and "closing widget are respectivey the index of the tab to close | ||||
# and a reference to the trontend to close | ||||
Matthias BUSSONNIER
|
r5051 | if type(current_tab) is not int : | ||
current_tab = self.tab_widget.indexOf(current_tab) | ||||
closing_widget=self.tab_widget.widget(current_tab) | ||||
Matthias BUSSONNIER
|
r5038 | |||
# when trying to be closed, widget might re-send a request to be closed again, but will | ||||
# be deleted when event will be processed. So need to check that widget still exist and | ||||
# skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be | ||||
# re-send by this fonction on the master widget, which ask all slaves widget to exit | ||||
if closing_widget==None: | ||||
return | ||||
#get a list of all wwidget not owning the kernel. | ||||
Matthias BUSSONNIER
|
r5052 | slave_tabs=self.find_slaves_tabs(closing_widget) | ||
Matthias BUSSONNIER
|
r5038 | |||
Matthias BUSSONNIER
|
r5037 | 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 | ||||
Matthias BUSSONNIER
|
r5038 | # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None) | ||
# we set local slave tabs._hidden to True to avoit prompting for kernel | ||||
# restart when they litt get the signal. and the "forward" the 'exit' | ||||
# to the main win | ||||
if keepkernel is not None: | ||||
Matthias BUSSONNIER
|
r5051 | for tab in slave_tabs: | ||
Matthias BUSSONNIER
|
r5038 | tab._hidden = True | ||
Matthias BUSSONNIER
|
r5051 | if closing_widget in slave_tabs : | ||
Matthias BUSSONNIER
|
r5038 | try : | ||
Matthias BUSSONNIER
|
r5051 | self.find_master_tab(closing_widget).execute('exit') | ||
Matthias BUSSONNIER
|
r5038 | except AttributeError: | ||
self.log.info("Master already closed or not local, closing only current tab") | ||||
Matthias BUSSONNIER
|
r5051 | self.tab_widget.removeTab(current_tab) | ||
Matthias BUSSONNIER
|
r5038 | return | ||
Matthias BUSSONNIER
|
r5037 | |||
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: | ||||
Matthias BUSSONNIER
|
r5051 | msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"' | ||
Matthias BUSSONNIER
|
r5037 | 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) | ||||
Matthias BUSSONNIER
|
r5055 | pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64))) | ||
box.setIconPixmap(pixmap) | ||||
Matthias BUSSONNIER
|
r5037 | reply = box.exec_() | ||
if reply == 1: # close All | ||||
Matthias BUSSONNIER
|
r5051 | for slave in slave_tabs: | ||
Matthias BUSSONNIER
|
r5047 | self.tab_widget.removeTab(self.tab_widget.indexOf(slave)) | ||
Matthias BUSSONNIER
|
r5046 | closing_widget.execute("exit") | ||
Matthias BUSSONNIER
|
r5051 | self.tab_widget.removeTab(current_tab) | ||
Matthias BUSSONNIER
|
r5037 | elif reply == 0: # close Console | ||
if not closing_widget._existing: | ||||
# Have kernel: don't quit, just close the window | ||||
Matthias BUSSONNIER
|
r5045 | self._app.setQuitOnLastWindowClosed(False) | ||
Matthias BUSSONNIER
|
r5046 | closing_widget.execute("exit True") | ||
Matthias BUSSONNIER
|
r5037 | 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: | ||||
Matthias BUSSONNIER
|
r5051 | self.tab_widget.removeTab(current_tab) | ||
Matthias BUSSONNIER
|
r5037 | elif keepkernel: #close console but leave kernel running (no prompt) | ||
if kernel_manager and kernel_manager.channels_running: | ||||
Matthias BUSSONNIER
|
r5038 | if not closing_widget._existing: | ||
Matthias BUSSONNIER
|
r5037 | # I have the kernel: don't quit, just close the window | ||
Matthias BUSSONNIER
|
r5051 | self.tab_widget.removeTab(current_tab) | ||
Matthias BUSSONNIER
|
r5037 | else: #close console and kernel (no prompt) | ||
if kernel_manager and kernel_manager.channels_running: | ||||
Matthias BUSSONNIER
|
r5051 | for slave in slave_tabs: | ||
Matthias BUSSONNIER
|
r5047 | self.tab_widget.removeTab(self.tab_widget.indexOf(slave)) | ||
Matthias BUSSONNIER
|
r5051 | self.tab_widget.removeTab(current_tab) | ||
Matthias BUSSONNIER
|
r5037 | kernel_manager.shutdown_kernel() | ||
Matthias BUSSONNIER
|
r5049 | self.update_tab_bar_visibility() | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5051 | def add_tab_with_frontend(self,frontend,name=None): | ||
Matthias BUSSONNIER
|
r5035 | """ insert a tab with a given frontend in the tab bar, and give it a name | ||
""" | ||||
if not name: | ||||
Matthias BUSSONNIER
|
r5047 | name=str('kernel '+str(self.tab_widget.count())) | ||
self.tab_widget.addTab(frontend,name) | ||||
Matthias BUSSONNIER
|
r5049 | self.update_tab_bar_visibility() | ||
Matthias BUSSONNIER
|
r5051 | self.make_frontend_visible(frontend) | ||
Matthias BUSSONNIER
|
r5048 | frontend.exit_requested.connect(self.close_tab) | ||
Matthias BUSSONNIER
|
r5038 | |||
Matthias BUSSONNIER
|
r5049 | def next_tab(self): | ||
Matthias BUSSONNIER
|
r5047 | self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1)) | ||
Matthias BUSSONNIER
|
r5042 | |||
Matthias BUSSONNIER
|
r5049 | def prev_tab(self): | ||
Matthias BUSSONNIER
|
r5047 | self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1)) | ||
Matthias BUSSONNIER
|
r5042 | |||
Matthias BUSSONNIER
|
r5051 | def make_frontend_visible(self,frontend): | ||
Matthias BUSSONNIER
|
r5052 | widget_index=self.tab_widget.indexOf(frontend) | ||
if widget_index > 0 : | ||||
self.tab_widget.setCurrentIndex(widget_index) | ||||
Matthias BUSSONNIER
|
r5041 | |||
Matthias BUSSONNIER
|
r5054 | def find_master_tab(self,tab,as_list=False): | ||
Matthias BUSSONNIER
|
r5038 | """ | ||
Try to return the frontend that own the kernel attached to the given widget/tab. | ||||
Only find frontend owed by the current application. Selection | ||||
based on port of the kernel, might be inacurate if several kernel | ||||
on different ip use same port number. | ||||
This fonction does the conversion tabNumber/widget if needed. | ||||
Might return None if no master widget (non local kernel) | ||||
Will crash IPython if more than 1 masterWidget | ||||
When asList set to True, always return a list of widget(s) owning | ||||
the kernel. The list might be empty or containing several Widget. | ||||
""" | ||||
#convert from/to int/richIpythonWidget if needed | ||||
if type(tab) == int: | ||||
Matthias BUSSONNIER
|
r5047 | tab = self.tab_widget.widget(tab) | ||
Matthias BUSSONNIER
|
r5038 | km=tab.kernel_manager; | ||
#build list of all widgets | ||||
Matthias BUSSONNIER
|
r5054 | widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())] | ||
Matthias BUSSONNIER
|
r5038 | |||
# widget that are candidate to be the owner of the kernel does have all the same port of the curent widget | ||||
# And should have a _may_close attribute | ||||
Matthias BUSSONNIER
|
r5054 | filtred_widget_list = [ widget for widget in widget_list if | ||
Matthias BUSSONNIER
|
r5038 | widget.kernel_manager.shell_address == km.shell_address and | ||
widget.kernel_manager.sub_address == km.sub_address and | ||||
widget.kernel_manager.stdin_address == km.stdin_address and | ||||
widget.kernel_manager.hb_address == km.hb_address and | ||||
hasattr(widget,'_may_close') ] | ||||
# the master widget is the one that may close the kernel | ||||
Matthias BUSSONNIER
|
r5054 | master_widget= [ widget for widget in filtred_widget_list if widget._may_close] | ||
if as_list: | ||||
return master_widget | ||||
assert(len(master_widget)<=1 ) | ||||
if len(master_widget)==0: | ||||
Matthias BUSSONNIER
|
r5038 | return None | ||
Matthias BUSSONNIER
|
r5054 | return master_widget[0] | ||
Matthias BUSSONNIER
|
r5038 | |||
Matthias BUSSONNIER
|
r5052 | def find_slaves_tabs(self,tab): | ||
Matthias BUSSONNIER
|
r5038 | """ | ||
Try to return all the frontend that do not own the kernel attached to the given widget/tab. | ||||
Only find frontend owed by the current application. Selection | ||||
based on port of the kernel, might be innacurate if several kernel | ||||
on different ip use same port number. | ||||
This fonction does the conversion tabNumber/widget if needed. | ||||
""" | ||||
#convert from/to int/richIpythonWidget if needed | ||||
if type(tab) == int: | ||||
Matthias BUSSONNIER
|
r5047 | tab = self.tab_widget.widget(tab) | ||
Matthias BUSSONNIER
|
r5038 | km=tab.kernel_manager; | ||
#build list of all widgets | ||||
Matthias BUSSONNIER
|
r5054 | widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())] | ||
Matthias BUSSONNIER
|
r5038 | |||
# widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget | ||||
Matthias BUSSONNIER
|
r5054 | filtered_widget_list = ( widget for widget in widget_list if | ||
Matthias BUSSONNIER
|
r5038 | widget.kernel_manager.shell_address == km.shell_address and | ||
widget.kernel_manager.sub_address == km.sub_address and | ||||
widget.kernel_manager.stdin_address == km.stdin_address and | ||||
widget.kernel_manager.hb_address == km.hb_address) | ||||
# Get a list of all widget owning the same kernel and removed it from | ||||
# the previous cadidate. (better using sets ?) | ||||
Matthias BUSSONNIER
|
r5054 | master_widget_list = self.find_master_tab(tab,as_list=True) | ||
slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list] | ||||
Matthias BUSSONNIER
|
r5038 | |||
Matthias BUSSONNIER
|
r5054 | return slave_list | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
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 | ||||
Matthias BUSSONNIER
|
r5052 | def init_menu_bar(self): | ||
Matthias BUSSONNIER
|
r5031 | #create menu in the order they should appear in the menu bar | ||
Matthias BUSSONNIER
|
r5054 | self.file_menu = self.menuBar().addMenu("&File") | ||
self.edit_menu = self.menuBar().addMenu("&Edit") | ||||
self.font_menu = self.menuBar().addMenu("F&ont") | ||||
self.window_menu = self.menuBar().addMenu("&Window") | ||||
self.magic_menu = self.menuBar().addMenu("&Magic") | ||||
Matthias BUSSONNIER
|
r5059 | self.all_magic_menu = self.magic_menu.addMenu("&All Magic") | ||
Matthias BUSSONNIER
|
r5031 | |||
# 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 | ||||
Matthias BUSSONNIER
|
r5054 | self.help_menu = self.menuBar().addMenu("&Help") | ||
Matthias BUSSONNIER
|
r5031 | |||
# 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 | ||||
Matthias BUSSONNIER
|
r5057 | self.print_action = QtGui.QAction("&Print", | ||
self, | ||||
shortcut="Ctrl+P", | ||||
triggered=self.print_action_active_frontend) | ||||
self.file_menu.addAction(self.print_action) | ||||
self.export_action=QtGui.QAction("E&xport", | ||||
self, | ||||
shortcut="Ctrl+S", | ||||
triggered=self.export_action_active_frontend | ||||
) | ||||
self.file_menu.addAction(self.export_action) | ||||
self.select_all_action = QtGui.QAction("Select &All", | ||||
self, | ||||
shortcut="Ctrl+A", | ||||
triggered=self.select_all_active_frontend | ||||
) | ||||
self.file_menu.addAction(self.select_all_action) | ||||
self.undo_action = QtGui.QAction("&Undo", | ||||
self, | ||||
shortcut="Ctrl+Z", | ||||
statusTip="Undo last action if possible", | ||||
triggered=self.undo_active_frontend | ||||
) | ||||
self.edit_menu.addAction(self.undo_action) | ||||
self.redo_action = QtGui.QAction("&Redo", | ||||
self, | ||||
shortcut="Ctrl+Shift+Z", | ||||
statusTip="Redo last action if possible", | ||||
triggered=self.redo_active_frontend) | ||||
self.edit_menu.addAction(self.redo_action) | ||||
self.increase_font_size = QtGui.QAction("&Increase Font Size", | ||||
self, | ||||
shortcut="Ctrl++", | ||||
triggered=self.increase_font_size_active_frontend | ||||
) | ||||
self.font_menu.addAction(self.increase_font_size) | ||||
self.decrease_font_size = QtGui.QAction("&Decrease Font Size", | ||||
self, | ||||
shortcut="Ctrl+-", | ||||
triggered=self.decrease_font_size_active_frontend | ||||
) | ||||
self.font_menu.addAction(self.decrease_font_size) | ||||
self.reset_font_size = QtGui.QAction("&Reset Font Size", | ||||
self, | ||||
shortcut="Ctrl+0", | ||||
triggered=self.reset_font_size_active_frontend | ||||
) | ||||
self.font_menu.addAction(self.reset_font_size) | ||||
self.reset_action = QtGui.QAction("&Reset", | ||||
self, | ||||
statusTip="Clear all varible from workspace", | ||||
triggered=self.reset_magic_active_frontend) | ||||
self.magic_menu.addAction(self.reset_action) | ||||
self.history_action = QtGui.QAction("&History", | ||||
self, | ||||
statusTip="show command history", | ||||
triggered=self.history_magic_active_frontend) | ||||
self.magic_menu.addAction(self.history_action) | ||||
self.save_action = QtGui.QAction("E&xport History ", | ||||
self, | ||||
statusTip="Export History as Python File", | ||||
triggered=self.save_magic_active_frontend) | ||||
self.magic_menu.addAction(self.save_action) | ||||
self.clear_action = QtGui.QAction("&Clear", | ||||
self, | ||||
statusTip="Clear the console", | ||||
triggered=self.clear_magic_active_frontend) | ||||
self.magic_menu.addAction(self.clear_action) | ||||
self.who_action = QtGui.QAction("&Who", | ||||
self, | ||||
statusTip="List interactive variable", | ||||
triggered=self.who_magic_active_frontend) | ||||
self.magic_menu.addAction(self.who_action) | ||||
self.who_ls_action = QtGui.QAction("Wh&o ls", | ||||
self, | ||||
statusTip="Return a list of interactive variable", | ||||
triggered=self.who_ls_magic_active_frontend) | ||||
self.magic_menu.addAction(self.who_ls_action) | ||||
self.whos_action = QtGui.QAction("Who&s", | ||||
self, | ||||
statusTip="List interactive variable with detail", | ||||
triggered=self.whos_magic_active_frontend) | ||||
self.magic_menu.addAction(self.whos_action) | ||||
Matthias BUSSONNIER
|
r5059 | self.intro_active_frontend_action = QtGui.QAction("Intro", | ||
Matthias BUSSONNIER
|
r5057 | self, | ||
triggered=self.intro_active_frontend | ||||
) | ||||
self.help_menu.addAction(self.intro_active_frontend_action) | ||||
Matthias BUSSONNIER
|
r5059 | self.guiref_active_frontend_action = QtGui.QAction("Gui references", | ||
Matthias BUSSONNIER
|
r5057 | self, | ||
triggered=self.guiref_active_frontend | ||||
) | ||||
self.help_menu.addAction(self.guiref_active_frontend_action) | ||||
Matthias BUSSONNIER
|
r5031 | |||
Matthias BUSSONNIER
|
r5059 | self.quickref_active_frontend_action = QtGui.QAction("Quick references", | ||
self, | ||||
triggered=self.quickref_active_frontend | ||||
) | ||||
self.help_menu.addAction(self.quickref_active_frontend_action) | ||||
magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear", | ||||
"%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui", | ||||
"%guiref", "%hist", "%history", "%install_default_config", "%install_profiles", | ||||
"%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate", | ||||
"%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page", | ||||
"%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint", | ||||
"%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat", | ||||
"%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun", | ||||
"%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit", | ||||
"%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"] | ||||
def make_dynamic_magic(i): | ||||
def inner_dynamic_magic(): | ||||
self.active_frontend.execute(i) | ||||
inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i | ||||
return inner_dynamic_magic | ||||
for magic in magiclist: | ||||
xaction = QtGui.QAction(magic, | ||||
self, | ||||
triggered=make_dynamic_magic(magic) | ||||
) | ||||
self.all_magic_menu.addAction(xaction) | ||||
Matthias BUSSONNIER
|
r5035 | def undo_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.undo() | ||
Matthias BUSSONNIER
|
r5035 | |||
def redo_active_frontend(self): | ||||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.redo() | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def reset_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.execute("%reset") | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def history_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.history_magic() | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def save_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.save_magic() | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def clear_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.execute("%clear") | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def who_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.execute("%who") | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def who_ls_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.execute("%who_ls") | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def whos_magic_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.execute("%whos") | ||
Matthias BUSSONNIER
|
r5035 | |||
def print_action_active_frontend(self): | ||||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.print_action.trigger() | ||
Matthias BUSSONNIER
|
r5035 | |||
def export_action_active_frontend(self): | ||||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.export_action.trigger() | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5036 | def select_all_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.select_all_action.trigger() | ||
Matthias BUSSONNIER
|
r5035 | |||
def increase_font_size_active_frontend(self): | ||||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.increase_font_size.trigger() | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def decrease_font_size_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.decrease_font_size.trigger() | ||
Matthias BUSSONNIER
|
r5058 | |||
Matthias BUSSONNIER
|
r5035 | def reset_font_size_active_frontend(self): | ||
Matthias BUSSONNIER
|
r5050 | self.active_frontend.reset_font_size.trigger() | ||
Matthias BUSSONNIER
|
r5057 | |||
def guiref_active_frontend(self): | ||||
self.active_frontend.execute("%guiref") | ||||
def intro_active_frontend(self): | ||||
self.active_frontend.execute("?") | ||||
Matthias BUSSONNIER
|
r5059 | |||
def quickref_active_frontend(self): | ||||
self.active_frontend.execute("%quickref") | ||||
epatters
|
r2961 | #--------------------------------------------------------------------------- | ||
# QWidget interface | ||||
#--------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5038 | |||
epatters
|
r2961 | def closeEvent(self, event): | ||
Matthias BUSSONNIER
|
r5038 | """ Forward the close event to every tabs contained by the windows | ||
epatters
|
r2961 | """ | ||
Matthias BUSSONNIER
|
r5038 | # Do Not loop on the widget count as it change while closing | ||
Matthias BUSSONNIER
|
r5054 | widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())] | ||
for widget in widget_list: | ||||
Matthias BUSSONNIER
|
r5048 | self.close_tab(widget) | ||
Matthias BUSSONNIER
|
r5038 | event.accept() | ||
epatters
|
r2961 | |||
#----------------------------------------------------------------------------- | ||||
MinRK
|
r3971 | # Aliases and Flags | ||
epatters
|
r2961 | #----------------------------------------------------------------------------- | ||
epatters
|
r2758 | |||
MinRK
|
r3971 | flags = dict(ipkernel_flags) | ||
MinRK
|
r4247 | qt_flags = { | ||
MinRK
|
r4958 | 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}}, | ||
"Connect to an existing kernel. If no argument specified, guess most recent"), | ||||
MinRK
|
r3971 | 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}}, | ||
"Use a pure Python kernel instead of an IPython kernel."), | ||||
'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}}, | ||||
"Disable rich text support."), | ||||
MinRK
|
r4247 | } | ||
qt_flags.update(boolean_flag( | ||||
MinRK
|
r3983 | 'gui-completion', 'ConsoleWidget.gui_completion', | ||
"use a GUI widget for tab completion", | ||||
"use plaintext output for completion" | ||||
)) | ||||
MinRK
|
r4247 | qt_flags.update(boolean_flag( | ||
MinRK
|
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. | ||||
""" | ||||
)) | ||||
MinRK
|
r4247 | flags.update(qt_flags) | ||
MinRK
|
r3971 | |||
aliases = dict(ipkernel_aliases) | ||||
MinRK
|
r4247 | qt_aliases = dict( | ||
MinRK
|
r3971 | hb = 'IPythonQtConsoleApp.hb_port', | ||
shell = 'IPythonQtConsoleApp.shell_port', | ||||
iopub = 'IPythonQtConsoleApp.iopub_port', | ||||
stdin = 'IPythonQtConsoleApp.stdin_port', | ||||
ip = 'IPythonQtConsoleApp.ip', | ||||
MinRK
|
r4958 | existing = 'IPythonQtConsoleApp.existing', | ||
f = 'IPythonQtConsoleApp.connection_file', | ||||
MinRK
|
r3971 | |||
style = 'IPythonWidget.syntax_style', | ||||
stylesheet = 'IPythonQtConsoleApp.stylesheet', | ||||
colors = 'ZMQInteractiveShell.colors', | ||||
editor = 'IPythonWidget.editor', | ||||
MinRK
|
r4222 | paging = 'ConsoleWidget.paging', | ||
MinRK
|
r4594 | ssh = 'IPythonQtConsoleApp.sshserver', | ||
MinRK
|
r4247 | ) | ||
aliases.update(qt_aliases) | ||||
MinRK
|
r4222 | |||
MinRK
|
r3971 | |||
#----------------------------------------------------------------------------- | ||||
# IPythonQtConsole | ||||
#----------------------------------------------------------------------------- | ||||
Brian Granger
|
r4215 | |||
MinRK
|
r3971 | class IPythonQtConsoleApp(BaseIPythonApplication): | ||
name = 'ipython-qtconsole' | ||||
default_config_file_name='ipython_config.py' | ||||
MinRK
|
r4021 | |||
description = """ | ||||
The IPython QtConsole. | ||||
This launches a Console-style application using Qt. It is not a full | ||||
MinRK
|
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 | ||||
MinRK
|
r4021 | |||
MinRK
|
r4222 | as well as saving your session as HTML, and printing the output. | ||
MinRK
|
r4021 | |||
""" | ||||
Brian Granger
|
r4216 | examples = _examples | ||
Brian Granger
|
r4215 | |||
MinRK
|
r4015 | classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session] | ||
MinRK
|
r3971 | flags = Dict(flags) | ||
aliases = Dict(aliases) | ||||
kernel_argv = List(Unicode) | ||||
MinRK
|
r4265 | # create requested profiles by default, if they don't exist: | ||
auto_create = CBool(True) | ||||
MinRK
|
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!""" | ||||
) | ||||
MinRK
|
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.""") | ||||
MinRK
|
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]") | ||||
MinRK
|
r4958 | connection_file = Unicode('', config=True, | ||
help="""JSON file in which to store connection info [default: kernel-<pid>.json] | ||||
MinRK
|
r3971 | |||
MinRK
|
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""") | ||||
MinRK
|
r3971 | |||
stylesheet = Unicode('', config=True, | ||||
help="path to a custom CSS stylesheet") | ||||
MinRK
|
r3983 | pure = CBool(False, config=True, | ||
MinRK
|
r3971 | help="Use a pure Python kernel instead of an IPython kernel.") | ||
MinRK
|
r3983 | plain = CBool(False, config=True, | ||
MinRK
|
r3976 | help="Use a plaintext widget instead of rich text (plain can't print/save).") | ||
MinRK
|
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 | ||||
MinRK
|
r3173 | else: | ||
MinRK
|
r3971 | self.widget_factory = RichIPythonWidget | ||
_plain_changed = _pure_changed | ||||
MinRK
|
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.""", | ||||
) | ||||
MinRK
|
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 | ||||
MinRK
|
r4118 | # kernel should inherit default config file from frontend | ||
MinRK
|
r4197 | self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name) | ||
MinRK
|
r4957 | # Scrub frontend-specific flags | ||
MinRK
|
r3971 | for a in argv: | ||
MinRK
|
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 | ||||
MinRK
|
r4247 | if a.startswith('-'): | ||
MinRK
|
r5015 | split = a.lstrip('-').split('=') | ||
MinRK
|
r4957 | alias = split[0] | ||
if alias in qt_aliases: | ||||
MinRK
|
r4247 | self.kernel_argv.remove(a) | ||
MinRK
|
r4957 | if len(split) == 1: | ||
# alias passed with arg via space | ||||
swallow_next = True | ||||
MinRK
|
r4594 | |||
MinRK
|
r4958 | def init_connection_file(self): | ||
MinRK
|
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. | ||||
""" | ||||
MinRK
|
r4958 | if self.existing: | ||
try: | ||||
MinRK
|
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) | ||||
MinRK
|
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 | ||||
MinRK
|
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) | ||||
MinRK
|
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: | ||||
MinRK
|
r4967 | self.config.Session.key = str_to_bytes(cfg['key']) | ||
MinRK
|
r4958 | |||
MinRK
|
r4594 | def init_ssh(self): | ||
MinRK
|
r4595 | """set up ssh tunnels, if needed.""" | ||
MinRK
|
r4594 | if not self.sshserver and not self.sshkey: | ||
return | ||||
if self.sshkey and not self.sshserver: | ||||
MinRK
|
r4971 | # specifying just the key implies that we are connecting directly | ||
MinRK
|
r4594 | self.sshserver = self.ip | ||
MinRK
|
r4971 | self.ip = LOCALHOST | ||
MinRK
|
r4594 | |||
MinRK
|
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 | ||||
) | ||||
MinRK
|
r4594 | |||
MinRK
|
r4971 | self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver)) | ||
MinRK
|
r4594 | |||
MinRK
|
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) | ||||
MinRK
|
r4594 | |||
MinRK
|
r4971 | self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports | ||
MinRK
|
r4594 | |||
MinRK
|
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) | ||||
Paul Ivanov
|
r4846 | |||
MinRK
|
r3971 | def init_kernel_manager(self): | ||
# Don't let Qt or ZMQ swallow KeyboardInterupts. | ||||
signal.signal(signal.SIGINT, signal.SIG_DFL) | ||||
MinRK
|
r4958 | sec = self.profile_dir.security_dir | ||
try: | ||||
cf = filefind(self.connection_file, ['.', sec]) | ||||
except IOError: | ||||
MinRK
|
r4969 | # file might not exist | ||
MinRK
|
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 | ||||
MinRK
|
r3971 | |||
# Create a KernelManager and start a kernel. | ||||
MinRK
|
r4958 | self.kernel_manager = QtKernelManager( | ||
ip=self.ip, | ||||
MinRK
|
r4956 | shell_port=self.shell_port, | ||
MinRK
|
r4959 | iopub_port=self.iopub_port, | ||
MinRK
|
r4956 | stdin_port=self.stdin_port, | ||
hb_port=self.hb_port, | ||||
MinRK
|
r4958 | connection_file=cf, | ||
MinRK
|
r4956 | config=self.config, | ||
MinRK
|
r3971 | ) | ||
# start the kernel | ||||
if not self.existing: | ||||
MinRK
|
r4958 | kwargs = dict(ipython=not self.pure) | ||
MinRK
|
r3971 | kwargs['extra_arguments'] = self.kernel_argv | ||
self.kernel_manager.start_kernel(**kwargs) | ||||
MinRK
|
r4961 | elif self.sshserver: | ||
# ssh, write new connection file | ||||
self.kernel_manager.write_connection_file() | ||||
MinRK
|
r3971 | self.kernel_manager.start_channels() | ||
Matthias BUSSONNIER
|
r5051 | def create_tab_with_new_frontend(self): | ||
Matthias BUSSONNIER
|
r5039 | """ Create new tab attached to new kernel, launched on localhost. | ||
""" | ||||
Matthias BUSSONNIER
|
r5035 | kernel_manager = QtKernelManager( | ||
Matthias BUSSONNIER
|
r5039 | shell_address=(LOCALHOST,0 ), | ||
sub_address=(LOCALHOST, 0), | ||||
stdin_address=(LOCALHOST, 0), | ||||
hb_address=(LOCALHOST, 0), | ||||
Matthias BUSSONNIER
|
r5035 | config=self.config | ||
) | ||||
# start the kernel | ||||
Matthias BUSSONNIER
|
r5039 | kwargs = dict(ip=LOCALHOST, ipython=not self.pure) | ||
kwargs['extra_arguments'] = self.kernel_argv | ||||
kernel_manager.start_kernel(**kwargs) | ||||
Matthias BUSSONNIER
|
r5035 | kernel_manager.start_channels() | ||
Matthias BUSSONNIER
|
r5039 | local_kernel = (not False) or self.ip in LOCAL_IPS | ||
Matthias BUSSONNIER
|
r5035 | widget = self.widget_factory(config=self.config, | ||
local_kernel=local_kernel) | ||||
widget.kernel_manager = kernel_manager | ||||
Matthias BUSSONNIER
|
r5038 | widget._existing=False; | ||
Matthias BUSSONNIER
|
r5037 | widget._confirm_exit=True; | ||
widget._may_close=True; | ||||
Matthias BUSSONNIER
|
r5051 | self.window.add_tab_with_frontend(widget) | ||
Matthias BUSSONNIER
|
r5035 | |||
Matthias BUSSONNIER
|
r5051 | def create_tab_attached_to_current_tab_kernel(self): | ||
Matthias BUSSONNIER
|
r5054 | current_widget = self.window.tab_widget.currentWidget() | ||
current_widget_index = self.window.tab_widget.indexOf(current_widget) | ||||
current_widget.kernel_manager = current_widget.kernel_manager; | ||||
current_widget_name = self.window.tab_widget.tabText(current_widget_index); | ||||
Matthias BUSSONNIER
|
r5035 | kernel_manager = QtKernelManager( | ||
Matthias BUSSONNIER
|
r5054 | shell_address = current_widget.kernel_manager.shell_address, | ||
sub_address = current_widget.kernel_manager.sub_address, | ||||
stdin_address = current_widget.kernel_manager.stdin_address, | ||||
hb_address = current_widget.kernel_manager.hb_address, | ||||
Matthias BUSSONNIER
|
r5039 | config = self.config | ||
Matthias BUSSONNIER
|
r5035 | ) | ||
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) | ||||
Matthias BUSSONNIER
|
r5037 | widget._confirm_exit=True; | ||
widget._may_close=False; | ||||
Matthias BUSSONNIER
|
r5035 | widget.kernel_manager = kernel_manager | ||
Matthias BUSSONNIER
|
r5054 | self.window.add_tab_with_frontend(widget,name=str('('+current_widget_name+') slave')) | ||
MinRK
|
r3971 | |||
def init_qt_elements(self): | ||||
# Create the widget. | ||||
self.app = QtGui.QApplication([]) | ||||
Matthias BUSSONNIER
|
r5053 | |||
base_path = os.path.abspath(os.path.dirname(__file__)) | ||||
icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg') | ||||
Matthias BUSSONNIER
|
r5055 | self.app.icon = QtGui.QIcon(icon_path) | ||
QtGui.QApplication.setWindowIcon(self.app.icon) | ||||
Matthias BUSSONNIER
|
r5028 | |||
MinRK
|
r3971 | local_kernel = (not self.existing) or self.ip in LOCAL_IPS | ||
self.widget = self.widget_factory(config=self.config, | ||||
local_kernel=local_kernel) | ||||
Matthias BUSSONNIER
|
r5038 | self.widget._existing = self.existing; | ||
self.widget._may_close = not self.existing; | ||||
self.widget._confirm_exit = not self.existing; | ||||
MinRK
|
r3971 | self.widget.kernel_manager = self.kernel_manager | ||
self.window = MainWindow(self.app, self.widget, self.existing, | ||||
MinRK
|
r3983 | may_close=local_kernel, | ||
confirm_exit=self.confirm_exit) | ||||
Matthias BUSSONNIER
|
r5038 | self.window.log = self.log | ||
Matthias BUSSONNIER
|
r5051 | self.window.add_tab_with_frontend(self.widget) | ||
Matthias BUSSONNIER
|
r5052 | self.window.init_menu_bar() | ||
MinRK
|
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' | ||||
MinRK
|
r3173 | else: | ||
MinRK
|
r3971 | colors=None | ||
# Configure the style. | ||||
widget = self.widget | ||||
if style: | ||||
widget.style_sheet = styles.sheet_from_template(style, colors) | ||||
widget.syntax_style = style | ||||
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 | |||
MinRK
|
r3971 | if self.stylesheet: | ||
MinRK
|
r3170 | # we got an expicit stylesheet | ||
MinRK
|
r3971 | if os.path.isfile(self.stylesheet): | ||
with open(self.stylesheet) as f: | ||||
MinRK
|
r3170 | sheet = f.read() | ||
widget.style_sheet = sheet | ||||
widget._style_sheet_changed() | ||||
else: | ||||
MinRK
|
r3971 | raise IOError("Stylesheet %r not found."%self.stylesheet) | ||
def initialize(self, argv=None): | ||||
super(IPythonQtConsoleApp, self).initialize(argv) | ||||
MinRK
|
r4958 | self.init_connection_file() | ||
MinRK
|
r4962 | default_secure(self.config) | ||
MinRK
|
r4961 | self.init_ssh() | ||
MinRK
|
r3971 | self.init_kernel_manager() | ||
self.init_qt_elements() | ||||
self.init_colors() | ||||
Matthias BUSSONNIER
|
r4817 | self.init_window_shortcut() | ||
def init_window_shortcut(self): | ||||
Matthias BUSSONNIER
|
r5049 | self.prev_tab_act = QtGui.QAction("Pre&vious Tab", | ||
Matthias BUSSONNIER
|
r5042 | self.window, | ||
shortcut="Ctrl+PgDown", | ||||
statusTip="Cahange to next tab", | ||||
Matthias BUSSONNIER
|
r5049 | triggered=self.window.prev_tab) | ||
Matthias BUSSONNIER
|
r5042 | |||
Matthias BUSSONNIER
|
r5049 | self.next_tab_act = QtGui.QAction("Ne&xt Tab", | ||
Matthias BUSSONNIER
|
r5042 | self.window, | ||
shortcut="Ctrl+PgUp", | ||||
statusTip="Cahange to next tab", | ||||
Matthias BUSSONNIER
|
r5049 | triggered=self.window.next_tab) | ||
Matthias BUSSONNIER
|
r5042 | |||
Matthias BUSSONNIER
|
r5043 | self.fullScreenAct = QtGui.QAction("&Full Screen", | ||
Matthias BUSSONNIER
|
r5027 | self.window, | ||
shortcut="Ctrl+Meta+Space", | ||||
statusTip="Toggle between Fullscreen and Normal Size", | ||||
triggered=self.toggleFullScreen) | ||||
Matthias BUSSONNIER
|
r5043 | self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel", | ||
Matthias BUSSONNIER
|
r5035 | self.window, | ||
shortcut="Ctrl+T", | ||||
Matthias BUSSONNIER
|
r5051 | triggered=self.create_tab_with_new_frontend) | ||
Matthias BUSSONNIER
|
r5054 | self.window.window_menu.addAction(self.tabAndNewKernelAct) | ||
Matthias BUSSONNIER
|
r5043 | self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel", | ||
Matthias BUSSONNIER
|
r5035 | self.window, | ||
shortcut="Ctrl+Shift+T", | ||||
Matthias BUSSONNIER
|
r5051 | triggered=self.create_tab_attached_to_current_tab_kernel) | ||
Matthias BUSSONNIER
|
r5054 | self.window.window_menu.addAction(self.tabSameKernalAct) | ||
self.window.window_menu.addSeparator() | ||||
Matthias BUSSONNIER
|
r5027 | |||
Matthias BUSSONNIER
|
r5056 | self.onlineHelpAct = QtGui.QAction("Open Online &Help", | ||
self.window, | ||||
triggered=self._open_online_help) | ||||
self.window.help_menu.addAction(self.onlineHelpAct) | ||||
Matthias BUSSONNIER
|
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': | ||||
Matthias BUSSONNIER
|
r5043 | self.minimizeAct = QtGui.QAction("Mini&mize", | ||
Matthias BUSSONNIER
|
r5027 | self.window, | ||
shortcut="Ctrl+m", | ||||
statusTip="Minimize the window/Restore Normal Size", | ||||
triggered=self.toggleMinimized) | ||||
Matthias BUSSONNIER
|
r5043 | self.maximizeAct = QtGui.QAction("Ma&ximize", | ||
Matthias BUSSONNIER
|
r5027 | self.window, | ||
shortcut="Ctrl+Shift+M", | ||||
statusTip="Maximize the window/Restore Normal Size", | ||||
triggered=self.toggleMaximized) | ||||
Matthias BUSSONNIER
|
r5054 | self.window_menu = self.window.window_menu | ||
Matthias BUSSONNIER
|
r5042 | |||
Matthias BUSSONNIER
|
r5054 | self.window_menu.addAction(self.next_tab_act) | ||
self.window_menu.addAction(self.prev_tab_act) | ||||
self.window_menu.addSeparator() | ||||
self.window_menu.addAction(self.minimizeAct) | ||||
self.window_menu.addAction(self.maximizeAct) | ||||
self.window_menu.addSeparator() | ||||
self.window_menu.addAction(self.fullScreenAct) | ||||
Matthias BUSSONNIER
|
r5027 | |||
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): | ||||
Matthias BUSSONNIER
|
r5059 | filename="http://ipython.org/ipython-doc/stable/index.html" | ||
webbrowser.open(filename, new=1, autoraise=True) | ||||
Matthias BUSSONNIER
|
r5027 | |||
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 | ||||
Matthias BUSSONNIER
|
r4817 | def toggleFullScreen(self): | ||
if not self.window.isFullScreen(): | ||||
self.window.showFullScreen() | ||||
Matthias BUSSONNIER
|
r5027 | if sys.platform == 'darwin': | ||
self.maximizeAct.setEnabled(False) | ||||
self.minimizeAct.setEnabled(False) | ||||
Matthias BUSSONNIER
|
r4817 | else: | ||
self.window.showNormal() | ||||
Matthias BUSSONNIER
|
r5027 | if sys.platform == 'darwin': | ||
self.maximizeAct.setEnabled(True) | ||||
self.minimizeAct.setEnabled(True) | ||||
MinRK
|
r3971 | |||
def start(self): | ||||
# draw the window | ||||
self.window.show() | ||||
MinRK
|
r3170 | |||
MinRK
|
r3971 | # Start the application main loop. | ||
self.app.exec_() | ||||
epatters
|
r2841 | |||
MinRK
|
r3971 | #----------------------------------------------------------------------------- | ||
# Main entry point | ||||
#----------------------------------------------------------------------------- | ||||
def main(): | ||||
app = IPythonQtConsoleApp() | ||||
app.initialize() | ||||
app.start() | ||||
epatters
|
r2758 | |||
if __name__ == '__main__': | ||||
main() | ||||