##// END OF EJS Templates
Temporary fix to work around #1057....
Fernando Perez -
Show More
@@ -1,906 +1,957 b''
1 """The Qt MainWindow for the QtConsole
1 """The Qt MainWindow for the QtConsole
2
2
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 common actions.
4 common actions.
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 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import sys
22 import sys
23 import re
23 import re
24 import webbrowser
24 import webbrowser
25 from threading import Thread
25 from threading import Thread
26
26
27 # System library imports
27 # System library imports
28 from IPython.external.qt import QtGui,QtCore
28 from IPython.external.qt import QtGui,QtCore
29
29
30 def background(f):
30 def background(f):
31 """call a function in a simple thread, to prevent blocking"""
31 """call a function in a simple thread, to prevent blocking"""
32 t = Thread(target=f)
32 t = Thread(target=f)
33 t.start()
33 t.start()
34 return t
34 return t
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Classes
37 # Classes
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class MainWindow(QtGui.QMainWindow):
40 class MainWindow(QtGui.QMainWindow):
41
41
42 #---------------------------------------------------------------------------
42 #---------------------------------------------------------------------------
43 # 'object' interface
43 # 'object' interface
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45
45
46 def __init__(self, app,
46 def __init__(self, app,
47 confirm_exit=True,
47 confirm_exit=True,
48 new_frontend_factory=None, slave_frontend_factory=None,
48 new_frontend_factory=None, slave_frontend_factory=None,
49 ):
49 ):
50 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
50 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54
54
55 app : reference to QApplication parent
55 app : reference to QApplication parent
56 confirm_exit : bool, optional
56 confirm_exit : bool, optional
57 Whether we should prompt on close of tabs
57 Whether we should prompt on close of tabs
58 new_frontend_factory : callable
58 new_frontend_factory : callable
59 A callable that returns a new IPythonWidget instance, attached to
59 A callable that returns a new IPythonWidget instance, attached to
60 its own running kernel.
60 its own running kernel.
61 slave_frontend_factory : callable
61 slave_frontend_factory : callable
62 A callable that takes an existing IPythonWidget, and returns a new
62 A callable that takes an existing IPythonWidget, and returns a new
63 IPythonWidget instance, attached to the same kernel.
63 IPythonWidget instance, attached to the same kernel.
64 """
64 """
65
65
66 super(MainWindow, self).__init__()
66 super(MainWindow, self).__init__()
67 self._kernel_counter = 0
67 self._kernel_counter = 0
68 self._app = app
68 self._app = app
69 self.confirm_exit = confirm_exit
69 self.confirm_exit = confirm_exit
70 self.new_frontend_factory = new_frontend_factory
70 self.new_frontend_factory = new_frontend_factory
71 self.slave_frontend_factory = slave_frontend_factory
71 self.slave_frontend_factory = slave_frontend_factory
72
72
73 self.tab_widget = QtGui.QTabWidget(self)
73 self.tab_widget = QtGui.QTabWidget(self)
74 self.tab_widget.setDocumentMode(True)
74 self.tab_widget.setDocumentMode(True)
75 self.tab_widget.setTabsClosable(True)
75 self.tab_widget.setTabsClosable(True)
76 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
76 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
77
77
78 self.setCentralWidget(self.tab_widget)
78 self.setCentralWidget(self.tab_widget)
79 # hide tab bar at first, since we have no tabs:
79 # hide tab bar at first, since we have no tabs:
80 self.tab_widget.tabBar().setVisible(False)
80 self.tab_widget.tabBar().setVisible(False)
81 # prevent focus in tab bar
81 # prevent focus in tab bar
82 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
82 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
83
83
84 def update_tab_bar_visibility(self):
84 def update_tab_bar_visibility(self):
85 """ update visibility of the tabBar depending of the number of tab
85 """ update visibility of the tabBar depending of the number of tab
86
86
87 0 or 1 tab, tabBar hidden
87 0 or 1 tab, tabBar hidden
88 2+ tabs, tabBar visible
88 2+ tabs, tabBar visible
89
89
90 send a self.close if number of tab ==0
90 send a self.close if number of tab ==0
91
91
92 need to be called explicitely, or be connected to tabInserted/tabRemoved
92 need to be called explicitely, or be connected to tabInserted/tabRemoved
93 """
93 """
94 if self.tab_widget.count() <= 1:
94 if self.tab_widget.count() <= 1:
95 self.tab_widget.tabBar().setVisible(False)
95 self.tab_widget.tabBar().setVisible(False)
96 else:
96 else:
97 self.tab_widget.tabBar().setVisible(True)
97 self.tab_widget.tabBar().setVisible(True)
98 if self.tab_widget.count()==0 :
98 if self.tab_widget.count()==0 :
99 self.close()
99 self.close()
100
100
101 @property
101 @property
102 def next_kernel_id(self):
102 def next_kernel_id(self):
103 """constantly increasing counter for kernel IDs"""
103 """constantly increasing counter for kernel IDs"""
104 c = self._kernel_counter
104 c = self._kernel_counter
105 self._kernel_counter += 1
105 self._kernel_counter += 1
106 return c
106 return c
107
107
108 @property
108 @property
109 def active_frontend(self):
109 def active_frontend(self):
110 return self.tab_widget.currentWidget()
110 return self.tab_widget.currentWidget()
111
111
112 def create_tab_with_new_frontend(self):
112 def create_tab_with_new_frontend(self):
113 """create a new frontend and attach it to a new tab"""
113 """create a new frontend and attach it to a new tab"""
114 widget = self.new_frontend_factory()
114 widget = self.new_frontend_factory()
115 self.add_tab_with_frontend(widget)
115 self.add_tab_with_frontend(widget)
116
116
117 def create_tab_with_current_kernel(self):
117 def create_tab_with_current_kernel(self):
118 """create a new frontend attached to the same kernel as the current tab"""
118 """create a new frontend attached to the same kernel as the current tab"""
119 current_widget = self.tab_widget.currentWidget()
119 current_widget = self.tab_widget.currentWidget()
120 current_widget_index = self.tab_widget.indexOf(current_widget)
120 current_widget_index = self.tab_widget.indexOf(current_widget)
121 current_widget_name = self.tab_widget.tabText(current_widget_index)
121 current_widget_name = self.tab_widget.tabText(current_widget_index)
122 widget = self.slave_frontend_factory(current_widget)
122 widget = self.slave_frontend_factory(current_widget)
123 if 'slave' in current_widget_name:
123 if 'slave' in current_widget_name:
124 # don't keep stacking slaves
124 # don't keep stacking slaves
125 name = current_widget_name
125 name = current_widget_name
126 else:
126 else:
127 name = '(%s) slave' % current_widget_name
127 name = '(%s) slave' % current_widget_name
128 self.add_tab_with_frontend(widget,name=name)
128 self.add_tab_with_frontend(widget,name=name)
129
129
130 def close_tab(self,current_tab):
130 def close_tab(self,current_tab):
131 """ Called when you need to try to close a tab.
131 """ Called when you need to try to close a tab.
132
132
133 It takes the number of the tab to be closed as argument, or a referece
133 It takes the number of the tab to be closed as argument, or a referece
134 to the wiget insite this tab
134 to the wiget insite this tab
135 """
135 """
136
136
137 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
137 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
138 # and a reference to the trontend to close
138 # and a reference to the trontend to close
139 if type(current_tab) is not int :
139 if type(current_tab) is not int :
140 current_tab = self.tab_widget.indexOf(current_tab)
140 current_tab = self.tab_widget.indexOf(current_tab)
141 closing_widget=self.tab_widget.widget(current_tab)
141 closing_widget=self.tab_widget.widget(current_tab)
142
142
143
143
144 # when trying to be closed, widget might re-send a request to be closed again, but will
144 # when trying to be closed, widget might re-send a request to be closed again, but will
145 # be deleted when event will be processed. So need to check that widget still exist and
145 # be deleted when event will be processed. So need to check that widget still exist and
146 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
146 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
147 # re-send by this fonction on the master widget, which ask all slaves widget to exit
147 # re-send by this fonction on the master widget, which ask all slaves widget to exit
148 if closing_widget==None:
148 if closing_widget==None:
149 return
149 return
150
150
151 #get a list of all slave widgets on the same kernel.
151 #get a list of all slave widgets on the same kernel.
152 slave_tabs = self.find_slave_widgets(closing_widget)
152 slave_tabs = self.find_slave_widgets(closing_widget)
153
153
154 keepkernel = None #Use the prompt by default
154 keepkernel = None #Use the prompt by default
155 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
155 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
156 keepkernel = closing_widget._keep_kernel_on_exit
156 keepkernel = closing_widget._keep_kernel_on_exit
157 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
157 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
158 # we set local slave tabs._hidden to True to avoid prompting for kernel
158 # we set local slave tabs._hidden to True to avoid prompting for kernel
159 # restart when they get the signal. and then "forward" the 'exit'
159 # restart when they get the signal. and then "forward" the 'exit'
160 # to the main window
160 # to the main window
161 if keepkernel is not None:
161 if keepkernel is not None:
162 for tab in slave_tabs:
162 for tab in slave_tabs:
163 tab._hidden = True
163 tab._hidden = True
164 if closing_widget in slave_tabs:
164 if closing_widget in slave_tabs:
165 try :
165 try :
166 self.find_master_tab(closing_widget).execute('exit')
166 self.find_master_tab(closing_widget).execute('exit')
167 except AttributeError:
167 except AttributeError:
168 self.log.info("Master already closed or not local, closing only current tab")
168 self.log.info("Master already closed or not local, closing only current tab")
169 self.tab_widget.removeTab(current_tab)
169 self.tab_widget.removeTab(current_tab)
170 self.update_tab_bar_visibility()
170 self.update_tab_bar_visibility()
171 return
171 return
172
172
173 kernel_manager = closing_widget.kernel_manager
173 kernel_manager = closing_widget.kernel_manager
174
174
175 if keepkernel is None and not closing_widget._confirm_exit:
175 if keepkernel is None and not closing_widget._confirm_exit:
176 # don't prompt, just terminate the kernel if we own it
176 # don't prompt, just terminate the kernel if we own it
177 # or leave it alone if we don't
177 # or leave it alone if we don't
178 keepkernel = closing_widget._existing
178 keepkernel = closing_widget._existing
179 if keepkernel is None: #show prompt
179 if keepkernel is None: #show prompt
180 if kernel_manager and kernel_manager.channels_running:
180 if kernel_manager and kernel_manager.channels_running:
181 title = self.window().windowTitle()
181 title = self.window().windowTitle()
182 cancel = QtGui.QMessageBox.Cancel
182 cancel = QtGui.QMessageBox.Cancel
183 okay = QtGui.QMessageBox.Ok
183 okay = QtGui.QMessageBox.Ok
184 if closing_widget._may_close:
184 if closing_widget._may_close:
185 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
185 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
186 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
186 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
187 justthis = QtGui.QPushButton("&No, just this Tab", self)
187 justthis = QtGui.QPushButton("&No, just this Tab", self)
188 justthis.setShortcut('N')
188 justthis.setShortcut('N')
189 closeall = QtGui.QPushButton("&Yes, close all", self)
189 closeall = QtGui.QPushButton("&Yes, close all", self)
190 closeall.setShortcut('Y')
190 closeall.setShortcut('Y')
191 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
191 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
192 title, msg)
192 title, msg)
193 box.setInformativeText(info)
193 box.setInformativeText(info)
194 box.addButton(cancel)
194 box.addButton(cancel)
195 box.addButton(justthis, QtGui.QMessageBox.NoRole)
195 box.addButton(justthis, QtGui.QMessageBox.NoRole)
196 box.addButton(closeall, QtGui.QMessageBox.YesRole)
196 box.addButton(closeall, QtGui.QMessageBox.YesRole)
197 box.setDefaultButton(closeall)
197 box.setDefaultButton(closeall)
198 box.setEscapeButton(cancel)
198 box.setEscapeButton(cancel)
199 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
199 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
200 box.setIconPixmap(pixmap)
200 box.setIconPixmap(pixmap)
201 reply = box.exec_()
201 reply = box.exec_()
202 if reply == 1: # close All
202 if reply == 1: # close All
203 for slave in slave_tabs:
203 for slave in slave_tabs:
204 background(slave.kernel_manager.stop_channels)
204 background(slave.kernel_manager.stop_channels)
205 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
205 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
206 closing_widget.execute("exit")
206 closing_widget.execute("exit")
207 self.tab_widget.removeTab(current_tab)
207 self.tab_widget.removeTab(current_tab)
208 background(kernel_manager.stop_channels)
208 background(kernel_manager.stop_channels)
209 elif reply == 0: # close Console
209 elif reply == 0: # close Console
210 if not closing_widget._existing:
210 if not closing_widget._existing:
211 # Have kernel: don't quit, just close the tab
211 # Have kernel: don't quit, just close the tab
212 closing_widget.execute("exit True")
212 closing_widget.execute("exit True")
213 self.tab_widget.removeTab(current_tab)
213 self.tab_widget.removeTab(current_tab)
214 background(kernel_manager.stop_channels)
214 background(kernel_manager.stop_channels)
215 else:
215 else:
216 reply = QtGui.QMessageBox.question(self, title,
216 reply = QtGui.QMessageBox.question(self, title,
217 "Are you sure you want to close this Console?"+
217 "Are you sure you want to close this Console?"+
218 "\nThe Kernel and other Consoles will remain active.",
218 "\nThe Kernel and other Consoles will remain active.",
219 okay|cancel,
219 okay|cancel,
220 defaultButton=okay
220 defaultButton=okay
221 )
221 )
222 if reply == okay:
222 if reply == okay:
223 self.tab_widget.removeTab(current_tab)
223 self.tab_widget.removeTab(current_tab)
224 elif keepkernel: #close console but leave kernel running (no prompt)
224 elif keepkernel: #close console but leave kernel running (no prompt)
225 self.tab_widget.removeTab(current_tab)
225 self.tab_widget.removeTab(current_tab)
226 background(kernel_manager.stop_channels)
226 background(kernel_manager.stop_channels)
227 else: #close console and kernel (no prompt)
227 else: #close console and kernel (no prompt)
228 self.tab_widget.removeTab(current_tab)
228 self.tab_widget.removeTab(current_tab)
229 if kernel_manager and kernel_manager.channels_running:
229 if kernel_manager and kernel_manager.channels_running:
230 for slave in slave_tabs:
230 for slave in slave_tabs:
231 background(slave.kernel_manager.stop_channels)
231 background(slave.kernel_manager.stop_channels)
232 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
232 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
233 kernel_manager.shutdown_kernel()
233 kernel_manager.shutdown_kernel()
234 background(kernel_manager.stop_channels)
234 background(kernel_manager.stop_channels)
235
235
236 self.update_tab_bar_visibility()
236 self.update_tab_bar_visibility()
237
237
238 def add_tab_with_frontend(self,frontend,name=None):
238 def add_tab_with_frontend(self,frontend,name=None):
239 """ insert a tab with a given frontend in the tab bar, and give it a name
239 """ insert a tab with a given frontend in the tab bar, and give it a name
240
240
241 """
241 """
242 if not name:
242 if not name:
243 name = 'kernel %i' % self.next_kernel_id
243 name = 'kernel %i' % self.next_kernel_id
244 self.tab_widget.addTab(frontend,name)
244 self.tab_widget.addTab(frontend,name)
245 self.update_tab_bar_visibility()
245 self.update_tab_bar_visibility()
246 self.make_frontend_visible(frontend)
246 self.make_frontend_visible(frontend)
247 frontend.exit_requested.connect(self.close_tab)
247 frontend.exit_requested.connect(self.close_tab)
248
248
249 def next_tab(self):
249 def next_tab(self):
250 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
250 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
251
251
252 def prev_tab(self):
252 def prev_tab(self):
253 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
253 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
254
254
255 def make_frontend_visible(self,frontend):
255 def make_frontend_visible(self,frontend):
256 widget_index=self.tab_widget.indexOf(frontend)
256 widget_index=self.tab_widget.indexOf(frontend)
257 if widget_index > 0 :
257 if widget_index > 0 :
258 self.tab_widget.setCurrentIndex(widget_index)
258 self.tab_widget.setCurrentIndex(widget_index)
259
259
260 def find_master_tab(self,tab,as_list=False):
260 def find_master_tab(self,tab,as_list=False):
261 """
261 """
262 Try to return the frontend that own the kernel attached to the given widget/tab.
262 Try to return the frontend that own the kernel attached to the given widget/tab.
263
263
264 Only find frontend owed by the current application. Selection
264 Only find frontend owed by the current application. Selection
265 based on port of the kernel, might be inacurate if several kernel
265 based on port of the kernel, might be inacurate if several kernel
266 on different ip use same port number.
266 on different ip use same port number.
267
267
268 This fonction does the conversion tabNumber/widget if needed.
268 This fonction does the conversion tabNumber/widget if needed.
269 Might return None if no master widget (non local kernel)
269 Might return None if no master widget (non local kernel)
270 Will crash IPython if more than 1 masterWidget
270 Will crash IPython if more than 1 masterWidget
271
271
272 When asList set to True, always return a list of widget(s) owning
272 When asList set to True, always return a list of widget(s) owning
273 the kernel. The list might be empty or containing several Widget.
273 the kernel. The list might be empty or containing several Widget.
274 """
274 """
275
275
276 #convert from/to int/richIpythonWidget if needed
276 #convert from/to int/richIpythonWidget if needed
277 if isinstance(tab, int):
277 if isinstance(tab, int):
278 tab = self.tab_widget.widget(tab)
278 tab = self.tab_widget.widget(tab)
279 km=tab.kernel_manager
279 km=tab.kernel_manager
280
280
281 #build list of all widgets
281 #build list of all widgets
282 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
282 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
283
283
284 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
284 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
285 # And should have a _may_close attribute
285 # And should have a _may_close attribute
286 filtered_widget_list = [ widget for widget in widget_list if
286 filtered_widget_list = [ widget for widget in widget_list if
287 widget.kernel_manager.connection_file == km.connection_file and
287 widget.kernel_manager.connection_file == km.connection_file and
288 hasattr(widget,'_may_close') ]
288 hasattr(widget,'_may_close') ]
289 # the master widget is the one that may close the kernel
289 # the master widget is the one that may close the kernel
290 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
290 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
291 if as_list:
291 if as_list:
292 return master_widget
292 return master_widget
293 assert(len(master_widget)<=1 )
293 assert(len(master_widget)<=1 )
294 if len(master_widget)==0:
294 if len(master_widget)==0:
295 return None
295 return None
296
296
297 return master_widget[0]
297 return master_widget[0]
298
298
299 def find_slave_widgets(self,tab):
299 def find_slave_widgets(self,tab):
300 """return all the frontends that do not own the kernel attached to the given widget/tab.
300 """return all the frontends that do not own the kernel attached to the given widget/tab.
301
301
302 Only find frontends owned by the current application. Selection
302 Only find frontends owned by the current application. Selection
303 based on connection file of the kernel.
303 based on connection file of the kernel.
304
304
305 This function does the conversion tabNumber/widget if needed.
305 This function does the conversion tabNumber/widget if needed.
306 """
306 """
307 #convert from/to int/richIpythonWidget if needed
307 #convert from/to int/richIpythonWidget if needed
308 if isinstance(tab, int):
308 if isinstance(tab, int):
309 tab = self.tab_widget.widget(tab)
309 tab = self.tab_widget.widget(tab)
310 km=tab.kernel_manager
310 km=tab.kernel_manager
311
311
312 #build list of all widgets
312 #build list of all widgets
313 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
313 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
314
314
315 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
315 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
316 filtered_widget_list = ( widget for widget in widget_list if
316 filtered_widget_list = ( widget for widget in widget_list if
317 widget.kernel_manager.connection_file == km.connection_file)
317 widget.kernel_manager.connection_file == km.connection_file)
318 # Get a list of all widget owning the same kernel and removed it from
318 # Get a list of all widget owning the same kernel and removed it from
319 # the previous cadidate. (better using sets ?)
319 # the previous cadidate. (better using sets ?)
320 master_widget_list = self.find_master_tab(tab, as_list=True)
320 master_widget_list = self.find_master_tab(tab, as_list=True)
321 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
321 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
322
322
323 return slave_list
323 return slave_list
324
324
325 # Populate the menu bar with common actions and shortcuts
325 # Populate the menu bar with common actions and shortcuts
326 def add_menu_action(self, menu, action, defer_shortcut=False):
326 def add_menu_action(self, menu, action, defer_shortcut=False):
327 """Add action to menu as well as self
327 """Add action to menu as well as self
328
328
329 So that when the menu bar is invisible, its actions are still available.
329 So that when the menu bar is invisible, its actions are still available.
330
330
331 If defer_shortcut is True, set the shortcut context to widget-only,
331 If defer_shortcut is True, set the shortcut context to widget-only,
332 where it will avoid conflict with shortcuts already bound to the
332 where it will avoid conflict with shortcuts already bound to the
333 widgets themselves.
333 widgets themselves.
334 """
334 """
335 menu.addAction(action)
335 menu.addAction(action)
336 self.addAction(action)
336 self.addAction(action)
337
337
338 if defer_shortcut:
338 if defer_shortcut:
339 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
339 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
340
340
341 def init_menu_bar(self):
341 def init_menu_bar(self):
342 #create menu in the order they should appear in the menu bar
342 #create menu in the order they should appear in the menu bar
343 self.init_file_menu()
343 self.init_file_menu()
344 self.init_edit_menu()
344 self.init_edit_menu()
345 self.init_view_menu()
345 self.init_view_menu()
346 self.init_kernel_menu()
346 self.init_kernel_menu()
347 self.init_magic_menu()
347 self.init_magic_menu()
348 self.init_window_menu()
348 self.init_window_menu()
349 self.init_help_menu()
349 self.init_help_menu()
350
350
351 def init_file_menu(self):
351 def init_file_menu(self):
352 self.file_menu = self.menuBar().addMenu("&File")
352 self.file_menu = self.menuBar().addMenu("&File")
353
353
354 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
354 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
355 self,
355 self,
356 shortcut="Ctrl+T",
356 shortcut="Ctrl+T",
357 triggered=self.create_tab_with_new_frontend)
357 triggered=self.create_tab_with_new_frontend)
358 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
358 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
359
359
360 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
360 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
361 self,
361 self,
362 shortcut="Ctrl+Shift+T",
362 shortcut="Ctrl+Shift+T",
363 triggered=self.create_tab_with_current_kernel)
363 triggered=self.create_tab_with_current_kernel)
364 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
364 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
365
365
366 self.file_menu.addSeparator()
366 self.file_menu.addSeparator()
367
367
368 self.close_action=QtGui.QAction("&Close Tab",
368 self.close_action=QtGui.QAction("&Close Tab",
369 self,
369 self,
370 shortcut=QtGui.QKeySequence.Close,
370 shortcut=QtGui.QKeySequence.Close,
371 triggered=self.close_active_frontend
371 triggered=self.close_active_frontend
372 )
372 )
373 self.add_menu_action(self.file_menu, self.close_action)
373 self.add_menu_action(self.file_menu, self.close_action)
374
374
375 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
375 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
376 self,
376 self,
377 shortcut=QtGui.QKeySequence.Save,
377 shortcut=QtGui.QKeySequence.Save,
378 triggered=self.export_action_active_frontend
378 triggered=self.export_action_active_frontend
379 )
379 )
380 self.add_menu_action(self.file_menu, self.export_action, True)
380 self.add_menu_action(self.file_menu, self.export_action, True)
381
381
382 self.file_menu.addSeparator()
382 self.file_menu.addSeparator()
383
383
384 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
384 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
385 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
385 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
386 # Only override the default if there is a collision.
386 # Only override the default if there is a collision.
387 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
387 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
388 printkey = "Ctrl+Shift+P"
388 printkey = "Ctrl+Shift+P"
389 self.print_action = QtGui.QAction("&Print",
389 self.print_action = QtGui.QAction("&Print",
390 self,
390 self,
391 shortcut=printkey,
391 shortcut=printkey,
392 triggered=self.print_action_active_frontend)
392 triggered=self.print_action_active_frontend)
393 self.add_menu_action(self.file_menu, self.print_action, True)
393 self.add_menu_action(self.file_menu, self.print_action, True)
394
394
395 if sys.platform != 'darwin':
395 if sys.platform != 'darwin':
396 # OSX always has Quit in the Application menu, only add it
396 # OSX always has Quit in the Application menu, only add it
397 # to the File menu elsewhere.
397 # to the File menu elsewhere.
398
398
399 self.file_menu.addSeparator()
399 self.file_menu.addSeparator()
400
400
401 self.quit_action = QtGui.QAction("&Quit",
401 self.quit_action = QtGui.QAction("&Quit",
402 self,
402 self,
403 shortcut=QtGui.QKeySequence.Quit,
403 shortcut=QtGui.QKeySequence.Quit,
404 triggered=self.close,
404 triggered=self.close,
405 )
405 )
406 self.add_menu_action(self.file_menu, self.quit_action)
406 self.add_menu_action(self.file_menu, self.quit_action)
407
407
408
408
409 def init_edit_menu(self):
409 def init_edit_menu(self):
410 self.edit_menu = self.menuBar().addMenu("&Edit")
410 self.edit_menu = self.menuBar().addMenu("&Edit")
411
411
412 self.undo_action = QtGui.QAction("&Undo",
412 self.undo_action = QtGui.QAction("&Undo",
413 self,
413 self,
414 shortcut=QtGui.QKeySequence.Undo,
414 shortcut=QtGui.QKeySequence.Undo,
415 statusTip="Undo last action if possible",
415 statusTip="Undo last action if possible",
416 triggered=self.undo_active_frontend
416 triggered=self.undo_active_frontend
417 )
417 )
418 self.add_menu_action(self.edit_menu, self.undo_action)
418 self.add_menu_action(self.edit_menu, self.undo_action)
419
419
420 self.redo_action = QtGui.QAction("&Redo",
420 self.redo_action = QtGui.QAction("&Redo",
421 self,
421 self,
422 shortcut=QtGui.QKeySequence.Redo,
422 shortcut=QtGui.QKeySequence.Redo,
423 statusTip="Redo last action if possible",
423 statusTip="Redo last action if possible",
424 triggered=self.redo_active_frontend)
424 triggered=self.redo_active_frontend)
425 self.add_menu_action(self.edit_menu, self.redo_action)
425 self.add_menu_action(self.edit_menu, self.redo_action)
426
426
427 self.edit_menu.addSeparator()
427 self.edit_menu.addSeparator()
428
428
429 self.cut_action = QtGui.QAction("&Cut",
429 self.cut_action = QtGui.QAction("&Cut",
430 self,
430 self,
431 shortcut=QtGui.QKeySequence.Cut,
431 shortcut=QtGui.QKeySequence.Cut,
432 triggered=self.cut_active_frontend
432 triggered=self.cut_active_frontend
433 )
433 )
434 self.add_menu_action(self.edit_menu, self.cut_action, True)
434 self.add_menu_action(self.edit_menu, self.cut_action, True)
435
435
436 self.copy_action = QtGui.QAction("&Copy",
436 self.copy_action = QtGui.QAction("&Copy",
437 self,
437 self,
438 shortcut=QtGui.QKeySequence.Copy,
438 shortcut=QtGui.QKeySequence.Copy,
439 triggered=self.copy_active_frontend
439 triggered=self.copy_active_frontend
440 )
440 )
441 self.add_menu_action(self.edit_menu, self.copy_action, True)
441 self.add_menu_action(self.edit_menu, self.copy_action, True)
442
442
443 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
443 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
444 self,
444 self,
445 shortcut="Ctrl+Shift+C",
445 shortcut="Ctrl+Shift+C",
446 triggered=self.copy_raw_active_frontend
446 triggered=self.copy_raw_active_frontend
447 )
447 )
448 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
448 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
449
449
450 self.paste_action = QtGui.QAction("&Paste",
450 self.paste_action = QtGui.QAction("&Paste",
451 self,
451 self,
452 shortcut=QtGui.QKeySequence.Paste,
452 shortcut=QtGui.QKeySequence.Paste,
453 triggered=self.paste_active_frontend
453 triggered=self.paste_active_frontend
454 )
454 )
455 self.add_menu_action(self.edit_menu, self.paste_action, True)
455 self.add_menu_action(self.edit_menu, self.paste_action, True)
456
456
457 self.edit_menu.addSeparator()
457 self.edit_menu.addSeparator()
458
458
459 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
459 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
460 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
460 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
461 # Only override the default if there is a collision.
461 # Only override the default if there is a collision.
462 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
462 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
463 selectall = "Ctrl+Shift+A"
463 selectall = "Ctrl+Shift+A"
464 self.select_all_action = QtGui.QAction("Select &All",
464 self.select_all_action = QtGui.QAction("Select &All",
465 self,
465 self,
466 shortcut=selectall,
466 shortcut=selectall,
467 triggered=self.select_all_active_frontend
467 triggered=self.select_all_active_frontend
468 )
468 )
469 self.add_menu_action(self.edit_menu, self.select_all_action, True)
469 self.add_menu_action(self.edit_menu, self.select_all_action, True)
470
470
471
471
472 def init_view_menu(self):
472 def init_view_menu(self):
473 self.view_menu = self.menuBar().addMenu("&View")
473 self.view_menu = self.menuBar().addMenu("&View")
474
474
475 if sys.platform != 'darwin':
475 if sys.platform != 'darwin':
476 # disable on OSX, where there is always a menu bar
476 # disable on OSX, where there is always a menu bar
477 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
477 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
478 self,
478 self,
479 shortcut="Ctrl+Shift+M",
479 shortcut="Ctrl+Shift+M",
480 statusTip="Toggle visibility of menubar",
480 statusTip="Toggle visibility of menubar",
481 triggered=self.toggle_menu_bar)
481 triggered=self.toggle_menu_bar)
482 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
482 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
483
483
484 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
484 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
485 self.full_screen_act = QtGui.QAction("&Full Screen",
485 self.full_screen_act = QtGui.QAction("&Full Screen",
486 self,
486 self,
487 shortcut=fs_key,
487 shortcut=fs_key,
488 statusTip="Toggle between Fullscreen and Normal Size",
488 statusTip="Toggle between Fullscreen and Normal Size",
489 triggered=self.toggleFullScreen)
489 triggered=self.toggleFullScreen)
490 self.add_menu_action(self.view_menu, self.full_screen_act)
490 self.add_menu_action(self.view_menu, self.full_screen_act)
491
491
492 self.view_menu.addSeparator()
492 self.view_menu.addSeparator()
493
493
494 self.increase_font_size = QtGui.QAction("Zoom &In",
494 self.increase_font_size = QtGui.QAction("Zoom &In",
495 self,
495 self,
496 shortcut=QtGui.QKeySequence.ZoomIn,
496 shortcut=QtGui.QKeySequence.ZoomIn,
497 triggered=self.increase_font_size_active_frontend
497 triggered=self.increase_font_size_active_frontend
498 )
498 )
499 self.add_menu_action(self.view_menu, self.increase_font_size, True)
499 self.add_menu_action(self.view_menu, self.increase_font_size, True)
500
500
501 self.decrease_font_size = QtGui.QAction("Zoom &Out",
501 self.decrease_font_size = QtGui.QAction("Zoom &Out",
502 self,
502 self,
503 shortcut=QtGui.QKeySequence.ZoomOut,
503 shortcut=QtGui.QKeySequence.ZoomOut,
504 triggered=self.decrease_font_size_active_frontend
504 triggered=self.decrease_font_size_active_frontend
505 )
505 )
506 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
506 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
507
507
508 self.reset_font_size = QtGui.QAction("Zoom &Reset",
508 self.reset_font_size = QtGui.QAction("Zoom &Reset",
509 self,
509 self,
510 shortcut="Ctrl+0",
510 shortcut="Ctrl+0",
511 triggered=self.reset_font_size_active_frontend
511 triggered=self.reset_font_size_active_frontend
512 )
512 )
513 self.add_menu_action(self.view_menu, self.reset_font_size, True)
513 self.add_menu_action(self.view_menu, self.reset_font_size, True)
514
514
515 self.view_menu.addSeparator()
515 self.view_menu.addSeparator()
516
516
517 self.clear_action = QtGui.QAction("&Clear Screen",
517 self.clear_action = QtGui.QAction("&Clear Screen",
518 self,
518 self,
519 shortcut='Ctrl+L',
519 shortcut='Ctrl+L',
520 statusTip="Clear the console",
520 statusTip="Clear the console",
521 triggered=self.clear_magic_active_frontend)
521 triggered=self.clear_magic_active_frontend)
522 self.add_menu_action(self.view_menu, self.clear_action)
522 self.add_menu_action(self.view_menu, self.clear_action)
523
523
524 def init_kernel_menu(self):
524 def init_kernel_menu(self):
525 self.kernel_menu = self.menuBar().addMenu("&Kernel")
525 self.kernel_menu = self.menuBar().addMenu("&Kernel")
526 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
526 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
527 # keep the signal shortcuts to ctrl, rather than
527 # keep the signal shortcuts to ctrl, rather than
528 # platform-default like we do elsewhere.
528 # platform-default like we do elsewhere.
529
529
530 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
530 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
531
531
532 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
532 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
533 self,
533 self,
534 triggered=self.interrupt_kernel_active_frontend,
534 triggered=self.interrupt_kernel_active_frontend,
535 shortcut=ctrl+"+C",
535 shortcut=ctrl+"+C",
536 )
536 )
537 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
537 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
538
538
539 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
539 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
540 self,
540 self,
541 triggered=self.restart_kernel_active_frontend,
541 triggered=self.restart_kernel_active_frontend,
542 shortcut=ctrl+"+.",
542 shortcut=ctrl+"+.",
543 )
543 )
544 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
544 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
545
545
546 self.kernel_menu.addSeparator()
546 self.kernel_menu.addSeparator()
547
547
548 def _make_dynamic_magic(self,magic):
548 def _make_dynamic_magic(self,magic):
549 """Return a function `fun` that will execute `magic` on active frontend.
549 """Return a function `fun` that will execute `magic` on active frontend.
550
550
551 Parameters
551 Parameters
552 ----------
552 ----------
553 magic : string
553 magic : string
554 string that will be executed as is when the returned function is called
554 string that will be executed as is when the returned function is called
555
555
556 Returns
556 Returns
557 -------
557 -------
558 fun : function
558 fun : function
559 function with no parameters, when called will execute `magic` on the
559 function with no parameters, when called will execute `magic` on the
560 current active frontend at call time
560 current active frontend at call time
561
561
562 See Also
562 See Also
563 --------
563 --------
564 populate_all_magic_menu : generate the "All Magics..." menu
564 populate_all_magic_menu : generate the "All Magics..." menu
565
565
566 Notes
566 Notes
567 -----
567 -----
568 `fun` execute `magic` an active frontend at the moment it is triggerd,
568 `fun` execute `magic` an active frontend at the moment it is triggerd,
569 not the active frontend at the moment it has been created.
569 not the active frontend at the moment it has been created.
570
570
571 This function is mostly used to create the "All Magics..." Menu at run time.
571 This function is mostly used to create the "All Magics..." Menu at run time.
572 """
572 """
573 # need to level nested function to be sure to past magic
573 # need to level nested function to be sure to past magic
574 # on active frontend **at run time**.
574 # on active frontend **at run time**.
575 def inner_dynamic_magic():
575 def inner_dynamic_magic():
576 self.active_frontend.execute(magic)
576 self.active_frontend.execute(magic)
577 inner_dynamic_magic.__name__ = "dynamics_magic_s"
577 inner_dynamic_magic.__name__ = "dynamics_magic_s"
578 return inner_dynamic_magic
578 return inner_dynamic_magic
579
579
580 def populate_all_magic_menu(self, listofmagic=None):
580 def populate_all_magic_menu(self, listofmagic=None):
581 """Clean "All Magics..." menu and repopulate it with `listofmagic`
581 """Clean "All Magics..." menu and repopulate it with `listofmagic`
582
582
583 Parameters
583 Parameters
584 ----------
584 ----------
585 listofmagic : string,
585 listofmagic : string,
586 repr() of a list of strings, send back by the kernel
586 repr() of a list of strings, send back by the kernel
587
587
588 Notes
588 Notes
589 -----
589 -----
590 `listofmagic`is a repr() of list because it is fed with the result of
590 `listofmagic`is a repr() of list because it is fed with the result of
591 a 'user_expression'
591 a 'user_expression'
592 """
592 """
593 alm_magic_menu = self.all_magic_menu
593 alm_magic_menu = self.all_magic_menu
594 alm_magic_menu.clear()
594 alm_magic_menu.clear()
595
595
596 # list of protected magic that don't like to be called without argument
596 # list of protected magic that don't like to be called without argument
597 # append '?' to the end to print the docstring when called from the menu
597 # append '?' to the end to print the docstring when called from the menu
598 protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
598 protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
599 magics=re.findall('\w+', listofmagic)
599 magics=re.findall('\w+', listofmagic)
600 for magic in magics:
600 for magic in magics:
601 if magic in protected_magic:
601 if magic in protected_magic:
602 pmagic = '%s%s%s'%('%',magic,'?')
602 pmagic = '%s%s%s'%('%',magic,'?')
603 else:
603 else:
604 pmagic = '%s%s'%('%',magic)
604 pmagic = '%s%s'%('%',magic)
605 xaction = QtGui.QAction(pmagic,
605 xaction = QtGui.QAction(pmagic,
606 self,
606 self,
607 triggered=self._make_dynamic_magic(pmagic)
607 triggered=self._make_dynamic_magic(pmagic)
608 )
608 )
609 alm_magic_menu.addAction(xaction)
609 alm_magic_menu.addAction(xaction)
610
610
611 def update_all_magic_menu(self):
611 def update_all_magic_menu(self):
612 """ Update the list on magic in the "All Magics..." Menu
612 """ Update the list on magic in the "All Magics..." Menu
613
613
614 Request the kernel with the list of availlable magic and populate the
614 Request the kernel with the list of availlable magic and populate the
615 menu with the list received back
615 menu with the list received back
616
616
617 """
617 """
618 # first define a callback which will get the list of all magic and put it in the menu.
618 # first define a callback which will get the list of all magic and put it in the menu.
619 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
619 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
620
620
621 def init_magic_menu(self):
621 def init_magic_menu(self):
622 self.magic_menu = self.menuBar().addMenu("&Magic")
622 self.magic_menu = self.menuBar().addMenu("&Magic")
623 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
623 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
624
624
625 # This action should usually not appear as it will be cleared when menu
625 # This action should usually not appear as it will be cleared when menu
626 # is updated at first kernel response. Though, it is necessary when
626 # is updated at first kernel response. Though, it is necessary when
627 # connecting through X-forwarding, as in this case, the menu is not
627 # connecting through X-forwarding, as in this case, the menu is not
628 # auto updated, SO DO NOT DELETE.
628 # auto updated, SO DO NOT DELETE.
629 self.pop = QtGui.QAction("&Update All Magic Menu ",
629
630 self, triggered=self.update_all_magic_menu)
630 ########################################################################
631 self.add_menu_action(self.all_magic_menu, self.pop)
631 ## TEMPORARILY DISABLED - see #1057 for details. Uncomment this
632 ## section when a proper fix is found
633
634 ## self.pop = QtGui.QAction("&Update All Magic Menu ",
635 ## self, triggered=self.update_all_magic_menu)
636 ## self.add_menu_action(self.all_magic_menu, self.pop)
637
638 ## END TEMPORARY FIX
639 ########################################################################
632
640
633 self.reset_action = QtGui.QAction("&Reset",
641 self.reset_action = QtGui.QAction("&Reset",
634 self,
642 self,
635 statusTip="Clear all varible from workspace",
643 statusTip="Clear all varible from workspace",
636 triggered=self.reset_magic_active_frontend)
644 triggered=self.reset_magic_active_frontend)
637 self.add_menu_action(self.magic_menu, self.reset_action)
645 self.add_menu_action(self.magic_menu, self.reset_action)
638
646
639 self.history_action = QtGui.QAction("&History",
647 self.history_action = QtGui.QAction("&History",
640 self,
648 self,
641 statusTip="show command history",
649 statusTip="show command history",
642 triggered=self.history_magic_active_frontend)
650 triggered=self.history_magic_active_frontend)
643 self.add_menu_action(self.magic_menu, self.history_action)
651 self.add_menu_action(self.magic_menu, self.history_action)
644
652
645 self.save_action = QtGui.QAction("E&xport History ",
653 self.save_action = QtGui.QAction("E&xport History ",
646 self,
654 self,
647 statusTip="Export History as Python File",
655 statusTip="Export History as Python File",
648 triggered=self.save_magic_active_frontend)
656 triggered=self.save_magic_active_frontend)
649 self.add_menu_action(self.magic_menu, self.save_action)
657 self.add_menu_action(self.magic_menu, self.save_action)
650
658
651 self.who_action = QtGui.QAction("&Who",
659 self.who_action = QtGui.QAction("&Who",
652 self,
660 self,
653 statusTip="List interactive variable",
661 statusTip="List interactive variable",
654 triggered=self.who_magic_active_frontend)
662 triggered=self.who_magic_active_frontend)
655 self.add_menu_action(self.magic_menu, self.who_action)
663 self.add_menu_action(self.magic_menu, self.who_action)
656
664
657 self.who_ls_action = QtGui.QAction("Wh&o ls",
665 self.who_ls_action = QtGui.QAction("Wh&o ls",
658 self,
666 self,
659 statusTip="Return a list of interactive variable",
667 statusTip="Return a list of interactive variable",
660 triggered=self.who_ls_magic_active_frontend)
668 triggered=self.who_ls_magic_active_frontend)
661 self.add_menu_action(self.magic_menu, self.who_ls_action)
669 self.add_menu_action(self.magic_menu, self.who_ls_action)
662
670
663 self.whos_action = QtGui.QAction("Who&s",
671 self.whos_action = QtGui.QAction("Who&s",
664 self,
672 self,
665 statusTip="List interactive variable with detail",
673 statusTip="List interactive variable with detail",
666 triggered=self.whos_magic_active_frontend)
674 triggered=self.whos_magic_active_frontend)
667 self.add_menu_action(self.magic_menu, self.whos_action)
675 self.add_menu_action(self.magic_menu, self.whos_action)
668
676
677
678 ########################################################################
679 ## TEMPORARILY ADDED BACK - see #1057 for details. The magic menu is
680 ## supposed to be dynamic, but the mechanism merged in #1057 has a race
681 ## condition that locks up the console very often. We're putting back
682 ## the static list temporarily/ Remove this code when a proper fix is
683 ## found.
684
685 # allmagics submenu.
686
687 # for now this is just a copy and paste, but we should get this
688 # dynamically
689 magiclist = ["%alias", "%autocall", "%automagic", "%bookmark", "%cd",
690 "%clear", "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode",
691 "%ed", "%edit", "%env", "%gui", "%guiref", "%hist", "%history",
692 "%install_default_config", "%install_profiles", "%less", "%load_ext",
693 "%loadpy", "%logoff", "%logon", "%logstart", "%logstate", "%logstop",
694 "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
695 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2",
696 "%popd", "%pprint", "%precision", "%profile", "%prun", "%psearch",
697 "%psource", "%pushd", "%pwd", "%pycat", "%pylab", "%quickref",
698 "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun", "%reset",
699 "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time",
700 "%timeit", "%unalias", "%unload_ext", "%who", "%who_ls", "%whos",
701 "%xdel", "%xmode"]
702
703 def make_dynamic_magic(i):
704 def inner_dynamic_magic():
705 self.active_frontend.execute(i)
706 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
707 return inner_dynamic_magic
708
709 for magic in magiclist:
710 xaction = QtGui.QAction(magic,
711 self,
712 triggered=make_dynamic_magic(magic)
713 )
714 self.all_magic_menu.addAction(xaction)
715
716 ## END TEMPORARY FIX
717 ########################################################################
718
719
669 def init_window_menu(self):
720 def init_window_menu(self):
670 self.window_menu = self.menuBar().addMenu("&Window")
721 self.window_menu = self.menuBar().addMenu("&Window")
671 if sys.platform == 'darwin':
722 if sys.platform == 'darwin':
672 # add min/maximize actions to OSX, which lacks default bindings.
723 # add min/maximize actions to OSX, which lacks default bindings.
673 self.minimizeAct = QtGui.QAction("Mini&mize",
724 self.minimizeAct = QtGui.QAction("Mini&mize",
674 self,
725 self,
675 shortcut="Ctrl+m",
726 shortcut="Ctrl+m",
676 statusTip="Minimize the window/Restore Normal Size",
727 statusTip="Minimize the window/Restore Normal Size",
677 triggered=self.toggleMinimized)
728 triggered=self.toggleMinimized)
678 # maximize is called 'Zoom' on OSX for some reason
729 # maximize is called 'Zoom' on OSX for some reason
679 self.maximizeAct = QtGui.QAction("&Zoom",
730 self.maximizeAct = QtGui.QAction("&Zoom",
680 self,
731 self,
681 shortcut="Ctrl+Shift+M",
732 shortcut="Ctrl+Shift+M",
682 statusTip="Maximize the window/Restore Normal Size",
733 statusTip="Maximize the window/Restore Normal Size",
683 triggered=self.toggleMaximized)
734 triggered=self.toggleMaximized)
684
735
685 self.add_menu_action(self.window_menu, self.minimizeAct)
736 self.add_menu_action(self.window_menu, self.minimizeAct)
686 self.add_menu_action(self.window_menu, self.maximizeAct)
737 self.add_menu_action(self.window_menu, self.maximizeAct)
687 self.window_menu.addSeparator()
738 self.window_menu.addSeparator()
688
739
689 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
740 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
690 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
741 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
691 self,
742 self,
692 shortcut=prev_key,
743 shortcut=prev_key,
693 statusTip="Select previous tab",
744 statusTip="Select previous tab",
694 triggered=self.prev_tab)
745 triggered=self.prev_tab)
695 self.add_menu_action(self.window_menu, self.prev_tab_act)
746 self.add_menu_action(self.window_menu, self.prev_tab_act)
696
747
697 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
748 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
698 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
749 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
699 self,
750 self,
700 shortcut=next_key,
751 shortcut=next_key,
701 statusTip="Select next tab",
752 statusTip="Select next tab",
702 triggered=self.next_tab)
753 triggered=self.next_tab)
703 self.add_menu_action(self.window_menu, self.next_tab_act)
754 self.add_menu_action(self.window_menu, self.next_tab_act)
704
755
705 def init_help_menu(self):
756 def init_help_menu(self):
706 # please keep the Help menu in Mac Os even if empty. It will
757 # please keep the Help menu in Mac Os even if empty. It will
707 # automatically contain a search field to search inside menus and
758 # automatically contain a search field to search inside menus and
708 # please keep it spelled in English, as long as Qt Doesn't support
759 # please keep it spelled in English, as long as Qt Doesn't support
709 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
760 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
710 # this search field fonctionality
761 # this search field fonctionality
711
762
712 self.help_menu = self.menuBar().addMenu("&Help")
763 self.help_menu = self.menuBar().addMenu("&Help")
713
764
714
765
715 # Help Menu
766 # Help Menu
716
767
717 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
768 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
718 self,
769 self,
719 triggered=self.intro_active_frontend
770 triggered=self.intro_active_frontend
720 )
771 )
721 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
772 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
722
773
723 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
774 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
724 self,
775 self,
725 triggered=self.quickref_active_frontend
776 triggered=self.quickref_active_frontend
726 )
777 )
727 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
778 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
728
779
729 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
780 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
730 self,
781 self,
731 triggered=self.guiref_active_frontend
782 triggered=self.guiref_active_frontend
732 )
783 )
733 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
784 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
734
785
735 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
786 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
736 self,
787 self,
737 triggered=self._open_online_help)
788 triggered=self._open_online_help)
738 self.add_menu_action(self.help_menu, self.onlineHelpAct)
789 self.add_menu_action(self.help_menu, self.onlineHelpAct)
739
790
740 # minimize/maximize/fullscreen actions:
791 # minimize/maximize/fullscreen actions:
741
792
742 def toggle_menu_bar(self):
793 def toggle_menu_bar(self):
743 menu_bar = self.menuBar()
794 menu_bar = self.menuBar()
744 if menu_bar.isVisible():
795 if menu_bar.isVisible():
745 menu_bar.setVisible(False)
796 menu_bar.setVisible(False)
746 else:
797 else:
747 menu_bar.setVisible(True)
798 menu_bar.setVisible(True)
748
799
749 def toggleMinimized(self):
800 def toggleMinimized(self):
750 if not self.isMinimized():
801 if not self.isMinimized():
751 self.showMinimized()
802 self.showMinimized()
752 else:
803 else:
753 self.showNormal()
804 self.showNormal()
754
805
755 def _open_online_help(self):
806 def _open_online_help(self):
756 filename="http://ipython.org/ipython-doc/stable/index.html"
807 filename="http://ipython.org/ipython-doc/stable/index.html"
757 webbrowser.open(filename, new=1, autoraise=True)
808 webbrowser.open(filename, new=1, autoraise=True)
758
809
759 def toggleMaximized(self):
810 def toggleMaximized(self):
760 if not self.isMaximized():
811 if not self.isMaximized():
761 self.showMaximized()
812 self.showMaximized()
762 else:
813 else:
763 self.showNormal()
814 self.showNormal()
764
815
765 # Min/Max imizing while in full screen give a bug
816 # Min/Max imizing while in full screen give a bug
766 # when going out of full screen, at least on OSX
817 # when going out of full screen, at least on OSX
767 def toggleFullScreen(self):
818 def toggleFullScreen(self):
768 if not self.isFullScreen():
819 if not self.isFullScreen():
769 self.showFullScreen()
820 self.showFullScreen()
770 if sys.platform == 'darwin':
821 if sys.platform == 'darwin':
771 self.maximizeAct.setEnabled(False)
822 self.maximizeAct.setEnabled(False)
772 self.minimizeAct.setEnabled(False)
823 self.minimizeAct.setEnabled(False)
773 else:
824 else:
774 self.showNormal()
825 self.showNormal()
775 if sys.platform == 'darwin':
826 if sys.platform == 'darwin':
776 self.maximizeAct.setEnabled(True)
827 self.maximizeAct.setEnabled(True)
777 self.minimizeAct.setEnabled(True)
828 self.minimizeAct.setEnabled(True)
778
829
779 def close_active_frontend(self):
830 def close_active_frontend(self):
780 self.close_tab(self.active_frontend)
831 self.close_tab(self.active_frontend)
781
832
782 def restart_kernel_active_frontend(self):
833 def restart_kernel_active_frontend(self):
783 self.active_frontend.request_restart_kernel()
834 self.active_frontend.request_restart_kernel()
784
835
785 def interrupt_kernel_active_frontend(self):
836 def interrupt_kernel_active_frontend(self):
786 self.active_frontend.request_interrupt_kernel()
837 self.active_frontend.request_interrupt_kernel()
787
838
788 def cut_active_frontend(self):
839 def cut_active_frontend(self):
789 widget = self.active_frontend
840 widget = self.active_frontend
790 if widget.can_cut():
841 if widget.can_cut():
791 widget.cut()
842 widget.cut()
792
843
793 def copy_active_frontend(self):
844 def copy_active_frontend(self):
794 widget = self.active_frontend
845 widget = self.active_frontend
795 if widget.can_copy():
846 if widget.can_copy():
796 widget.copy()
847 widget.copy()
797
848
798 def copy_raw_active_frontend(self):
849 def copy_raw_active_frontend(self):
799 self.active_frontend._copy_raw_action.trigger()
850 self.active_frontend._copy_raw_action.trigger()
800
851
801 def paste_active_frontend(self):
852 def paste_active_frontend(self):
802 widget = self.active_frontend
853 widget = self.active_frontend
803 if widget.can_paste():
854 if widget.can_paste():
804 widget.paste()
855 widget.paste()
805
856
806 def undo_active_frontend(self):
857 def undo_active_frontend(self):
807 self.active_frontend.undo()
858 self.active_frontend.undo()
808
859
809 def redo_active_frontend(self):
860 def redo_active_frontend(self):
810 self.active_frontend.redo()
861 self.active_frontend.redo()
811
862
812 def reset_magic_active_frontend(self):
863 def reset_magic_active_frontend(self):
813 self.active_frontend.execute("%reset")
864 self.active_frontend.execute("%reset")
814
865
815 def history_magic_active_frontend(self):
866 def history_magic_active_frontend(self):
816 self.active_frontend.execute("%history")
867 self.active_frontend.execute("%history")
817
868
818 def save_magic_active_frontend(self):
869 def save_magic_active_frontend(self):
819 self.active_frontend.save_magic()
870 self.active_frontend.save_magic()
820
871
821 def clear_magic_active_frontend(self):
872 def clear_magic_active_frontend(self):
822 self.active_frontend.execute("%clear")
873 self.active_frontend.execute("%clear")
823
874
824 def who_magic_active_frontend(self):
875 def who_magic_active_frontend(self):
825 self.active_frontend.execute("%who")
876 self.active_frontend.execute("%who")
826
877
827 def who_ls_magic_active_frontend(self):
878 def who_ls_magic_active_frontend(self):
828 self.active_frontend.execute("%who_ls")
879 self.active_frontend.execute("%who_ls")
829
880
830 def whos_magic_active_frontend(self):
881 def whos_magic_active_frontend(self):
831 self.active_frontend.execute("%whos")
882 self.active_frontend.execute("%whos")
832
883
833 def print_action_active_frontend(self):
884 def print_action_active_frontend(self):
834 self.active_frontend.print_action.trigger()
885 self.active_frontend.print_action.trigger()
835
886
836 def export_action_active_frontend(self):
887 def export_action_active_frontend(self):
837 self.active_frontend.export_action.trigger()
888 self.active_frontend.export_action.trigger()
838
889
839 def select_all_active_frontend(self):
890 def select_all_active_frontend(self):
840 self.active_frontend.select_all_action.trigger()
891 self.active_frontend.select_all_action.trigger()
841
892
842 def increase_font_size_active_frontend(self):
893 def increase_font_size_active_frontend(self):
843 self.active_frontend.increase_font_size.trigger()
894 self.active_frontend.increase_font_size.trigger()
844
895
845 def decrease_font_size_active_frontend(self):
896 def decrease_font_size_active_frontend(self):
846 self.active_frontend.decrease_font_size.trigger()
897 self.active_frontend.decrease_font_size.trigger()
847
898
848 def reset_font_size_active_frontend(self):
899 def reset_font_size_active_frontend(self):
849 self.active_frontend.reset_font_size.trigger()
900 self.active_frontend.reset_font_size.trigger()
850
901
851 def guiref_active_frontend(self):
902 def guiref_active_frontend(self):
852 self.active_frontend.execute("%guiref")
903 self.active_frontend.execute("%guiref")
853
904
854 def intro_active_frontend(self):
905 def intro_active_frontend(self):
855 self.active_frontend.execute("?")
906 self.active_frontend.execute("?")
856
907
857 def quickref_active_frontend(self):
908 def quickref_active_frontend(self):
858 self.active_frontend.execute("%quickref")
909 self.active_frontend.execute("%quickref")
859 #---------------------------------------------------------------------------
910 #---------------------------------------------------------------------------
860 # QWidget interface
911 # QWidget interface
861 #---------------------------------------------------------------------------
912 #---------------------------------------------------------------------------
862
913
863 def closeEvent(self, event):
914 def closeEvent(self, event):
864 """ Forward the close event to every tabs contained by the windows
915 """ Forward the close event to every tabs contained by the windows
865 """
916 """
866 if self.tab_widget.count() == 0:
917 if self.tab_widget.count() == 0:
867 # no tabs, just close
918 # no tabs, just close
868 event.accept()
919 event.accept()
869 return
920 return
870 # Do Not loop on the widget count as it change while closing
921 # Do Not loop on the widget count as it change while closing
871 title = self.window().windowTitle()
922 title = self.window().windowTitle()
872 cancel = QtGui.QMessageBox.Cancel
923 cancel = QtGui.QMessageBox.Cancel
873 okay = QtGui.QMessageBox.Ok
924 okay = QtGui.QMessageBox.Ok
874
925
875 if self.confirm_exit:
926 if self.confirm_exit:
876 if self.tab_widget.count() > 1:
927 if self.tab_widget.count() > 1:
877 msg = "Close all tabs, stop all kernels, and Quit?"
928 msg = "Close all tabs, stop all kernels, and Quit?"
878 else:
929 else:
879 msg = "Close console, stop kernel, and Quit?"
930 msg = "Close console, stop kernel, and Quit?"
880 info = "Kernels not started here (e.g. notebooks) will be left alone."
931 info = "Kernels not started here (e.g. notebooks) will be left alone."
881 closeall = QtGui.QPushButton("&Yes, quit everything", self)
932 closeall = QtGui.QPushButton("&Yes, quit everything", self)
882 closeall.setShortcut('Y')
933 closeall.setShortcut('Y')
883 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
934 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
884 title, msg)
935 title, msg)
885 box.setInformativeText(info)
936 box.setInformativeText(info)
886 box.addButton(cancel)
937 box.addButton(cancel)
887 box.addButton(closeall, QtGui.QMessageBox.YesRole)
938 box.addButton(closeall, QtGui.QMessageBox.YesRole)
888 box.setDefaultButton(closeall)
939 box.setDefaultButton(closeall)
889 box.setEscapeButton(cancel)
940 box.setEscapeButton(cancel)
890 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
941 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
891 box.setIconPixmap(pixmap)
942 box.setIconPixmap(pixmap)
892 reply = box.exec_()
943 reply = box.exec_()
893 else:
944 else:
894 reply = okay
945 reply = okay
895
946
896 if reply == cancel:
947 if reply == cancel:
897 event.ignore()
948 event.ignore()
898 return
949 return
899 if reply == okay:
950 if reply == okay:
900 while self.tab_widget.count() >= 1:
951 while self.tab_widget.count() >= 1:
901 # prevent further confirmations:
952 # prevent further confirmations:
902 widget = self.active_frontend
953 widget = self.active_frontend
903 widget._confirm_exit = False
954 widget._confirm_exit = False
904 self.close_tab(widget)
955 self.close_tab(widget)
905 event.accept()
956 event.accept()
906
957
@@ -1,552 +1,559 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 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import json
22 import json
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 import uuid
26 import uuid
27
27
28 # System library imports
28 # System library imports
29 from IPython.external.qt import QtGui
29 from IPython.external.qt import QtGui
30
30
31 # Local imports
31 # Local imports
32 from IPython.config.application import boolean_flag, catch_config_error
32 from IPython.config.application import boolean_flag, catch_config_error
33 from IPython.core.application import BaseIPythonApplication
33 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console import styles
39 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console.mainwindow import MainWindow
40 from IPython.frontend.qt.console.mainwindow import MainWindow
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, Integer, CaselessStrEnum, CBool, Any
45 Dict, List, Unicode, Integer, 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 # Aliases and Flags
71 # Aliases and Flags
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 flags = dict(ipkernel_flags)
74 flags = dict(ipkernel_flags)
75 qt_flags = {
75 qt_flags = {
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 "Use a pure Python kernel instead of an IPython kernel."),
79 "Use a pure Python kernel instead of an IPython kernel."),
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 "Disable rich text support."),
81 "Disable rich text support."),
82 }
82 }
83 qt_flags.update(boolean_flag(
83 qt_flags.update(boolean_flag(
84 'gui-completion', 'ConsoleWidget.gui_completion',
84 'gui-completion', 'ConsoleWidget.gui_completion',
85 "use a GUI widget for tab completion",
85 "use a GUI widget for tab completion",
86 "use plaintext output for completion"
86 "use plaintext output for completion"
87 ))
87 ))
88 qt_flags.update(boolean_flag(
88 qt_flags.update(boolean_flag(
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
91 to force a direct exit without any confirmation.
91 to force a direct exit without any confirmation.
92 """,
92 """,
93 """Don't prompt the user when exiting. This will terminate the kernel
93 """Don't prompt the user when exiting. This will terminate the kernel
94 if it is owned by the frontend, and leave it alive if it is external.
94 if it is owned by the frontend, and leave it alive if it is external.
95 """
95 """
96 ))
96 ))
97 flags.update(qt_flags)
97 flags.update(qt_flags)
98
98
99 aliases = dict(ipkernel_aliases)
99 aliases = dict(ipkernel_aliases)
100
100
101 qt_aliases = dict(
101 qt_aliases = dict(
102 hb = 'IPythonQtConsoleApp.hb_port',
102 hb = 'IPythonQtConsoleApp.hb_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
106 ip = 'IPythonQtConsoleApp.ip',
106 ip = 'IPythonQtConsoleApp.ip',
107 existing = 'IPythonQtConsoleApp.existing',
107 existing = 'IPythonQtConsoleApp.existing',
108 f = 'IPythonQtConsoleApp.connection_file',
108 f = 'IPythonQtConsoleApp.connection_file',
109
109
110 style = 'IPythonWidget.syntax_style',
110 style = 'IPythonWidget.syntax_style',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 colors = 'ZMQInteractiveShell.colors',
112 colors = 'ZMQInteractiveShell.colors',
113
113
114 editor = 'IPythonWidget.editor',
114 editor = 'IPythonWidget.editor',
115 paging = 'ConsoleWidget.paging',
115 paging = 'ConsoleWidget.paging',
116 ssh = 'IPythonQtConsoleApp.sshserver',
116 ssh = 'IPythonQtConsoleApp.sshserver',
117 )
117 )
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Classes
121 # Classes
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 # IPythonQtConsole
125 # IPythonQtConsole
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127
127
128
128
129 class IPythonQtConsoleApp(BaseIPythonApplication):
129 class IPythonQtConsoleApp(BaseIPythonApplication):
130 name = 'ipython-qtconsole'
130 name = 'ipython-qtconsole'
131 default_config_file_name='ipython_config.py'
131 default_config_file_name='ipython_config.py'
132
132
133 description = """
133 description = """
134 The IPython QtConsole.
134 The IPython QtConsole.
135
135
136 This launches a Console-style application using Qt. It is not a full
136 This launches a Console-style application using Qt. It is not a full
137 console, in that launched terminal subprocesses will not be able to accept
137 console, in that launched terminal subprocesses will not be able to accept
138 input.
138 input.
139
139
140 The QtConsole supports various extra features beyond the Terminal IPython
140 The QtConsole supports various extra features beyond the Terminal IPython
141 shell, such as inline plotting with matplotlib, via:
141 shell, such as inline plotting with matplotlib, via:
142
142
143 ipython qtconsole --pylab=inline
143 ipython qtconsole --pylab=inline
144
144
145 as well as saving your session as HTML, and printing the output.
145 as well as saving your session as HTML, and printing the output.
146
146
147 """
147 """
148 examples = _examples
148 examples = _examples
149
149
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
151 flags = Dict(flags)
151 flags = Dict(flags)
152 aliases = Dict(aliases)
152 aliases = Dict(aliases)
153
153
154 kernel_argv = List(Unicode)
154 kernel_argv = List(Unicode)
155
155
156 # create requested profiles by default, if they don't exist:
156 # create requested profiles by default, if they don't exist:
157 auto_create = CBool(True)
157 auto_create = CBool(True)
158 # connection info:
158 # connection info:
159 ip = Unicode(LOCALHOST, config=True,
159 ip = Unicode(LOCALHOST, config=True,
160 help="""Set the kernel\'s IP address [default localhost].
160 help="""Set the kernel\'s IP address [default localhost].
161 If the IP address is something other than localhost, then
161 If the IP address is something other than localhost, then
162 Consoles on other machines will be able to connect
162 Consoles on other machines will be able to connect
163 to the Kernel, so be careful!"""
163 to the Kernel, so be careful!"""
164 )
164 )
165
165
166 sshserver = Unicode('', config=True,
166 sshserver = Unicode('', config=True,
167 help="""The SSH server to use to connect to the kernel.""")
167 help="""The SSH server to use to connect to the kernel.""")
168 sshkey = Unicode('', config=True,
168 sshkey = Unicode('', config=True,
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
170
170
171 hb_port = Integer(0, config=True,
171 hb_port = Integer(0, config=True,
172 help="set the heartbeat port [default: random]")
172 help="set the heartbeat port [default: random]")
173 shell_port = Integer(0, config=True,
173 shell_port = Integer(0, config=True,
174 help="set the shell (XREP) port [default: random]")
174 help="set the shell (XREP) port [default: random]")
175 iopub_port = Integer(0, config=True,
175 iopub_port = Integer(0, config=True,
176 help="set the iopub (PUB) port [default: random]")
176 help="set the iopub (PUB) port [default: random]")
177 stdin_port = Integer(0, config=True,
177 stdin_port = Integer(0, config=True,
178 help="set the stdin (XREQ) port [default: random]")
178 help="set the stdin (XREQ) port [default: random]")
179 connection_file = Unicode('', config=True,
179 connection_file = Unicode('', config=True,
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
181
181
182 This file will contain the IP, ports, and authentication key needed to connect
182 This file will contain the IP, ports, and authentication key needed to connect
183 clients to this kernel. By default, this file will be created in the security-dir
183 clients to this kernel. By default, this file will be created in the security-dir
184 of the current profile, but can be specified by absolute path.
184 of the current profile, but can be specified by absolute path.
185 """)
185 """)
186 def _connection_file_default(self):
186 def _connection_file_default(self):
187 return 'kernel-%i.json' % os.getpid()
187 return 'kernel-%i.json' % os.getpid()
188
188
189 existing = Unicode('', config=True,
189 existing = Unicode('', config=True,
190 help="""Connect to an already running kernel""")
190 help="""Connect to an already running kernel""")
191
191
192 stylesheet = Unicode('', config=True,
192 stylesheet = Unicode('', config=True,
193 help="path to a custom CSS stylesheet")
193 help="path to a custom CSS stylesheet")
194
194
195 pure = CBool(False, config=True,
195 pure = CBool(False, config=True,
196 help="Use a pure Python kernel instead of an IPython kernel.")
196 help="Use a pure Python kernel instead of an IPython kernel.")
197 plain = CBool(False, config=True,
197 plain = CBool(False, config=True,
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
199
199
200 def _pure_changed(self, name, old, new):
200 def _pure_changed(self, name, old, new):
201 kind = 'plain' if self.plain else 'rich'
201 kind = 'plain' if self.plain else 'rich'
202 self.config.ConsoleWidget.kind = kind
202 self.config.ConsoleWidget.kind = kind
203 if self.pure:
203 if self.pure:
204 self.widget_factory = FrontendWidget
204 self.widget_factory = FrontendWidget
205 elif self.plain:
205 elif self.plain:
206 self.widget_factory = IPythonWidget
206 self.widget_factory = IPythonWidget
207 else:
207 else:
208 self.widget_factory = RichIPythonWidget
208 self.widget_factory = RichIPythonWidget
209
209
210 _plain_changed = _pure_changed
210 _plain_changed = _pure_changed
211
211
212 confirm_exit = CBool(True, config=True,
212 confirm_exit = CBool(True, config=True,
213 help="""
213 help="""
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
215 to force a direct exit without any confirmation.""",
215 to force a direct exit without any confirmation.""",
216 )
216 )
217
217
218 # the factory for creating a widget
218 # the factory for creating a widget
219 widget_factory = Any(RichIPythonWidget)
219 widget_factory = Any(RichIPythonWidget)
220
220
221 def parse_command_line(self, argv=None):
221 def parse_command_line(self, argv=None):
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
223 if argv is None:
223 if argv is None:
224 argv = sys.argv[1:]
224 argv = sys.argv[1:]
225 self.kernel_argv = list(argv) # copy
225 self.kernel_argv = list(argv) # copy
226 # kernel should inherit default config file from frontend
226 # kernel should inherit default config file from frontend
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
228 # Scrub frontend-specific flags
228 # Scrub frontend-specific flags
229 swallow_next = False
229 swallow_next = False
230 was_flag = False
230 was_flag = False
231 # copy again, in case some aliases have the same name as a flag
231 # copy again, in case some aliases have the same name as a flag
232 # argv = list(self.kernel_argv)
232 # argv = list(self.kernel_argv)
233 for a in argv:
233 for a in argv:
234 if swallow_next:
234 if swallow_next:
235 swallow_next = False
235 swallow_next = False
236 # last arg was an alias, remove the next one
236 # last arg was an alias, remove the next one
237 # *unless* the last alias has a no-arg flag version, in which
237 # *unless* the last alias has a no-arg flag version, in which
238 # case, don't swallow the next arg if it's also a flag:
238 # case, don't swallow the next arg if it's also a flag:
239 if not (was_flag and a.startswith('-')):
239 if not (was_flag and a.startswith('-')):
240 self.kernel_argv.remove(a)
240 self.kernel_argv.remove(a)
241 continue
241 continue
242 if a.startswith('-'):
242 if a.startswith('-'):
243 split = a.lstrip('-').split('=')
243 split = a.lstrip('-').split('=')
244 alias = split[0]
244 alias = split[0]
245 if alias in qt_aliases:
245 if alias in qt_aliases:
246 self.kernel_argv.remove(a)
246 self.kernel_argv.remove(a)
247 if len(split) == 1:
247 if len(split) == 1:
248 # alias passed with arg via space
248 # alias passed with arg via space
249 swallow_next = True
249 swallow_next = True
250 # could have been a flag that matches an alias, e.g. `existing`
250 # could have been a flag that matches an alias, e.g. `existing`
251 # in which case, we might not swallow the next arg
251 # in which case, we might not swallow the next arg
252 was_flag = alias in qt_flags
252 was_flag = alias in qt_flags
253 elif alias in qt_flags:
253 elif alias in qt_flags:
254 # strip flag, but don't swallow next, as flags don't take args
254 # strip flag, but don't swallow next, as flags don't take args
255 self.kernel_argv.remove(a)
255 self.kernel_argv.remove(a)
256
256
257 def init_connection_file(self):
257 def init_connection_file(self):
258 """find the connection file, and load the info if found.
258 """find the connection file, and load the info if found.
259
259
260 The current working directory and the current profile's security
260 The current working directory and the current profile's security
261 directory will be searched for the file if it is not given by
261 directory will be searched for the file if it is not given by
262 absolute path.
262 absolute path.
263
263
264 When attempting to connect to an existing kernel and the `--existing`
264 When attempting to connect to an existing kernel and the `--existing`
265 argument does not match an existing file, it will be interpreted as a
265 argument does not match an existing file, it will be interpreted as a
266 fileglob, and the matching file in the current profile's security dir
266 fileglob, and the matching file in the current profile's security dir
267 with the latest access time will be used.
267 with the latest access time will be used.
268 """
268 """
269 if self.existing:
269 if self.existing:
270 try:
270 try:
271 cf = find_connection_file(self.existing)
271 cf = find_connection_file(self.existing)
272 except Exception:
272 except Exception:
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
274 self.exit(1)
274 self.exit(1)
275 self.log.info("Connecting to existing kernel: %s" % cf)
275 self.log.info("Connecting to existing kernel: %s" % cf)
276 self.connection_file = cf
276 self.connection_file = cf
277 # should load_connection_file only be used for existing?
277 # should load_connection_file only be used for existing?
278 # as it is now, this allows reusing ports if an existing
278 # as it is now, this allows reusing ports if an existing
279 # file is requested
279 # file is requested
280 try:
280 try:
281 self.load_connection_file()
281 self.load_connection_file()
282 except Exception:
282 except Exception:
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
284 self.exit(1)
284 self.exit(1)
285
285
286 def load_connection_file(self):
286 def load_connection_file(self):
287 """load ip/port/hmac config from JSON connection file"""
287 """load ip/port/hmac config from JSON connection file"""
288 # this is identical to KernelApp.load_connection_file
288 # this is identical to KernelApp.load_connection_file
289 # perhaps it can be centralized somewhere?
289 # perhaps it can be centralized somewhere?
290 try:
290 try:
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
292 except IOError:
292 except IOError:
293 self.log.debug("Connection File not found: %s", self.connection_file)
293 self.log.debug("Connection File not found: %s", self.connection_file)
294 return
294 return
295 self.log.debug(u"Loading connection file %s", fname)
295 self.log.debug(u"Loading connection file %s", fname)
296 with open(fname) as f:
296 with open(fname) as f:
297 s = f.read()
297 s = f.read()
298 cfg = json.loads(s)
298 cfg = json.loads(s)
299 if self.ip == LOCALHOST and 'ip' in cfg:
299 if self.ip == LOCALHOST and 'ip' in cfg:
300 # not overridden by config or cl_args
300 # not overridden by config or cl_args
301 self.ip = cfg['ip']
301 self.ip = cfg['ip']
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
303 name = channel + '_port'
303 name = channel + '_port'
304 if getattr(self, name) == 0 and name in cfg:
304 if getattr(self, name) == 0 and name in cfg:
305 # not overridden by config or cl_args
305 # not overridden by config or cl_args
306 setattr(self, name, cfg[name])
306 setattr(self, name, cfg[name])
307 if 'key' in cfg:
307 if 'key' in cfg:
308 self.config.Session.key = str_to_bytes(cfg['key'])
308 self.config.Session.key = str_to_bytes(cfg['key'])
309
309
310 def init_ssh(self):
310 def init_ssh(self):
311 """set up ssh tunnels, if needed."""
311 """set up ssh tunnels, if needed."""
312 if not self.sshserver and not self.sshkey:
312 if not self.sshserver and not self.sshkey:
313 return
313 return
314
314
315 if self.sshkey and not self.sshserver:
315 if self.sshkey and not self.sshserver:
316 # specifying just the key implies that we are connecting directly
316 # specifying just the key implies that we are connecting directly
317 self.sshserver = self.ip
317 self.sshserver = self.ip
318 self.ip = LOCALHOST
318 self.ip = LOCALHOST
319
319
320 # build connection dict for tunnels:
320 # build connection dict for tunnels:
321 info = dict(ip=self.ip,
321 info = dict(ip=self.ip,
322 shell_port=self.shell_port,
322 shell_port=self.shell_port,
323 iopub_port=self.iopub_port,
323 iopub_port=self.iopub_port,
324 stdin_port=self.stdin_port,
324 stdin_port=self.stdin_port,
325 hb_port=self.hb_port
325 hb_port=self.hb_port
326 )
326 )
327
327
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
329
329
330 # tunnels return a new set of ports, which will be on localhost:
330 # tunnels return a new set of ports, which will be on localhost:
331 self.ip = LOCALHOST
331 self.ip = LOCALHOST
332 try:
332 try:
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
334 except:
334 except:
335 # even catch KeyboardInterrupt
335 # even catch KeyboardInterrupt
336 self.log.error("Could not setup tunnels", exc_info=True)
336 self.log.error("Could not setup tunnels", exc_info=True)
337 self.exit(1)
337 self.exit(1)
338
338
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
340
340
341 cf = self.connection_file
341 cf = self.connection_file
342 base,ext = os.path.splitext(cf)
342 base,ext = os.path.splitext(cf)
343 base = os.path.basename(base)
343 base = os.path.basename(base)
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
345 self.log.critical("To connect another client via this tunnel, use:")
345 self.log.critical("To connect another client via this tunnel, use:")
346 self.log.critical("--existing %s" % self.connection_file)
346 self.log.critical("--existing %s" % self.connection_file)
347
347
348 def _new_connection_file(self):
348 def _new_connection_file(self):
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
350
350
351 def init_kernel_manager(self):
351 def init_kernel_manager(self):
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
354 sec = self.profile_dir.security_dir
354 sec = self.profile_dir.security_dir
355 try:
355 try:
356 cf = filefind(self.connection_file, ['.', sec])
356 cf = filefind(self.connection_file, ['.', sec])
357 except IOError:
357 except IOError:
358 # file might not exist
358 # file might not exist
359 if self.connection_file == os.path.basename(self.connection_file):
359 if self.connection_file == os.path.basename(self.connection_file):
360 # just shortname, put it in security dir
360 # just shortname, put it in security dir
361 cf = os.path.join(sec, self.connection_file)
361 cf = os.path.join(sec, self.connection_file)
362 else:
362 else:
363 cf = self.connection_file
363 cf = self.connection_file
364
364
365 # Create a KernelManager and start a kernel.
365 # Create a KernelManager and start a kernel.
366 self.kernel_manager = QtKernelManager(
366 self.kernel_manager = QtKernelManager(
367 ip=self.ip,
367 ip=self.ip,
368 shell_port=self.shell_port,
368 shell_port=self.shell_port,
369 iopub_port=self.iopub_port,
369 iopub_port=self.iopub_port,
370 stdin_port=self.stdin_port,
370 stdin_port=self.stdin_port,
371 hb_port=self.hb_port,
371 hb_port=self.hb_port,
372 connection_file=cf,
372 connection_file=cf,
373 config=self.config,
373 config=self.config,
374 )
374 )
375 # start the kernel
375 # start the kernel
376 if not self.existing:
376 if not self.existing:
377 kwargs = dict(ipython=not self.pure)
377 kwargs = dict(ipython=not self.pure)
378 kwargs['extra_arguments'] = self.kernel_argv
378 kwargs['extra_arguments'] = self.kernel_argv
379 self.kernel_manager.start_kernel(**kwargs)
379 self.kernel_manager.start_kernel(**kwargs)
380 elif self.sshserver:
380 elif self.sshserver:
381 # ssh, write new connection file
381 # ssh, write new connection file
382 self.kernel_manager.write_connection_file()
382 self.kernel_manager.write_connection_file()
383 self.kernel_manager.start_channels()
383 self.kernel_manager.start_channels()
384
384
385 def new_frontend_master(self):
385 def new_frontend_master(self):
386 """ Create and return new frontend attached to new kernel, launched on localhost.
386 """ Create and return new frontend attached to new kernel, launched on localhost.
387 """
387 """
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
389 kernel_manager = QtKernelManager(
389 kernel_manager = QtKernelManager(
390 ip=ip,
390 ip=ip,
391 connection_file=self._new_connection_file(),
391 connection_file=self._new_connection_file(),
392 config=self.config,
392 config=self.config,
393 )
393 )
394 # start the kernel
394 # start the kernel
395 kwargs = dict(ipython=not self.pure)
395 kwargs = dict(ipython=not self.pure)
396 kwargs['extra_arguments'] = self.kernel_argv
396 kwargs['extra_arguments'] = self.kernel_argv
397 kernel_manager.start_kernel(**kwargs)
397 kernel_manager.start_kernel(**kwargs)
398 kernel_manager.start_channels()
398 kernel_manager.start_channels()
399 widget = self.widget_factory(config=self.config,
399 widget = self.widget_factory(config=self.config,
400 local_kernel=True)
400 local_kernel=True)
401 widget.kernel_manager = kernel_manager
401 widget.kernel_manager = kernel_manager
402 widget._existing = False
402 widget._existing = False
403 widget._may_close = True
403 widget._may_close = True
404 widget._confirm_exit = self.confirm_exit
404 widget._confirm_exit = self.confirm_exit
405 return widget
405 return widget
406
406
407 def new_frontend_slave(self, current_widget):
407 def new_frontend_slave(self, current_widget):
408 """Create and return a new frontend attached to an existing kernel.
408 """Create and return a new frontend attached to an existing kernel.
409
409
410 Parameters
410 Parameters
411 ----------
411 ----------
412 current_widget : IPythonWidget
412 current_widget : IPythonWidget
413 The IPythonWidget whose kernel this frontend is to share
413 The IPythonWidget whose kernel this frontend is to share
414 """
414 """
415 kernel_manager = QtKernelManager(
415 kernel_manager = QtKernelManager(
416 connection_file=current_widget.kernel_manager.connection_file,
416 connection_file=current_widget.kernel_manager.connection_file,
417 config = self.config,
417 config = self.config,
418 )
418 )
419 kernel_manager.load_connection_file()
419 kernel_manager.load_connection_file()
420 kernel_manager.start_channels()
420 kernel_manager.start_channels()
421 widget = self.widget_factory(config=self.config,
421 widget = self.widget_factory(config=self.config,
422 local_kernel=False)
422 local_kernel=False)
423 widget._existing = True
423 widget._existing = True
424 widget._may_close = False
424 widget._may_close = False
425 widget._confirm_exit = False
425 widget._confirm_exit = False
426 widget.kernel_manager = kernel_manager
426 widget.kernel_manager = kernel_manager
427 return widget
427 return widget
428
428
429 def init_qt_elements(self):
429 def init_qt_elements(self):
430 # Create the widget.
430 # Create the widget.
431 self.app = QtGui.QApplication([])
431 self.app = QtGui.QApplication([])
432
432
433 base_path = os.path.abspath(os.path.dirname(__file__))
433 base_path = os.path.abspath(os.path.dirname(__file__))
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
435 self.app.icon = QtGui.QIcon(icon_path)
435 self.app.icon = QtGui.QIcon(icon_path)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
437
437
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
439 self.widget = self.widget_factory(config=self.config,
439 self.widget = self.widget_factory(config=self.config,
440 local_kernel=local_kernel)
440 local_kernel=local_kernel)
441 self.widget._existing = self.existing
441 self.widget._existing = self.existing
442 self.widget._may_close = not self.existing
442 self.widget._may_close = not self.existing
443 self.widget._confirm_exit = self.confirm_exit
443 self.widget._confirm_exit = self.confirm_exit
444
444
445 self.widget.kernel_manager = self.kernel_manager
445 self.widget.kernel_manager = self.kernel_manager
446 self.window = MainWindow(self.app,
446 self.window = MainWindow(self.app,
447 confirm_exit=self.confirm_exit,
447 confirm_exit=self.confirm_exit,
448 new_frontend_factory=self.new_frontend_master,
448 new_frontend_factory=self.new_frontend_master,
449 slave_frontend_factory=self.new_frontend_slave,
449 slave_frontend_factory=self.new_frontend_slave,
450 )
450 )
451 self.window.log = self.log
451 self.window.log = self.log
452 self.window.add_tab_with_frontend(self.widget)
452 self.window.add_tab_with_frontend(self.widget)
453 self.window.init_menu_bar()
453 self.window.init_menu_bar()
454
454
455 # we need to populate the 'Magic Menu' once the kernel has answer at least once
455 # we need to populate the 'Magic Menu' once the kernel has answer at
456 self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger)
456 # least once
457
458 ########################################################################
459 ## TEMPORARILY DISABLED - see #1057 for details, uncomment the next
460 ## line when a proper fix is found:
461 ## self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger)
462 ## END TEMPORARY FIX
463 ########################################################################
457
464
458 self.window.setWindowTitle('Python' if self.pure else 'IPython')
465 self.window.setWindowTitle('Python' if self.pure else 'IPython')
459
466
460 def init_colors(self):
467 def init_colors(self):
461 """Configure the coloring of the widget"""
468 """Configure the coloring of the widget"""
462 # Note: This will be dramatically simplified when colors
469 # Note: This will be dramatically simplified when colors
463 # are removed from the backend.
470 # are removed from the backend.
464
471
465 if self.pure:
472 if self.pure:
466 # only IPythonWidget supports styling
473 # only IPythonWidget supports styling
467 return
474 return
468
475
469 # parse the colors arg down to current known labels
476 # parse the colors arg down to current known labels
470 try:
477 try:
471 colors = self.config.ZMQInteractiveShell.colors
478 colors = self.config.ZMQInteractiveShell.colors
472 except AttributeError:
479 except AttributeError:
473 colors = None
480 colors = None
474 try:
481 try:
475 style = self.config.IPythonWidget.syntax_style
482 style = self.config.IPythonWidget.syntax_style
476 except AttributeError:
483 except AttributeError:
477 style = None
484 style = None
478
485
479 # find the value for colors:
486 # find the value for colors:
480 if colors:
487 if colors:
481 colors=colors.lower()
488 colors=colors.lower()
482 if colors in ('lightbg', 'light'):
489 if colors in ('lightbg', 'light'):
483 colors='lightbg'
490 colors='lightbg'
484 elif colors in ('dark', 'linux'):
491 elif colors in ('dark', 'linux'):
485 colors='linux'
492 colors='linux'
486 else:
493 else:
487 colors='nocolor'
494 colors='nocolor'
488 elif style:
495 elif style:
489 if style=='bw':
496 if style=='bw':
490 colors='nocolor'
497 colors='nocolor'
491 elif styles.dark_style(style):
498 elif styles.dark_style(style):
492 colors='linux'
499 colors='linux'
493 else:
500 else:
494 colors='lightbg'
501 colors='lightbg'
495 else:
502 else:
496 colors=None
503 colors=None
497
504
498 # Configure the style.
505 # Configure the style.
499 widget = self.widget
506 widget = self.widget
500 if style:
507 if style:
501 widget.style_sheet = styles.sheet_from_template(style, colors)
508 widget.style_sheet = styles.sheet_from_template(style, colors)
502 widget.syntax_style = style
509 widget.syntax_style = style
503 widget._syntax_style_changed()
510 widget._syntax_style_changed()
504 widget._style_sheet_changed()
511 widget._style_sheet_changed()
505 elif colors:
512 elif colors:
506 # use a default style
513 # use a default style
507 widget.set_default_style(colors=colors)
514 widget.set_default_style(colors=colors)
508 else:
515 else:
509 # this is redundant for now, but allows the widget's
516 # this is redundant for now, but allows the widget's
510 # defaults to change
517 # defaults to change
511 widget.set_default_style()
518 widget.set_default_style()
512
519
513 if self.stylesheet:
520 if self.stylesheet:
514 # we got an expicit stylesheet
521 # we got an expicit stylesheet
515 if os.path.isfile(self.stylesheet):
522 if os.path.isfile(self.stylesheet):
516 with open(self.stylesheet) as f:
523 with open(self.stylesheet) as f:
517 sheet = f.read()
524 sheet = f.read()
518 widget.style_sheet = sheet
525 widget.style_sheet = sheet
519 widget._style_sheet_changed()
526 widget._style_sheet_changed()
520 else:
527 else:
521 raise IOError("Stylesheet %r not found."%self.stylesheet)
528 raise IOError("Stylesheet %r not found."%self.stylesheet)
522
529
523 @catch_config_error
530 @catch_config_error
524 def initialize(self, argv=None):
531 def initialize(self, argv=None):
525 super(IPythonQtConsoleApp, self).initialize(argv)
532 super(IPythonQtConsoleApp, self).initialize(argv)
526 self.init_connection_file()
533 self.init_connection_file()
527 default_secure(self.config)
534 default_secure(self.config)
528 self.init_ssh()
535 self.init_ssh()
529 self.init_kernel_manager()
536 self.init_kernel_manager()
530 self.init_qt_elements()
537 self.init_qt_elements()
531 self.init_colors()
538 self.init_colors()
532
539
533 def start(self):
540 def start(self):
534
541
535 # draw the window
542 # draw the window
536 self.window.show()
543 self.window.show()
537
544
538 # Start the application main loop.
545 # Start the application main loop.
539 self.app.exec_()
546 self.app.exec_()
540
547
541 #-----------------------------------------------------------------------------
548 #-----------------------------------------------------------------------------
542 # Main entry point
549 # Main entry point
543 #-----------------------------------------------------------------------------
550 #-----------------------------------------------------------------------------
544
551
545 def main():
552 def main():
546 app = IPythonQtConsoleApp()
553 app = IPythonQtConsoleApp()
547 app.initialize()
554 app.initialize()
548 app.start()
555 app.start()
549
556
550
557
551 if __name__ == '__main__':
558 if __name__ == '__main__':
552 main()
559 main()
General Comments 0
You need to be logged in to leave comments. Login now