##// END OF EJS Templates
cleanup some closing logic...
MinRK -
Show More
@@ -1,782 +1,806 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 webbrowser
23 import webbrowser
24
24
25 # System library imports
25 # System library imports
26 from IPython.external.qt import QtGui,QtCore
26 from IPython.external.qt import QtGui,QtCore
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class MainWindow(QtGui.QMainWindow):
32 class MainWindow(QtGui.QMainWindow):
33
33
34 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
35 # 'object' interface
35 # 'object' interface
36 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
37
37
38 def __init__(self, app, frontend, existing=False, may_close=True,
38 def __init__(self, app,
39 confirm_exit=True,
39 confirm_exit=True,
40 new_frontend_factory=None, slave_frontend_factory=None,
40 new_frontend_factory=None, slave_frontend_factory=None,
41 ):
41 ):
42 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
42 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
43
43
44 Parameters
44 Parameters
45 ----------
45 ----------
46
46
47 app : reference to QApplication parent
47 app : reference to QApplication parent
48 frontend : IPythonWidget
49 The first IPython frontend to start with
50 existing : bool, optional
51 Whether the first frontend is connected to en existing Kernel,
52 or if we own it.
53 may_close : bool, optional
54 Whether we are permitted to close the kernel (determines close dialog behavior)
55 confirm_exit : bool, optional
48 confirm_exit : bool, optional
56 Whether we should prompt on close of tabs
49 Whether we should prompt on close of tabs
57 new_frontend_factory : callable
50 new_frontend_factory : callable
58 A callable that returns a new IPythonWidget instance, attached to
51 A callable that returns a new IPythonWidget instance, attached to
59 its own running kernel.
52 its own running kernel.
60 slave_frontend_factory : callable
53 slave_frontend_factory : callable
61 A callable that takes an existing IPythonWidget, and returns a new
54 A callable that takes an existing IPythonWidget, and returns a new
62 IPythonWidget instance, attached to the same kernel.
55 IPythonWidget instance, attached to the same kernel.
63 """
56 """
64
57
65 super(MainWindow, self).__init__()
58 super(MainWindow, self).__init__()
66 self._app = app
59 self._app = app
60 self.confirm_exit = confirm_exit
67 self.new_frontend_factory = new_frontend_factory
61 self.new_frontend_factory = new_frontend_factory
68 self.slave_frontend_factory = slave_frontend_factory
62 self.slave_frontend_factory = slave_frontend_factory
69
63
70 self.tab_widget = QtGui.QTabWidget(self)
64 self.tab_widget = QtGui.QTabWidget(self)
71 self.tab_widget.setDocumentMode(True)
65 self.tab_widget.setDocumentMode(True)
72 self.tab_widget.setTabsClosable(True)
66 self.tab_widget.setTabsClosable(True)
73 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
67 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
74
68
75 self.setCentralWidget(self.tab_widget)
69 self.setCentralWidget(self.tab_widget)
76 self.update_tab_bar_visibility()
70 self.tab_widget.tabBar().setVisible(False)
77
71
78 def update_tab_bar_visibility(self):
72 def update_tab_bar_visibility(self):
79 """ update visibility of the tabBar depending of the number of tab
73 """ update visibility of the tabBar depending of the number of tab
80
74
81 0 or 1 tab, tabBar hidden
75 0 or 1 tab, tabBar hidden
82 2+ tabs, tabBar visible
76 2+ tabs, tabBar visible
83
77
84 send a self.close if number of tab ==0
78 send a self.close if number of tab ==0
85
79
86 need to be called explicitely, or be connected to tabInserted/tabRemoved
80 need to be called explicitely, or be connected to tabInserted/tabRemoved
87 """
81 """
88 if self.tab_widget.count() <= 1:
82 if self.tab_widget.count() <= 1:
89 self.tab_widget.tabBar().setVisible(False)
83 self.tab_widget.tabBar().setVisible(False)
90 else:
84 else:
91 self.tab_widget.tabBar().setVisible(True)
85 self.tab_widget.tabBar().setVisible(True)
92 if self.tab_widget.count()==0 :
86 if self.tab_widget.count()==0 :
93 self.close()
87 self.close()
94
88
95 @property
89 @property
96 def active_frontend(self):
90 def active_frontend(self):
97 return self.tab_widget.currentWidget()
91 return self.tab_widget.currentWidget()
98
92
99 def create_tab_with_new_frontend(self):
93 def create_tab_with_new_frontend(self):
100 """create a new frontend and attach it to a new tab"""
94 """create a new frontend and attach it to a new tab"""
101 widget = self.new_frontend_factory()
95 widget = self.new_frontend_factory()
102 self.add_tab_with_frontend(widget)
96 self.add_tab_with_frontend(widget)
103
97
104 def create_tab_with_current_kernel(self):
98 def create_tab_with_current_kernel(self):
105 """create a new frontend attached to the same kernel as the current tab"""
99 """create a new frontend attached to the same kernel as the current tab"""
106 current_widget = self.tab_widget.currentWidget()
100 current_widget = self.tab_widget.currentWidget()
107 current_widget_index = self.tab_widget.indexOf(current_widget)
101 current_widget_index = self.tab_widget.indexOf(current_widget)
108 current_widget_name = self.tab_widget.tabText(current_widget_index)
102 current_widget_name = self.tab_widget.tabText(current_widget_index)
109 widget = self.slave_frontend_factory(current_widget)
103 widget = self.slave_frontend_factory(current_widget)
110 if 'slave' in current_widget_name:
104 if 'slave' in current_widget_name:
111 # don't keep stacking slaves
105 # don't keep stacking slaves
112 name = current_widget_name
106 name = current_widget_name
113 else:
107 else:
114 name = str('('+current_widget_name+') slave')
108 name = str('('+current_widget_name+') slave')
115 self.add_tab_with_frontend(widget,name=name)
109 self.add_tab_with_frontend(widget,name=name)
116
110
117 def close_tab(self,current_tab):
111 def close_tab(self,current_tab):
118 """ Called when you need to try to close a tab.
112 """ Called when you need to try to close a tab.
119
113
120 It takes the number of the tab to be closed as argument, or a referece
114 It takes the number of the tab to be closed as argument, or a referece
121 to the wiget insite this tab
115 to the wiget insite this tab
122 """
116 """
123
117
124 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
118 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
125 # and a reference to the trontend to close
119 # and a reference to the trontend to close
126 if type(current_tab) is not int :
120 if type(current_tab) is not int :
127 current_tab = self.tab_widget.indexOf(current_tab)
121 current_tab = self.tab_widget.indexOf(current_tab)
128 closing_widget=self.tab_widget.widget(current_tab)
122 closing_widget=self.tab_widget.widget(current_tab)
129
123
130
124
131 # when trying to be closed, widget might re-send a request to be closed again, but will
125 # when trying to be closed, widget might re-send a request to be closed again, but will
132 # be deleted when event will be processed. So need to check that widget still exist and
126 # be deleted when event will be processed. So need to check that widget still exist and
133 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
127 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
134 # re-send by this fonction on the master widget, which ask all slaves widget to exit
128 # re-send by this fonction on the master widget, which ask all slaves widget to exit
135 if closing_widget==None:
129 if closing_widget==None:
136 return
130 return
137
131
138 #get a list of all wwidget not owning the kernel.
132 #get a list of all slave widgets on the same kernel.
139 slave_tabs=self.find_slaves_tabs(closing_widget)
133 slave_tabs = self.find_slave_widgets(closing_widget)
140
134
141 keepkernel = None #Use the prompt by default
135 keepkernel = None #Use the prompt by default
142 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
136 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
143 keepkernel = closing_widget._keep_kernel_on_exit
137 keepkernel = closing_widget._keep_kernel_on_exit
144 # If signal sent by exist magic (_keep_kernel_on_exit, exist and not None)
138 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
145 # we set local slave tabs._hidden to True to avoit prompting for kernel
139 # we set local slave tabs._hidden to True to avoid prompting for kernel
146 # restart when they litt get the signal. and the "forward" the 'exit'
140 # restart when they get the signal. and then "forward" the 'exit'
147 # to the main win
141 # to the main window
148 if keepkernel is not None:
142 if keepkernel is not None:
149 for tab in slave_tabs:
143 for tab in slave_tabs:
150 tab._hidden = True
144 tab._hidden = True
151 if closing_widget in slave_tabs :
145 if closing_widget in slave_tabs:
152 try :
146 try :
153 self.find_master_tab(closing_widget).execute('exit')
147 self.find_master_tab(closing_widget).execute('exit')
154 except AttributeError:
148 except AttributeError:
155 self.log.info("Master already closed or not local, closing only current tab")
149 self.log.info("Master already closed or not local, closing only current tab")
156 self.tab_widget.removeTab(current_tab)
150 self.tab_widget.removeTab(current_tab)
151 self.update_tab_bar_visibility()
157 return
152 return
158
153
159 kernel_manager = closing_widget.kernel_manager
154 kernel_manager = closing_widget.kernel_manager
160
155
161 if keepkernel is None and not closing_widget._confirm_exit:
156 if keepkernel is None and not closing_widget._confirm_exit:
162 # don't prompt, just terminate the kernel if we own it
157 # don't prompt, just terminate the kernel if we own it
163 # or leave it alone if we don't
158 # or leave it alone if we don't
164 keepkernel = not closing_widget._existing
159 keepkernel = closing_widget._existing
165
166 if keepkernel is None: #show prompt
160 if keepkernel is None: #show prompt
167 if kernel_manager and kernel_manager.channels_running:
161 if kernel_manager and kernel_manager.channels_running:
168 title = self.window().windowTitle()
162 title = self.window().windowTitle()
169 cancel = QtGui.QMessageBox.Cancel
163 cancel = QtGui.QMessageBox.Cancel
170 okay = QtGui.QMessageBox.Ok
164 okay = QtGui.QMessageBox.Ok
171 if closing_widget._may_close:
165 if closing_widget._may_close:
172 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
166 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
173 info = "Would you like to quit the Kernel and all attached Consoles as well?"
167 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
174 justthis = QtGui.QPushButton("&No, just this Console", self)
168 justthis = QtGui.QPushButton("&No, just this Tab", self)
175 justthis.setShortcut('N')
169 justthis.setShortcut('N')
176 closeall = QtGui.QPushButton("&Yes, quit everything", self)
170 closeall = QtGui.QPushButton("&Yes, close all", self)
177 closeall.setShortcut('Y')
171 closeall.setShortcut('Y')
178 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
172 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
179 title, msg)
173 title, msg)
180 box.setInformativeText(info)
174 box.setInformativeText(info)
181 box.addButton(cancel)
175 box.addButton(cancel)
182 box.addButton(justthis, QtGui.QMessageBox.NoRole)
176 box.addButton(justthis, QtGui.QMessageBox.NoRole)
183 box.addButton(closeall, QtGui.QMessageBox.YesRole)
177 box.addButton(closeall, QtGui.QMessageBox.YesRole)
184 box.setDefaultButton(closeall)
178 box.setDefaultButton(closeall)
185 box.setEscapeButton(cancel)
179 box.setEscapeButton(cancel)
186 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
180 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
187 box.setIconPixmap(pixmap)
181 box.setIconPixmap(pixmap)
188 reply = box.exec_()
182 reply = box.exec_()
189 if reply == 1: # close All
183 if reply == 1: # close All
190 for slave in slave_tabs:
184 for slave in slave_tabs:
191 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
185 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
192 closing_widget.execute("exit")
186 closing_widget.execute("exit")
193 self.tab_widget.removeTab(current_tab)
187 self.tab_widget.removeTab(current_tab)
194 elif reply == 0: # close Console
188 elif reply == 0: # close Console
195 if not closing_widget._existing:
189 if not closing_widget._existing:
196 # Have kernel: don't quit, just close the window
190 # Have kernel: don't quit, just close the tab
197 self._app.setQuitOnLastWindowClosed(False)
198 closing_widget.execute("exit True")
191 closing_widget.execute("exit True")
192 self.tab_widget.removeTab(current_tab)
199 else:
193 else:
200 reply = QtGui.QMessageBox.question(self, title,
194 reply = QtGui.QMessageBox.question(self, title,
201 "Are you sure you want to close this Console?"+
195 "Are you sure you want to close this Console?"+
202 "\nThe Kernel and other Consoles will remain active.",
196 "\nThe Kernel and other Consoles will remain active.",
203 okay|cancel,
197 okay|cancel,
204 defaultButton=okay
198 defaultButton=okay
205 )
199 )
206 if reply == okay:
200 if reply == okay:
207 self.tab_widget.removeTab(current_tab)
201 self.tab_widget.removeTab(current_tab)
208 elif keepkernel: #close console but leave kernel running (no prompt)
202 elif keepkernel: #close console but leave kernel running (no prompt)
203 self.tab_widget.removeTab(current_tab)
209 if kernel_manager and kernel_manager.channels_running:
204 if kernel_manager and kernel_manager.channels_running:
210 if not closing_widget._existing:
205 kernel_manager.stop_channels()
211 # I have the kernel: don't quit, just close the window
212 self.tab_widget.removeTab(current_tab)
213 else: #close console and kernel (no prompt)
206 else: #close console and kernel (no prompt)
207 self.tab_widget.removeTab(current_tab)
214 if kernel_manager and kernel_manager.channels_running:
208 if kernel_manager and kernel_manager.channels_running:
209 kernel_manager.shutdown_kernel()
215 for slave in slave_tabs:
210 for slave in slave_tabs:
211 slave.kernel_manager.stop_channels()
216 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
212 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
217 self.tab_widget.removeTab(current_tab)
213
218 kernel_manager.shutdown_kernel()
219 self.update_tab_bar_visibility()
214 self.update_tab_bar_visibility()
220
215
221 def add_tab_with_frontend(self,frontend,name=None):
216 def add_tab_with_frontend(self,frontend,name=None):
222 """ insert a tab with a given frontend in the tab bar, and give it a name
217 """ insert a tab with a given frontend in the tab bar, and give it a name
223
218
224 """
219 """
225 if not name:
220 if not name:
226 name=str('kernel '+str(self.tab_widget.count()))
221 name=str('kernel '+str(self.tab_widget.count()))
227 self.tab_widget.addTab(frontend,name)
222 self.tab_widget.addTab(frontend,name)
228 self.update_tab_bar_visibility()
223 self.update_tab_bar_visibility()
229 self.make_frontend_visible(frontend)
224 self.make_frontend_visible(frontend)
230 frontend.exit_requested.connect(self.close_tab)
225 frontend.exit_requested.connect(self.close_tab)
231
226
232 def next_tab(self):
227 def next_tab(self):
233 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
228 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
234
229
235 def prev_tab(self):
230 def prev_tab(self):
236 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
231 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
237
232
238 def make_frontend_visible(self,frontend):
233 def make_frontend_visible(self,frontend):
239 widget_index=self.tab_widget.indexOf(frontend)
234 widget_index=self.tab_widget.indexOf(frontend)
240 if widget_index > 0 :
235 if widget_index > 0 :
241 self.tab_widget.setCurrentIndex(widget_index)
236 self.tab_widget.setCurrentIndex(widget_index)
242
237
243 def find_master_tab(self,tab,as_list=False):
238 def find_master_tab(self,tab,as_list=False):
244 """
239 """
245 Try to return the frontend that own the kernel attached to the given widget/tab.
240 Try to return the frontend that own the kernel attached to the given widget/tab.
246
241
247 Only find frontend owed by the current application. Selection
242 Only find frontend owed by the current application. Selection
248 based on port of the kernel, might be inacurate if several kernel
243 based on port of the kernel, might be inacurate if several kernel
249 on different ip use same port number.
244 on different ip use same port number.
250
245
251 This fonction does the conversion tabNumber/widget if needed.
246 This fonction does the conversion tabNumber/widget if needed.
252 Might return None if no master widget (non local kernel)
247 Might return None if no master widget (non local kernel)
253 Will crash IPython if more than 1 masterWidget
248 Will crash IPython if more than 1 masterWidget
254
249
255 When asList set to True, always return a list of widget(s) owning
250 When asList set to True, always return a list of widget(s) owning
256 the kernel. The list might be empty or containing several Widget.
251 the kernel. The list might be empty or containing several Widget.
257 """
252 """
258
253
259 #convert from/to int/richIpythonWidget if needed
254 #convert from/to int/richIpythonWidget if needed
260 if type(tab) == int:
255 if isinstance(tab, int):
261 tab = self.tab_widget.widget(tab)
256 tab = self.tab_widget.widget(tab)
262 km=tab.kernel_manager;
257 km=tab.kernel_manager
263
258
264 #build list of all widgets
259 #build list of all widgets
265 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
260 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
266
261
267 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
262 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
268 # And should have a _may_close attribute
263 # And should have a _may_close attribute
269 filtred_widget_list = [ widget for widget in widget_list if
264 filtered_widget_list = [ widget for widget in widget_list if
270 widget.kernel_manager.connection_file == km.connection_file and
265 widget.kernel_manager.connection_file == km.connection_file and
271 hasattr(widget,'_may_close') ]
266 hasattr(widget,'_may_close') ]
272 # the master widget is the one that may close the kernel
267 # the master widget is the one that may close the kernel
273 master_widget= [ widget for widget in filtred_widget_list if widget._may_close]
268 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
274 if as_list:
269 if as_list:
275 return master_widget
270 return master_widget
276 assert(len(master_widget)<=1 )
271 assert(len(master_widget)<=1 )
277 if len(master_widget)==0:
272 if len(master_widget)==0:
278 return None
273 return None
279
274
280 return master_widget[0]
275 return master_widget[0]
281
276
282 def find_slaves_tabs(self,tab):
277 def find_slave_widgets(self,tab):
283 """
278 """return all the frontends that do not own the kernel attached to the given widget/tab.
284 Try to return all the frontend that do not own the kernel attached to the given widget/tab.
285
279
286 Only find frontend owed by the current application. Selection
280 Only find frontends owned by the current application. Selection
287 based on port of the kernel, might be innacurate if several kernel
281 based on connection file of the kernel.
288 on different ip use same port number.
289
282
290 This fonction does the conversion tabNumber/widget if needed.
283 This function does the conversion tabNumber/widget if needed.
291 """
284 """
292 #convert from/to int/richIpythonWidget if needed
285 #convert from/to int/richIpythonWidget if needed
293 if type(tab) == int:
286 if isinstance(tab, int):
294 tab = self.tab_widget.widget(tab)
287 tab = self.tab_widget.widget(tab)
295 km=tab.kernel_manager;
288 km=tab.kernel_manager
296
289
297 #build list of all widgets
290 #build list of all widgets
298 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
291 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
299
292
300 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
293 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
301 filtered_widget_list = ( widget for widget in widget_list if
294 filtered_widget_list = ( widget for widget in widget_list if
302 widget.kernel_manager.connection_file == km.connection_file)
295 widget.kernel_manager.connection_file == km.connection_file)
303 # Get a list of all widget owning the same kernel and removed it from
296 # Get a list of all widget owning the same kernel and removed it from
304 # the previous cadidate. (better using sets ?)
297 # the previous cadidate. (better using sets ?)
305 master_widget_list = self.find_master_tab(tab,as_list=True)
298 master_widget_list = self.find_master_tab(tab, as_list=True)
306 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
299 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
307
300
308 return slave_list
301 return slave_list
309
302
310 # Populate the menu bar with common actions and shortcuts
303 # Populate the menu bar with common actions and shortcuts
311 def add_menu_action(self, menu, action):
304 def add_menu_action(self, menu, action):
312 """Add action to menu as well as self
305 """Add action to menu as well as self
313
306
314 So that when the menu bar is invisible, its actions are still available.
307 So that when the menu bar is invisible, its actions are still available.
315 """
308 """
316 menu.addAction(action)
309 menu.addAction(action)
317 self.addAction(action)
310 self.addAction(action)
318
311
319 def init_menu_bar(self):
312 def init_menu_bar(self):
320 #create menu in the order they should appear in the menu bar
313 #create menu in the order they should appear in the menu bar
321 self.init_file_menu()
314 self.init_file_menu()
322 self.init_edit_menu()
315 self.init_edit_menu()
323 self.init_view_menu()
316 self.init_view_menu()
324 self.init_kernel_menu()
317 self.init_kernel_menu()
325 self.init_magic_menu()
318 self.init_magic_menu()
326 self.init_window_menu()
319 self.init_window_menu()
327 self.init_help_menu()
320 self.init_help_menu()
328
321
329 def init_file_menu(self):
322 def init_file_menu(self):
330 self.file_menu = self.menuBar().addMenu("&File")
323 self.file_menu = self.menuBar().addMenu("&File")
331
324
332 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
325 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
333 self,
326 self,
334 shortcut="Ctrl+T",
327 shortcut="Ctrl+T",
335 triggered=self.create_tab_with_new_frontend)
328 triggered=self.create_tab_with_new_frontend)
336 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
329 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
337
330
338 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
331 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
339 self,
332 self,
340 shortcut="Ctrl+Shift+T",
333 shortcut="Ctrl+Shift+T",
341 triggered=self.create_tab_with_current_kernel)
334 triggered=self.create_tab_with_current_kernel)
342 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
335 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
343
336
344 self.file_menu.addSeparator()
337 self.file_menu.addSeparator()
345
338
346 self.close_action=QtGui.QAction("&Close Tab",
339 self.close_action=QtGui.QAction("&Close Tab",
347 self,
340 self,
348 shortcut="Ctrl+W",
341 shortcut="Ctrl+W",
349 triggered=self.close_active_frontend
342 triggered=self.close_active_frontend
350 )
343 )
351 self.add_menu_action(self.file_menu, self.close_action)
344 self.add_menu_action(self.file_menu, self.close_action)
352
345
353 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
346 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
354 self,
347 self,
355 shortcut="Ctrl+S",
348 shortcut="Ctrl+S",
356 triggered=self.export_action_active_frontend
349 triggered=self.export_action_active_frontend
357 )
350 )
358 self.add_menu_action(self.file_menu, self.export_action)
351 self.add_menu_action(self.file_menu, self.export_action)
359
352
360 self.file_menu.addSeparator()
353 self.file_menu.addSeparator()
361
354
362 # Ctrl actually maps to Cmd on OSX, which avoids conflict with history
355 # Ctrl actually maps to Cmd on OSX, which avoids conflict with history
363 # action, which is already bound to true Ctrl+P
356 # action, which is already bound to true Ctrl+P
364 print_shortcut = "Ctrl+P" if sys.platform == 'darwin' else 'Ctrl+Shift+P'
357 print_shortcut = "Ctrl+P" if sys.platform == 'darwin' else 'Ctrl+Shift+P'
365 self.print_action = QtGui.QAction("&Print",
358 self.print_action = QtGui.QAction("&Print",
366 self,
359 self,
367 shortcut=print_shortcut,
360 shortcut=print_shortcut,
368 triggered=self.print_action_active_frontend)
361 triggered=self.print_action_active_frontend)
369 self.add_menu_action(self.file_menu, self.print_action)
362 self.add_menu_action(self.file_menu, self.print_action)
370
363
371 if sys.platform == 'darwin':
364 if sys.platform == 'darwin':
372 # OSX always has Quit in the Application menu, only add it
365 # OSX always has Quit in the Application menu, only add it
373 # to the File menu elsewhere.
366 # to the File menu elsewhere.
374
367
375 self.file_menu.addSeparator()
368 self.file_menu.addSeparator()
376
369
377 self.quit_action = QtGui.QAction("&Quit",
370 self.quit_action = QtGui.QAction("&Quit",
378 self,
371 self,
379 shortcut=QtGui.QKeySequence.Quit,
372 shortcut=QtGui.QKeySequence.Quit,
380 triggered=self.close,
373 triggered=self.close,
381 )
374 )
382 self.add_menu_action(self.file_menu, self.quit_action)
375 self.add_menu_action(self.file_menu, self.quit_action)
383
376
384
377
385 def init_edit_menu(self):
378 def init_edit_menu(self):
386 self.edit_menu = self.menuBar().addMenu("&Edit")
379 self.edit_menu = self.menuBar().addMenu("&Edit")
387
380
388 self.undo_action = QtGui.QAction("&Undo",
381 self.undo_action = QtGui.QAction("&Undo",
389 self,
382 self,
390 shortcut="Ctrl+Z",
383 shortcut="Ctrl+Z",
391 statusTip="Undo last action if possible",
384 statusTip="Undo last action if possible",
392 triggered=self.undo_active_frontend
385 triggered=self.undo_active_frontend
393 )
386 )
394 self.add_menu_action(self.edit_menu, self.undo_action)
387 self.add_menu_action(self.edit_menu, self.undo_action)
395
388
396 self.redo_action = QtGui.QAction("&Redo",
389 self.redo_action = QtGui.QAction("&Redo",
397 self,
390 self,
398 shortcut="Ctrl+Shift+Z",
391 shortcut="Ctrl+Shift+Z",
399 statusTip="Redo last action if possible",
392 statusTip="Redo last action if possible",
400 triggered=self.redo_active_frontend)
393 triggered=self.redo_active_frontend)
401 self.add_menu_action(self.edit_menu, self.redo_action)
394 self.add_menu_action(self.edit_menu, self.redo_action)
402
395
403 self.edit_menu.addSeparator()
396 self.edit_menu.addSeparator()
404
397
405 self.cut_action = QtGui.QAction("&Cut",
398 self.cut_action = QtGui.QAction("&Cut",
406 self,
399 self,
407 shortcut=QtGui.QKeySequence.Cut,
400 shortcut=QtGui.QKeySequence.Cut,
408 triggered=self.cut_active_frontend
401 triggered=self.cut_active_frontend
409 )
402 )
410 self.add_menu_action(self.edit_menu, self.cut_action)
403 self.add_menu_action(self.edit_menu, self.cut_action)
411
404
412 self.copy_action = QtGui.QAction("&Copy",
405 self.copy_action = QtGui.QAction("&Copy",
413 self,
406 self,
414 shortcut=QtGui.QKeySequence.Copy,
407 shortcut=QtGui.QKeySequence.Copy,
415 triggered=self.copy_active_frontend
408 triggered=self.copy_active_frontend
416 )
409 )
417 self.add_menu_action(self.edit_menu, self.copy_action)
410 self.add_menu_action(self.edit_menu, self.copy_action)
418
411
419 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
412 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
420 self,
413 self,
421 shortcut="Ctrl+Shift+C",
414 shortcut="Ctrl+Shift+C",
422 triggered=self.copy_raw_active_frontend
415 triggered=self.copy_raw_active_frontend
423 )
416 )
424 self.add_menu_action(self.edit_menu, self.copy_raw_action)
417 self.add_menu_action(self.edit_menu, self.copy_raw_action)
425
418
426 self.paste_action = QtGui.QAction("&Paste",
419 self.paste_action = QtGui.QAction("&Paste",
427 self,
420 self,
428 shortcut=QtGui.QKeySequence.Paste,
421 shortcut=QtGui.QKeySequence.Paste,
429 triggered=self.paste_active_frontend
422 triggered=self.paste_active_frontend
430 )
423 )
431 self.add_menu_action(self.edit_menu, self.paste_action)
424 self.add_menu_action(self.edit_menu, self.paste_action)
432
425
433 self.edit_menu.addSeparator()
426 self.edit_menu.addSeparator()
434
427
435 self.select_all_action = QtGui.QAction("Select &All",
428 self.select_all_action = QtGui.QAction("Select &All",
436 self,
429 self,
437 shortcut="Ctrl+A",
430 shortcut="Ctrl+A",
438 triggered=self.select_all_active_frontend
431 triggered=self.select_all_active_frontend
439 )
432 )
440 self.add_menu_action(self.edit_menu, self.select_all_action)
433 self.add_menu_action(self.edit_menu, self.select_all_action)
441
434
442
435
443 def init_view_menu(self):
436 def init_view_menu(self):
444 self.view_menu = self.menuBar().addMenu("&View")
437 self.view_menu = self.menuBar().addMenu("&View")
445
438
446 if sys.platform != 'darwin':
439 if sys.platform != 'darwin':
447 # disable on OSX, where there is always a menu bar
440 # disable on OSX, where there is always a menu bar
448 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
441 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
449 self,
442 self,
450 shortcut="Ctrl+Shift+M",
443 shortcut="Ctrl+Shift+M",
451 statusTip="Toggle visibility of menubar",
444 statusTip="Toggle visibility of menubar",
452 triggered=self.toggle_menu_bar)
445 triggered=self.toggle_menu_bar)
453 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
446 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
454
447
455 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
448 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
456 self.full_screen_act = QtGui.QAction("&Full Screen",
449 self.full_screen_act = QtGui.QAction("&Full Screen",
457 self,
450 self,
458 shortcut=fs_key,
451 shortcut=fs_key,
459 statusTip="Toggle between Fullscreen and Normal Size",
452 statusTip="Toggle between Fullscreen and Normal Size",
460 triggered=self.toggleFullScreen)
453 triggered=self.toggleFullScreen)
461 self.add_menu_action(self.view_menu, self.full_screen_act)
454 self.add_menu_action(self.view_menu, self.full_screen_act)
462
455
463 self.view_menu.addSeparator()
456 self.view_menu.addSeparator()
464
457
465 self.increase_font_size = QtGui.QAction("Zoom &In",
458 self.increase_font_size = QtGui.QAction("Zoom &In",
466 self,
459 self,
467 shortcut="Ctrl++",
460 shortcut="Ctrl++",
468 triggered=self.increase_font_size_active_frontend
461 triggered=self.increase_font_size_active_frontend
469 )
462 )
470 self.add_menu_action(self.view_menu, self.increase_font_size)
463 self.add_menu_action(self.view_menu, self.increase_font_size)
471
464
472 self.decrease_font_size = QtGui.QAction("Zoom &Out",
465 self.decrease_font_size = QtGui.QAction("Zoom &Out",
473 self,
466 self,
474 shortcut="Ctrl+-",
467 shortcut="Ctrl+-",
475 triggered=self.decrease_font_size_active_frontend
468 triggered=self.decrease_font_size_active_frontend
476 )
469 )
477 self.add_menu_action(self.view_menu, self.decrease_font_size)
470 self.add_menu_action(self.view_menu, self.decrease_font_size)
478
471
479 self.reset_font_size = QtGui.QAction("Zoom &Reset",
472 self.reset_font_size = QtGui.QAction("Zoom &Reset",
480 self,
473 self,
481 shortcut="Ctrl+0",
474 shortcut="Ctrl+0",
482 triggered=self.reset_font_size_active_frontend
475 triggered=self.reset_font_size_active_frontend
483 )
476 )
484 self.add_menu_action(self.view_menu, self.reset_font_size)
477 self.add_menu_action(self.view_menu, self.reset_font_size)
485
478
486 self.view_menu.addSeparator()
479 self.view_menu.addSeparator()
487
480
488 self.clear_action = QtGui.QAction("&Clear Screen",
481 self.clear_action = QtGui.QAction("&Clear Screen",
489 self,
482 self,
490 shortcut='Ctrl+L',
483 shortcut='Ctrl+L',
491 statusTip="Clear the console",
484 statusTip="Clear the console",
492 triggered=self.clear_magic_active_frontend)
485 triggered=self.clear_magic_active_frontend)
493 self.add_menu_action(self.view_menu, self.clear_action)
486 self.add_menu_action(self.view_menu, self.clear_action)
494
487
495 def init_kernel_menu(self):
488 def init_kernel_menu(self):
496 self.kernel_menu = self.menuBar().addMenu("&Kernel")
489 self.kernel_menu = self.menuBar().addMenu("&Kernel")
497 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
490 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
498 # keep the signal shortcuts to ctrl, rather than
491 # keep the signal shortcuts to ctrl, rather than
499 # platform-default like we do elsewhere.
492 # platform-default like we do elsewhere.
500
493
501 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
494 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
502
495
503 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
496 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
504 self,
497 self,
505 triggered=self.interrupt_kernel_active_frontend,
498 triggered=self.interrupt_kernel_active_frontend,
506 shortcut=ctrl+"+C",
499 shortcut=ctrl+"+C",
507 )
500 )
508 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
501 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
509
502
510 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
503 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
511 self,
504 self,
512 triggered=self.restart_kernel_active_frontend,
505 triggered=self.restart_kernel_active_frontend,
513 shortcut=ctrl+"+.",
506 shortcut=ctrl+"+.",
514 )
507 )
515 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
508 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
516
509
517 self.kernel_menu.addSeparator()
510 self.kernel_menu.addSeparator()
518
511
519 def init_magic_menu(self):
512 def init_magic_menu(self):
520 self.magic_menu = self.menuBar().addMenu("&Magic")
513 self.magic_menu = self.menuBar().addMenu("&Magic")
521 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
514 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
522
515
523 self.reset_action = QtGui.QAction("&Reset",
516 self.reset_action = QtGui.QAction("&Reset",
524 self,
517 self,
525 statusTip="Clear all varible from workspace",
518 statusTip="Clear all varible from workspace",
526 triggered=self.reset_magic_active_frontend)
519 triggered=self.reset_magic_active_frontend)
527 self.add_menu_action(self.magic_menu, self.reset_action)
520 self.add_menu_action(self.magic_menu, self.reset_action)
528
521
529 self.history_action = QtGui.QAction("&History",
522 self.history_action = QtGui.QAction("&History",
530 self,
523 self,
531 statusTip="show command history",
524 statusTip="show command history",
532 triggered=self.history_magic_active_frontend)
525 triggered=self.history_magic_active_frontend)
533 self.add_menu_action(self.magic_menu, self.history_action)
526 self.add_menu_action(self.magic_menu, self.history_action)
534
527
535 self.save_action = QtGui.QAction("E&xport History ",
528 self.save_action = QtGui.QAction("E&xport History ",
536 self,
529 self,
537 statusTip="Export History as Python File",
530 statusTip="Export History as Python File",
538 triggered=self.save_magic_active_frontend)
531 triggered=self.save_magic_active_frontend)
539 self.add_menu_action(self.magic_menu, self.save_action)
532 self.add_menu_action(self.magic_menu, self.save_action)
540
533
541 self.who_action = QtGui.QAction("&Who",
534 self.who_action = QtGui.QAction("&Who",
542 self,
535 self,
543 statusTip="List interactive variable",
536 statusTip="List interactive variable",
544 triggered=self.who_magic_active_frontend)
537 triggered=self.who_magic_active_frontend)
545 self.add_menu_action(self.magic_menu, self.who_action)
538 self.add_menu_action(self.magic_menu, self.who_action)
546
539
547 self.who_ls_action = QtGui.QAction("Wh&o ls",
540 self.who_ls_action = QtGui.QAction("Wh&o ls",
548 self,
541 self,
549 statusTip="Return a list of interactive variable",
542 statusTip="Return a list of interactive variable",
550 triggered=self.who_ls_magic_active_frontend)
543 triggered=self.who_ls_magic_active_frontend)
551 self.add_menu_action(self.magic_menu, self.who_ls_action)
544 self.add_menu_action(self.magic_menu, self.who_ls_action)
552
545
553 self.whos_action = QtGui.QAction("Who&s",
546 self.whos_action = QtGui.QAction("Who&s",
554 self,
547 self,
555 statusTip="List interactive variable with detail",
548 statusTip="List interactive variable with detail",
556 triggered=self.whos_magic_active_frontend)
549 triggered=self.whos_magic_active_frontend)
557 self.add_menu_action(self.magic_menu, self.whos_action)
550 self.add_menu_action(self.magic_menu, self.whos_action)
558
551
559 # allmagics submenu:
552 # allmagics submenu:
560
553
561 #for now this is just a copy and paste, but we should get this dynamically
554 #for now this is just a copy and paste, but we should get this dynamically
562 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
555 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
563 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
556 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
564 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
557 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
565 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
558 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
566 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
559 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
567 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
560 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
568 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
561 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
569 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
562 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
570 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
563 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
571 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
564 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
572
565
573 def make_dynamic_magic(i):
566 def make_dynamic_magic(i):
574 def inner_dynamic_magic():
567 def inner_dynamic_magic():
575 self.active_frontend.execute(i)
568 self.active_frontend.execute(i)
576 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
569 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
577 return inner_dynamic_magic
570 return inner_dynamic_magic
578
571
579 for magic in magiclist:
572 for magic in magiclist:
580 xaction = QtGui.QAction(magic,
573 xaction = QtGui.QAction(magic,
581 self,
574 self,
582 triggered=make_dynamic_magic(magic)
575 triggered=make_dynamic_magic(magic)
583 )
576 )
584 self.all_magic_menu.addAction(xaction)
577 self.all_magic_menu.addAction(xaction)
585
578
586 def init_window_menu(self):
579 def init_window_menu(self):
587 self.window_menu = self.menuBar().addMenu("&Window")
580 self.window_menu = self.menuBar().addMenu("&Window")
588 if sys.platform == 'darwin':
581 if sys.platform == 'darwin':
589 # add min/maximize actions to OSX, which lacks default bindings.
582 # add min/maximize actions to OSX, which lacks default bindings.
590 self.minimizeAct = QtGui.QAction("Mini&mize",
583 self.minimizeAct = QtGui.QAction("Mini&mize",
591 self,
584 self,
592 shortcut="Ctrl+m",
585 shortcut="Ctrl+m",
593 statusTip="Minimize the window/Restore Normal Size",
586 statusTip="Minimize the window/Restore Normal Size",
594 triggered=self.toggleMinimized)
587 triggered=self.toggleMinimized)
595 # maximize is called 'Zoom' on OSX for some reason
588 # maximize is called 'Zoom' on OSX for some reason
596 self.maximizeAct = QtGui.QAction("&Zoom",
589 self.maximizeAct = QtGui.QAction("&Zoom",
597 self,
590 self,
598 shortcut="Ctrl+Shift+M",
591 shortcut="Ctrl+Shift+M",
599 statusTip="Maximize the window/Restore Normal Size",
592 statusTip="Maximize the window/Restore Normal Size",
600 triggered=self.toggleMaximized)
593 triggered=self.toggleMaximized)
601
594
602 self.add_menu_action(self.window_menu, self.minimizeAct)
595 self.add_menu_action(self.window_menu, self.minimizeAct)
603 self.add_menu_action(self.window_menu, self.maximizeAct)
596 self.add_menu_action(self.window_menu, self.maximizeAct)
604 self.window_menu.addSeparator()
597 self.window_menu.addSeparator()
605
598
606 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
599 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
607 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
600 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
608 self,
601 self,
609 shortcut=prev_key,
602 shortcut=prev_key,
610 statusTip="Select previous tab",
603 statusTip="Select previous tab",
611 triggered=self.prev_tab)
604 triggered=self.prev_tab)
612 self.add_menu_action(self.window_menu, self.prev_tab_act)
605 self.add_menu_action(self.window_menu, self.prev_tab_act)
613
606
614 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
607 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
615 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
608 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
616 self,
609 self,
617 shortcut=next_key,
610 shortcut=next_key,
618 statusTip="Select next tab",
611 statusTip="Select next tab",
619 triggered=self.next_tab)
612 triggered=self.next_tab)
620 self.add_menu_action(self.window_menu, self.next_tab_act)
613 self.add_menu_action(self.window_menu, self.next_tab_act)
621
614
622 def init_help_menu(self):
615 def init_help_menu(self):
623 # please keep the Help menu in Mac Os even if empty. It will
616 # please keep the Help menu in Mac Os even if empty. It will
624 # automatically contain a search field to search inside menus and
617 # automatically contain a search field to search inside menus and
625 # please keep it spelled in English, as long as Qt Doesn't support
618 # please keep it spelled in English, as long as Qt Doesn't support
626 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
619 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
627 # this search field fonctionality
620 # this search field fonctionality
628
621
629 self.help_menu = self.menuBar().addMenu("&Help")
622 self.help_menu = self.menuBar().addMenu("&Help")
630
623
631
624
632 # Help Menu
625 # Help Menu
633
626
634 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
627 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
635 self,
628 self,
636 triggered=self.intro_active_frontend
629 triggered=self.intro_active_frontend
637 )
630 )
638 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
631 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
639
632
640 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
633 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
641 self,
634 self,
642 triggered=self.quickref_active_frontend
635 triggered=self.quickref_active_frontend
643 )
636 )
644 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
637 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
645
638
646 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
639 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
647 self,
640 self,
648 triggered=self.guiref_active_frontend
641 triggered=self.guiref_active_frontend
649 )
642 )
650 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
643 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
651
644
652 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
645 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
653 self,
646 self,
654 triggered=self._open_online_help)
647 triggered=self._open_online_help)
655 self.add_menu_action(self.help_menu, self.onlineHelpAct)
648 self.add_menu_action(self.help_menu, self.onlineHelpAct)
656
649
657 # minimize/maximize/fullscreen actions:
650 # minimize/maximize/fullscreen actions:
658
651
659 def toggle_menu_bar(self):
652 def toggle_menu_bar(self):
660 menu_bar = self.menuBar();
653 menu_bar = self.menuBar()
661 if menu_bar.isVisible():
654 if menu_bar.isVisible():
662 menu_bar.setVisible(False)
655 menu_bar.setVisible(False)
663 else:
656 else:
664 menu_bar.setVisible(True)
657 menu_bar.setVisible(True)
665
658
666 def toggleMinimized(self):
659 def toggleMinimized(self):
667 if not self.isMinimized():
660 if not self.isMinimized():
668 self.showMinimized()
661 self.showMinimized()
669 else:
662 else:
670 self.showNormal()
663 self.showNormal()
671
664
672 def _open_online_help(self):
665 def _open_online_help(self):
673 filename="http://ipython.org/ipython-doc/stable/index.html"
666 filename="http://ipython.org/ipython-doc/stable/index.html"
674 webbrowser.open(filename, new=1, autoraise=True)
667 webbrowser.open(filename, new=1, autoraise=True)
675
668
676 def toggleMaximized(self):
669 def toggleMaximized(self):
677 if not self.isMaximized():
670 if not self.isMaximized():
678 self.showMaximized()
671 self.showMaximized()
679 else:
672 else:
680 self.showNormal()
673 self.showNormal()
681
674
682 # Min/Max imizing while in full screen give a bug
675 # Min/Max imizing while in full screen give a bug
683 # when going out of full screen, at least on OSX
676 # when going out of full screen, at least on OSX
684 def toggleFullScreen(self):
677 def toggleFullScreen(self):
685 if not self.isFullScreen():
678 if not self.isFullScreen():
686 self.showFullScreen()
679 self.showFullScreen()
687 if sys.platform == 'darwin':
680 if sys.platform == 'darwin':
688 self.maximizeAct.setEnabled(False)
681 self.maximizeAct.setEnabled(False)
689 self.minimizeAct.setEnabled(False)
682 self.minimizeAct.setEnabled(False)
690 else:
683 else:
691 self.showNormal()
684 self.showNormal()
692 if sys.platform == 'darwin':
685 if sys.platform == 'darwin':
693 self.maximizeAct.setEnabled(True)
686 self.maximizeAct.setEnabled(True)
694 self.minimizeAct.setEnabled(True)
687 self.minimizeAct.setEnabled(True)
695
688
696 def close_active_frontend(self):
689 def close_active_frontend(self):
697 self.close_tab(self.active_frontend)
690 self.close_tab(self.active_frontend)
698
691
699 def restart_kernel_active_frontend(self):
692 def restart_kernel_active_frontend(self):
700 self.active_frontend.request_restart_kernel()
693 self.active_frontend.request_restart_kernel()
701
694
702 def interrupt_kernel_active_frontend(self):
695 def interrupt_kernel_active_frontend(self):
703 self.active_frontend.request_interrupt_kernel()
696 self.active_frontend.request_interrupt_kernel()
704
697
705 def cut_active_frontend(self):
698 def cut_active_frontend(self):
706 self.active_frontend.cut_action.trigger()
699 self.active_frontend.cut_action.trigger()
707
700
708 def copy_active_frontend(self):
701 def copy_active_frontend(self):
709 self.active_frontend.copy_action.trigger()
702 self.active_frontend.copy_action.trigger()
710
703
711 def copy_raw_active_frontend(self):
704 def copy_raw_active_frontend(self):
712 self.active_frontend._copy_raw_action.trigger()
705 self.active_frontend._copy_raw_action.trigger()
713
706
714 def paste_active_frontend(self):
707 def paste_active_frontend(self):
715 self.active_frontend.paste_action.trigger()
708 self.active_frontend.paste_action.trigger()
716
709
717 def undo_active_frontend(self):
710 def undo_active_frontend(self):
718 self.active_frontend.undo()
711 self.active_frontend.undo()
719
712
720 def redo_active_frontend(self):
713 def redo_active_frontend(self):
721 self.active_frontend.redo()
714 self.active_frontend.redo()
722
715
723 def reset_magic_active_frontend(self):
716 def reset_magic_active_frontend(self):
724 self.active_frontend.execute("%reset")
717 self.active_frontend.execute("%reset")
725
718
726 def history_magic_active_frontend(self):
719 def history_magic_active_frontend(self):
727 self.active_frontend.execute("%history")
720 self.active_frontend.execute("%history")
728
721
729 def save_magic_active_frontend(self):
722 def save_magic_active_frontend(self):
730 self.active_frontend.save_magic()
723 self.active_frontend.save_magic()
731
724
732 def clear_magic_active_frontend(self):
725 def clear_magic_active_frontend(self):
733 self.active_frontend.execute("%clear")
726 self.active_frontend.execute("%clear")
734
727
735 def who_magic_active_frontend(self):
728 def who_magic_active_frontend(self):
736 self.active_frontend.execute("%who")
729 self.active_frontend.execute("%who")
737
730
738 def who_ls_magic_active_frontend(self):
731 def who_ls_magic_active_frontend(self):
739 self.active_frontend.execute("%who_ls")
732 self.active_frontend.execute("%who_ls")
740
733
741 def whos_magic_active_frontend(self):
734 def whos_magic_active_frontend(self):
742 self.active_frontend.execute("%whos")
735 self.active_frontend.execute("%whos")
743
736
744 def print_action_active_frontend(self):
737 def print_action_active_frontend(self):
745 self.active_frontend.print_action.trigger()
738 self.active_frontend.print_action.trigger()
746
739
747 def export_action_active_frontend(self):
740 def export_action_active_frontend(self):
748 self.active_frontend.export_action.trigger()
741 self.active_frontend.export_action.trigger()
749
742
750 def select_all_active_frontend(self):
743 def select_all_active_frontend(self):
751 self.active_frontend.select_all_action.trigger()
744 self.active_frontend.select_all_action.trigger()
752
745
753 def increase_font_size_active_frontend(self):
746 def increase_font_size_active_frontend(self):
754 self.active_frontend.increase_font_size.trigger()
747 self.active_frontend.increase_font_size.trigger()
755
748
756 def decrease_font_size_active_frontend(self):
749 def decrease_font_size_active_frontend(self):
757 self.active_frontend.decrease_font_size.trigger()
750 self.active_frontend.decrease_font_size.trigger()
758
751
759 def reset_font_size_active_frontend(self):
752 def reset_font_size_active_frontend(self):
760 self.active_frontend.reset_font_size.trigger()
753 self.active_frontend.reset_font_size.trigger()
761
754
762 def guiref_active_frontend(self):
755 def guiref_active_frontend(self):
763 self.active_frontend.execute("%guiref")
756 self.active_frontend.execute("%guiref")
764
757
765 def intro_active_frontend(self):
758 def intro_active_frontend(self):
766 self.active_frontend.execute("?")
759 self.active_frontend.execute("?")
767
760
768 def quickref_active_frontend(self):
761 def quickref_active_frontend(self):
769 self.active_frontend.execute("%quickref")
762 self.active_frontend.execute("%quickref")
770 #---------------------------------------------------------------------------
763 #---------------------------------------------------------------------------
771 # QWidget interface
764 # QWidget interface
772 #---------------------------------------------------------------------------
765 #---------------------------------------------------------------------------
773
766
774 def closeEvent(self, event):
767 def closeEvent(self, event):
775 """ Forward the close event to every tabs contained by the windows
768 """ Forward the close event to every tabs contained by the windows
776 """
769 """
770 if self.tab_widget.count() == 0:
771 # no tabs, just close
772 event.accept()
773 return
777 # Do Not loop on the widget count as it change while closing
774 # Do Not loop on the widget count as it change while closing
778 widget_list=[ self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
775 title = self.window().windowTitle()
779 for widget in widget_list:
776 cancel = QtGui.QMessageBox.Cancel
780 self.close_tab(widget)
777 okay = QtGui.QMessageBox.Ok
778
779 if self.confirm_exit:
780 msg = "Close all tabs, stop all kernels, and Quit?"
781 closeall = QtGui.QPushButton("&Yes, quit everything", self)
782 closeall.setShortcut('Y')
783 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
784 title, msg)
785 # box.setInformativeText(info)
786 box.addButton(cancel)
787 box.addButton(closeall, QtGui.QMessageBox.YesRole)
788 box.setDefaultButton(closeall)
789 box.setEscapeButton(cancel)
790 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
791 box.setIconPixmap(pixmap)
792 reply = box.exec_()
793 else:
794 reply = okay
795
796 if reply == cancel:
797 return
798 if reply == okay:
799 while self.tab_widget.count() >= 1:
800 # prevent further confirmations:
801 widget = self.active_frontend
802 widget._confirm_exit = False
803 self.close_tab(widget)
804
781 event.accept()
805 event.accept()
782
806
@@ -1,538 +1,538 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
32 from IPython.config.application import boolean_flag
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, Int, CaselessStrEnum, CBool, Any
45 Dict, List, Unicode, Int, CaselessStrEnum, CBool, Any
46 )
46 )
47 from IPython.zmq.ipkernel import (
47 from IPython.zmq.ipkernel import (
48 flags as ipkernel_flags,
48 flags as ipkernel_flags,
49 aliases as ipkernel_aliases,
49 aliases as ipkernel_aliases,
50 IPKernelApp
50 IPKernelApp
51 )
51 )
52 from IPython.zmq.session import Session, default_secure
52 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Network Constants
56 # Network Constants
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Globals
62 # Globals
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 _examples = """
65 _examples = """
66 ipython qtconsole # start the qtconsole
66 ipython qtconsole # start the qtconsole
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 """
68 """
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # 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 = Int(0, config=True,
171 hb_port = Int(0, config=True,
172 help="set the heartbeat port [default: random]")
172 help="set the heartbeat port [default: random]")
173 shell_port = Int(0, config=True,
173 shell_port = Int(0, config=True,
174 help="set the shell (XREP) port [default: random]")
174 help="set the shell (XREP) port [default: random]")
175 iopub_port = Int(0, config=True,
175 iopub_port = Int(0, config=True,
176 help="set the iopub (PUB) port [default: random]")
176 help="set the iopub (PUB) port [default: random]")
177 stdin_port = Int(0, config=True,
177 stdin_port = Int(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
225
226 self.kernel_argv = list(argv) # copy
226 self.kernel_argv = list(argv) # copy
227 # kernel should inherit default config file from frontend
227 # kernel should inherit default config file from frontend
228 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
228 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
229 # Scrub frontend-specific flags
229 # Scrub frontend-specific flags
230 for a in argv:
230 for a in argv:
231 if a.startswith('-') and a.lstrip('-') in qt_flags:
231 if a.startswith('-') and a.lstrip('-') in qt_flags:
232 self.kernel_argv.remove(a)
232 self.kernel_argv.remove(a)
233 swallow_next = False
233 swallow_next = False
234 for a in argv:
234 for a in argv:
235 if swallow_next:
235 if swallow_next:
236 self.kernel_argv.remove(a)
236 self.kernel_argv.remove(a)
237 swallow_next = False
237 swallow_next = False
238 continue
238 continue
239 if a.startswith('-'):
239 if a.startswith('-'):
240 split = a.lstrip('-').split('=')
240 split = a.lstrip('-').split('=')
241 alias = split[0]
241 alias = split[0]
242 if alias in qt_aliases:
242 if alias in qt_aliases:
243 self.kernel_argv.remove(a)
243 self.kernel_argv.remove(a)
244 if len(split) == 1:
244 if len(split) == 1:
245 # alias passed with arg via space
245 # alias passed with arg via space
246 swallow_next = True
246 swallow_next = True
247
247
248 def init_connection_file(self):
248 def init_connection_file(self):
249 """find the connection file, and load the info if found.
249 """find the connection file, and load the info if found.
250
250
251 The current working directory and the current profile's security
251 The current working directory and the current profile's security
252 directory will be searched for the file if it is not given by
252 directory will be searched for the file if it is not given by
253 absolute path.
253 absolute path.
254
254
255 When attempting to connect to an existing kernel and the `--existing`
255 When attempting to connect to an existing kernel and the `--existing`
256 argument does not match an existing file, it will be interpreted as a
256 argument does not match an existing file, it will be interpreted as a
257 fileglob, and the matching file in the current profile's security dir
257 fileglob, and the matching file in the current profile's security dir
258 with the latest access time will be used.
258 with the latest access time will be used.
259 """
259 """
260 if self.existing:
260 if self.existing:
261 try:
261 try:
262 cf = find_connection_file(self.existing)
262 cf = find_connection_file(self.existing)
263 except Exception:
263 except Exception:
264 self.log.critical("Could not find existing kernel connection file %s", self.existing)
264 self.log.critical("Could not find existing kernel connection file %s", self.existing)
265 self.exit(1)
265 self.exit(1)
266 self.log.info("Connecting to existing kernel: %s" % cf)
266 self.log.info("Connecting to existing kernel: %s" % cf)
267 self.connection_file = cf
267 self.connection_file = cf
268 # should load_connection_file only be used for existing?
268 # should load_connection_file only be used for existing?
269 # as it is now, this allows reusing ports if an existing
269 # as it is now, this allows reusing ports if an existing
270 # file is requested
270 # file is requested
271 try:
271 try:
272 self.load_connection_file()
272 self.load_connection_file()
273 except Exception:
273 except Exception:
274 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
274 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
275 self.exit(1)
275 self.exit(1)
276
276
277 def load_connection_file(self):
277 def load_connection_file(self):
278 """load ip/port/hmac config from JSON connection file"""
278 """load ip/port/hmac config from JSON connection file"""
279 # this is identical to KernelApp.load_connection_file
279 # this is identical to KernelApp.load_connection_file
280 # perhaps it can be centralized somewhere?
280 # perhaps it can be centralized somewhere?
281 try:
281 try:
282 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
282 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
283 except IOError:
283 except IOError:
284 self.log.debug("Connection File not found: %s", self.connection_file)
284 self.log.debug("Connection File not found: %s", self.connection_file)
285 return
285 return
286 self.log.debug(u"Loading connection file %s", fname)
286 self.log.debug(u"Loading connection file %s", fname)
287 with open(fname) as f:
287 with open(fname) as f:
288 s = f.read()
288 s = f.read()
289 cfg = json.loads(s)
289 cfg = json.loads(s)
290 if self.ip == LOCALHOST and 'ip' in cfg:
290 if self.ip == LOCALHOST and 'ip' in cfg:
291 # not overridden by config or cl_args
291 # not overridden by config or cl_args
292 self.ip = cfg['ip']
292 self.ip = cfg['ip']
293 for channel in ('hb', 'shell', 'iopub', 'stdin'):
293 for channel in ('hb', 'shell', 'iopub', 'stdin'):
294 name = channel + '_port'
294 name = channel + '_port'
295 if getattr(self, name) == 0 and name in cfg:
295 if getattr(self, name) == 0 and name in cfg:
296 # not overridden by config or cl_args
296 # not overridden by config or cl_args
297 setattr(self, name, cfg[name])
297 setattr(self, name, cfg[name])
298 if 'key' in cfg:
298 if 'key' in cfg:
299 self.config.Session.key = str_to_bytes(cfg['key'])
299 self.config.Session.key = str_to_bytes(cfg['key'])
300
300
301 def init_ssh(self):
301 def init_ssh(self):
302 """set up ssh tunnels, if needed."""
302 """set up ssh tunnels, if needed."""
303 if not self.sshserver and not self.sshkey:
303 if not self.sshserver and not self.sshkey:
304 return
304 return
305
305
306 if self.sshkey and not self.sshserver:
306 if self.sshkey and not self.sshserver:
307 # specifying just the key implies that we are connecting directly
307 # specifying just the key implies that we are connecting directly
308 self.sshserver = self.ip
308 self.sshserver = self.ip
309 self.ip = LOCALHOST
309 self.ip = LOCALHOST
310
310
311 # build connection dict for tunnels:
311 # build connection dict for tunnels:
312 info = dict(ip=self.ip,
312 info = dict(ip=self.ip,
313 shell_port=self.shell_port,
313 shell_port=self.shell_port,
314 iopub_port=self.iopub_port,
314 iopub_port=self.iopub_port,
315 stdin_port=self.stdin_port,
315 stdin_port=self.stdin_port,
316 hb_port=self.hb_port
316 hb_port=self.hb_port
317 )
317 )
318
318
319 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
319 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
320
320
321 # tunnels return a new set of ports, which will be on localhost:
321 # tunnels return a new set of ports, which will be on localhost:
322 self.ip = LOCALHOST
322 self.ip = LOCALHOST
323 try:
323 try:
324 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
324 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
325 except:
325 except:
326 # even catch KeyboardInterrupt
326 # even catch KeyboardInterrupt
327 self.log.error("Could not setup tunnels", exc_info=True)
327 self.log.error("Could not setup tunnels", exc_info=True)
328 self.exit(1)
328 self.exit(1)
329
329
330 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
330 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
331
331
332 cf = self.connection_file
332 cf = self.connection_file
333 base,ext = os.path.splitext(cf)
333 base,ext = os.path.splitext(cf)
334 base = os.path.basename(base)
334 base = os.path.basename(base)
335 self.connection_file = os.path.basename(base)+'-ssh'+ext
335 self.connection_file = os.path.basename(base)+'-ssh'+ext
336 self.log.critical("To connect another client via this tunnel, use:")
336 self.log.critical("To connect another client via this tunnel, use:")
337 self.log.critical("--existing %s" % self.connection_file)
337 self.log.critical("--existing %s" % self.connection_file)
338
338
339 def _new_connection_file(self):
339 def _new_connection_file(self):
340 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
340 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
341
341
342 def init_kernel_manager(self):
342 def init_kernel_manager(self):
343 # Don't let Qt or ZMQ swallow KeyboardInterupts.
343 # Don't let Qt or ZMQ swallow KeyboardInterupts.
344 signal.signal(signal.SIGINT, signal.SIG_DFL)
344 signal.signal(signal.SIGINT, signal.SIG_DFL)
345 sec = self.profile_dir.security_dir
345 sec = self.profile_dir.security_dir
346 try:
346 try:
347 cf = filefind(self.connection_file, ['.', sec])
347 cf = filefind(self.connection_file, ['.', sec])
348 except IOError:
348 except IOError:
349 # file might not exist
349 # file might not exist
350 if self.connection_file == os.path.basename(self.connection_file):
350 if self.connection_file == os.path.basename(self.connection_file):
351 # just shortname, put it in security dir
351 # just shortname, put it in security dir
352 cf = os.path.join(sec, self.connection_file)
352 cf = os.path.join(sec, self.connection_file)
353 else:
353 else:
354 cf = self.connection_file
354 cf = self.connection_file
355
355
356 # Create a KernelManager and start a kernel.
356 # Create a KernelManager and start a kernel.
357 self.kernel_manager = QtKernelManager(
357 self.kernel_manager = QtKernelManager(
358 ip=self.ip,
358 ip=self.ip,
359 shell_port=self.shell_port,
359 shell_port=self.shell_port,
360 iopub_port=self.iopub_port,
360 iopub_port=self.iopub_port,
361 stdin_port=self.stdin_port,
361 stdin_port=self.stdin_port,
362 hb_port=self.hb_port,
362 hb_port=self.hb_port,
363 connection_file=cf,
363 connection_file=cf,
364 config=self.config,
364 config=self.config,
365 )
365 )
366 # start the kernel
366 # start the kernel
367 if not self.existing:
367 if not self.existing:
368 kwargs = dict(ipython=not self.pure)
368 kwargs = dict(ipython=not self.pure)
369 kwargs['extra_arguments'] = self.kernel_argv
369 kwargs['extra_arguments'] = self.kernel_argv
370 self.kernel_manager.start_kernel(**kwargs)
370 self.kernel_manager.start_kernel(**kwargs)
371 elif self.sshserver:
371 elif self.sshserver:
372 # ssh, write new connection file
372 # ssh, write new connection file
373 self.kernel_manager.write_connection_file()
373 self.kernel_manager.write_connection_file()
374 self.kernel_manager.start_channels()
374 self.kernel_manager.start_channels()
375
375
376 def new_frontend_master(self):
376 def new_frontend_master(self):
377 """ Create and return new frontend attached to new kernel, launched on localhost.
377 """ Create and return new frontend attached to new kernel, launched on localhost.
378 """
378 """
379 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
379 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
380 kernel_manager = QtKernelManager(
380 kernel_manager = QtKernelManager(
381 ip=ip,
381 ip=ip,
382 connection_file=self._new_connection_file(),
382 connection_file=self._new_connection_file(),
383 config=self.config,
383 config=self.config,
384 )
384 )
385 # start the kernel
385 # start the kernel
386 kwargs = dict(ipython=not self.pure)
386 kwargs = dict(ipython=not self.pure)
387 kwargs['extra_arguments'] = self.kernel_argv
387 kwargs['extra_arguments'] = self.kernel_argv
388 kernel_manager.start_kernel(**kwargs)
388 kernel_manager.start_kernel(**kwargs)
389 kernel_manager.start_channels()
389 kernel_manager.start_channels()
390 widget = self.widget_factory(config=self.config,
390 widget = self.widget_factory(config=self.config,
391 local_kernel=True)
391 local_kernel=True)
392 widget.kernel_manager = kernel_manager
392 widget.kernel_manager = kernel_manager
393 widget._existing=False
393 widget._existing = False
394 widget._confirm_exit=True
394 widget._may_close = True
395 widget._may_close=True
395 widget._confirm_exit = self.confirm_exit
396 return widget
396 return widget
397
397
398 def new_frontend_slave(self, current_widget):
398 def new_frontend_slave(self, current_widget):
399 """Create and return a new frontend attached to an existing kernel.
399 """Create and return a new frontend attached to an existing kernel.
400
400
401 Parameters
401 Parameters
402 ----------
402 ----------
403 current_widget : IPythonWidget
403 current_widget : IPythonWidget
404 The IPythonWidget whose kernel this frontend is to share
404 The IPythonWidget whose kernel this frontend is to share
405 """
405 """
406 kernel_manager = QtKernelManager(
406 kernel_manager = QtKernelManager(
407 connection_file=current_widget.kernel_manager.connection_file,
407 connection_file=current_widget.kernel_manager.connection_file,
408 config = self.config,
408 config = self.config,
409 )
409 )
410 kernel_manager.load_connection_file()
410 kernel_manager.load_connection_file()
411 kernel_manager.start_channels()
411 kernel_manager.start_channels()
412 widget = self.widget_factory(config=self.config,
412 widget = self.widget_factory(config=self.config,
413 local_kernel=False)
413 local_kernel=False)
414 widget._confirm_exit=True;
414 widget._existing = True
415 widget._may_close=False;
415 widget._may_close = False
416 widget._confirm_exit = False
416 widget.kernel_manager = kernel_manager
417 widget.kernel_manager = kernel_manager
417 return widget
418 return widget
418
419
419 def init_qt_elements(self):
420 def init_qt_elements(self):
420 # Create the widget.
421 # Create the widget.
421 self.app = QtGui.QApplication([])
422 self.app = QtGui.QApplication([])
422
423
423 base_path = os.path.abspath(os.path.dirname(__file__))
424 base_path = os.path.abspath(os.path.dirname(__file__))
424 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
425 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
425 self.app.icon = QtGui.QIcon(icon_path)
426 self.app.icon = QtGui.QIcon(icon_path)
426 QtGui.QApplication.setWindowIcon(self.app.icon)
427 QtGui.QApplication.setWindowIcon(self.app.icon)
427
428
428 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
429 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
429 self.widget = self.widget_factory(config=self.config,
430 self.widget = self.widget_factory(config=self.config,
430 local_kernel=local_kernel)
431 local_kernel=local_kernel)
431 self.widget._existing = self.existing;
432 self.widget._existing = self.existing
432 self.widget._may_close = not self.existing;
433 self.widget._may_close = not self.existing
433 self.widget._confirm_exit = not self.existing;
434 self.widget._confirm_exit = self.confirm_exit
434
435
435 self.widget.kernel_manager = self.kernel_manager
436 self.widget.kernel_manager = self.kernel_manager
436 self.window = MainWindow(self.app, self.widget, self.existing,
437 self.window = MainWindow(self.app,
437 may_close=local_kernel,
438 confirm_exit=self.confirm_exit,
438 confirm_exit=self.confirm_exit,
439 new_frontend_factory=self.new_frontend_master,
439 new_frontend_factory=self.new_frontend_master,
440 slave_frontend_factory=self.new_frontend_slave,
440 slave_frontend_factory=self.new_frontend_slave,
441 )
441 )
442 self.window.log = self.log
442 self.window.log = self.log
443 self.window.add_tab_with_frontend(self.widget)
443 self.window.add_tab_with_frontend(self.widget)
444 self.window.init_menu_bar()
444 self.window.init_menu_bar()
445 self.window.setWindowTitle('Python' if self.pure else 'IPython')
445 self.window.setWindowTitle('Python' if self.pure else 'IPython')
446
446
447 def init_colors(self):
447 def init_colors(self):
448 """Configure the coloring of the widget"""
448 """Configure the coloring of the widget"""
449 # Note: This will be dramatically simplified when colors
449 # Note: This will be dramatically simplified when colors
450 # are removed from the backend.
450 # are removed from the backend.
451
451
452 if self.pure:
452 if self.pure:
453 # only IPythonWidget supports styling
453 # only IPythonWidget supports styling
454 return
454 return
455
455
456 # parse the colors arg down to current known labels
456 # parse the colors arg down to current known labels
457 try:
457 try:
458 colors = self.config.ZMQInteractiveShell.colors
458 colors = self.config.ZMQInteractiveShell.colors
459 except AttributeError:
459 except AttributeError:
460 colors = None
460 colors = None
461 try:
461 try:
462 style = self.config.IPythonWidget.colors
462 style = self.config.IPythonWidget.colors
463 except AttributeError:
463 except AttributeError:
464 style = None
464 style = None
465
465
466 # find the value for colors:
466 # find the value for colors:
467 if colors:
467 if colors:
468 colors=colors.lower()
468 colors=colors.lower()
469 if colors in ('lightbg', 'light'):
469 if colors in ('lightbg', 'light'):
470 colors='lightbg'
470 colors='lightbg'
471 elif colors in ('dark', 'linux'):
471 elif colors in ('dark', 'linux'):
472 colors='linux'
472 colors='linux'
473 else:
473 else:
474 colors='nocolor'
474 colors='nocolor'
475 elif style:
475 elif style:
476 if style=='bw':
476 if style=='bw':
477 colors='nocolor'
477 colors='nocolor'
478 elif styles.dark_style(style):
478 elif styles.dark_style(style):
479 colors='linux'
479 colors='linux'
480 else:
480 else:
481 colors='lightbg'
481 colors='lightbg'
482 else:
482 else:
483 colors=None
483 colors=None
484
484
485 # Configure the style.
485 # Configure the style.
486 widget = self.widget
486 widget = self.widget
487 if style:
487 if style:
488 widget.style_sheet = styles.sheet_from_template(style, colors)
488 widget.style_sheet = styles.sheet_from_template(style, colors)
489 widget.syntax_style = style
489 widget.syntax_style = style
490 widget._syntax_style_changed()
490 widget._syntax_style_changed()
491 widget._style_sheet_changed()
491 widget._style_sheet_changed()
492 elif colors:
492 elif colors:
493 # use a default style
493 # use a default style
494 widget.set_default_style(colors=colors)
494 widget.set_default_style(colors=colors)
495 else:
495 else:
496 # this is redundant for now, but allows the widget's
496 # this is redundant for now, but allows the widget's
497 # defaults to change
497 # defaults to change
498 widget.set_default_style()
498 widget.set_default_style()
499
499
500 if self.stylesheet:
500 if self.stylesheet:
501 # we got an expicit stylesheet
501 # we got an expicit stylesheet
502 if os.path.isfile(self.stylesheet):
502 if os.path.isfile(self.stylesheet):
503 with open(self.stylesheet) as f:
503 with open(self.stylesheet) as f:
504 sheet = f.read()
504 sheet = f.read()
505 widget.style_sheet = sheet
505 widget.style_sheet = sheet
506 widget._style_sheet_changed()
506 widget._style_sheet_changed()
507 else:
507 else:
508 raise IOError("Stylesheet %r not found."%self.stylesheet)
508 raise IOError("Stylesheet %r not found."%self.stylesheet)
509
509
510 def initialize(self, argv=None):
510 def initialize(self, argv=None):
511 super(IPythonQtConsoleApp, self).initialize(argv)
511 super(IPythonQtConsoleApp, self).initialize(argv)
512 self.init_connection_file()
512 self.init_connection_file()
513 default_secure(self.config)
513 default_secure(self.config)
514 self.init_ssh()
514 self.init_ssh()
515 self.init_kernel_manager()
515 self.init_kernel_manager()
516 self.init_qt_elements()
516 self.init_qt_elements()
517 self.init_colors()
517 self.init_colors()
518
518
519 def start(self):
519 def start(self):
520
520
521 # draw the window
521 # draw the window
522 self.window.show()
522 self.window.show()
523
523
524 # Start the application main loop.
524 # Start the application main loop.
525 self.app.exec_()
525 self.app.exec_()
526
526
527 #-----------------------------------------------------------------------------
527 #-----------------------------------------------------------------------------
528 # Main entry point
528 # Main entry point
529 #-----------------------------------------------------------------------------
529 #-----------------------------------------------------------------------------
530
530
531 def main():
531 def main():
532 app = IPythonQtConsoleApp()
532 app = IPythonQtConsoleApp()
533 app.initialize()
533 app.initialize()
534 app.start()
534 app.start()
535
535
536
536
537 if __name__ == '__main__':
537 if __name__ == '__main__':
538 main()
538 main()
General Comments 0
You need to be logged in to leave comments. Login now