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