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