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