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