##// END OF EJS Templates
add missing kernel_manager in mainwindow.close_tab
MinRK -
Show More
@@ -1,990 +1,992 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 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import sys
23 import sys
24 import re
24 import re
25 import webbrowser
25 import webbrowser
26 import ast
26 import ast
27 from threading import Thread
27 from threading import Thread
28
28
29 # System library imports
29 # System library imports
30 from IPython.external.qt import QtGui,QtCore
30 from IPython.external.qt import QtGui,QtCore
31
31
32 def background(f):
32 def background(f):
33 """call a function in a simple thread, to prevent blocking"""
33 """call a function in a simple thread, to prevent blocking"""
34 t = Thread(target=f)
34 t = Thread(target=f)
35 t.start()
35 t.start()
36 return t
36 return t
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Classes
39 # Classes
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class MainWindow(QtGui.QMainWindow):
42 class MainWindow(QtGui.QMainWindow):
43
43
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45 # 'object' interface
45 # 'object' interface
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47
47
48 _magic_menu_dict = {}
48 _magic_menu_dict = {}
49
49
50 def __init__(self, app,
50 def __init__(self, app,
51 confirm_exit=True,
51 confirm_exit=True,
52 new_frontend_factory=None, slave_frontend_factory=None,
52 new_frontend_factory=None, slave_frontend_factory=None,
53 ):
53 ):
54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58
58
59 app : reference to QApplication parent
59 app : reference to QApplication parent
60 confirm_exit : bool, optional
60 confirm_exit : bool, optional
61 Whether we should prompt on close of tabs
61 Whether we should prompt on close of tabs
62 new_frontend_factory : callable
62 new_frontend_factory : callable
63 A callable that returns a new IPythonWidget instance, attached to
63 A callable that returns a new IPythonWidget instance, attached to
64 its own running kernel.
64 its own running kernel.
65 slave_frontend_factory : callable
65 slave_frontend_factory : callable
66 A callable that takes an existing IPythonWidget, and returns a new
66 A callable that takes an existing IPythonWidget, and returns a new
67 IPythonWidget instance, attached to the same kernel.
67 IPythonWidget instance, attached to the same kernel.
68 """
68 """
69
69
70 super(MainWindow, self).__init__()
70 super(MainWindow, self).__init__()
71 self._kernel_counter = 0
71 self._kernel_counter = 0
72 self._app = app
72 self._app = app
73 self.confirm_exit = confirm_exit
73 self.confirm_exit = confirm_exit
74 self.new_frontend_factory = new_frontend_factory
74 self.new_frontend_factory = new_frontend_factory
75 self.slave_frontend_factory = slave_frontend_factory
75 self.slave_frontend_factory = slave_frontend_factory
76
76
77 self.tab_widget = QtGui.QTabWidget(self)
77 self.tab_widget = QtGui.QTabWidget(self)
78 self.tab_widget.setDocumentMode(True)
78 self.tab_widget.setDocumentMode(True)
79 self.tab_widget.setTabsClosable(True)
79 self.tab_widget.setTabsClosable(True)
80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
81
81
82 self.setCentralWidget(self.tab_widget)
82 self.setCentralWidget(self.tab_widget)
83 # hide tab bar at first, since we have no tabs:
83 # hide tab bar at first, since we have no tabs:
84 self.tab_widget.tabBar().setVisible(False)
84 self.tab_widget.tabBar().setVisible(False)
85 # prevent focus in tab bar
85 # prevent focus in tab bar
86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
87
87
88 def update_tab_bar_visibility(self):
88 def update_tab_bar_visibility(self):
89 """ update visibility of the tabBar depending of the number of tab
89 """ update visibility of the tabBar depending of the number of tab
90
90
91 0 or 1 tab, tabBar hidden
91 0 or 1 tab, tabBar hidden
92 2+ tabs, tabBar visible
92 2+ tabs, tabBar visible
93
93
94 send a self.close if number of tab ==0
94 send a self.close if number of tab ==0
95
95
96 need to be called explicitly, or be connected to tabInserted/tabRemoved
96 need to be called explicitly, or be connected to tabInserted/tabRemoved
97 """
97 """
98 if self.tab_widget.count() <= 1:
98 if self.tab_widget.count() <= 1:
99 self.tab_widget.tabBar().setVisible(False)
99 self.tab_widget.tabBar().setVisible(False)
100 else:
100 else:
101 self.tab_widget.tabBar().setVisible(True)
101 self.tab_widget.tabBar().setVisible(True)
102 if self.tab_widget.count()==0 :
102 if self.tab_widget.count()==0 :
103 self.close()
103 self.close()
104
104
105 @property
105 @property
106 def next_kernel_id(self):
106 def next_kernel_id(self):
107 """constantly increasing counter for kernel IDs"""
107 """constantly increasing counter for kernel IDs"""
108 c = self._kernel_counter
108 c = self._kernel_counter
109 self._kernel_counter += 1
109 self._kernel_counter += 1
110 return c
110 return c
111
111
112 @property
112 @property
113 def active_frontend(self):
113 def active_frontend(self):
114 return self.tab_widget.currentWidget()
114 return self.tab_widget.currentWidget()
115
115
116 def create_tab_with_new_frontend(self):
116 def create_tab_with_new_frontend(self):
117 """create a new frontend and attach it to a new tab"""
117 """create a new frontend and attach it to a new tab"""
118 widget = self.new_frontend_factory()
118 widget = self.new_frontend_factory()
119 self.add_tab_with_frontend(widget)
119 self.add_tab_with_frontend(widget)
120
120
121 def create_tab_with_current_kernel(self):
121 def create_tab_with_current_kernel(self):
122 """create a new frontend attached to the same kernel as the current tab"""
122 """create a new frontend attached to the same kernel as the current tab"""
123 current_widget = self.tab_widget.currentWidget()
123 current_widget = self.tab_widget.currentWidget()
124 current_widget_index = self.tab_widget.indexOf(current_widget)
124 current_widget_index = self.tab_widget.indexOf(current_widget)
125 current_widget_name = self.tab_widget.tabText(current_widget_index)
125 current_widget_name = self.tab_widget.tabText(current_widget_index)
126 widget = self.slave_frontend_factory(current_widget)
126 widget = self.slave_frontend_factory(current_widget)
127 if 'slave' in current_widget_name:
127 if 'slave' in current_widget_name:
128 # don't keep stacking slaves
128 # don't keep stacking slaves
129 name = current_widget_name
129 name = current_widget_name
130 else:
130 else:
131 name = '(%s) slave' % current_widget_name
131 name = '(%s) slave' % current_widget_name
132 self.add_tab_with_frontend(widget,name=name)
132 self.add_tab_with_frontend(widget,name=name)
133
133
134 def close_tab(self,current_tab):
134 def close_tab(self,current_tab):
135 """ Called when you need to try to close a tab.
135 """ Called when you need to try to close a tab.
136
136
137 It takes the number of the tab to be closed as argument, or a reference
137 It takes the number of the tab to be closed as argument, or a reference
138 to the widget inside this tab
138 to the widget inside this tab
139 """
139 """
140
140
141 # let's be sure "tab" and "closing widget" are respectively the index
141 # let's be sure "tab" and "closing widget" are respectively the index
142 # of the tab to close and a reference to the frontend to close
142 # of the tab to close and a reference to the frontend to close
143 if type(current_tab) is not int :
143 if type(current_tab) is not int :
144 current_tab = self.tab_widget.indexOf(current_tab)
144 current_tab = self.tab_widget.indexOf(current_tab)
145 closing_widget=self.tab_widget.widget(current_tab)
145 closing_widget=self.tab_widget.widget(current_tab)
146
146
147
147
148 # when trying to be closed, widget might re-send a request to be
148 # when trying to be closed, widget might re-send a request to be
149 # closed again, but will be deleted when event will be processed. So
149 # closed again, but will be deleted when event will be processed. So
150 # need to check that widget still exists and skip if not. One example
150 # need to check that widget still exists and skip if not. One example
151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
152 # re-sent by this function on the master widget, which ask all slave
152 # re-sent by this function on the master widget, which ask all slave
153 # widgets to exit
153 # widgets to exit
154 if closing_widget==None:
154 if closing_widget==None:
155 return
155 return
156
156
157 #get a list of all slave widgets on the same kernel.
157 #get a list of all slave widgets on the same kernel.
158 slave_tabs = self.find_slave_widgets(closing_widget)
158 slave_tabs = self.find_slave_widgets(closing_widget)
159
159
160 keepkernel = None #Use the prompt by default
160 keepkernel = None #Use the prompt by default
161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
162 keepkernel = closing_widget._keep_kernel_on_exit
162 keepkernel = closing_widget._keep_kernel_on_exit
163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
164 # we set local slave tabs._hidden to True to avoid prompting for kernel
164 # we set local slave tabs._hidden to True to avoid prompting for kernel
165 # restart when they get the signal. and then "forward" the 'exit'
165 # restart when they get the signal. and then "forward" the 'exit'
166 # to the main window
166 # to the main window
167 if keepkernel is not None:
167 if keepkernel is not None:
168 for tab in slave_tabs:
168 for tab in slave_tabs:
169 tab._hidden = True
169 tab._hidden = True
170 if closing_widget in slave_tabs:
170 if closing_widget in slave_tabs:
171 try :
171 try :
172 self.find_master_tab(closing_widget).execute('exit')
172 self.find_master_tab(closing_widget).execute('exit')
173 except AttributeError:
173 except AttributeError:
174 self.log.info("Master already closed or not local, closing only current tab")
174 self.log.info("Master already closed or not local, closing only current tab")
175 self.tab_widget.removeTab(current_tab)
175 self.tab_widget.removeTab(current_tab)
176 self.update_tab_bar_visibility()
176 self.update_tab_bar_visibility()
177 return
177 return
178
178
179 kernel_client = closing_widget.kernel_client
179 kernel_client = closing_widget.kernel_client
180 kernel_manager = closing_widget.kernel_manager
180
181
181 if keepkernel is None and not closing_widget._confirm_exit:
182 if keepkernel is None and not closing_widget._confirm_exit:
182 # don't prompt, just terminate the kernel if we own it
183 # don't prompt, just terminate the kernel if we own it
183 # or leave it alone if we don't
184 # or leave it alone if we don't
184 keepkernel = closing_widget._existing
185 keepkernel = closing_widget._existing
185 if keepkernel is None: #show prompt
186 if keepkernel is None: #show prompt
186 if kernel_client and kernel_client.channels_running:
187 if kernel_client and kernel_client.channels_running:
187 title = self.window().windowTitle()
188 title = self.window().windowTitle()
188 cancel = QtGui.QMessageBox.Cancel
189 cancel = QtGui.QMessageBox.Cancel
189 okay = QtGui.QMessageBox.Ok
190 okay = QtGui.QMessageBox.Ok
190 if closing_widget._may_close:
191 if closing_widget._may_close:
191 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
192 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
192 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
193 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
193 justthis = QtGui.QPushButton("&No, just this Tab", self)
194 justthis = QtGui.QPushButton("&No, just this Tab", self)
194 justthis.setShortcut('N')
195 justthis.setShortcut('N')
195 closeall = QtGui.QPushButton("&Yes, close all", self)
196 closeall = QtGui.QPushButton("&Yes, close all", self)
196 closeall.setShortcut('Y')
197 closeall.setShortcut('Y')
197 # allow ctrl-d ctrl-d exit, like in terminal
198 # allow ctrl-d ctrl-d exit, like in terminal
198 closeall.setShortcut('Ctrl+D')
199 closeall.setShortcut('Ctrl+D')
199 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
200 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
200 title, msg)
201 title, msg)
201 box.setInformativeText(info)
202 box.setInformativeText(info)
202 box.addButton(cancel)
203 box.addButton(cancel)
203 box.addButton(justthis, QtGui.QMessageBox.NoRole)
204 box.addButton(justthis, QtGui.QMessageBox.NoRole)
204 box.addButton(closeall, QtGui.QMessageBox.YesRole)
205 box.addButton(closeall, QtGui.QMessageBox.YesRole)
205 box.setDefaultButton(closeall)
206 box.setDefaultButton(closeall)
206 box.setEscapeButton(cancel)
207 box.setEscapeButton(cancel)
207 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
208 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
208 box.setIconPixmap(pixmap)
209 box.setIconPixmap(pixmap)
209 reply = box.exec_()
210 reply = box.exec_()
210 if reply == 1: # close All
211 if reply == 1: # close All
211 for slave in slave_tabs:
212 for slave in slave_tabs:
212 background(slave.kernel_client.stop_channels)
213 background(slave.kernel_client.stop_channels)
213 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 closing_widget.execute("exit")
215 closing_widget.execute("exit")
215 self.tab_widget.removeTab(current_tab)
216 self.tab_widget.removeTab(current_tab)
216 background(kernel_client.stop_channels)
217 background(kernel_client.stop_channels)
217 elif reply == 0: # close Console
218 elif reply == 0: # close Console
218 if not closing_widget._existing:
219 if not closing_widget._existing:
219 # Have kernel: don't quit, just close the tab
220 # Have kernel: don't quit, just close the tab
220 closing_widget.execute("exit True")
221 closing_widget.execute("exit True")
221 self.tab_widget.removeTab(current_tab)
222 self.tab_widget.removeTab(current_tab)
222 background(kernel_client.stop_channels)
223 background(kernel_client.stop_channels)
223 else:
224 else:
224 reply = QtGui.QMessageBox.question(self, title,
225 reply = QtGui.QMessageBox.question(self, title,
225 "Are you sure you want to close this Console?"+
226 "Are you sure you want to close this Console?"+
226 "\nThe Kernel and other Consoles will remain active.",
227 "\nThe Kernel and other Consoles will remain active.",
227 okay|cancel,
228 okay|cancel,
228 defaultButton=okay
229 defaultButton=okay
229 )
230 )
230 if reply == okay:
231 if reply == okay:
231 self.tab_widget.removeTab(current_tab)
232 self.tab_widget.removeTab(current_tab)
232 elif keepkernel: #close console but leave kernel running (no prompt)
233 elif keepkernel: #close console but leave kernel running (no prompt)
233 self.tab_widget.removeTab(current_tab)
234 self.tab_widget.removeTab(current_tab)
234 background(kernel_client.stop_channels)
235 background(kernel_client.stop_channels)
235 else: #close console and kernel (no prompt)
236 else: #close console and kernel (no prompt)
236 self.tab_widget.removeTab(current_tab)
237 self.tab_widget.removeTab(current_tab)
237 if kernel_client and kernel_client.channels_running:
238 if kernel_client and kernel_client.channels_running:
238 for slave in slave_tabs:
239 for slave in slave_tabs:
239 background(slave.kernel_client.stop_channels)
240 background(slave.kernel_client.stop_channels)
240 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 kernel_manager.shutdown_kernel()
242 if kernel_manager:
243 kernel_manager.shutdown_kernel()
242 background(kernel_client.stop_channels)
244 background(kernel_client.stop_channels)
243
245
244 self.update_tab_bar_visibility()
246 self.update_tab_bar_visibility()
245
247
246 def add_tab_with_frontend(self,frontend,name=None):
248 def add_tab_with_frontend(self,frontend,name=None):
247 """ insert a tab with a given frontend in the tab bar, and give it a name
249 """ insert a tab with a given frontend in the tab bar, and give it a name
248
250
249 """
251 """
250 if not name:
252 if not name:
251 name = 'kernel %i' % self.next_kernel_id
253 name = 'kernel %i' % self.next_kernel_id
252 self.tab_widget.addTab(frontend,name)
254 self.tab_widget.addTab(frontend,name)
253 self.update_tab_bar_visibility()
255 self.update_tab_bar_visibility()
254 self.make_frontend_visible(frontend)
256 self.make_frontend_visible(frontend)
255 frontend.exit_requested.connect(self.close_tab)
257 frontend.exit_requested.connect(self.close_tab)
256
258
257 def next_tab(self):
259 def next_tab(self):
258 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
260 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
259
261
260 def prev_tab(self):
262 def prev_tab(self):
261 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
263 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
262
264
263 def make_frontend_visible(self,frontend):
265 def make_frontend_visible(self,frontend):
264 widget_index=self.tab_widget.indexOf(frontend)
266 widget_index=self.tab_widget.indexOf(frontend)
265 if widget_index > 0 :
267 if widget_index > 0 :
266 self.tab_widget.setCurrentIndex(widget_index)
268 self.tab_widget.setCurrentIndex(widget_index)
267
269
268 def find_master_tab(self,tab,as_list=False):
270 def find_master_tab(self,tab,as_list=False):
269 """
271 """
270 Try to return the frontend that owns the kernel attached to the given widget/tab.
272 Try to return the frontend that owns the kernel attached to the given widget/tab.
271
273
272 Only finds frontend owned by the current application. Selection
274 Only finds frontend owned by the current application. Selection
273 based on port of the kernel might be inaccurate if several kernel
275 based on port of the kernel might be inaccurate if several kernel
274 on different ip use same port number.
276 on different ip use same port number.
275
277
276 This function does the conversion tabNumber/widget if needed.
278 This function does the conversion tabNumber/widget if needed.
277 Might return None if no master widget (non local kernel)
279 Might return None if no master widget (non local kernel)
278 Will crash IPython if more than 1 masterWidget
280 Will crash IPython if more than 1 masterWidget
279
281
280 When asList set to True, always return a list of widget(s) owning
282 When asList set to True, always return a list of widget(s) owning
281 the kernel. The list might be empty or containing several Widget.
283 the kernel. The list might be empty or containing several Widget.
282 """
284 """
283
285
284 #convert from/to int/richIpythonWidget if needed
286 #convert from/to int/richIpythonWidget if needed
285 if isinstance(tab, int):
287 if isinstance(tab, int):
286 tab = self.tab_widget.widget(tab)
288 tab = self.tab_widget.widget(tab)
287 km=tab.kernel_client
289 km=tab.kernel_client
288
290
289 #build list of all widgets
291 #build list of all widgets
290 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
292 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
291
293
292 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
294 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 # And should have a _may_close attribute
295 # And should have a _may_close attribute
294 filtered_widget_list = [ widget for widget in widget_list if
296 filtered_widget_list = [ widget for widget in widget_list if
295 widget.kernel_client.connection_file == km.connection_file and
297 widget.kernel_client.connection_file == km.connection_file and
296 hasattr(widget,'_may_close') ]
298 hasattr(widget,'_may_close') ]
297 # the master widget is the one that may close the kernel
299 # the master widget is the one that may close the kernel
298 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
300 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
299 if as_list:
301 if as_list:
300 return master_widget
302 return master_widget
301 assert(len(master_widget)<=1 )
303 assert(len(master_widget)<=1 )
302 if len(master_widget)==0:
304 if len(master_widget)==0:
303 return None
305 return None
304
306
305 return master_widget[0]
307 return master_widget[0]
306
308
307 def find_slave_widgets(self,tab):
309 def find_slave_widgets(self,tab):
308 """return all the frontends that do not own the kernel attached to the given widget/tab.
310 """return all the frontends that do not own the kernel attached to the given widget/tab.
309
311
310 Only find frontends owned by the current application. Selection
312 Only find frontends owned by the current application. Selection
311 based on connection file of the kernel.
313 based on connection file of the kernel.
312
314
313 This function does the conversion tabNumber/widget if needed.
315 This function does the conversion tabNumber/widget if needed.
314 """
316 """
315 #convert from/to int/richIpythonWidget if needed
317 #convert from/to int/richIpythonWidget if needed
316 if isinstance(tab, int):
318 if isinstance(tab, int):
317 tab = self.tab_widget.widget(tab)
319 tab = self.tab_widget.widget(tab)
318 km=tab.kernel_client
320 km=tab.kernel_client
319
321
320 #build list of all widgets
322 #build list of all widgets
321 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
323 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322
324
323 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
325 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 filtered_widget_list = ( widget for widget in widget_list if
326 filtered_widget_list = ( widget for widget in widget_list if
325 widget.kernel_client.connection_file == km.connection_file)
327 widget.kernel_client.connection_file == km.connection_file)
326 # Get a list of all widget owning the same kernel and removed it from
328 # Get a list of all widget owning the same kernel and removed it from
327 # the previous cadidate. (better using sets ?)
329 # the previous cadidate. (better using sets ?)
328 master_widget_list = self.find_master_tab(tab, as_list=True)
330 master_widget_list = self.find_master_tab(tab, as_list=True)
329 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
331 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
330
332
331 return slave_list
333 return slave_list
332
334
333 # Populate the menu bar with common actions and shortcuts
335 # Populate the menu bar with common actions and shortcuts
334 def add_menu_action(self, menu, action, defer_shortcut=False):
336 def add_menu_action(self, menu, action, defer_shortcut=False):
335 """Add action to menu as well as self
337 """Add action to menu as well as self
336
338
337 So that when the menu bar is invisible, its actions are still available.
339 So that when the menu bar is invisible, its actions are still available.
338
340
339 If defer_shortcut is True, set the shortcut context to widget-only,
341 If defer_shortcut is True, set the shortcut context to widget-only,
340 where it will avoid conflict with shortcuts already bound to the
342 where it will avoid conflict with shortcuts already bound to the
341 widgets themselves.
343 widgets themselves.
342 """
344 """
343 menu.addAction(action)
345 menu.addAction(action)
344 self.addAction(action)
346 self.addAction(action)
345
347
346 if defer_shortcut:
348 if defer_shortcut:
347 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
349 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
348
350
349 def init_menu_bar(self):
351 def init_menu_bar(self):
350 #create menu in the order they should appear in the menu bar
352 #create menu in the order they should appear in the menu bar
351 self.init_file_menu()
353 self.init_file_menu()
352 self.init_edit_menu()
354 self.init_edit_menu()
353 self.init_view_menu()
355 self.init_view_menu()
354 self.init_kernel_menu()
356 self.init_kernel_menu()
355 self.init_magic_menu()
357 self.init_magic_menu()
356 self.init_window_menu()
358 self.init_window_menu()
357 self.init_help_menu()
359 self.init_help_menu()
358
360
359 def init_file_menu(self):
361 def init_file_menu(self):
360 self.file_menu = self.menuBar().addMenu("&File")
362 self.file_menu = self.menuBar().addMenu("&File")
361
363
362 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
364 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
363 self,
365 self,
364 shortcut="Ctrl+T",
366 shortcut="Ctrl+T",
365 triggered=self.create_tab_with_new_frontend)
367 triggered=self.create_tab_with_new_frontend)
366 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
368 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
367
369
368 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
370 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
369 self,
371 self,
370 shortcut="Ctrl+Shift+T",
372 shortcut="Ctrl+Shift+T",
371 triggered=self.create_tab_with_current_kernel)
373 triggered=self.create_tab_with_current_kernel)
372 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
374 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
373
375
374 self.file_menu.addSeparator()
376 self.file_menu.addSeparator()
375
377
376 self.close_action=QtGui.QAction("&Close Tab",
378 self.close_action=QtGui.QAction("&Close Tab",
377 self,
379 self,
378 shortcut=QtGui.QKeySequence.Close,
380 shortcut=QtGui.QKeySequence.Close,
379 triggered=self.close_active_frontend
381 triggered=self.close_active_frontend
380 )
382 )
381 self.add_menu_action(self.file_menu, self.close_action)
383 self.add_menu_action(self.file_menu, self.close_action)
382
384
383 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
385 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
384 self,
386 self,
385 shortcut=QtGui.QKeySequence.Save,
387 shortcut=QtGui.QKeySequence.Save,
386 triggered=self.export_action_active_frontend
388 triggered=self.export_action_active_frontend
387 )
389 )
388 self.add_menu_action(self.file_menu, self.export_action, True)
390 self.add_menu_action(self.file_menu, self.export_action, True)
389
391
390 self.file_menu.addSeparator()
392 self.file_menu.addSeparator()
391
393
392 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
394 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
393 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
395 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
394 # Only override the default if there is a collision.
396 # Only override the default if there is a collision.
395 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
397 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
396 printkey = "Ctrl+Shift+P"
398 printkey = "Ctrl+Shift+P"
397 self.print_action = QtGui.QAction("&Print",
399 self.print_action = QtGui.QAction("&Print",
398 self,
400 self,
399 shortcut=printkey,
401 shortcut=printkey,
400 triggered=self.print_action_active_frontend)
402 triggered=self.print_action_active_frontend)
401 self.add_menu_action(self.file_menu, self.print_action, True)
403 self.add_menu_action(self.file_menu, self.print_action, True)
402
404
403 if sys.platform != 'darwin':
405 if sys.platform != 'darwin':
404 # OSX always has Quit in the Application menu, only add it
406 # OSX always has Quit in the Application menu, only add it
405 # to the File menu elsewhere.
407 # to the File menu elsewhere.
406
408
407 self.file_menu.addSeparator()
409 self.file_menu.addSeparator()
408
410
409 self.quit_action = QtGui.QAction("&Quit",
411 self.quit_action = QtGui.QAction("&Quit",
410 self,
412 self,
411 shortcut=QtGui.QKeySequence.Quit,
413 shortcut=QtGui.QKeySequence.Quit,
412 triggered=self.close,
414 triggered=self.close,
413 )
415 )
414 self.add_menu_action(self.file_menu, self.quit_action)
416 self.add_menu_action(self.file_menu, self.quit_action)
415
417
416
418
417 def init_edit_menu(self):
419 def init_edit_menu(self):
418 self.edit_menu = self.menuBar().addMenu("&Edit")
420 self.edit_menu = self.menuBar().addMenu("&Edit")
419
421
420 self.undo_action = QtGui.QAction("&Undo",
422 self.undo_action = QtGui.QAction("&Undo",
421 self,
423 self,
422 shortcut=QtGui.QKeySequence.Undo,
424 shortcut=QtGui.QKeySequence.Undo,
423 statusTip="Undo last action if possible",
425 statusTip="Undo last action if possible",
424 triggered=self.undo_active_frontend
426 triggered=self.undo_active_frontend
425 )
427 )
426 self.add_menu_action(self.edit_menu, self.undo_action)
428 self.add_menu_action(self.edit_menu, self.undo_action)
427
429
428 self.redo_action = QtGui.QAction("&Redo",
430 self.redo_action = QtGui.QAction("&Redo",
429 self,
431 self,
430 shortcut=QtGui.QKeySequence.Redo,
432 shortcut=QtGui.QKeySequence.Redo,
431 statusTip="Redo last action if possible",
433 statusTip="Redo last action if possible",
432 triggered=self.redo_active_frontend)
434 triggered=self.redo_active_frontend)
433 self.add_menu_action(self.edit_menu, self.redo_action)
435 self.add_menu_action(self.edit_menu, self.redo_action)
434
436
435 self.edit_menu.addSeparator()
437 self.edit_menu.addSeparator()
436
438
437 self.cut_action = QtGui.QAction("&Cut",
439 self.cut_action = QtGui.QAction("&Cut",
438 self,
440 self,
439 shortcut=QtGui.QKeySequence.Cut,
441 shortcut=QtGui.QKeySequence.Cut,
440 triggered=self.cut_active_frontend
442 triggered=self.cut_active_frontend
441 )
443 )
442 self.add_menu_action(self.edit_menu, self.cut_action, True)
444 self.add_menu_action(self.edit_menu, self.cut_action, True)
443
445
444 self.copy_action = QtGui.QAction("&Copy",
446 self.copy_action = QtGui.QAction("&Copy",
445 self,
447 self,
446 shortcut=QtGui.QKeySequence.Copy,
448 shortcut=QtGui.QKeySequence.Copy,
447 triggered=self.copy_active_frontend
449 triggered=self.copy_active_frontend
448 )
450 )
449 self.add_menu_action(self.edit_menu, self.copy_action, True)
451 self.add_menu_action(self.edit_menu, self.copy_action, True)
450
452
451 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
453 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
452 self,
454 self,
453 shortcut="Ctrl+Shift+C",
455 shortcut="Ctrl+Shift+C",
454 triggered=self.copy_raw_active_frontend
456 triggered=self.copy_raw_active_frontend
455 )
457 )
456 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
458 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
457
459
458 self.paste_action = QtGui.QAction("&Paste",
460 self.paste_action = QtGui.QAction("&Paste",
459 self,
461 self,
460 shortcut=QtGui.QKeySequence.Paste,
462 shortcut=QtGui.QKeySequence.Paste,
461 triggered=self.paste_active_frontend
463 triggered=self.paste_active_frontend
462 )
464 )
463 self.add_menu_action(self.edit_menu, self.paste_action, True)
465 self.add_menu_action(self.edit_menu, self.paste_action, True)
464
466
465 self.edit_menu.addSeparator()
467 self.edit_menu.addSeparator()
466
468
467 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
469 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
468 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
470 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
469 # Only override the default if there is a collision.
471 # Only override the default if there is a collision.
470 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
472 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
471 selectall = "Ctrl+Shift+A"
473 selectall = "Ctrl+Shift+A"
472 self.select_all_action = QtGui.QAction("Select &All",
474 self.select_all_action = QtGui.QAction("Select &All",
473 self,
475 self,
474 shortcut=selectall,
476 shortcut=selectall,
475 triggered=self.select_all_active_frontend
477 triggered=self.select_all_active_frontend
476 )
478 )
477 self.add_menu_action(self.edit_menu, self.select_all_action, True)
479 self.add_menu_action(self.edit_menu, self.select_all_action, True)
478
480
479
481
480 def init_view_menu(self):
482 def init_view_menu(self):
481 self.view_menu = self.menuBar().addMenu("&View")
483 self.view_menu = self.menuBar().addMenu("&View")
482
484
483 if sys.platform != 'darwin':
485 if sys.platform != 'darwin':
484 # disable on OSX, where there is always a menu bar
486 # disable on OSX, where there is always a menu bar
485 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
487 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
486 self,
488 self,
487 shortcut="Ctrl+Shift+M",
489 shortcut="Ctrl+Shift+M",
488 statusTip="Toggle visibility of menubar",
490 statusTip="Toggle visibility of menubar",
489 triggered=self.toggle_menu_bar)
491 triggered=self.toggle_menu_bar)
490 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
492 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
491
493
492 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
494 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
493 self.full_screen_act = QtGui.QAction("&Full Screen",
495 self.full_screen_act = QtGui.QAction("&Full Screen",
494 self,
496 self,
495 shortcut=fs_key,
497 shortcut=fs_key,
496 statusTip="Toggle between Fullscreen and Normal Size",
498 statusTip="Toggle between Fullscreen and Normal Size",
497 triggered=self.toggleFullScreen)
499 triggered=self.toggleFullScreen)
498 self.add_menu_action(self.view_menu, self.full_screen_act)
500 self.add_menu_action(self.view_menu, self.full_screen_act)
499
501
500 self.view_menu.addSeparator()
502 self.view_menu.addSeparator()
501
503
502 self.increase_font_size = QtGui.QAction("Zoom &In",
504 self.increase_font_size = QtGui.QAction("Zoom &In",
503 self,
505 self,
504 shortcut=QtGui.QKeySequence.ZoomIn,
506 shortcut=QtGui.QKeySequence.ZoomIn,
505 triggered=self.increase_font_size_active_frontend
507 triggered=self.increase_font_size_active_frontend
506 )
508 )
507 self.add_menu_action(self.view_menu, self.increase_font_size, True)
509 self.add_menu_action(self.view_menu, self.increase_font_size, True)
508
510
509 self.decrease_font_size = QtGui.QAction("Zoom &Out",
511 self.decrease_font_size = QtGui.QAction("Zoom &Out",
510 self,
512 self,
511 shortcut=QtGui.QKeySequence.ZoomOut,
513 shortcut=QtGui.QKeySequence.ZoomOut,
512 triggered=self.decrease_font_size_active_frontend
514 triggered=self.decrease_font_size_active_frontend
513 )
515 )
514 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
516 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
515
517
516 self.reset_font_size = QtGui.QAction("Zoom &Reset",
518 self.reset_font_size = QtGui.QAction("Zoom &Reset",
517 self,
519 self,
518 shortcut="Ctrl+0",
520 shortcut="Ctrl+0",
519 triggered=self.reset_font_size_active_frontend
521 triggered=self.reset_font_size_active_frontend
520 )
522 )
521 self.add_menu_action(self.view_menu, self.reset_font_size, True)
523 self.add_menu_action(self.view_menu, self.reset_font_size, True)
522
524
523 self.view_menu.addSeparator()
525 self.view_menu.addSeparator()
524
526
525 self.clear_action = QtGui.QAction("&Clear Screen",
527 self.clear_action = QtGui.QAction("&Clear Screen",
526 self,
528 self,
527 shortcut='Ctrl+L',
529 shortcut='Ctrl+L',
528 statusTip="Clear the console",
530 statusTip="Clear the console",
529 triggered=self.clear_magic_active_frontend)
531 triggered=self.clear_magic_active_frontend)
530 self.add_menu_action(self.view_menu, self.clear_action)
532 self.add_menu_action(self.view_menu, self.clear_action)
531
533
532 self.pager_menu = self.view_menu.addMenu("&Pager")
534 self.pager_menu = self.view_menu.addMenu("&Pager")
533
535
534 hsplit_action = QtGui.QAction(".. &Horizontal Split",
536 hsplit_action = QtGui.QAction(".. &Horizontal Split",
535 self,
537 self,
536 triggered=lambda: self.set_paging_active_frontend('hsplit'))
538 triggered=lambda: self.set_paging_active_frontend('hsplit'))
537
539
538 vsplit_action = QtGui.QAction(" : &Vertical Split",
540 vsplit_action = QtGui.QAction(" : &Vertical Split",
539 self,
541 self,
540 triggered=lambda: self.set_paging_active_frontend('vsplit'))
542 triggered=lambda: self.set_paging_active_frontend('vsplit'))
541
543
542 inside_action = QtGui.QAction(" &Inside Pager",
544 inside_action = QtGui.QAction(" &Inside Pager",
543 self,
545 self,
544 triggered=lambda: self.set_paging_active_frontend('inside'))
546 triggered=lambda: self.set_paging_active_frontend('inside'))
545
547
546 self.pager_menu.addAction(hsplit_action)
548 self.pager_menu.addAction(hsplit_action)
547 self.pager_menu.addAction(vsplit_action)
549 self.pager_menu.addAction(vsplit_action)
548 self.pager_menu.addAction(inside_action)
550 self.pager_menu.addAction(inside_action)
549
551
550 def init_kernel_menu(self):
552 def init_kernel_menu(self):
551 self.kernel_menu = self.menuBar().addMenu("&Kernel")
553 self.kernel_menu = self.menuBar().addMenu("&Kernel")
552 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
554 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
553 # keep the signal shortcuts to ctrl, rather than
555 # keep the signal shortcuts to ctrl, rather than
554 # platform-default like we do elsewhere.
556 # platform-default like we do elsewhere.
555
557
556 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
558 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
557
559
558 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
560 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
559 self,
561 self,
560 triggered=self.interrupt_kernel_active_frontend,
562 triggered=self.interrupt_kernel_active_frontend,
561 shortcut=ctrl+"+C",
563 shortcut=ctrl+"+C",
562 )
564 )
563 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
565 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
564
566
565 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
567 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
566 self,
568 self,
567 triggered=self.restart_kernel_active_frontend,
569 triggered=self.restart_kernel_active_frontend,
568 shortcut=ctrl+"+.",
570 shortcut=ctrl+"+.",
569 )
571 )
570 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
572 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
571
573
572 self.kernel_menu.addSeparator()
574 self.kernel_menu.addSeparator()
573
575
574 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
576 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
575 self,
577 self,
576 checkable=True,
578 checkable=True,
577 checked=self.active_frontend.confirm_restart,
579 checked=self.active_frontend.confirm_restart,
578 triggered=self.toggle_confirm_restart_active_frontend
580 triggered=self.toggle_confirm_restart_active_frontend
579 )
581 )
580
582
581 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
583 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
582 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
584 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
583
585
584 def _make_dynamic_magic(self,magic):
586 def _make_dynamic_magic(self,magic):
585 """Return a function `fun` that will execute `magic` on active frontend.
587 """Return a function `fun` that will execute `magic` on active frontend.
586
588
587 Parameters
589 Parameters
588 ----------
590 ----------
589 magic : string
591 magic : string
590 string that will be executed as is when the returned function is called
592 string that will be executed as is when the returned function is called
591
593
592 Returns
594 Returns
593 -------
595 -------
594 fun : function
596 fun : function
595 function with no parameters, when called will execute `magic` on the
597 function with no parameters, when called will execute `magic` on the
596 current active frontend at call time
598 current active frontend at call time
597
599
598 See Also
600 See Also
599 --------
601 --------
600 populate_all_magic_menu : generate the "All Magics..." menu
602 populate_all_magic_menu : generate the "All Magics..." menu
601
603
602 Notes
604 Notes
603 -----
605 -----
604 `fun` executes `magic` in active frontend at the moment it is triggered,
606 `fun` executes `magic` in active frontend at the moment it is triggered,
605 not the active frontend at the moment it was created.
607 not the active frontend at the moment it was created.
606
608
607 This function is mostly used to create the "All Magics..." Menu at run time.
609 This function is mostly used to create the "All Magics..." Menu at run time.
608 """
610 """
609 # need two level nested function to be sure to pass magic
611 # need two level nested function to be sure to pass magic
610 # to active frontend **at run time**.
612 # to active frontend **at run time**.
611 def inner_dynamic_magic():
613 def inner_dynamic_magic():
612 self.active_frontend.execute(magic)
614 self.active_frontend.execute(magic)
613 inner_dynamic_magic.__name__ = "dynamics_magic_s"
615 inner_dynamic_magic.__name__ = "dynamics_magic_s"
614 return inner_dynamic_magic
616 return inner_dynamic_magic
615
617
616 def populate_all_magic_menu(self, listofmagic=None):
618 def populate_all_magic_menu(self, listofmagic=None):
617 """Clean "All Magics..." menu and repopulate it with `listofmagic`
619 """Clean "All Magics..." menu and repopulate it with `listofmagic`
618
620
619 Parameters
621 Parameters
620 ----------
622 ----------
621 listofmagic : string,
623 listofmagic : string,
622 repr() of a list of strings, send back by the kernel
624 repr() of a list of strings, send back by the kernel
623
625
624 Notes
626 Notes
625 -----
627 -----
626 `listofmagic`is a repr() of list because it is fed with the result of
628 `listofmagic`is a repr() of list because it is fed with the result of
627 a 'user_expression'
629 a 'user_expression'
628 """
630 """
629 for k,v in self._magic_menu_dict.items():
631 for k,v in self._magic_menu_dict.items():
630 v.clear()
632 v.clear()
631 self.all_magic_menu.clear()
633 self.all_magic_menu.clear()
632
634
633
635
634 mlist=ast.literal_eval(listofmagic)
636 mlist=ast.literal_eval(listofmagic)
635 for magic in mlist:
637 for magic in mlist:
636 cell = (magic['type'] == 'cell')
638 cell = (magic['type'] == 'cell')
637 name = magic['name']
639 name = magic['name']
638 mclass = magic['class']
640 mclass = magic['class']
639 if cell :
641 if cell :
640 prefix='%%'
642 prefix='%%'
641 else :
643 else :
642 prefix='%'
644 prefix='%'
643 magic_menu = self._get_magic_menu(mclass)
645 magic_menu = self._get_magic_menu(mclass)
644
646
645 pmagic = '%s%s'%(prefix,name)
647 pmagic = '%s%s'%(prefix,name)
646
648
647 xaction = QtGui.QAction(pmagic,
649 xaction = QtGui.QAction(pmagic,
648 self,
650 self,
649 triggered=self._make_dynamic_magic(pmagic)
651 triggered=self._make_dynamic_magic(pmagic)
650 )
652 )
651 magic_menu.addAction(xaction)
653 magic_menu.addAction(xaction)
652 self.all_magic_menu.addAction(xaction)
654 self.all_magic_menu.addAction(xaction)
653
655
654 def update_all_magic_menu(self):
656 def update_all_magic_menu(self):
655 """ Update the list of magics in the "All Magics..." Menu
657 """ Update the list of magics in the "All Magics..." Menu
656
658
657 Request the kernel with the list of available magics and populate the
659 Request the kernel with the list of available magics and populate the
658 menu with the list received back
660 menu with the list received back
659
661
660 """
662 """
661 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
663 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
662 self.populate_all_magic_menu)
664 self.populate_all_magic_menu)
663
665
664 def _get_magic_menu(self,menuidentifier, menulabel=None):
666 def _get_magic_menu(self,menuidentifier, menulabel=None):
665 """return a submagic menu by name, and create it if needed
667 """return a submagic menu by name, and create it if needed
666
668
667 parameters:
669 parameters:
668 -----------
670 -----------
669
671
670 menulabel : str
672 menulabel : str
671 Label for the menu
673 Label for the menu
672
674
673 Will infere the menu name from the identifier at creation if menulabel not given.
675 Will infere the menu name from the identifier at creation if menulabel not given.
674 To do so you have too give menuidentifier as a CamelCassedString
676 To do so you have too give menuidentifier as a CamelCassedString
675 """
677 """
676 menu = self._magic_menu_dict.get(menuidentifier,None)
678 menu = self._magic_menu_dict.get(menuidentifier,None)
677 if not menu :
679 if not menu :
678 if not menulabel:
680 if not menulabel:
679 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
681 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
680 menu = QtGui.QMenu(menulabel,self.magic_menu)
682 menu = QtGui.QMenu(menulabel,self.magic_menu)
681 self._magic_menu_dict[menuidentifier]=menu
683 self._magic_menu_dict[menuidentifier]=menu
682 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
684 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
683 return menu
685 return menu
684
686
685
687
686
688
687 def init_magic_menu(self):
689 def init_magic_menu(self):
688 self.magic_menu = self.menuBar().addMenu("&Magic")
690 self.magic_menu = self.menuBar().addMenu("&Magic")
689 self.magic_menu_separator = self.magic_menu.addSeparator()
691 self.magic_menu_separator = self.magic_menu.addSeparator()
690
692
691 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
693 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
692
694
693 # This action should usually not appear as it will be cleared when menu
695 # This action should usually not appear as it will be cleared when menu
694 # is updated at first kernel response. Though, it is necessary when
696 # is updated at first kernel response. Though, it is necessary when
695 # connecting through X-forwarding, as in this case, the menu is not
697 # connecting through X-forwarding, as in this case, the menu is not
696 # auto updated, SO DO NOT DELETE.
698 # auto updated, SO DO NOT DELETE.
697 self.pop = QtGui.QAction("&Update All Magic Menu ",
699 self.pop = QtGui.QAction("&Update All Magic Menu ",
698 self, triggered=self.update_all_magic_menu)
700 self, triggered=self.update_all_magic_menu)
699 self.add_menu_action(self.all_magic_menu, self.pop)
701 self.add_menu_action(self.all_magic_menu, self.pop)
700 # we need to populate the 'Magic Menu' once the kernel has answer at
702 # we need to populate the 'Magic Menu' once the kernel has answer at
701 # least once let's do it immediately, but it's assured to works
703 # least once let's do it immediately, but it's assured to works
702 self.pop.trigger()
704 self.pop.trigger()
703
705
704 self.reset_action = QtGui.QAction("&Reset",
706 self.reset_action = QtGui.QAction("&Reset",
705 self,
707 self,
706 statusTip="Clear all variables from workspace",
708 statusTip="Clear all variables from workspace",
707 triggered=self.reset_magic_active_frontend)
709 triggered=self.reset_magic_active_frontend)
708 self.add_menu_action(self.magic_menu, self.reset_action)
710 self.add_menu_action(self.magic_menu, self.reset_action)
709
711
710 self.history_action = QtGui.QAction("&History",
712 self.history_action = QtGui.QAction("&History",
711 self,
713 self,
712 statusTip="show command history",
714 statusTip="show command history",
713 triggered=self.history_magic_active_frontend)
715 triggered=self.history_magic_active_frontend)
714 self.add_menu_action(self.magic_menu, self.history_action)
716 self.add_menu_action(self.magic_menu, self.history_action)
715
717
716 self.save_action = QtGui.QAction("E&xport History ",
718 self.save_action = QtGui.QAction("E&xport History ",
717 self,
719 self,
718 statusTip="Export History as Python File",
720 statusTip="Export History as Python File",
719 triggered=self.save_magic_active_frontend)
721 triggered=self.save_magic_active_frontend)
720 self.add_menu_action(self.magic_menu, self.save_action)
722 self.add_menu_action(self.magic_menu, self.save_action)
721
723
722 self.who_action = QtGui.QAction("&Who",
724 self.who_action = QtGui.QAction("&Who",
723 self,
725 self,
724 statusTip="List interactive variables",
726 statusTip="List interactive variables",
725 triggered=self.who_magic_active_frontend)
727 triggered=self.who_magic_active_frontend)
726 self.add_menu_action(self.magic_menu, self.who_action)
728 self.add_menu_action(self.magic_menu, self.who_action)
727
729
728 self.who_ls_action = QtGui.QAction("Wh&o ls",
730 self.who_ls_action = QtGui.QAction("Wh&o ls",
729 self,
731 self,
730 statusTip="Return a list of interactive variables",
732 statusTip="Return a list of interactive variables",
731 triggered=self.who_ls_magic_active_frontend)
733 triggered=self.who_ls_magic_active_frontend)
732 self.add_menu_action(self.magic_menu, self.who_ls_action)
734 self.add_menu_action(self.magic_menu, self.who_ls_action)
733
735
734 self.whos_action = QtGui.QAction("Who&s",
736 self.whos_action = QtGui.QAction("Who&s",
735 self,
737 self,
736 statusTip="List interactive variables with details",
738 statusTip="List interactive variables with details",
737 triggered=self.whos_magic_active_frontend)
739 triggered=self.whos_magic_active_frontend)
738 self.add_menu_action(self.magic_menu, self.whos_action)
740 self.add_menu_action(self.magic_menu, self.whos_action)
739
741
740 def init_window_menu(self):
742 def init_window_menu(self):
741 self.window_menu = self.menuBar().addMenu("&Window")
743 self.window_menu = self.menuBar().addMenu("&Window")
742 if sys.platform == 'darwin':
744 if sys.platform == 'darwin':
743 # add min/maximize actions to OSX, which lacks default bindings.
745 # add min/maximize actions to OSX, which lacks default bindings.
744 self.minimizeAct = QtGui.QAction("Mini&mize",
746 self.minimizeAct = QtGui.QAction("Mini&mize",
745 self,
747 self,
746 shortcut="Ctrl+m",
748 shortcut="Ctrl+m",
747 statusTip="Minimize the window/Restore Normal Size",
749 statusTip="Minimize the window/Restore Normal Size",
748 triggered=self.toggleMinimized)
750 triggered=self.toggleMinimized)
749 # maximize is called 'Zoom' on OSX for some reason
751 # maximize is called 'Zoom' on OSX for some reason
750 self.maximizeAct = QtGui.QAction("&Zoom",
752 self.maximizeAct = QtGui.QAction("&Zoom",
751 self,
753 self,
752 shortcut="Ctrl+Shift+M",
754 shortcut="Ctrl+Shift+M",
753 statusTip="Maximize the window/Restore Normal Size",
755 statusTip="Maximize the window/Restore Normal Size",
754 triggered=self.toggleMaximized)
756 triggered=self.toggleMaximized)
755
757
756 self.add_menu_action(self.window_menu, self.minimizeAct)
758 self.add_menu_action(self.window_menu, self.minimizeAct)
757 self.add_menu_action(self.window_menu, self.maximizeAct)
759 self.add_menu_action(self.window_menu, self.maximizeAct)
758 self.window_menu.addSeparator()
760 self.window_menu.addSeparator()
759
761
760 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
762 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
761 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
763 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
762 self,
764 self,
763 shortcut=prev_key,
765 shortcut=prev_key,
764 statusTip="Select previous tab",
766 statusTip="Select previous tab",
765 triggered=self.prev_tab)
767 triggered=self.prev_tab)
766 self.add_menu_action(self.window_menu, self.prev_tab_act)
768 self.add_menu_action(self.window_menu, self.prev_tab_act)
767
769
768 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
770 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
769 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
771 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
770 self,
772 self,
771 shortcut=next_key,
773 shortcut=next_key,
772 statusTip="Select next tab",
774 statusTip="Select next tab",
773 triggered=self.next_tab)
775 triggered=self.next_tab)
774 self.add_menu_action(self.window_menu, self.next_tab_act)
776 self.add_menu_action(self.window_menu, self.next_tab_act)
775
777
776 def init_help_menu(self):
778 def init_help_menu(self):
777 # please keep the Help menu in Mac Os even if empty. It will
779 # please keep the Help menu in Mac Os even if empty. It will
778 # automatically contain a search field to search inside menus and
780 # automatically contain a search field to search inside menus and
779 # please keep it spelled in English, as long as Qt Doesn't support
781 # please keep it spelled in English, as long as Qt Doesn't support
780 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
782 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
781 # this search field functionality
783 # this search field functionality
782
784
783 self.help_menu = self.menuBar().addMenu("&Help")
785 self.help_menu = self.menuBar().addMenu("&Help")
784
786
785
787
786 # Help Menu
788 # Help Menu
787
789
788 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
790 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
789 self,
791 self,
790 triggered=self.intro_active_frontend
792 triggered=self.intro_active_frontend
791 )
793 )
792 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
794 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
793
795
794 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
796 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
795 self,
797 self,
796 triggered=self.quickref_active_frontend
798 triggered=self.quickref_active_frontend
797 )
799 )
798 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
800 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
799
801
800 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
802 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
801 self,
803 self,
802 triggered=self.guiref_active_frontend
804 triggered=self.guiref_active_frontend
803 )
805 )
804 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
806 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
805
807
806 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
808 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
807 self,
809 self,
808 triggered=self._open_online_help)
810 triggered=self._open_online_help)
809 self.add_menu_action(self.help_menu, self.onlineHelpAct)
811 self.add_menu_action(self.help_menu, self.onlineHelpAct)
810
812
811 # minimize/maximize/fullscreen actions:
813 # minimize/maximize/fullscreen actions:
812
814
813 def toggle_menu_bar(self):
815 def toggle_menu_bar(self):
814 menu_bar = self.menuBar()
816 menu_bar = self.menuBar()
815 if menu_bar.isVisible():
817 if menu_bar.isVisible():
816 menu_bar.setVisible(False)
818 menu_bar.setVisible(False)
817 else:
819 else:
818 menu_bar.setVisible(True)
820 menu_bar.setVisible(True)
819
821
820 def toggleMinimized(self):
822 def toggleMinimized(self):
821 if not self.isMinimized():
823 if not self.isMinimized():
822 self.showMinimized()
824 self.showMinimized()
823 else:
825 else:
824 self.showNormal()
826 self.showNormal()
825
827
826 def _open_online_help(self):
828 def _open_online_help(self):
827 filename="http://ipython.org/ipython-doc/stable/index.html"
829 filename="http://ipython.org/ipython-doc/stable/index.html"
828 webbrowser.open(filename, new=1, autoraise=True)
830 webbrowser.open(filename, new=1, autoraise=True)
829
831
830 def toggleMaximized(self):
832 def toggleMaximized(self):
831 if not self.isMaximized():
833 if not self.isMaximized():
832 self.showMaximized()
834 self.showMaximized()
833 else:
835 else:
834 self.showNormal()
836 self.showNormal()
835
837
836 # Min/Max imizing while in full screen give a bug
838 # Min/Max imizing while in full screen give a bug
837 # when going out of full screen, at least on OSX
839 # when going out of full screen, at least on OSX
838 def toggleFullScreen(self):
840 def toggleFullScreen(self):
839 if not self.isFullScreen():
841 if not self.isFullScreen():
840 self.showFullScreen()
842 self.showFullScreen()
841 if sys.platform == 'darwin':
843 if sys.platform == 'darwin':
842 self.maximizeAct.setEnabled(False)
844 self.maximizeAct.setEnabled(False)
843 self.minimizeAct.setEnabled(False)
845 self.minimizeAct.setEnabled(False)
844 else:
846 else:
845 self.showNormal()
847 self.showNormal()
846 if sys.platform == 'darwin':
848 if sys.platform == 'darwin':
847 self.maximizeAct.setEnabled(True)
849 self.maximizeAct.setEnabled(True)
848 self.minimizeAct.setEnabled(True)
850 self.minimizeAct.setEnabled(True)
849
851
850 def set_paging_active_frontend(self, paging):
852 def set_paging_active_frontend(self, paging):
851 self.active_frontend._set_paging(paging)
853 self.active_frontend._set_paging(paging)
852
854
853 def close_active_frontend(self):
855 def close_active_frontend(self):
854 self.close_tab(self.active_frontend)
856 self.close_tab(self.active_frontend)
855
857
856 def restart_kernel_active_frontend(self):
858 def restart_kernel_active_frontend(self):
857 self.active_frontend.request_restart_kernel()
859 self.active_frontend.request_restart_kernel()
858
860
859 def interrupt_kernel_active_frontend(self):
861 def interrupt_kernel_active_frontend(self):
860 self.active_frontend.request_interrupt_kernel()
862 self.active_frontend.request_interrupt_kernel()
861
863
862 def toggle_confirm_restart_active_frontend(self):
864 def toggle_confirm_restart_active_frontend(self):
863 widget = self.active_frontend
865 widget = self.active_frontend
864 widget.confirm_restart = not widget.confirm_restart
866 widget.confirm_restart = not widget.confirm_restart
865 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
867 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
866
868
867 def update_restart_checkbox(self):
869 def update_restart_checkbox(self):
868 if self.active_frontend is None:
870 if self.active_frontend is None:
869 return
871 return
870 widget = self.active_frontend
872 widget = self.active_frontend
871 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
873 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
872
874
873 def cut_active_frontend(self):
875 def cut_active_frontend(self):
874 widget = self.active_frontend
876 widget = self.active_frontend
875 if widget.can_cut():
877 if widget.can_cut():
876 widget.cut()
878 widget.cut()
877
879
878 def copy_active_frontend(self):
880 def copy_active_frontend(self):
879 widget = self.active_frontend
881 widget = self.active_frontend
880 widget.copy()
882 widget.copy()
881
883
882 def copy_raw_active_frontend(self):
884 def copy_raw_active_frontend(self):
883 self.active_frontend._copy_raw_action.trigger()
885 self.active_frontend._copy_raw_action.trigger()
884
886
885 def paste_active_frontend(self):
887 def paste_active_frontend(self):
886 widget = self.active_frontend
888 widget = self.active_frontend
887 if widget.can_paste():
889 if widget.can_paste():
888 widget.paste()
890 widget.paste()
889
891
890 def undo_active_frontend(self):
892 def undo_active_frontend(self):
891 self.active_frontend.undo()
893 self.active_frontend.undo()
892
894
893 def redo_active_frontend(self):
895 def redo_active_frontend(self):
894 self.active_frontend.redo()
896 self.active_frontend.redo()
895
897
896 def reset_magic_active_frontend(self):
898 def reset_magic_active_frontend(self):
897 self.active_frontend.execute("%reset")
899 self.active_frontend.execute("%reset")
898
900
899 def history_magic_active_frontend(self):
901 def history_magic_active_frontend(self):
900 self.active_frontend.execute("%history")
902 self.active_frontend.execute("%history")
901
903
902 def save_magic_active_frontend(self):
904 def save_magic_active_frontend(self):
903 self.active_frontend.save_magic()
905 self.active_frontend.save_magic()
904
906
905 def clear_magic_active_frontend(self):
907 def clear_magic_active_frontend(self):
906 self.active_frontend.execute("%clear")
908 self.active_frontend.execute("%clear")
907
909
908 def who_magic_active_frontend(self):
910 def who_magic_active_frontend(self):
909 self.active_frontend.execute("%who")
911 self.active_frontend.execute("%who")
910
912
911 def who_ls_magic_active_frontend(self):
913 def who_ls_magic_active_frontend(self):
912 self.active_frontend.execute("%who_ls")
914 self.active_frontend.execute("%who_ls")
913
915
914 def whos_magic_active_frontend(self):
916 def whos_magic_active_frontend(self):
915 self.active_frontend.execute("%whos")
917 self.active_frontend.execute("%whos")
916
918
917 def print_action_active_frontend(self):
919 def print_action_active_frontend(self):
918 self.active_frontend.print_action.trigger()
920 self.active_frontend.print_action.trigger()
919
921
920 def export_action_active_frontend(self):
922 def export_action_active_frontend(self):
921 self.active_frontend.export_action.trigger()
923 self.active_frontend.export_action.trigger()
922
924
923 def select_all_active_frontend(self):
925 def select_all_active_frontend(self):
924 self.active_frontend.select_all_action.trigger()
926 self.active_frontend.select_all_action.trigger()
925
927
926 def increase_font_size_active_frontend(self):
928 def increase_font_size_active_frontend(self):
927 self.active_frontend.increase_font_size.trigger()
929 self.active_frontend.increase_font_size.trigger()
928
930
929 def decrease_font_size_active_frontend(self):
931 def decrease_font_size_active_frontend(self):
930 self.active_frontend.decrease_font_size.trigger()
932 self.active_frontend.decrease_font_size.trigger()
931
933
932 def reset_font_size_active_frontend(self):
934 def reset_font_size_active_frontend(self):
933 self.active_frontend.reset_font_size.trigger()
935 self.active_frontend.reset_font_size.trigger()
934
936
935 def guiref_active_frontend(self):
937 def guiref_active_frontend(self):
936 self.active_frontend.execute("%guiref")
938 self.active_frontend.execute("%guiref")
937
939
938 def intro_active_frontend(self):
940 def intro_active_frontend(self):
939 self.active_frontend.execute("?")
941 self.active_frontend.execute("?")
940
942
941 def quickref_active_frontend(self):
943 def quickref_active_frontend(self):
942 self.active_frontend.execute("%quickref")
944 self.active_frontend.execute("%quickref")
943 #---------------------------------------------------------------------------
945 #---------------------------------------------------------------------------
944 # QWidget interface
946 # QWidget interface
945 #---------------------------------------------------------------------------
947 #---------------------------------------------------------------------------
946
948
947 def closeEvent(self, event):
949 def closeEvent(self, event):
948 """ Forward the close event to every tabs contained by the windows
950 """ Forward the close event to every tabs contained by the windows
949 """
951 """
950 if self.tab_widget.count() == 0:
952 if self.tab_widget.count() == 0:
951 # no tabs, just close
953 # no tabs, just close
952 event.accept()
954 event.accept()
953 return
955 return
954 # Do Not loop on the widget count as it change while closing
956 # Do Not loop on the widget count as it change while closing
955 title = self.window().windowTitle()
957 title = self.window().windowTitle()
956 cancel = QtGui.QMessageBox.Cancel
958 cancel = QtGui.QMessageBox.Cancel
957 okay = QtGui.QMessageBox.Ok
959 okay = QtGui.QMessageBox.Ok
958
960
959 if self.confirm_exit:
961 if self.confirm_exit:
960 if self.tab_widget.count() > 1:
962 if self.tab_widget.count() > 1:
961 msg = "Close all tabs, stop all kernels, and Quit?"
963 msg = "Close all tabs, stop all kernels, and Quit?"
962 else:
964 else:
963 msg = "Close console, stop kernel, and Quit?"
965 msg = "Close console, stop kernel, and Quit?"
964 info = "Kernels not started here (e.g. notebooks) will be left alone."
966 info = "Kernels not started here (e.g. notebooks) will be left alone."
965 closeall = QtGui.QPushButton("&Quit", self)
967 closeall = QtGui.QPushButton("&Quit", self)
966 closeall.setShortcut('Q')
968 closeall.setShortcut('Q')
967 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
969 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
968 title, msg)
970 title, msg)
969 box.setInformativeText(info)
971 box.setInformativeText(info)
970 box.addButton(cancel)
972 box.addButton(cancel)
971 box.addButton(closeall, QtGui.QMessageBox.YesRole)
973 box.addButton(closeall, QtGui.QMessageBox.YesRole)
972 box.setDefaultButton(closeall)
974 box.setDefaultButton(closeall)
973 box.setEscapeButton(cancel)
975 box.setEscapeButton(cancel)
974 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
976 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
975 box.setIconPixmap(pixmap)
977 box.setIconPixmap(pixmap)
976 reply = box.exec_()
978 reply = box.exec_()
977 else:
979 else:
978 reply = okay
980 reply = okay
979
981
980 if reply == cancel:
982 if reply == cancel:
981 event.ignore()
983 event.ignore()
982 return
984 return
983 if reply == okay:
985 if reply == okay:
984 while self.tab_widget.count() >= 1:
986 while self.tab_widget.count() >= 1:
985 # prevent further confirmations:
987 # prevent further confirmations:
986 widget = self.active_frontend
988 widget = self.active_frontend
987 widget._confirm_exit = False
989 widget._confirm_exit = False
988 self.close_tab(widget)
990 self.close_tab(widget)
989 event.accept()
991 event.accept()
990
992
General Comments 0
You need to be logged in to leave comments. Login now