##// END OF EJS Templates
Work on QtConsole Menu...
MinRK -
Show More
This diff has been collapsed as it changes many lines, (575 lines changed) Show them Hide them
@@ -1,1187 +1,1290 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12
12
13 """
13 """
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 # stdlib imports
19 # stdlib imports
20 import json
20 import json
21 import os
21 import os
22 import signal
22 import signal
23 import sys
23 import sys
24 import webbrowser
24 import webbrowser
25 import uuid
25 import uuid
26 from getpass import getpass
26 from getpass import getpass
27
27
28 # System library imports
28 # System library imports
29 from IPython.external.qt import QtGui,QtCore
29 from IPython.external.qt import QtGui,QtCore
30 from pygments.styles import get_all_styles
30 from pygments.styles import get_all_styles
31
31
32 # Local imports
32 # Local imports
33 from IPython.config.application import boolean_flag
33 from IPython.config.application import boolean_flag
34 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.application import BaseIPythonApplication
35 from IPython.core.profiledir import ProfileDir
35 from IPython.core.profiledir import ProfileDir
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
40 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console import styles
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import str_to_bytes
43 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.traitlets import (
44 from IPython.utils.traitlets import (
45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
46 )
46 )
47 from IPython.zmq.ipkernel import (
47 from IPython.zmq.ipkernel import (
48 flags as ipkernel_flags,
48 flags as ipkernel_flags,
49 aliases as ipkernel_aliases,
49 aliases as ipkernel_aliases,
50 IPKernelApp
50 IPKernelApp
51 )
51 )
52 from IPython.zmq.session import Session, default_secure
52 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Network Constants
56 # Network Constants
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Globals
62 # Globals
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 _examples = """
65 _examples = """
66 ipython qtconsole # start the qtconsole
66 ipython qtconsole # start the qtconsole
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 """
68 """
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Classes
71 # Classes
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 class MainWindow(QtGui.QMainWindow):
74 class MainWindow(QtGui.QMainWindow):
75
75
76 #---------------------------------------------------------------------------
76 #---------------------------------------------------------------------------
77 # 'object' interface
77 # 'object' interface
78 #---------------------------------------------------------------------------
78 #---------------------------------------------------------------------------
79
79
80 def __init__(self, app, frontend, existing=False, may_close=True,
80 def __init__(self, app, frontend, existing=False, may_close=True,
81 confirm_exit=True):
81 confirm_exit=True,
82 """ Create a MainWindow for the specified FrontendWidget.
82 new_frontend_factory=None, slave_frontend_factory=None,
83
83 ):
84 The app is passed as an argument to allow for different
84 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
85 closing behavior depending on whether we are the Kernel's parent.
85
86
86 Parameters
87 If existing is True, then this Console does not own the Kernel.
87 ----------
88
88
89 If may_close is True, then this Console is permitted to close the kernel
89 app : reference to QApplication parent
90 frontend : IPythonWidget
91 The first IPython frontend to start with
92 existing : bool, optional
93 Whether the first frontend is connected to en existing Kernel,
94 or if we own it.
95 may_close : bool, optional
96 Whether we are permitted to close the kernel (determines close dialog behavior)
97 confirm_exit : bool, optional
98 Whether we should prompt on close of tabs
99 new_frontend_factory : callable
100 A callable that returns a new IPythonWidget instance, attached to
101 its own running kernel.
102 slave_frontend_factory : callable
103 A callable that takes an existing IPythonWidget, and returns a new
104 IPythonWidget instance, attached to the same kernel.
90 """
105 """
91
106
92 super(MainWindow, self).__init__()
107 super(MainWindow, self).__init__()
93 self._app = app
108 self._app = app
109 self.new_frontend_factory = new_frontend_factory
110 self.slave_frontend_factory = slave_frontend_factory
94
111
95 self.tab_widget = QtGui.QTabWidget(self)
112 self.tab_widget = QtGui.QTabWidget(self)
96 self.tab_widget.setDocumentMode(True)
113 self.tab_widget.setDocumentMode(True)
97 self.tab_widget.setTabsClosable(True)
114 self.tab_widget.setTabsClosable(True)
98 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
115 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
99
116
100 self.setCentralWidget(self.tab_widget)
117 self.setCentralWidget(self.tab_widget)
101 self.update_tab_bar_visibility()
118 self.update_tab_bar_visibility()
102
119
103 def update_tab_bar_visibility(self):
120 def update_tab_bar_visibility(self):
104 """ update visibility of the tabBar depending of the number of tab
121 """ update visibility of the tabBar depending of the number of tab
105
122
106 0 or 1 tab, tabBar hidden
123 0 or 1 tab, tabBar hidden
107 2+ tabs, tabBar visible
124 2+ tabs, tabBar visible
108
125
109 send a self.close if number of tab ==0
126 send a self.close if number of tab ==0
110
127
111 need to be called explicitely, or be connected to tabInserted/tabRemoved
128 need to be called explicitely, or be connected to tabInserted/tabRemoved
112 """
129 """
113 if self.tab_widget.count() <= 1:
130 if self.tab_widget.count() <= 1:
114 self.tab_widget.tabBar().setVisible(False)
131 self.tab_widget.tabBar().setVisible(False)
115 else:
132 else:
116 self.tab_widget.tabBar().setVisible(True)
133 self.tab_widget.tabBar().setVisible(True)
117 if self.tab_widget.count()==0 :
134 if self.tab_widget.count()==0 :
118 self.close()
135 self.close()
119
136
120 @property
137 @property
121 def active_frontend(self):
138 def active_frontend(self):
122 return self.tab_widget.currentWidget()
139 return self.tab_widget.currentWidget()
123
140
141 def create_tab_with_new_frontend(self):
142 """create a new frontend and attach it to a new tab"""
143 widget = self.new_frontend_factory()
144 self.add_tab_with_frontend(widget)
145
146 def create_tab_with_current_kernel(self):
147 """create a new frontend attached to the same kernel as the current tab"""
148 current_widget = self.tab_widget.currentWidget()
149 current_widget_index = self.tab_widget.indexOf(current_widget)
150 current_widget_name = self.tab_widget.tabText(current_widget_index)
151 widget = self.slave_frontend_factory(current_widget)
152 if 'slave' in current_widget_name:
153 # don't keep stacking slaves
154 name = current_widget_name
155 else:
156 name = str('('+current_widget_name+') slave')
157 self.add_tab_with_frontend(widget,name=name)
158
124 def close_tab(self,current_tab):
159 def close_tab(self,current_tab):
125 """ Called when you need to try to close a tab.
160 """ Called when you need to try to close a tab.
126
161
127 It takes the number of the tab to be closed as argument, or a referece
162 It takes the number of the tab to be closed as argument, or a referece
128 to the wiget insite this tab
163 to the wiget insite this tab
129 """
164 """
130
165
131 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
166 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
132 # and a reference to the trontend to close
167 # and a reference to the trontend to close
133 if type(current_tab) is not int :
168 if type(current_tab) is not int :
134 current_tab = self.tab_widget.indexOf(current_tab)
169 current_tab = self.tab_widget.indexOf(current_tab)
135 closing_widget=self.tab_widget.widget(current_tab)
170 closing_widget=self.tab_widget.widget(current_tab)
136
171
137
172
138 # when trying to be closed, widget might re-send a request to be closed again, but will
173 # when trying to be closed, widget might re-send a request to be closed again, but will
139 # be deleted when event will be processed. So need to check that widget still exist and
174 # be deleted when event will be processed. So need to check that widget still exist and
140 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
175 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
141 # re-send by this fonction on the master widget, which ask all slaves widget to exit
176 # re-send by this fonction on the master widget, which ask all slaves widget to exit
142 if closing_widget==None:
177 if closing_widget==None:
143 return
178 return
144
179
145 #get a list of all wwidget not owning the kernel.
180 #get a list of all wwidget not owning the kernel.
146 slave_tabs=self.find_slaves_tabs(closing_widget)
181 slave_tabs=self.find_slaves_tabs(closing_widget)
147
182
148 keepkernel = None #Use the prompt by default
183 keepkernel = None #Use the prompt by default
149 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
184 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
150 keepkernel = closing_widget._keep_kernel_on_exit
185 keepkernel = closing_widget._keep_kernel_on_exit
151 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
186 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
152 # we set local slave tabs._hidden to True to avoit prompting for kernel
187 # we set local slave tabs._hidden to True to avoit prompting for kernel
153 # restart when they litt get the signal. and the "forward" the 'exit'
188 # restart when they litt get the signal. and the "forward" the 'exit'
154 # to the main win
189 # to the main win
155 if keepkernel is not None:
190 if keepkernel is not None:
156 for tab in slave_tabs:
191 for tab in slave_tabs:
157 tab._hidden = True
192 tab._hidden = True
158 if closing_widget in slave_tabs :
193 if closing_widget in slave_tabs :
159 try :
194 try :
160 self.find_master_tab(closing_widget).execute('exit')
195 self.find_master_tab(closing_widget).execute('exit')
161 except AttributeError:
196 except AttributeError:
162 self.log.info("Master already closed or not local, closing only current tab")
197 self.log.info("Master already closed or not local, closing only current tab")
163 self.tab_widget.removeTab(current_tab)
198 self.tab_widget.removeTab(current_tab)
164 return
199 return
165
200
166 kernel_manager = closing_widget.kernel_manager
201 kernel_manager = closing_widget.kernel_manager
167
202
168 if keepkernel is None and not closing_widget._confirm_exit:
203 if keepkernel is None and not closing_widget._confirm_exit:
169 # don't prompt, just terminate the kernel if we own it
204 # don't prompt, just terminate the kernel if we own it
170 # or leave it alone if we don't
205 # or leave it alone if we don't
171 keepkernel = not closing_widget._existing
206 keepkernel = not closing_widget._existing
172
207
173 if keepkernel is None: #show prompt
208 if keepkernel is None: #show prompt
174 if kernel_manager and kernel_manager.channels_running:
209 if kernel_manager and kernel_manager.channels_running:
175 title = self.window().windowTitle()
210 title = self.window().windowTitle()
176 cancel = QtGui.QMessageBox.Cancel
211 cancel = QtGui.QMessageBox.Cancel
177 okay = QtGui.QMessageBox.Ok
212 okay = QtGui.QMessageBox.Ok
178 if closing_widget._may_close:
213 if closing_widget._may_close:
179 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
214 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
180 info = "Would you like to quit the Kernel and all attached Consoles as well?"
215 info = "Would you like to quit the Kernel and all attached Consoles as well?"
181 justthis = QtGui.QPushButton("&No, just this Console", self)
216 justthis = QtGui.QPushButton("&No, just this Console", self)
182 justthis.setShortcut('N')
217 justthis.setShortcut('N')
183 closeall = QtGui.QPushButton("&Yes, quit everything", self)
218 closeall = QtGui.QPushButton("&Yes, quit everything", self)
184 closeall.setShortcut('Y')
219 closeall.setShortcut('Y')
185 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
220 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
186 title, msg)
221 title, msg)
187 box.setInformativeText(info)
222 box.setInformativeText(info)
188 box.addButton(cancel)
223 box.addButton(cancel)
189 box.addButton(justthis, QtGui.QMessageBox.NoRole)
224 box.addButton(justthis, QtGui.QMessageBox.NoRole)
190 box.addButton(closeall, QtGui.QMessageBox.YesRole)
225 box.addButton(closeall, QtGui.QMessageBox.YesRole)
191 box.setDefaultButton(closeall)
226 box.setDefaultButton(closeall)
192 box.setEscapeButton(cancel)
227 box.setEscapeButton(cancel)
193 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
228 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
194 box.setIconPixmap(pixmap)
229 box.setIconPixmap(pixmap)
195 reply = box.exec_()
230 reply = box.exec_()
196 if reply == 1: # close All
231 if reply == 1: # close All
197 for slave in slave_tabs:
232 for slave in slave_tabs:
198 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
233 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
199 closing_widget.execute("exit")
234 closing_widget.execute("exit")
200 self.tab_widget.removeTab(current_tab)
235 self.tab_widget.removeTab(current_tab)
201 elif reply == 0: # close Console
236 elif reply == 0: # close Console
202 if not closing_widget._existing:
237 if not closing_widget._existing:
203 # Have kernel: don't quit, just close the window
238 # Have kernel: don't quit, just close the window
204 self._app.setQuitOnLastWindowClosed(False)
239 self._app.setQuitOnLastWindowClosed(False)
205 closing_widget.execute("exit True")
240 closing_widget.execute("exit True")
206 else:
241 else:
207 reply = QtGui.QMessageBox.question(self, title,
242 reply = QtGui.QMessageBox.question(self, title,
208 "Are you sure you want to close this Console?"+
243 "Are you sure you want to close this Console?"+
209 "\nThe Kernel and other Consoles will remain active.",
244 "\nThe Kernel and other Consoles will remain active.",
210 okay|cancel,
245 okay|cancel,
211 defaultButton=okay
246 defaultButton=okay
212 )
247 )
213 if reply == okay:
248 if reply == okay:
214 self.tab_widget.removeTab(current_tab)
249 self.tab_widget.removeTab(current_tab)
215 elif keepkernel: #close console but leave kernel running (no prompt)
250 elif keepkernel: #close console but leave kernel running (no prompt)
216 if kernel_manager and kernel_manager.channels_running:
251 if kernel_manager and kernel_manager.channels_running:
217 if not closing_widget._existing:
252 if not closing_widget._existing:
218 # I have the kernel: don't quit, just close the window
253 # I have the kernel: don't quit, just close the window
219 self.tab_widget.removeTab(current_tab)
254 self.tab_widget.removeTab(current_tab)
220 else: #close console and kernel (no prompt)
255 else: #close console and kernel (no prompt)
221 if kernel_manager and kernel_manager.channels_running:
256 if kernel_manager and kernel_manager.channels_running:
222 for slave in slave_tabs:
257 for slave in slave_tabs:
223 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
258 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
224 self.tab_widget.removeTab(current_tab)
259 self.tab_widget.removeTab(current_tab)
225 kernel_manager.shutdown_kernel()
260 kernel_manager.shutdown_kernel()
226 self.update_tab_bar_visibility()
261 self.update_tab_bar_visibility()
227
262
228 def add_tab_with_frontend(self,frontend,name=None):
263 def add_tab_with_frontend(self,frontend,name=None):
229 """ insert a tab with a given frontend in the tab bar, and give it a name
264 """ insert a tab with a given frontend in the tab bar, and give it a name
230
265
231 """
266 """
232 if not name:
267 if not name:
233 name=str('kernel '+str(self.tab_widget.count()))
268 name=str('kernel '+str(self.tab_widget.count()))
234 self.tab_widget.addTab(frontend,name)
269 self.tab_widget.addTab(frontend,name)
235 self.update_tab_bar_visibility()
270 self.update_tab_bar_visibility()
236 self.make_frontend_visible(frontend)
271 self.make_frontend_visible(frontend)
237 frontend.exit_requested.connect(self.close_tab)
272 frontend.exit_requested.connect(self.close_tab)
238
273
239 def next_tab(self):
274 def next_tab(self):
240 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
275 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
241
276
242 def prev_tab(self):
277 def prev_tab(self):
243 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
278 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
244
279
245 def make_frontend_visible(self,frontend):
280 def make_frontend_visible(self,frontend):
246 widget_index=self.tab_widget.indexOf(frontend)
281 widget_index=self.tab_widget.indexOf(frontend)
247 if widget_index > 0 :
282 if widget_index > 0 :
248 self.tab_widget.setCurrentIndex(widget_index)
283 self.tab_widget.setCurrentIndex(widget_index)
249
284
250 def find_master_tab(self,tab,as_list=False):
285 def find_master_tab(self,tab,as_list=False):
251 """
286 """
252 Try to return the frontend that own the kernel attached to the given widget/tab.
287 Try to return the frontend that own the kernel attached to the given widget/tab.
253
288
254 Only find frontend owed by the current application. Selection
289 Only find frontend owed by the current application. Selection
255 based on port of the kernel, might be inacurate if several kernel
290 based on port of the kernel, might be inacurate if several kernel
256 on different ip use same port number.
291 on different ip use same port number.
257
292
258 This fonction does the conversion tabNumber/widget if needed.
293 This fonction does the conversion tabNumber/widget if needed.
259 Might return None if no master widget (non local kernel)
294 Might return None if no master widget (non local kernel)
260 Will crash IPython if more than 1 masterWidget
295 Will crash IPython if more than 1 masterWidget
261
296
262 When asList set to True, always return a list of widget(s) owning
297 When asList set to True, always return a list of widget(s) owning
263 the kernel. The list might be empty or containing several Widget.
298 the kernel. The list might be empty or containing several Widget.
264 """
299 """
265
300
266 #convert from/to int/richIpythonWidget if needed
301 #convert from/to int/richIpythonWidget if needed
267 if type(tab) == int:
302 if type(tab) == int:
268 tab = self.tab_widget.widget(tab)
303 tab = self.tab_widget.widget(tab)
269 km=tab.kernel_manager;
304 km=tab.kernel_manager;
270
305
271 #build list of all widgets
306 #build list of all widgets
272 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
307 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
273
308
274 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
309 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
275 # And should have a _may_close attribute
310 # And should have a _may_close attribute
276 filtred_widget_list = [ widget for widget in widget_list if
311 filtred_widget_list = [ widget for widget in widget_list if
277 widget.kernel_manager.connection_file == km.connection_file and
312 widget.kernel_manager.connection_file == km.connection_file and
278 hasattr(widget,'_may_close') ]
313 hasattr(widget,'_may_close') ]
279 # the master widget is the one that may close the kernel
314 # the master widget is the one that may close the kernel
280 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
315 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
281 if as_list:
316 if as_list:
282 return master_widget
317 return master_widget
283 assert(len(master_widget)<=1 )
318 assert(len(master_widget)<=1 )
284 if len(master_widget)==0:
319 if len(master_widget)==0:
285 return None
320 return None
286
321
287 return master_widget[0]
322 return master_widget[0]
288
323
289 def find_slaves_tabs(self,tab):
324 def find_slaves_tabs(self,tab):
290 """
325 """
291 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
326 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
292
327
293 Only find frontend owed by the current application. Selection
328 Only find frontend owed by the current application. Selection
294 based on port of the kernel, might be innacurate if several kernel
329 based on port of the kernel, might be innacurate if several kernel
295 on different ip use same port number.
330 on different ip use same port number.
296
331
297 This fonction does the conversion tabNumber/widget if needed.
332 This fonction does the conversion tabNumber/widget if needed.
298 """
333 """
299 #convert from/to int/richIpythonWidget if needed
334 #convert from/to int/richIpythonWidget if needed
300 if type(tab) == int:
335 if type(tab) == int:
301 tab = self.tab_widget.widget(tab)
336 tab = self.tab_widget.widget(tab)
302 km=tab.kernel_manager;
337 km=tab.kernel_manager;
303
338
304 #build list of all widgets
339 #build list of all widgets
305 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
340 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
306
341
307 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
342 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
308 filtered_widget_list = ( widget for widget in widget_list if
343 filtered_widget_list = ( widget for widget in widget_list if
309 widget.kernel_manager.connection_file == km.connection_file)
344 widget.kernel_manager.connection_file == km.connection_file)
310 # Get a list of all widget owning the same kernel and removed it from
345 # Get a list of all widget owning the same kernel and removed it from
311 # the previous cadidate. (better using sets ?)
346 # the previous cadidate. (better using sets ?)
312 master_widget_list = self.find_master_tab(tab,as_list=True)
347 master_widget_list = self.find_master_tab(tab,as_list=True)
313 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
348 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
314
349
315 return slave_list
350 return slave_list
316
351
317 # MenuBar is always present on Mac Os, so let's populate it with possible
352 # Populate the menu bar with common actions and shortcuts
318 # action, don't do it on other platform as some user might not want the
353 def add_menu_action(self, menu, action):
319 # menu bar, or give them an option to remove it
354 """Add action to menu as well as self
355
356 So that when the menu bar is invisible, its actions are still available.
357 """
358 menu.addAction(action)
359 self.addAction(action)
360
320 def init_menu_bar(self):
361 def init_menu_bar(self):
321 #create menu in the order they should appear in the menu bar
362 #create menu in the order they should appear in the menu bar
363 self.init_file_menu()
364 self.init_edit_menu()
365 self.init_view_menu()
366 self.init_kernel_menu()
367 self.init_magic_menu()
368 self.init_window_menu()
369 self.init_help_menu()
370
371 def init_file_menu(self):
322 self.file_menu = self.menuBar().addMenu("&File")
372 self.file_menu = self.menuBar().addMenu("&File")
323 self.edit_menu = self.menuBar().addMenu("&Edit")
324 self.kernel_menu = self.menuBar().addMenu("&Kernel")
325 self.window_menu = self.menuBar().addMenu("&Window")
326 self.magic_menu = self.menuBar().addMenu("&Magic")
327 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
328
373
329 # please keep the Help menu in Mac Os even if empty. It will
374 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
330 # automatically contain a search field to search inside menus and
375 self,
331 # please keep it spelled in English, as long as Qt Doesn't support
376 shortcut="Ctrl+T",
332 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
377 triggered=self.create_tab_with_new_frontend)
333 # this search field fonctionnality
378 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
334
379
335 self.help_menu = self.menuBar().addMenu("&Help")
380 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
381 self,
382 shortcut="Ctrl+Shift+T",
383 triggered=self.create_tab_with_current_kernel)
384 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
336
385
337 self.print_action = QtGui.QAction("&Print",
386 self.file_menu.addSeparator()
387
388 self.close_action=QtGui.QAction("&Close Tab",
338 self,
389 self,
339 shortcut="Ctrl+P",
390 shortcut="Ctrl+W",
340 triggered=self.print_action_active_frontend)
391 triggered=self.close_active_frontend
341 self.file_menu.addAction(self.print_action)
392 )
393 self.add_menu_action(self.file_menu, self.close_action)
342
394
343 self.export_action=QtGui.QAction("E&xport",
395 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
344 self,
396 self,
345 shortcut="Ctrl+S",
397 shortcut="Ctrl+S",
346 triggered=self.export_action_active_frontend
398 triggered=self.export_action_active_frontend
347 )
399 )
348 self.file_menu.addAction(self.export_action)
400 self.add_menu_action(self.file_menu, self.export_action)
349
401
350 self.select_all_action = QtGui.QAction("Select &All",
402 self.file_menu.addSeparator()
403
404 # Ctrl actually maps to Cmd on OSX, which avoids conflict with history
405 # action, which is already bound to true Ctrl+P
406 print_shortcut = "Ctrl+P" if sys.platform == 'darwin' else 'Ctrl+Shift+P'
407 self.print_action = QtGui.QAction("&Print",
351 self,
408 self,
352 shortcut="Ctrl+A",
409 shortcut=print_shortcut,
353 triggered=self.select_all_active_frontend
410 triggered=self.print_action_active_frontend)
411 self.add_menu_action(self.file_menu, self.print_action)
412
413 if sys.platform == 'darwin':
414 # OSX always has Quit in the Application menu, only add it
415 # to the File menu elsewhere.
416
417 self.file_menu.addSeparator()
418
419 self.quit_action = QtGui.QAction("&Quit",
420 self,
421 shortcut=QtGui.QKeySequence.Quit,
422 triggered=self.close,
354 )
423 )
355 self.file_menu.addAction(self.select_all_action)
424 self.add_menu_action(self.file_menu, self.quit_action)
356
425
357 self.paste_action = QtGui.QAction("&Paste",
426
427 def init_edit_menu(self):
428 self.edit_menu = self.menuBar().addMenu("&Edit")
429
430 self.undo_action = QtGui.QAction("&Undo",
358 self,
431 self,
359 shortcut=QtGui.QKeySequence.Paste,
432 shortcut="Ctrl+Z",
360 triggered=self.paste_active_frontend
433 statusTip="Undo last action if possible",
434 triggered=self.undo_active_frontend
435 )
436 self.add_menu_action(self.edit_menu, self.undo_action)
437
438 self.redo_action = QtGui.QAction("&Redo",
439 self,
440 shortcut="Ctrl+Shift+Z",
441 statusTip="Redo last action if possible",
442 triggered=self.redo_active_frontend)
443 self.add_menu_action(self.edit_menu, self.redo_action)
444
445 self.edit_menu.addSeparator()
446
447 self.cut_action = QtGui.QAction("&Cut",
448 self,
449 shortcut=QtGui.QKeySequence.Cut,
450 triggered=self.cut_active_frontend
361 )
451 )
362 self.edit_menu.addAction(self.paste_action)
452 self.add_menu_action(self.edit_menu, self.cut_action)
363
453
364 self.copy_action = QtGui.QAction("&Copy",
454 self.copy_action = QtGui.QAction("&Copy",
365 self,
455 self,
366 shortcut=QtGui.QKeySequence.Copy,
456 shortcut=QtGui.QKeySequence.Copy,
367 triggered=self.copy_active_frontend
457 triggered=self.copy_active_frontend
368 )
458 )
369 self.edit_menu.addAction(self.copy_action)
459 self.add_menu_action(self.edit_menu, self.copy_action)
370
460
371 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
461 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
372 self,
462 self,
373 shortcut="Ctrl+Shift+C",
463 shortcut="Ctrl+Shift+C",
374 triggered=self.copy_raw_active_frontend
464 triggered=self.copy_raw_active_frontend
375 )
465 )
376 self.edit_menu.addAction(self.copy_raw_action)
466 self.add_menu_action(self.edit_menu, self.copy_raw_action)
377
467
378 self.cut_action = QtGui.QAction("&Cut",
468 self.paste_action = QtGui.QAction("&Paste",
379 self,
469 self,
380 shortcut=QtGui.QKeySequence.Cut,
470 shortcut=QtGui.QKeySequence.Paste,
381 triggered=self.cut_active_frontend
471 triggered=self.paste_active_frontend
382 )
472 )
383 self.edit_menu.addAction(self.cut_action)
473 self.add_menu_action(self.edit_menu, self.paste_action)
384
474
385 self.edit_menu.addSeparator()
475 self.edit_menu.addSeparator()
386
476
387 self.undo_action = QtGui.QAction("&Undo",
477 self.select_all_action = QtGui.QAction("Select &All",
388 self,
478 self,
389 shortcut="Ctrl+Z",
479 shortcut="Ctrl+A",
390 statusTip="Undo last action if possible",
480 triggered=self.select_all_active_frontend
391 triggered=self.undo_active_frontend
392 )
481 )
393 self.edit_menu.addAction(self.undo_action)
482 self.add_menu_action(self.edit_menu, self.select_all_action)
394
483
395 self.redo_action = QtGui.QAction("&Redo",
484
485 def init_view_menu(self):
486 self.view_menu = self.menuBar().addMenu("&View")
487
488 if sys.platform != 'darwin':
489 # disable on OSX, where there is always a menu bar
490 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
396 self,
491 self,
397 shortcut="Ctrl+Shift+Z",
492 shortcut="Ctrl+Meta+M",
398 statusTip="Redo last action if possible",
493 statusTip="Toggle visibility of menubar",
399 triggered=self.redo_active_frontend)
494 triggered=self.toggle_menu_bar)
400 self.edit_menu.addAction(self.redo_action)
495 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
401
496
402 self.window_menu.addSeparator()
497 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
498 self.full_screen_act = QtGui.QAction("&Full Screen",
499 self,
500 shortcut=fs_key,
501 statusTip="Toggle between Fullscreen and Normal Size",
502 triggered=self.toggleFullScreen)
503 self.add_menu_action(self.view_menu, self.full_screen_act)
504
505 self.view_menu.addSeparator()
403
506
404 self.increase_font_size = QtGui.QAction("&Increase Font Size",
507 self.increase_font_size = QtGui.QAction("Zoom &In",
405 self,
508 self,
406 shortcut="Ctrl++",
509 shortcut="Ctrl++",
407 triggered=self.increase_font_size_active_frontend
510 triggered=self.increase_font_size_active_frontend
408 )
511 )
409 self.window_menu.addAction(self.increase_font_size)
512 self.add_menu_action(self.view_menu, self.increase_font_size)
410
513
411 self.decrease_font_size = QtGui.QAction("&Decrease Font Size",
514 self.decrease_font_size = QtGui.QAction("Zoom &Out",
412 self,
515 self,
413 shortcut="Ctrl+-",
516 shortcut="Ctrl+-",
414 triggered=self.decrease_font_size_active_frontend
517 triggered=self.decrease_font_size_active_frontend
415 )
518 )
416 self.window_menu.addAction(self.decrease_font_size)
519 self.add_menu_action(self.view_menu, self.decrease_font_size)
417
520
418 self.reset_font_size = QtGui.QAction("&Reset Font Size",
521 self.reset_font_size = QtGui.QAction("Zoom &Reset",
419 self,
522 self,
420 shortcut="Ctrl+0",
523 shortcut="Ctrl+0",
421 triggered=self.reset_font_size_active_frontend
524 triggered=self.reset_font_size_active_frontend
422 )
525 )
423 self.window_menu.addAction(self.reset_font_size)
526 self.add_menu_action(self.view_menu, self.reset_font_size)
424
527
425 self.window_menu.addSeparator()
528 self.view_menu.addSeparator()
529
530 self.clear_action = QtGui.QAction("&Clear Screen",
531 self,
532 shortcut='Ctrl+L',
533 statusTip="Clear the console",
534 triggered=self.clear_magic_active_frontend)
535 self.add_menu_action(self.view_menu, self.clear_action)
536
537 def init_kernel_menu(self):
538 self.kernel_menu = self.menuBar().addMenu("&Kernel")
539 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
540 # keep the signal shortcuts to ctrl, rather than
541 # platform-default like we do elsewhere.
542
543 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
544
545 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
546 self,
547 triggered=self.interrupt_kernel_active_frontend,
548 shortcut=ctrl+"+C",
549 )
550 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
551
552 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
553 self,
554 triggered=self.restart_kernel_active_frontend,
555 shortcut=ctrl+"+.",
556 )
557 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
558
559 self.kernel_menu.addSeparator()
560
561 def init_magic_menu(self):
562 self.magic_menu = self.menuBar().addMenu("&Magic")
563 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
426
564
427 self.reset_action = QtGui.QAction("&Reset",
565 self.reset_action = QtGui.QAction("&Reset",
428 self,
566 self,
429 statusTip="Clear all varible from workspace",
567 statusTip="Clear all varible from workspace",
430 triggered=self.reset_magic_active_frontend)
568 triggered=self.reset_magic_active_frontend)
431 self.magic_menu.addAction(self.reset_action)
569 self.add_menu_action(self.magic_menu, self.reset_action)
432
570
433 self.history_action = QtGui.QAction("&History",
571 self.history_action = QtGui.QAction("&History",
434 self,
572 self,
435 statusTip="show command history",
573 statusTip="show command history",
436 triggered=self.history_magic_active_frontend)
574 triggered=self.history_magic_active_frontend)
437 self.magic_menu.addAction(self.history_action)
575 self.add_menu_action(self.magic_menu, self.history_action)
438
576
439 self.save_action = QtGui.QAction("E&xport History ",
577 self.save_action = QtGui.QAction("E&xport History ",
440 self,
578 self,
441 statusTip="Export History as Python File",
579 statusTip="Export History as Python File",
442 triggered=self.save_magic_active_frontend)
580 triggered=self.save_magic_active_frontend)
443 self.magic_menu.addAction(self.save_action)
581 self.add_menu_action(self.magic_menu, self.save_action)
444
445 self.clear_action = QtGui.QAction("&Clear Screen",
446 self,
447 shortcut='Ctrl+L',
448 statusTip="Clear the console",
449 triggered=self.clear_magic_active_frontend)
450 self.window_menu.addAction(self.clear_action)
451
582
452 self.who_action = QtGui.QAction("&Who",
583 self.who_action = QtGui.QAction("&Who",
453 self,
584 self,
454 statusTip="List interactive variable",
585 statusTip="List interactive variable",
455 triggered=self.who_magic_active_frontend)
586 triggered=self.who_magic_active_frontend)
456 self.magic_menu.addAction(self.who_action)
587 self.add_menu_action(self.magic_menu, self.who_action)
457
588
458 self.who_ls_action = QtGui.QAction("Wh&o ls",
589 self.who_ls_action = QtGui.QAction("Wh&o ls",
459 self,
590 self,
460 statusTip="Return a list of interactive variable",
591 statusTip="Return a list of interactive variable",
461 triggered=self.who_ls_magic_active_frontend)
592 triggered=self.who_ls_magic_active_frontend)
462 self.magic_menu.addAction(self.who_ls_action)
593 self.add_menu_action(self.magic_menu, self.who_ls_action)
463
594
464 self.whos_action = QtGui.QAction("Who&s",
595 self.whos_action = QtGui.QAction("Who&s",
465 self,
596 self,
466 statusTip="List interactive variable with detail",
597 statusTip="List interactive variable with detail",
467 triggered=self.whos_magic_active_frontend)
598 triggered=self.whos_magic_active_frontend)
468 self.magic_menu.addAction(self.whos_action)
599 self.add_menu_action(self.magic_menu, self.whos_action)
469
470 self.intro_active_frontend_action = QtGui.QAction("Intro to IPython",
471 self,
472 triggered=self.intro_active_frontend
473 )
474 self.help_menu.addAction(self.intro_active_frontend_action)
475
476 self.quickref_active_frontend_action = QtGui.QAction("IPython Cheat Sheet",
477 self,
478 triggered=self.quickref_active_frontend
479 )
480 self.help_menu.addAction(self.quickref_active_frontend_action)
481
482 self.guiref_active_frontend_action = QtGui.QAction("Qt Console",
483 self,
484 triggered=self.guiref_active_frontend
485 )
486 self.help_menu.addAction(self.guiref_active_frontend_action)
487
600
488 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
601 # allmagics submenu:
489 self,
490 triggered=self.interrupt_kernel_active_frontend
491 )
492 self.kernel_menu.addAction(self.interrupt_kernel_action)
493
494 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
495 self,
496 triggered=self.restart_kernel_active_frontend
497 )
498 self.kernel_menu.addAction(self.restart_kernel_action)
499 self.kernel_menu.addSeparator()
500
602
501 #for now this is just a copy and paste, but we should get this dynamically
603 #for now this is just a copy and paste, but we should get this dynamically
502 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
604 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
503 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
605 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
504 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
606 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
505 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
607 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
506 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
608 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
507 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
609 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
508 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
610 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
509 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
611 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
510 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
612 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
511 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
613 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
512
614
513 def make_dynamic_magic(i):
615 def make_dynamic_magic(i):
514 def inner_dynamic_magic():
616 def inner_dynamic_magic():
515 self.active_frontend.execute(i)
617 self.active_frontend.execute(i)
516 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
618 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
517 return inner_dynamic_magic
619 return inner_dynamic_magic
518
620
519 for magic in magiclist:
621 for magic in magiclist:
520 xaction = QtGui.QAction(magic,
622 xaction = QtGui.QAction(magic,
521 self,
623 self,
522 triggered=make_dynamic_magic(magic)
624 triggered=make_dynamic_magic(magic)
523 )
625 )
524 self.all_magic_menu.addAction(xaction)
626 self.all_magic_menu.addAction(xaction)
525
627
628 def init_window_menu(self):
629 self.window_menu = self.menuBar().addMenu("&Window")
630 if sys.platform == 'darwin':
631 # add min/maximize actions to OSX, which lacks default bindings.
632 self.minimizeAct = QtGui.QAction("Mini&mize",
633 self,
634 shortcut="Ctrl+m",
635 statusTip="Minimize the window/Restore Normal Size",
636 triggered=self.toggleMinimized)
637 # maximize is called 'Zoom' on OSX for some reason
638 self.maximizeAct = QtGui.QAction("&Zoom",
639 self,
640 shortcut="Ctrl+Shift+M",
641 statusTip="Maximize the window/Restore Normal Size",
642 triggered=self.toggleMaximized)
643
644 self.add_menu_action(self.window_menu, self.minimizeAct)
645 self.add_menu_action(self.window_menu, self.maximizeAct)
646 self.window_menu.addSeparator()
647
648 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgDown"
649 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
650 self,
651 shortcut=prev_key,
652 statusTip="Select previous tab",
653 triggered=self.prev_tab)
654 self.add_menu_action(self.window_menu, self.prev_tab_act)
655
656 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgUp"
657 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
658 self,
659 shortcut=next_key,
660 statusTip="Select next tab",
661 triggered=self.next_tab)
662 self.add_menu_action(self.window_menu, self.next_tab_act)
663
664 def init_help_menu(self):
665 # please keep the Help menu in Mac Os even if empty. It will
666 # automatically contain a search field to search inside menus and
667 # please keep it spelled in English, as long as Qt Doesn't support
668 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
669 # this search field fonctionality
670
671 self.help_menu = self.menuBar().addMenu("&Help")
672
673
674 # Help Menu
675
676 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
677 self,
678 triggered=self.intro_active_frontend
679 )
680 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
681
682 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
683 self,
684 triggered=self.quickref_active_frontend
685 )
686 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
687
688 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
689 self,
690 triggered=self.guiref_active_frontend
691 )
692 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
693
694 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
695 self,
696 triggered=self._open_online_help)
697 self.add_menu_action(self.help_menu, self.onlineHelpAct)
698
699 # minimize/maximize/fullscreen actions:
700
701 def toggle_menu_bar(self):
702 menu_bar = self.menuBar();
703 if menu_bar.isVisible():
704 menu_bar.setVisible(False)
705 else:
706 menu_bar.setVisible(True)
707
708 def toggleMinimized(self):
709 if not self.isMinimized():
710 self.showMinimized()
711 else:
712 self.showNormal()
713
714 def _open_online_help(self):
715 filename="http://ipython.org/ipython-doc/stable/index.html"
716 webbrowser.open(filename, new=1, autoraise=True)
717
718 def toggleMaximized(self):
719 if not self.isMaximized():
720 self.showMaximized()
721 else:
722 self.showNormal()
723
724 # Min/Max imizing while in full screen give a bug
725 # when going out of full screen, at least on OSX
726 def toggleFullScreen(self):
727 if not self.isFullScreen():
728 self.showFullScreen()
729 if sys.platform == 'darwin':
730 self.maximizeAct.setEnabled(False)
731 self.minimizeAct.setEnabled(False)
732 else:
733 self.showNormal()
734 if sys.platform == 'darwin':
735 self.maximizeAct.setEnabled(True)
736 self.minimizeAct.setEnabled(True)
737
738 def close_active_frontend(self):
739 self.close_tab(self.active_frontend)
740
526 def restart_kernel_active_frontend(self):
741 def restart_kernel_active_frontend(self):
527 self.active_frontend.request_restart_kernel()
742 self.active_frontend.request_restart_kernel()
528
743
529 def interrupt_kernel_active_frontend(self):
744 def interrupt_kernel_active_frontend(self):
530 self.active_frontend.request_interrupt_kernel()
745 self.active_frontend.request_interrupt_kernel()
531
746
532 def cut_active_frontend(self):
747 def cut_active_frontend(self):
533 self.active_frontend.cut_action.trigger()
748 self.active_frontend.cut_action.trigger()
534
749
535 def copy_active_frontend(self):
750 def copy_active_frontend(self):
536 self.active_frontend.copy_action.trigger()
751 self.active_frontend.copy_action.trigger()
537
752
538 def copy_raw_active_frontend(self):
753 def copy_raw_active_frontend(self):
539 self.active_frontend._copy_raw_action.trigger()
754 self.active_frontend._copy_raw_action.trigger()
540
755
541 def paste_active_frontend(self):
756 def paste_active_frontend(self):
542 self.active_frontend.paste_action.trigger()
757 self.active_frontend.paste_action.trigger()
543
758
544 def undo_active_frontend(self):
759 def undo_active_frontend(self):
545 self.active_frontend.undo()
760 self.active_frontend.undo()
546
761
547 def redo_active_frontend(self):
762 def redo_active_frontend(self):
548 self.active_frontend.redo()
763 self.active_frontend.redo()
549
764
550 def reset_magic_active_frontend(self):
765 def reset_magic_active_frontend(self):
551 self.active_frontend.execute("%reset")
766 self.active_frontend.execute("%reset")
552
767
553 def history_magic_active_frontend(self):
768 def history_magic_active_frontend(self):
554 self.active_frontend.execute("%history")
769 self.active_frontend.execute("%history")
555
770
556 def save_magic_active_frontend(self):
771 def save_magic_active_frontend(self):
557 self.active_frontend.save_magic()
772 self.active_frontend.save_magic()
558
773
559 def clear_magic_active_frontend(self):
774 def clear_magic_active_frontend(self):
560 self.active_frontend.execute("%clear")
775 self.active_frontend.execute("%clear")
561
776
562 def who_magic_active_frontend(self):
777 def who_magic_active_frontend(self):
563 self.active_frontend.execute("%who")
778 self.active_frontend.execute("%who")
564
779
565 def who_ls_magic_active_frontend(self):
780 def who_ls_magic_active_frontend(self):
566 self.active_frontend.execute("%who_ls")
781 self.active_frontend.execute("%who_ls")
567
782
568 def whos_magic_active_frontend(self):
783 def whos_magic_active_frontend(self):
569 self.active_frontend.execute("%whos")
784 self.active_frontend.execute("%whos")
570
785
571 def print_action_active_frontend(self):
786 def print_action_active_frontend(self):
572 self.active_frontend.print_action.trigger()
787 self.active_frontend.print_action.trigger()
573
788
574 def export_action_active_frontend(self):
789 def export_action_active_frontend(self):
575 self.active_frontend.export_action.trigger()
790 self.active_frontend.export_action.trigger()
576
791
577 def select_all_active_frontend(self):
792 def select_all_active_frontend(self):
578 self.active_frontend.select_all_action.trigger()
793 self.active_frontend.select_all_action.trigger()
579
794
580 def increase_font_size_active_frontend(self):
795 def increase_font_size_active_frontend(self):
581 self.active_frontend.increase_font_size.trigger()
796 self.active_frontend.increase_font_size.trigger()
582
797
583 def decrease_font_size_active_frontend(self):
798 def decrease_font_size_active_frontend(self):
584 self.active_frontend.decrease_font_size.trigger()
799 self.active_frontend.decrease_font_size.trigger()
585
800
586 def reset_font_size_active_frontend(self):
801 def reset_font_size_active_frontend(self):
587 self.active_frontend.reset_font_size.trigger()
802 self.active_frontend.reset_font_size.trigger()
588
803
589 def guiref_active_frontend(self):
804 def guiref_active_frontend(self):
590 self.active_frontend.execute("%guiref")
805 self.active_frontend.execute("%guiref")
591
806
592 def intro_active_frontend(self):
807 def intro_active_frontend(self):
593 self.active_frontend.execute("?")
808 self.active_frontend.execute("?")
594
809
595 def quickref_active_frontend(self):
810 def quickref_active_frontend(self):
596 self.active_frontend.execute("%quickref")
811 self.active_frontend.execute("%quickref")
597 #---------------------------------------------------------------------------
812 #---------------------------------------------------------------------------
598 # QWidget interface
813 # QWidget interface
599 #---------------------------------------------------------------------------
814 #---------------------------------------------------------------------------
600
815
601 def closeEvent(self, event):
816 def closeEvent(self, event):
602 """ Forward the close event to every tabs contained by the windows
817 """ Forward the close event to every tabs contained by the windows
603 """
818 """
604 # Do Not loop on the widget count as it change while closing
819 # Do Not loop on the widget count as it change while closing
605 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
820 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
606 for widget in widget_list:
821 for widget in widget_list:
607 self.close_tab(widget)
822 self.close_tab(widget)
608 event.accept()
823 event.accept()
609
824
610 #-----------------------------------------------------------------------------
825 #-----------------------------------------------------------------------------
611 # Aliases and Flags
826 # Aliases and Flags
612 #-----------------------------------------------------------------------------
827 #-----------------------------------------------------------------------------
613
828
614 flags = dict(ipkernel_flags)
829 flags = dict(ipkernel_flags)
615 qt_flags = {
830 qt_flags = {
616 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
831 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
617 "Connect to an existing kernel. If no argument specified, guess most recent"),
832 "Connect to an existing kernel. If no argument specified, guess most recent"),
618 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
833 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
619 "Use a pure Python kernel instead of an IPython kernel."),
834 "Use a pure Python kernel instead of an IPython kernel."),
620 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
835 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
621 "Disable rich text support."),
836 "Disable rich text support."),
622 }
837 }
623 qt_flags.update(boolean_flag(
838 qt_flags.update(boolean_flag(
624 'gui-completion', 'ConsoleWidget.gui_completion',
839 'gui-completion', 'ConsoleWidget.gui_completion',
625 "use a GUI widget for tab completion",
840 "use a GUI widget for tab completion",
626 "use plaintext output for completion"
841 "use plaintext output for completion"
627 ))
842 ))
628 qt_flags.update(boolean_flag(
843 qt_flags.update(boolean_flag(
629 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
844 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
630 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
845 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
631 to force a direct exit without any confirmation.
846 to force a direct exit without any confirmation.
632 """,
847 """,
633 """Don't prompt the user when exiting. This will terminate the kernel
848 """Don't prompt the user when exiting. This will terminate the kernel
634 if it is owned by the frontend, and leave it alive if it is external.
849 if it is owned by the frontend, and leave it alive if it is external.
635 """
850 """
636 ))
851 ))
637 flags.update(qt_flags)
852 flags.update(qt_flags)
638
853
639 aliases = dict(ipkernel_aliases)
854 aliases = dict(ipkernel_aliases)
640
855
641 qt_aliases = dict(
856 qt_aliases = dict(
642 hb = 'IPythonQtConsoleApp.hb_port',
857 hb = 'IPythonQtConsoleApp.hb_port',
643 shell = 'IPythonQtConsoleApp.shell_port',
858 shell = 'IPythonQtConsoleApp.shell_port',
644 iopub = 'IPythonQtConsoleApp.iopub_port',
859 iopub = 'IPythonQtConsoleApp.iopub_port',
645 stdin = 'IPythonQtConsoleApp.stdin_port',
860 stdin = 'IPythonQtConsoleApp.stdin_port',
646 ip = 'IPythonQtConsoleApp.ip',
861 ip = 'IPythonQtConsoleApp.ip',
647 existing = 'IPythonQtConsoleApp.existing',
862 existing = 'IPythonQtConsoleApp.existing',
648 f = 'IPythonQtConsoleApp.connection_file',
863 f = 'IPythonQtConsoleApp.connection_file',
649
864
650 style = 'IPythonWidget.syntax_style',
865 style = 'IPythonWidget.syntax_style',
651 stylesheet = 'IPythonQtConsoleApp.stylesheet',
866 stylesheet = 'IPythonQtConsoleApp.stylesheet',
652 colors = 'ZMQInteractiveShell.colors',
867 colors = 'ZMQInteractiveShell.colors',
653
868
654 editor = 'IPythonWidget.editor',
869 editor = 'IPythonWidget.editor',
655 paging = 'ConsoleWidget.paging',
870 paging = 'ConsoleWidget.paging',
656 ssh = 'IPythonQtConsoleApp.sshserver',
871 ssh = 'IPythonQtConsoleApp.sshserver',
657 )
872 )
658 aliases.update(qt_aliases)
873 aliases.update(qt_aliases)
659
874
660
875
661 #-----------------------------------------------------------------------------
876 #-----------------------------------------------------------------------------
662 # IPythonQtConsole
877 # IPythonQtConsole
663 #-----------------------------------------------------------------------------
878 #-----------------------------------------------------------------------------
664
879
665
880
666 class IPythonQtConsoleApp(BaseIPythonApplication):
881 class IPythonQtConsoleApp(BaseIPythonApplication):
667 name = 'ipython-qtconsole'
882 name = 'ipython-qtconsole'
668 default_config_file_name='ipython_config.py'
883 default_config_file_name='ipython_config.py'
669
884
670 description = """
885 description = """
671 The IPython QtConsole.
886 The IPython QtConsole.
672
887
673 This launches a Console-style application using Qt. It is not a full
888 This launches a Console-style application using Qt. It is not a full
674 console, in that launched terminal subprocesses will not be able to accept
889 console, in that launched terminal subprocesses will not be able to accept
675 input.
890 input.
676
891
677 The QtConsole supports various extra features beyond the Terminal IPython
892 The QtConsole supports various extra features beyond the Terminal IPython
678 shell, such as inline plotting with matplotlib, via:
893 shell, such as inline plotting with matplotlib, via:
679
894
680 ipython qtconsole --pylab=inline
895 ipython qtconsole --pylab=inline
681
896
682 as well as saving your session as HTML, and printing the output.
897 as well as saving your session as HTML, and printing the output.
683
898
684 """
899 """
685 examples = _examples
900 examples = _examples
686
901
687 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
902 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
688 flags = Dict(flags)
903 flags = Dict(flags)
689 aliases = Dict(aliases)
904 aliases = Dict(aliases)
690
905
691 kernel_argv = List(Unicode)
906 kernel_argv = List(Unicode)
692
907
693 # create requested profiles by default, if they don't exist:
908 # create requested profiles by default, if they don't exist:
694 auto_create = CBool(True)
909 auto_create = CBool(True)
695 # connection info:
910 # connection info:
696 ip = Unicode(LOCALHOST, config=True,
911 ip = Unicode(LOCALHOST, config=True,
697 help="""Set the kernel\'s IP address [default localhost].
912 help="""Set the kernel\'s IP address [default localhost].
698 If the IP address is something other than localhost, then
913 If the IP address is something other than localhost, then
699 Consoles on other machines will be able to connect
914 Consoles on other machines will be able to connect
700 to the Kernel, so be careful!"""
915 to the Kernel, so be careful!"""
701 )
916 )
702
917
703 sshserver = Unicode('', config=True,
918 sshserver = Unicode('', config=True,
704 help="""The SSH server to use to connect to the kernel.""")
919 help="""The SSH server to use to connect to the kernel.""")
705 sshkey = Unicode('', config=True,
920 sshkey = Unicode('', config=True,
706 help="""Path to the ssh key to use for logging in to the ssh server.""")
921 help="""Path to the ssh key to use for logging in to the ssh server.""")
707
922
708 hb_port = Int(0, config=True,
923 hb_port = Int(0, config=True,
709 help="set the heartbeat port [default: random]")
924 help="set the heartbeat port [default: random]")
710 shell_port = Int(0, config=True,
925 shell_port = Int(0, config=True,
711 help="set the shell (XREP) port [default: random]")
926 help="set the shell (XREP) port [default: random]")
712 iopub_port = Int(0, config=True,
927 iopub_port = Int(0, config=True,
713 help="set the iopub (PUB) port [default: random]")
928 help="set the iopub (PUB) port [default: random]")
714 stdin_port = Int(0, config=True,
929 stdin_port = Int(0, config=True,
715 help="set the stdin (XREQ) port [default: random]")
930 help="set the stdin (XREQ) port [default: random]")
716 connection_file = Unicode('', config=True,
931 connection_file = Unicode('', config=True,
717 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
932 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
718
933
719 This file will contain the IP, ports, and authentication key needed to connect
934 This file will contain the IP, ports, and authentication key needed to connect
720 clients to this kernel. By default, this file will be created in the security-dir
935 clients to this kernel. By default, this file will be created in the security-dir
721 of the current profile, but can be specified by absolute path.
936 of the current profile, but can be specified by absolute path.
722 """)
937 """)
723 def _connection_file_default(self):
938 def _connection_file_default(self):
724 return 'kernel-%i.json' % os.getpid()
939 return 'kernel-%i.json' % os.getpid()
725
940
726 existing = Unicode('', config=True,
941 existing = Unicode('', config=True,
727 help="""Connect to an already running kernel""")
942 help="""Connect to an already running kernel""")
728
943
729 stylesheet = Unicode('', config=True,
944 stylesheet = Unicode('', config=True,
730 help="path to a custom CSS stylesheet")
945 help="path to a custom CSS stylesheet")
731
946
732 pure = CBool(False, config=True,
947 pure = CBool(False, config=True,
733 help="Use a pure Python kernel instead of an IPython kernel.")
948 help="Use a pure Python kernel instead of an IPython kernel.")
734 plain = CBool(False, config=True,
949 plain = CBool(False, config=True,
735 help="Use a plaintext widget instead of rich text (plain can't print/save).")
950 help="Use a plaintext widget instead of rich text (plain can't print/save).")
736
951
737 def _pure_changed(self, name, old, new):
952 def _pure_changed(self, name, old, new):
738 kind = 'plain' if self.plain else 'rich'
953 kind = 'plain' if self.plain else 'rich'
739 self.config.ConsoleWidget.kind = kind
954 self.config.ConsoleWidget.kind = kind
740 if self.pure:
955 if self.pure:
741 self.widget_factory = FrontendWidget
956 self.widget_factory = FrontendWidget
742 elif self.plain:
957 elif self.plain:
743 self.widget_factory = IPythonWidget
958 self.widget_factory = IPythonWidget
744 else:
959 else:
745 self.widget_factory = RichIPythonWidget
960 self.widget_factory = RichIPythonWidget
746
961
747 _plain_changed = _pure_changed
962 _plain_changed = _pure_changed
748
963
749 confirm_exit = CBool(True, config=True,
964 confirm_exit = CBool(True, config=True,
750 help="""
965 help="""
751 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
966 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
752 to force a direct exit without any confirmation.""",
967 to force a direct exit without any confirmation.""",
753 )
968 )
754
969
755 # the factory for creating a widget
970 # the factory for creating a widget
756 widget_factory = Any(RichIPythonWidget)
971 widget_factory = Any(RichIPythonWidget)
757
972
758 def parse_command_line(self, argv=None):
973 def parse_command_line(self, argv=None):
759 super(IPythonQtConsoleApp, self).parse_command_line(argv)
974 super(IPythonQtConsoleApp, self).parse_command_line(argv)
760 if argv is None:
975 if argv is None:
761 argv = sys.argv[1:]
976 argv = sys.argv[1:]
762
977
763 self.kernel_argv = list(argv) # copy
978 self.kernel_argv = list(argv) # copy
764 # kernel should inherit default config file from frontend
979 # kernel should inherit default config file from frontend
765 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
980 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
766 # Scrub frontend-specific flags
981 # Scrub frontend-specific flags
767 for a in argv:
982 for a in argv:
768 if a.startswith('-') and a.lstrip('-') in qt_flags:
983 if a.startswith('-') and a.lstrip('-') in qt_flags:
769 self.kernel_argv.remove(a)
984 self.kernel_argv.remove(a)
770 swallow_next = False
985 swallow_next = False
771 for a in argv:
986 for a in argv:
772 if swallow_next:
987 if swallow_next:
773 self.kernel_argv.remove(a)
988 self.kernel_argv.remove(a)
774 swallow_next = False
989 swallow_next = False
775 continue
990 continue
776 if a.startswith('-'):
991 if a.startswith('-'):
777 split = a.lstrip('-').split('=')
992 split = a.lstrip('-').split('=')
778 alias = split[0]
993 alias = split[0]
779 if alias in qt_aliases:
994 if alias in qt_aliases:
780 self.kernel_argv.remove(a)
995 self.kernel_argv.remove(a)
781 if len(split) == 1:
996 if len(split) == 1:
782 # alias passed with arg via space
997 # alias passed with arg via space
783 swallow_next = True
998 swallow_next = True
784
999
785 def init_connection_file(self):
1000 def init_connection_file(self):
786 """find the connection file, and load the info if found.
1001 """find the connection file, and load the info if found.
787
1002
788 The current working directory and the current profile's security
1003 The current working directory and the current profile's security
789 directory will be searched for the file if it is not given by
1004 directory will be searched for the file if it is not given by
790 absolute path.
1005 absolute path.
791
1006
792 When attempting to connect to an existing kernel and the `--existing`
1007 When attempting to connect to an existing kernel and the `--existing`
793 argument does not match an existing file, it will be interpreted as a
1008 argument does not match an existing file, it will be interpreted as a
794 fileglob, and the matching file in the current profile's security dir
1009 fileglob, and the matching file in the current profile's security dir
795 with the latest access time will be used.
1010 with the latest access time will be used.
796 """
1011 """
797 if self.existing:
1012 if self.existing:
798 try:
1013 try:
799 cf = find_connection_file(self.existing)
1014 cf = find_connection_file(self.existing)
800 except Exception:
1015 except Exception:
801 self.log.critical("Could not find existing kernel connection file %s", self.existing)
1016 self.log.critical("Could not find existing kernel connection file %s", self.existing)
802 self.exit(1)
1017 self.exit(1)
803 self.log.info("Connecting to existing kernel: %s" % cf)
1018 self.log.info("Connecting to existing kernel: %s" % cf)
804 self.connection_file = cf
1019 self.connection_file = cf
805 # should load_connection_file only be used for existing?
1020 # should load_connection_file only be used for existing?
806 # as it is now, this allows reusing ports if an existing
1021 # as it is now, this allows reusing ports if an existing
807 # file is requested
1022 # file is requested
808 try:
1023 try:
809 self.load_connection_file()
1024 self.load_connection_file()
810 except Exception:
1025 except Exception:
811 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
1026 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
812 self.exit(1)
1027 self.exit(1)
813
1028
814 def load_connection_file(self):
1029 def load_connection_file(self):
815 """load ip/port/hmac config from JSON connection file"""
1030 """load ip/port/hmac config from JSON connection file"""
816 # this is identical to KernelApp.load_connection_file
1031 # this is identical to KernelApp.load_connection_file
817 # perhaps it can be centralized somewhere?
1032 # perhaps it can be centralized somewhere?
818 try:
1033 try:
819 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
1034 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
820 except IOError:
1035 except IOError:
821 self.log.debug("Connection File not found: %s", self.connection_file)
1036 self.log.debug("Connection File not found: %s", self.connection_file)
822 return
1037 return
823 self.log.debug(u"Loading connection file %s", fname)
1038 self.log.debug(u"Loading connection file %s", fname)
824 with open(fname) as f:
1039 with open(fname) as f:
825 s = f.read()
1040 s = f.read()
826 cfg = json.loads(s)
1041 cfg = json.loads(s)
827 if self.ip == LOCALHOST and 'ip' in cfg:
1042 if self.ip == LOCALHOST and 'ip' in cfg:
828 # not overridden by config or cl_args
1043 # not overridden by config or cl_args
829 self.ip = cfg['ip']
1044 self.ip = cfg['ip']
830 for channel in ('hb', 'shell', 'iopub', 'stdin'):
1045 for channel in ('hb', 'shell', 'iopub', 'stdin'):
831 name = channel + '_port'
1046 name = channel + '_port'
832 if getattr(self, name) == 0 and name in cfg:
1047 if getattr(self, name) == 0 and name in cfg:
833 # not overridden by config or cl_args
1048 # not overridden by config or cl_args
834 setattr(self, name, cfg[name])
1049 setattr(self, name, cfg[name])
835 if 'key' in cfg:
1050 if 'key' in cfg:
836 self.config.Session.key = str_to_bytes(cfg['key'])
1051 self.config.Session.key = str_to_bytes(cfg['key'])
837
1052
838 def init_ssh(self):
1053 def init_ssh(self):
839 """set up ssh tunnels, if needed."""
1054 """set up ssh tunnels, if needed."""
840 if not self.sshserver and not self.sshkey:
1055 if not self.sshserver and not self.sshkey:
841 return
1056 return
842
1057
843 if self.sshkey and not self.sshserver:
1058 if self.sshkey and not self.sshserver:
844 # specifying just the key implies that we are connecting directly
1059 # specifying just the key implies that we are connecting directly
845 self.sshserver = self.ip
1060 self.sshserver = self.ip
846 self.ip = LOCALHOST
1061 self.ip = LOCALHOST
847
1062
848 # build connection dict for tunnels:
1063 # build connection dict for tunnels:
849 info = dict(ip=self.ip,
1064 info = dict(ip=self.ip,
850 shell_port=self.shell_port,
1065 shell_port=self.shell_port,
851 iopub_port=self.iopub_port,
1066 iopub_port=self.iopub_port,
852 stdin_port=self.stdin_port,
1067 stdin_port=self.stdin_port,
853 hb_port=self.hb_port
1068 hb_port=self.hb_port
854 )
1069 )
855
1070
856 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
1071 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
857
1072
858 # tunnels return a new set of ports, which will be on localhost:
1073 # tunnels return a new set of ports, which will be on localhost:
859 self.ip = LOCALHOST
1074 self.ip = LOCALHOST
860 try:
1075 try:
861 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
1076 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
862 except:
1077 except:
863 # even catch KeyboardInterrupt
1078 # even catch KeyboardInterrupt
864 self.log.error("Could not setup tunnels", exc_info=True)
1079 self.log.error("Could not setup tunnels", exc_info=True)
865 self.exit(1)
1080 self.exit(1)
866
1081
867 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
1082 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
868
1083
869 cf = self.connection_file
1084 cf = self.connection_file
870 base,ext = os.path.splitext(cf)
1085 base,ext = os.path.splitext(cf)
871 base = os.path.basename(base)
1086 base = os.path.basename(base)
872 self.connection_file = os.path.basename(base)+'-ssh'+ext
1087 self.connection_file = os.path.basename(base)+'-ssh'+ext
873 self.log.critical("To connect another client via this tunnel, use:")
1088 self.log.critical("To connect another client via this tunnel, use:")
874 self.log.critical("--existing %s" % self.connection_file)
1089 self.log.critical("--existing %s" % self.connection_file)
875
1090
876 def _new_connection_file(self):
1091 def _new_connection_file(self):
877 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
1092 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
878
1093
879 def init_kernel_manager(self):
1094 def init_kernel_manager(self):
880 # Don't let Qt or ZMQ swallow KeyboardInterupts.
1095 # Don't let Qt or ZMQ swallow KeyboardInterupts.
881 signal.signal(signal.SIGINT, signal.SIG_DFL)
1096 signal.signal(signal.SIGINT, signal.SIG_DFL)
882 sec = self.profile_dir.security_dir
1097 sec = self.profile_dir.security_dir
883 try:
1098 try:
884 cf = filefind(self.connection_file, ['.', sec])
1099 cf = filefind(self.connection_file, ['.', sec])
885 except IOError:
1100 except IOError:
886 # file might not exist
1101 # file might not exist
887 if self.connection_file == os.path.basename(self.connection_file):
1102 if self.connection_file == os.path.basename(self.connection_file):
888 # just shortname, put it in security dir
1103 # just shortname, put it in security dir
889 cf = os.path.join(sec, self.connection_file)
1104 cf = os.path.join(sec, self.connection_file)
890 else:
1105 else:
891 cf = self.connection_file
1106 cf = self.connection_file
892
1107
893 # Create a KernelManager and start a kernel.
1108 # Create a KernelManager and start a kernel.
894 self.kernel_manager = QtKernelManager(
1109 self.kernel_manager = QtKernelManager(
895 ip=self.ip,
1110 ip=self.ip,
896 shell_port=self.shell_port,
1111 shell_port=self.shell_port,
897 iopub_port=self.iopub_port,
1112 iopub_port=self.iopub_port,
898 stdin_port=self.stdin_port,
1113 stdin_port=self.stdin_port,
899 hb_port=self.hb_port,
1114 hb_port=self.hb_port,
900 connection_file=cf,
1115 connection_file=cf,
901 config=self.config,
1116 config=self.config,
902 )
1117 )
903 # start the kernel
1118 # start the kernel
904 if not self.existing:
1119 if not self.existing:
905 kwargs = dict(ipython=not self.pure)
1120 kwargs = dict(ipython=not self.pure)
906 kwargs['extra_arguments'] = self.kernel_argv
1121 kwargs['extra_arguments'] = self.kernel_argv
907 self.kernel_manager.start_kernel(**kwargs)
1122 self.kernel_manager.start_kernel(**kwargs)
908 elif self.sshserver:
1123 elif self.sshserver:
909 # ssh, write new connection file
1124 # ssh, write new connection file
910 self.kernel_manager.write_connection_file()
1125 self.kernel_manager.write_connection_file()
911 self.kernel_manager.start_channels()
1126 self.kernel_manager.start_channels()
912
1127
913 def create_tab_with_new_frontend(self):
1128 def new_frontend_master(self):
914 """ Create new tab attached to new kernel, launched on localhost.
1129 """ Create and return new frontend attached to new kernel, launched on localhost.
915 """
1130 """
916 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
1131 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
917 kernel_manager = QtKernelManager(
1132 kernel_manager = QtKernelManager(
918 ip=ip,
1133 ip=ip,
919 connection_file=self._new_connection_file(),
1134 connection_file=self._new_connection_file(),
920 config=self.config,
1135 config=self.config,
921 )
1136 )
922 # start the kernel
1137 # start the kernel
923 kwargs = dict(ipython=not self.pure)
1138 kwargs = dict(ipython=not self.pure)
924 kwargs['extra_arguments'] = self.kernel_argv
1139 kwargs['extra_arguments'] = self.kernel_argv
925 kernel_manager.start_kernel(**kwargs)
1140 kernel_manager.start_kernel(**kwargs)
926 kernel_manager.start_channels()
1141 kernel_manager.start_channels()
927 widget = self.widget_factory(config=self.config,
1142 widget = self.widget_factory(config=self.config,
928 local_kernel=True)
1143 local_kernel=True)
929 widget.kernel_manager = kernel_manager
1144 widget.kernel_manager = kernel_manager
930 widget._existing=False;
1145 widget._existing=False
931 widget._confirm_exit=True;
1146 widget._confirm_exit=True
932 widget._may_close=True;
1147 widget._may_close=True
933 self.window.add_tab_with_frontend(widget)
1148 return widget
934
1149
935 def create_tab_attached_to_current_tab_kernel(self):
1150 def new_frontend_slave(self, current_widget):
936 current_widget = self.window.tab_widget.currentWidget()
1151 """Create and return a new frontend attached to an existing kernel.
937 current_widget_index = self.window.tab_widget.indexOf(current_widget)
1152
938 current_widget.kernel_manager = current_widget.kernel_manager;
1153 Parameters
939 current_widget_name = self.window.tab_widget.tabText(current_widget_index);
1154 ----------
1155 current_widget : IPythonWidget
1156 The IPythonWidget whose kernel this frontend is to share
1157 """
940 kernel_manager = QtKernelManager(
1158 kernel_manager = QtKernelManager(
941 connection_file=current_widget.kernel_manager.connection_file,
1159 connection_file=current_widget.kernel_manager.connection_file,
942 config = self.config,
1160 config = self.config,
943 )
1161 )
944 kernel_manager.load_connection_file()
1162 kernel_manager.load_connection_file()
945 kernel_manager.start_channels()
1163 kernel_manager.start_channels()
946 widget = self.widget_factory(config=self.config,
1164 widget = self.widget_factory(config=self.config,
947 local_kernel=False)
1165 local_kernel=False)
948 widget._confirm_exit=True;
1166 widget._confirm_exit=True;
949 widget._may_close=False;
1167 widget._may_close=False;
950 widget.kernel_manager = kernel_manager
1168 widget.kernel_manager = kernel_manager
951 self.window.add_tab_with_frontend(widget,name=str('('+current_widget_name+') slave'))
1169 return widget
952
1170
953 def init_qt_elements(self):
1171 def init_qt_elements(self):
954 # Create the widget.
1172 # Create the widget.
955 self.app = QtGui.QApplication([])
1173 self.app = QtGui.QApplication([])
956
1174
957 base_path = os.path.abspath(os.path.dirname(__file__))
1175 base_path = os.path.abspath(os.path.dirname(__file__))
958 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
1176 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
959 self.app.icon = QtGui.QIcon(icon_path)
1177 self.app.icon = QtGui.QIcon(icon_path)
960 QtGui.QApplication.setWindowIcon(self.app.icon)
1178 QtGui.QApplication.setWindowIcon(self.app.icon)
961
1179
962 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
1180 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
963 self.widget = self.widget_factory(config=self.config,
1181 self.widget = self.widget_factory(config=self.config,
964 local_kernel=local_kernel)
1182 local_kernel=local_kernel)
965 self.widget._existing = self.existing;
1183 self.widget._existing = self.existing;
966 self.widget._may_close = not self.existing;
1184 self.widget._may_close = not self.existing;
967 self.widget._confirm_exit = not self.existing;
1185 self.widget._confirm_exit = not self.existing;
968
1186
969 self.widget.kernel_manager = self.kernel_manager
1187 self.widget.kernel_manager = self.kernel_manager
970 self.window = MainWindow(self.app, self.widget, self.existing,
1188 self.window = MainWindow(self.app, self.widget, self.existing,
971 may_close=local_kernel,
1189 may_close=local_kernel,
972 confirm_exit=self.confirm_exit)
1190 confirm_exit=self.confirm_exit,
1191 new_frontend_factory=self.new_frontend_master,
1192 slave_frontend_factory=self.new_frontend_slave,
1193 )
973 self.window.log = self.log
1194 self.window.log = self.log
974 self.window.add_tab_with_frontend(self.widget)
1195 self.window.add_tab_with_frontend(self.widget)
975 self.window.init_menu_bar()
1196 self.window.init_menu_bar()
976 self.window.setWindowTitle('Python' if self.pure else 'IPython')
1197 self.window.setWindowTitle('Python' if self.pure else 'IPython')
977
1198
978 def init_colors(self):
1199 def init_colors(self):
979 """Configure the coloring of the widget"""
1200 """Configure the coloring of the widget"""
980 # Note: This will be dramatically simplified when colors
1201 # Note: This will be dramatically simplified when colors
981 # are removed from the backend.
1202 # are removed from the backend.
982
1203
983 if self.pure:
1204 if self.pure:
984 # only IPythonWidget supports styling
1205 # only IPythonWidget supports styling
985 return
1206 return
986
1207
987 # parse the colors arg down to current known labels
1208 # parse the colors arg down to current known labels
988 try:
1209 try:
989 colors = self.config.ZMQInteractiveShell.colors
1210 colors = self.config.ZMQInteractiveShell.colors
990 except AttributeError:
1211 except AttributeError:
991 colors = None
1212 colors = None
992 try:
1213 try:
993 style = self.config.IPythonWidget.colors
1214 style = self.config.IPythonWidget.colors
994 except AttributeError:
1215 except AttributeError:
995 style = None
1216 style = None
996
1217
997 # find the value for colors:
1218 # find the value for colors:
998 if colors:
1219 if colors:
999 colors=colors.lower()
1220 colors=colors.lower()
1000 if colors in ('lightbg', 'light'):
1221 if colors in ('lightbg', 'light'):
1001 colors='lightbg'
1222 colors='lightbg'
1002 elif colors in ('dark', 'linux'):
1223 elif colors in ('dark', 'linux'):
1003 colors='linux'
1224 colors='linux'
1004 else:
1225 else:
1005 colors='nocolor'
1226 colors='nocolor'
1006 elif style:
1227 elif style:
1007 if style=='bw':
1228 if style=='bw':
1008 colors='nocolor'
1229 colors='nocolor'
1009 elif styles.dark_style(style):
1230 elif styles.dark_style(style):
1010 colors='linux'
1231 colors='linux'
1011 else:
1232 else:
1012 colors='lightbg'
1233 colors='lightbg'
1013 else:
1234 else:
1014 colors=None
1235 colors=None
1015
1236
1016 # Configure the style.
1237 # Configure the style.
1017 widget = self.widget
1238 widget = self.widget
1018 if style:
1239 if style:
1019 widget.style_sheet = styles.sheet_from_template(style, colors)
1240 widget.style_sheet = styles.sheet_from_template(style, colors)
1020 widget.syntax_style = style
1241 widget.syntax_style = style
1021 widget._syntax_style_changed()
1242 widget._syntax_style_changed()
1022 widget._style_sheet_changed()
1243 widget._style_sheet_changed()
1023 elif colors:
1244 elif colors:
1024 # use a default style
1245 # use a default style
1025 widget.set_default_style(colors=colors)
1246 widget.set_default_style(colors=colors)
1026 else:
1247 else:
1027 # this is redundant for now, but allows the widget's
1248 # this is redundant for now, but allows the widget's
1028 # defaults to change
1249 # defaults to change
1029 widget.set_default_style()
1250 widget.set_default_style()
1030
1251
1031 if self.stylesheet:
1252 if self.stylesheet:
1032 # we got an expicit stylesheet
1253 # we got an expicit stylesheet
1033 if os.path.isfile(self.stylesheet):
1254 if os.path.isfile(self.stylesheet):
1034 with open(self.stylesheet) as f:
1255 with open(self.stylesheet) as f:
1035 sheet = f.read()
1256 sheet = f.read()
1036 widget.style_sheet = sheet
1257 widget.style_sheet = sheet
1037 widget._style_sheet_changed()
1258 widget._style_sheet_changed()
1038 else:
1259 else:
1039 raise IOError("Stylesheet %r not found."%self.stylesheet)
1260 raise IOError("Stylesheet %r not found."%self.stylesheet)
1040
1261
1041 def initialize(self, argv=None):
1262 def initialize(self, argv=None):
1042 super(IPythonQtConsoleApp, self).initialize(argv)
1263 super(IPythonQtConsoleApp, self).initialize(argv)
1043 self.init_connection_file()
1264 self.init_connection_file()
1044 default_secure(self.config)
1265 default_secure(self.config)
1045 self.init_ssh()
1266 self.init_ssh()
1046 self.init_kernel_manager()
1267 self.init_kernel_manager()
1047 self.init_qt_elements()
1268 self.init_qt_elements()
1048 self.init_colors()
1269 self.init_colors()
1049 self.init_window_shortcut()
1050
1051 def init_window_shortcut(self):
1052
1053 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
1054 self.window,
1055 shortcut="Ctrl+PgDown",
1056 statusTip="Cahange to next tab",
1057 triggered=self.window.prev_tab)
1058
1059 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
1060 self.window,
1061 shortcut="Ctrl+PgUp",
1062 statusTip="Cahange to next tab",
1063 triggered=self.window.next_tab)
1064
1065 self.fullScreenAct = QtGui.QAction("&Full Screen",
1066 self.window,
1067 shortcut="Ctrl+Meta+Space",
1068 statusTip="Toggle between Fullscreen and Normal Size",
1069 triggered=self.toggleFullScreen)
1070
1071
1072
1073 self.tabAndNewKernelAct =QtGui.QAction("Tab with &New kernel",
1074 self.window,
1075 shortcut="Ctrl+T",
1076 triggered=self.create_tab_with_new_frontend)
1077 self.window.kernel_menu.addAction(self.tabAndNewKernelAct)
1078
1079 self.tabSameKernalAct =QtGui.QAction("Tab with Sa&me kernel",
1080 self.window,
1081 shortcut="Ctrl+Shift+T",
1082 triggered=self.create_tab_attached_to_current_tab_kernel)
1083 self.window.kernel_menu.addAction(self.tabSameKernalAct)
1084 self.window.kernel_menu.addSeparator()
1085
1086 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
1087 self.window,
1088 triggered=self._open_online_help)
1089 self.window.help_menu.addAction(self.onlineHelpAct)
1090 # creating shortcut in menubar only for Mac OS as I don't
1091 # know the shortcut or if the windows manager assign it in
1092 # other platform.
1093 if sys.platform == 'darwin':
1094 self.minimizeAct = QtGui.QAction("Mini&mize",
1095 self.window,
1096 shortcut="Ctrl+m",
1097 statusTip="Minimize the window/Restore Normal Size",
1098 triggered=self.toggleMinimized)
1099 self.maximizeAct = QtGui.QAction("Ma&ximize",
1100 self.window,
1101 shortcut="Ctrl+Shift+M",
1102 statusTip="Maximize the window/Restore Normal Size",
1103 triggered=self.toggleMaximized)
1104
1105
1106 self.window_menu = self.window.window_menu
1107 self.kernel_menu = self.window.kernel_menu
1108
1109 self.kernel_menu.addAction(self.next_tab_act)
1110 self.kernel_menu.addAction(self.prev_tab_act)
1111 self.window_menu.addSeparator()
1112 self.window_menu.addAction(self.minimizeAct)
1113 self.window_menu.addAction(self.maximizeAct)
1114 self.window_menu.addSeparator()
1115 self.window_menu.addAction(self.fullScreenAct)
1116
1117 else:
1118 # if we don't put it in a menu, we add it to the window so
1119 # that it can still be triggerd by shortcut
1120 self.window.addAction(self.fullScreenAct)
1121
1122 # Don't activate toggleMenubar on mac, doen't work,
1123 # as toolbar always here
1124 self.toggle_menu_bar_act = QtGui.QAction("&Toggle Menu Bar",
1125 self.window,
1126 shortcut="Ctrl+Meta+H",
1127 statusTip="Toggle menubar betwin visible and not",
1128 triggered=self.toggle_menu_bar)
1129 self.window.window_menu.addAction(self.toggle_menu_bar_act)
1130
1131 def toggle_menu_bar(self):
1132 menu_bar = self.window.menuBar();
1133 if not menu_bar.isVisible():
1134 menu_bar.setVisible(False)
1135 else:
1136 menu_bar.setVisible(True)
1137
1138 def toggleMinimized(self):
1139 if not self.window.isMinimized():
1140 self.window.showMinimized()
1141 else:
1142 self.window.showNormal()
1143
1144 def _open_online_help(self):
1145 filename="http://ipython.org/ipython-doc/stable/index.html"
1146 webbrowser.open(filename, new=1, autoraise=True)
1147
1148 def toggleMaximized(self):
1149 if not self.window.isMaximized():
1150 self.window.showMaximized()
1151 else:
1152 self.window.showNormal()
1153
1154 # Min/Max imizing while in full screen give a bug
1155 # when going out of full screen, at least on OSX
1156 def toggleFullScreen(self):
1157 if not self.window.isFullScreen():
1158 self.window.showFullScreen()
1159 if sys.platform == 'darwin':
1160 self.maximizeAct.setEnabled(False)
1161 self.minimizeAct.setEnabled(False)
1162 else:
1163 self.window.showNormal()
1164 if sys.platform == 'darwin':
1165 self.maximizeAct.setEnabled(True)
1166 self.minimizeAct.setEnabled(True)
1167
1270
1168 def start(self):
1271 def start(self):
1169
1272
1170 # draw the window
1273 # draw the window
1171 self.window.show()
1274 self.window.show()
1172
1275
1173 # Start the application main loop.
1276 # Start the application main loop.
1174 self.app.exec_()
1277 self.app.exec_()
1175
1278
1176 #-----------------------------------------------------------------------------
1279 #-----------------------------------------------------------------------------
1177 # Main entry point
1280 # Main entry point
1178 #-----------------------------------------------------------------------------
1281 #-----------------------------------------------------------------------------
1179
1282
1180 def main():
1283 def main():
1181 app = IPythonQtConsoleApp()
1284 app = IPythonQtConsoleApp()
1182 app.initialize()
1285 app.initialize()
1183 app.start()
1286 app.start()
1184
1287
1185
1288
1186 if __name__ == '__main__':
1289 if __name__ == '__main__':
1187 main()
1290 main()
General Comments 0
You need to be logged in to leave comments. Login now