##// END OF EJS Templates
Introduce a doclkable widget that will substitute "all magics" menu....
Dimitry Kloper -
Show More
@@ -1,995 +1,1151 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 magic_menu.addAction(xaction)
656 magic_menu.addAction(xaction)
657 self.all_magic_menu.addAction(xaction)
657 self.all_magic_menu.addAction(xaction)
658
658
659 def update_all_magic_menu(self):
659 def update_all_magic_menu(self):
660 """ Update the list of magics in the "All Magics..." Menu
660 """ Update the list of magics in the "All Magics..." Menu
661
661
662 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
663 menu with the list received back
663 menu with the list received back
664
664
665 """
665 """
666 self.active_frontend._silent_exec_callback('get_ipython().magic("lsmagic")',
666 self.active_frontend._silent_exec_callback('get_ipython().magic("lsmagic")',
667 self.populate_all_magic_menu)
667 self.populate_all_magic_menu)
668
668
669 def _get_magic_menu(self,menuidentifier, menulabel=None):
669 def _get_magic_menu(self,menuidentifier, menulabel=None):
670 """return a submagic menu by name, and create it if needed
670 """return a submagic menu by name, and create it if needed
671
671
672 Parameters
672 Parameters
673 ----------
673 ----------
674
674
675 menulabel : str
675 menulabel : str
676 Label for the menu
676 Label for the menu
677
677
678 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.
679 To do so you have too give menuidentifier as a CamelCassedString
679 To do so you have too give menuidentifier as a CamelCassedString
680 """
680 """
681 menu = self._magic_menu_dict.get(menuidentifier,None)
681 menu = self._magic_menu_dict.get(menuidentifier,None)
682 if not menu :
682 if not menu :
683 if not menulabel:
683 if not menulabel:
684 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)
685 menu = QtGui.QMenu(menulabel,self.magic_menu)
685 menu = QtGui.QMenu(menulabel,self.magic_menu)
686 self._magic_menu_dict[menuidentifier]=menu
686 self._magic_menu_dict[menuidentifier]=menu
687 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
687 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
688 return menu
688 return menu
689
689
690
690
691
691
692 def init_magic_menu(self):
692 def init_magic_menu(self):
693 self.magic_menu = self.menuBar().addMenu("&Magic")
693 self.magic_menu = self.menuBar().addMenu("&Magic")
694 self.magic_menu_separator = self.magic_menu.addSeparator()
694 self.magic_menu_separator = self.magic_menu.addSeparator()
695
695
696 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...")
697
697
698 # 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
699 # is updated at first kernel response. Though, it is necessary when
699 # is updated at first kernel response. Though, it is necessary when
700 # 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
701 # auto updated, SO DO NOT DELETE.
701 # auto updated, SO DO NOT DELETE.
702 self.pop = QtGui.QAction("&Update All Magic Menu ",
702 self.pop = QtGui.QAction("&Update All Magic Menu ",
703 self, triggered=self.update_all_magic_menu)
703 self, triggered=self.update_all_magic_menu)
704 self.add_menu_action(self.all_magic_menu, self.pop)
704 self.add_menu_action(self.all_magic_menu, self.pop)
705 # 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
706 # 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
707 self.pop.trigger()
707 self.pop.trigger()
708
708
709 self.reset_action = QtGui.QAction("&Reset",
709 self.reset_action = QtGui.QAction("&Reset",
710 self,
710 self,
711 statusTip="Clear all variables from workspace",
711 statusTip="Clear all variables from workspace",
712 triggered=self.reset_magic_active_frontend)
712 triggered=self.reset_magic_active_frontend)
713 self.add_menu_action(self.magic_menu, self.reset_action)
713 self.add_menu_action(self.magic_menu, self.reset_action)
714
714
715 self.history_action = QtGui.QAction("&History",
715 self.history_action = QtGui.QAction("&History",
716 self,
716 self,
717 statusTip="show command history",
717 statusTip="show command history",
718 triggered=self.history_magic_active_frontend)
718 triggered=self.history_magic_active_frontend)
719 self.add_menu_action(self.magic_menu, self.history_action)
719 self.add_menu_action(self.magic_menu, self.history_action)
720
720
721 self.save_action = QtGui.QAction("E&xport History ",
721 self.save_action = QtGui.QAction("E&xport History ",
722 self,
722 self,
723 statusTip="Export History as Python File",
723 statusTip="Export History as Python File",
724 triggered=self.save_magic_active_frontend)
724 triggered=self.save_magic_active_frontend)
725 self.add_menu_action(self.magic_menu, self.save_action)
725 self.add_menu_action(self.magic_menu, self.save_action)
726
726
727 self.who_action = QtGui.QAction("&Who",
727 self.who_action = QtGui.QAction("&Who",
728 self,
728 self,
729 statusTip="List interactive variables",
729 statusTip="List interactive variables",
730 triggered=self.who_magic_active_frontend)
730 triggered=self.who_magic_active_frontend)
731 self.add_menu_action(self.magic_menu, self.who_action)
731 self.add_menu_action(self.magic_menu, self.who_action)
732
732
733 self.who_ls_action = QtGui.QAction("Wh&o ls",
733 self.who_ls_action = QtGui.QAction("Wh&o ls",
734 self,
734 self,
735 statusTip="Return a list of interactive variables",
735 statusTip="Return a list of interactive variables",
736 triggered=self.who_ls_magic_active_frontend)
736 triggered=self.who_ls_magic_active_frontend)
737 self.add_menu_action(self.magic_menu, self.who_ls_action)
737 self.add_menu_action(self.magic_menu, self.who_ls_action)
738
738
739 self.whos_action = QtGui.QAction("Who&s",
739 self.whos_action = QtGui.QAction("Who&s",
740 self,
740 self,
741 statusTip="List interactive variables with details",
741 statusTip="List interactive variables with details",
742 triggered=self.whos_magic_active_frontend)
742 triggered=self.whos_magic_active_frontend)
743 self.add_menu_action(self.magic_menu, self.whos_action)
743 self.add_menu_action(self.magic_menu, self.whos_action)
744
744
745 def init_window_menu(self):
745 def init_window_menu(self):
746 self.window_menu = self.menuBar().addMenu("&Window")
746 self.window_menu = self.menuBar().addMenu("&Window")
747 if sys.platform == 'darwin':
747 if sys.platform == 'darwin':
748 # add min/maximize actions to OSX, which lacks default bindings.
748 # add min/maximize actions to OSX, which lacks default bindings.
749 self.minimizeAct = QtGui.QAction("Mini&mize",
749 self.minimizeAct = QtGui.QAction("Mini&mize",
750 self,
750 self,
751 shortcut="Ctrl+m",
751 shortcut="Ctrl+m",
752 statusTip="Minimize the window/Restore Normal Size",
752 statusTip="Minimize the window/Restore Normal Size",
753 triggered=self.toggleMinimized)
753 triggered=self.toggleMinimized)
754 # maximize is called 'Zoom' on OSX for some reason
754 # maximize is called 'Zoom' on OSX for some reason
755 self.maximizeAct = QtGui.QAction("&Zoom",
755 self.maximizeAct = QtGui.QAction("&Zoom",
756 self,
756 self,
757 shortcut="Ctrl+Shift+M",
757 shortcut="Ctrl+Shift+M",
758 statusTip="Maximize the window/Restore Normal Size",
758 statusTip="Maximize the window/Restore Normal Size",
759 triggered=self.toggleMaximized)
759 triggered=self.toggleMaximized)
760
760
761 self.add_menu_action(self.window_menu, self.minimizeAct)
761 self.add_menu_action(self.window_menu, self.minimizeAct)
762 self.add_menu_action(self.window_menu, self.maximizeAct)
762 self.add_menu_action(self.window_menu, self.maximizeAct)
763 self.window_menu.addSeparator()
763 self.window_menu.addSeparator()
764
764
765 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"
766 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
766 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
767 self,
767 self,
768 shortcut=prev_key,
768 shortcut=prev_key,
769 statusTip="Select previous tab",
769 statusTip="Select previous tab",
770 triggered=self.prev_tab)
770 triggered=self.prev_tab)
771 self.add_menu_action(self.window_menu, self.prev_tab_act)
771 self.add_menu_action(self.window_menu, self.prev_tab_act)
772
772
773 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"
774 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
774 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
775 self,
775 self,
776 shortcut=next_key,
776 shortcut=next_key,
777 statusTip="Select next tab",
777 statusTip="Select next tab",
778 triggered=self.next_tab)
778 triggered=self.next_tab)
779 self.add_menu_action(self.window_menu, self.next_tab_act)
779 self.add_menu_action(self.window_menu, self.next_tab_act)
780
780
781 def init_help_menu(self):
781 def init_help_menu(self):
782 # 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
783 # automatically contain a search field to search inside menus and
783 # automatically contain a search field to search inside menus and
784 # 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
785 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
785 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
786 # this search field functionality
786 # this search field functionality
787
787
788 self.help_menu = self.menuBar().addMenu("&Help")
788 self.help_menu = self.menuBar().addMenu("&Help")
789
789
790
790
791 # Help Menu
791 # Help Menu
792
792
793 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
793 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
794 self,
794 self,
795 triggered=self.intro_active_frontend
795 triggered=self.intro_active_frontend
796 )
796 )
797 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)
798
798
799 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
799 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
800 self,
800 self,
801 triggered=self.quickref_active_frontend
801 triggered=self.quickref_active_frontend
802 )
802 )
803 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)
804
804
805 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
805 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
806 self,
806 self,
807 triggered=self.guiref_active_frontend
807 triggered=self.guiref_active_frontend
808 )
808 )
809 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)
810
810
811 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
811 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
812 self,
812 self,
813 triggered=self._open_online_help)
813 triggered=self._open_online_help)
814 self.add_menu_action(self.help_menu, self.onlineHelpAct)
814 self.add_menu_action(self.help_menu, self.onlineHelpAct)
815
815
816 def init_magic_helper(self):
817 self.magic_helper_data = None
818 self.magic_helper = QtGui.QDockWidget("Magics", self)
819 self.magic_helper.setAllowedAreas(QtCore.Qt.LeftDockWidgetArea |
820 QtCore.Qt.RightDockWidgetArea)
821 self.magic_helper.setVisible(False)
822
823 class MinListWidget(QtGui.QListWidget):
824 def sizeHint(self):
825 s = QtCore.QSize()
826 s.setHeight(super(MinListWidget,self).sizeHint().height())
827 s.setWidth(self.sizeHintForColumn(0))
828 return s
829
830 self.magic_helper_frame = QtGui.QFrame()
831 self.magic_helper_searchl = QtGui.QLabel("Search:")
832 self.magic_helper_search = QtGui.QLineEdit()
833 self.magic_helper_class = QtGui.QComboBox()
834 self.magic_helper_list = MinListWidget()
835 self.magic_helper_paste = QtGui.QPushButton("Paste")
836 self.magic_helper_run = QtGui.QPushButton("Run")
837
838 main_layout = QtGui.QVBoxLayout()
839 search_layout = QtGui.QHBoxLayout()
840 search_layout.addWidget(self.magic_helper_searchl)
841 search_layout.addWidget(self.magic_helper_search, 10)
842 main_layout.addLayout(search_layout)
843 main_layout.addWidget(self.magic_helper_class)
844 main_layout.addWidget(self.magic_helper_list, 10)
845 action_layout = QtGui.QHBoxLayout()
846 action_layout.addWidget(self.magic_helper_paste)
847 action_layout.addWidget(self.magic_helper_run)
848 main_layout.addLayout(action_layout)
849
850 self.magic_helper_frame.setLayout(main_layout)
851 self.magic_helper.setWidget(self.magic_helper_frame)
852
853 self.magic_helper.visibilityChanged[bool].connect(
854 self.update_magic_helper
855 )
856 self.magic_helper_class.activated[int].connect(
857 self.magic_helper_class_selected
858 )
859 self.magic_helper_search.textChanged[str].connect(
860 self.magic_helper_search_changed
861 )
862 self.magic_helper_list.itemDoubleClicked[QtGui.QListWidgetItem].connect(
863 self.magic_helper_paste_requested
864 )
865 self.magic_helper_paste.clicked[bool].connect(
866 self.magic_helper_paste_requested
867 )
868 self.magic_helper_run.clicked[bool].connect(
869 self.magic_helper_run_requested
870 )
871
872 self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.magic_helper)
873 self.add_menu_action(self.magic_menu,
874 self.magic_helper.toggleViewAction())
875
876 def update_magic_helper(self, visible):
877 if not visible or self.magic_helper_data != None:
878 return
879 self.magic_helper_data = {}
880 self.magic_helper_class.clear()
881 self.magic_helper_class.addItem("Populating...")
882 self.active_frontend._silent_exec_callback(
883 'get_ipython().magic("lsmagic")',
884 self.populate_magic_helper
885 )
886
887 def populate_magic_helper(self, data):
888 if not data:
889 return
890
891 if data['status'] != 'ok':
892 self.log.warn("%%lsmagic user-expression failed: {}".format(data))
893 return
894
895 self.magic_helper_class.clear()
896 self.magic_helper_list.clear()
897
898 self.magic_helper_data = json.loads(
899 data['data'].get('application/json', {})
900 )
901
902 self.magic_helper_class.addItem('All Magics', 'any')
903 classes = set()
904
905 for mtype in sorted(self.magic_helper_data):
906 subdict = self.magic_helper_data[mtype]
907 for name in sorted(subdict):
908 classes.add(subdict[name])
909
910 for cls in sorted(classes):
911 label = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>", cls)
912 self.magic_helper_class.addItem(label, cls)
913
914 self.filter_magic_helper('.', 'any')
915
916 def magic_helper_class_selected(self, index):
917 item = self.magic_helper_class.itemData(index)
918 regex = self.magic_helper_search.text()
919 self.filter_magic_helper(regex = regex, cls = item)
920
921 def magic_helper_search_changed(self, search_string):
922 item = self.magic_helper_class.itemData(
923 self.magic_helper_class.currentIndex()
924 )
925 self.filter_magic_helper(regex = search_string, cls = item)
926
927 def _magic_helper_get_current(self, item = None):
928 text = None
929 if not isinstance(item, QtGui.QListWidgetItem):
930 item = self.magic_helper_list.currentItem()
931 text = item.text()
932 return text
933
934 def _set_active_frontend_focus(self):
935 # this is a hack, self.active_frontend._control seems to be
936 # a private member. Unfortunately this is the only method
937 # to set focus reliably
938 QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus)
939
940 def magic_helper_paste_requested(self, item = None):
941 text = self._magic_helper_get_current(item)
942 if text != None:
943 self.active_frontend.input_buffer = text
944 self._set_active_frontend_focus()
945
946 def magic_helper_run_requested(self, item = None):
947 text = self._magic_helper_get_current(item)
948 if text != None:
949 self.active_frontend.execute(text)
950 self._set_active_frontend_focus()
951
952 def filter_magic_helper(self, regex, cls):
953 if regex == "" or regex == None:
954 regex = '.'
955 if cls == None:
956 cls = 'any'
957
958 self.magic_helper_list.clear()
959 for mtype in sorted(self.magic_helper_data):
960 subdict = self.magic_helper_data[mtype]
961 prefix = magic_escapes[mtype]
962
963 for name in sorted(subdict):
964 mclass = subdict[name]
965 pmagic = prefix + name
966
967 if (re.match(regex, name) or re.match(regex, pmagic)) and \
968 (cls == 'any' or cls == mclass):
969 self.magic_helper_list.addItem(pmagic)
970
971
816 # minimize/maximize/fullscreen actions:
972 # minimize/maximize/fullscreen actions:
817
973
818 def toggle_menu_bar(self):
974 def toggle_menu_bar(self):
819 menu_bar = self.menuBar()
975 menu_bar = self.menuBar()
820 if menu_bar.isVisible():
976 if menu_bar.isVisible():
821 menu_bar.setVisible(False)
977 menu_bar.setVisible(False)
822 else:
978 else:
823 menu_bar.setVisible(True)
979 menu_bar.setVisible(True)
824
980
825 def toggleMinimized(self):
981 def toggleMinimized(self):
826 if not self.isMinimized():
982 if not self.isMinimized():
827 self.showMinimized()
983 self.showMinimized()
828 else:
984 else:
829 self.showNormal()
985 self.showNormal()
830
986
831 def _open_online_help(self):
987 def _open_online_help(self):
832 filename="http://ipython.org/ipython-doc/stable/index.html"
988 filename="http://ipython.org/ipython-doc/stable/index.html"
833 webbrowser.open(filename, new=1, autoraise=True)
989 webbrowser.open(filename, new=1, autoraise=True)
834
990
835 def toggleMaximized(self):
991 def toggleMaximized(self):
836 if not self.isMaximized():
992 if not self.isMaximized():
837 self.showMaximized()
993 self.showMaximized()
838 else:
994 else:
839 self.showNormal()
995 self.showNormal()
840
996
841 # Min/Max imizing while in full screen give a bug
997 # Min/Max imizing while in full screen give a bug
842 # when going out of full screen, at least on OSX
998 # when going out of full screen, at least on OSX
843 def toggleFullScreen(self):
999 def toggleFullScreen(self):
844 if not self.isFullScreen():
1000 if not self.isFullScreen():
845 self.showFullScreen()
1001 self.showFullScreen()
846 if sys.platform == 'darwin':
1002 if sys.platform == 'darwin':
847 self.maximizeAct.setEnabled(False)
1003 self.maximizeAct.setEnabled(False)
848 self.minimizeAct.setEnabled(False)
1004 self.minimizeAct.setEnabled(False)
849 else:
1005 else:
850 self.showNormal()
1006 self.showNormal()
851 if sys.platform == 'darwin':
1007 if sys.platform == 'darwin':
852 self.maximizeAct.setEnabled(True)
1008 self.maximizeAct.setEnabled(True)
853 self.minimizeAct.setEnabled(True)
1009 self.minimizeAct.setEnabled(True)
854
1010
855 def set_paging_active_frontend(self, paging):
1011 def set_paging_active_frontend(self, paging):
856 self.active_frontend._set_paging(paging)
1012 self.active_frontend._set_paging(paging)
857
1013
858 def close_active_frontend(self):
1014 def close_active_frontend(self):
859 self.close_tab(self.active_frontend)
1015 self.close_tab(self.active_frontend)
860
1016
861 def restart_kernel_active_frontend(self):
1017 def restart_kernel_active_frontend(self):
862 self.active_frontend.request_restart_kernel()
1018 self.active_frontend.request_restart_kernel()
863
1019
864 def interrupt_kernel_active_frontend(self):
1020 def interrupt_kernel_active_frontend(self):
865 self.active_frontend.request_interrupt_kernel()
1021 self.active_frontend.request_interrupt_kernel()
866
1022
867 def toggle_confirm_restart_active_frontend(self):
1023 def toggle_confirm_restart_active_frontend(self):
868 widget = self.active_frontend
1024 widget = self.active_frontend
869 widget.confirm_restart = not widget.confirm_restart
1025 widget.confirm_restart = not widget.confirm_restart
870 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
1026 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
871
1027
872 def update_restart_checkbox(self):
1028 def update_restart_checkbox(self):
873 if self.active_frontend is None:
1029 if self.active_frontend is None:
874 return
1030 return
875 widget = self.active_frontend
1031 widget = self.active_frontend
876 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
1032 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
877
1033
878 def cut_active_frontend(self):
1034 def cut_active_frontend(self):
879 widget = self.active_frontend
1035 widget = self.active_frontend
880 if widget.can_cut():
1036 if widget.can_cut():
881 widget.cut()
1037 widget.cut()
882
1038
883 def copy_active_frontend(self):
1039 def copy_active_frontend(self):
884 widget = self.active_frontend
1040 widget = self.active_frontend
885 widget.copy()
1041 widget.copy()
886
1042
887 def copy_raw_active_frontend(self):
1043 def copy_raw_active_frontend(self):
888 self.active_frontend._copy_raw_action.trigger()
1044 self.active_frontend._copy_raw_action.trigger()
889
1045
890 def paste_active_frontend(self):
1046 def paste_active_frontend(self):
891 widget = self.active_frontend
1047 widget = self.active_frontend
892 if widget.can_paste():
1048 if widget.can_paste():
893 widget.paste()
1049 widget.paste()
894
1050
895 def undo_active_frontend(self):
1051 def undo_active_frontend(self):
896 self.active_frontend.undo()
1052 self.active_frontend.undo()
897
1053
898 def redo_active_frontend(self):
1054 def redo_active_frontend(self):
899 self.active_frontend.redo()
1055 self.active_frontend.redo()
900
1056
901 def reset_magic_active_frontend(self):
1057 def reset_magic_active_frontend(self):
902 self.active_frontend.execute("%reset")
1058 self.active_frontend.execute("%reset")
903
1059
904 def history_magic_active_frontend(self):
1060 def history_magic_active_frontend(self):
905 self.active_frontend.execute("%history")
1061 self.active_frontend.execute("%history")
906
1062
907 def save_magic_active_frontend(self):
1063 def save_magic_active_frontend(self):
908 self.active_frontend.save_magic()
1064 self.active_frontend.save_magic()
909
1065
910 def clear_magic_active_frontend(self):
1066 def clear_magic_active_frontend(self):
911 self.active_frontend.execute("%clear")
1067 self.active_frontend.execute("%clear")
912
1068
913 def who_magic_active_frontend(self):
1069 def who_magic_active_frontend(self):
914 self.active_frontend.execute("%who")
1070 self.active_frontend.execute("%who")
915
1071
916 def who_ls_magic_active_frontend(self):
1072 def who_ls_magic_active_frontend(self):
917 self.active_frontend.execute("%who_ls")
1073 self.active_frontend.execute("%who_ls")
918
1074
919 def whos_magic_active_frontend(self):
1075 def whos_magic_active_frontend(self):
920 self.active_frontend.execute("%whos")
1076 self.active_frontend.execute("%whos")
921
1077
922 def print_action_active_frontend(self):
1078 def print_action_active_frontend(self):
923 self.active_frontend.print_action.trigger()
1079 self.active_frontend.print_action.trigger()
924
1080
925 def export_action_active_frontend(self):
1081 def export_action_active_frontend(self):
926 self.active_frontend.export_action.trigger()
1082 self.active_frontend.export_action.trigger()
927
1083
928 def select_all_active_frontend(self):
1084 def select_all_active_frontend(self):
929 self.active_frontend.select_all_action.trigger()
1085 self.active_frontend.select_all_action.trigger()
930
1086
931 def increase_font_size_active_frontend(self):
1087 def increase_font_size_active_frontend(self):
932 self.active_frontend.increase_font_size.trigger()
1088 self.active_frontend.increase_font_size.trigger()
933
1089
934 def decrease_font_size_active_frontend(self):
1090 def decrease_font_size_active_frontend(self):
935 self.active_frontend.decrease_font_size.trigger()
1091 self.active_frontend.decrease_font_size.trigger()
936
1092
937 def reset_font_size_active_frontend(self):
1093 def reset_font_size_active_frontend(self):
938 self.active_frontend.reset_font_size.trigger()
1094 self.active_frontend.reset_font_size.trigger()
939
1095
940 def guiref_active_frontend(self):
1096 def guiref_active_frontend(self):
941 self.active_frontend.execute("%guiref")
1097 self.active_frontend.execute("%guiref")
942
1098
943 def intro_active_frontend(self):
1099 def intro_active_frontend(self):
944 self.active_frontend.execute("?")
1100 self.active_frontend.execute("?")
945
1101
946 def quickref_active_frontend(self):
1102 def quickref_active_frontend(self):
947 self.active_frontend.execute("%quickref")
1103 self.active_frontend.execute("%quickref")
948 #---------------------------------------------------------------------------
1104 #---------------------------------------------------------------------------
949 # QWidget interface
1105 # QWidget interface
950 #---------------------------------------------------------------------------
1106 #---------------------------------------------------------------------------
951
1107
952 def closeEvent(self, event):
1108 def closeEvent(self, event):
953 """ Forward the close event to every tabs contained by the windows
1109 """ Forward the close event to every tabs contained by the windows
954 """
1110 """
955 if self.tab_widget.count() == 0:
1111 if self.tab_widget.count() == 0:
956 # no tabs, just close
1112 # no tabs, just close
957 event.accept()
1113 event.accept()
958 return
1114 return
959 # Do Not loop on the widget count as it change while closing
1115 # Do Not loop on the widget count as it change while closing
960 title = self.window().windowTitle()
1116 title = self.window().windowTitle()
961 cancel = QtGui.QMessageBox.Cancel
1117 cancel = QtGui.QMessageBox.Cancel
962 okay = QtGui.QMessageBox.Ok
1118 okay = QtGui.QMessageBox.Ok
963
1119
964 if self.confirm_exit:
1120 if self.confirm_exit:
965 if self.tab_widget.count() > 1:
1121 if self.tab_widget.count() > 1:
966 msg = "Close all tabs, stop all kernels, and Quit?"
1122 msg = "Close all tabs, stop all kernels, and Quit?"
967 else:
1123 else:
968 msg = "Close console, stop kernel, and Quit?"
1124 msg = "Close console, stop kernel, and Quit?"
969 info = "Kernels not started here (e.g. notebooks) will be left alone."
1125 info = "Kernels not started here (e.g. notebooks) will be left alone."
970 closeall = QtGui.QPushButton("&Quit", self)
1126 closeall = QtGui.QPushButton("&Quit", self)
971 closeall.setShortcut('Q')
1127 closeall.setShortcut('Q')
972 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
1128 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
973 title, msg)
1129 title, msg)
974 box.setInformativeText(info)
1130 box.setInformativeText(info)
975 box.addButton(cancel)
1131 box.addButton(cancel)
976 box.addButton(closeall, QtGui.QMessageBox.YesRole)
1132 box.addButton(closeall, QtGui.QMessageBox.YesRole)
977 box.setDefaultButton(closeall)
1133 box.setDefaultButton(closeall)
978 box.setEscapeButton(cancel)
1134 box.setEscapeButton(cancel)
979 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
1135 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
980 box.setIconPixmap(pixmap)
1136 box.setIconPixmap(pixmap)
981 reply = box.exec_()
1137 reply = box.exec_()
982 else:
1138 else:
983 reply = okay
1139 reply = okay
984
1140
985 if reply == cancel:
1141 if reply == cancel:
986 event.ignore()
1142 event.ignore()
987 return
1143 return
988 if reply == okay:
1144 if reply == okay:
989 while self.tab_widget.count() >= 1:
1145 while self.tab_widget.count() >= 1:
990 # prevent further confirmations:
1146 # prevent further confirmations:
991 widget = self.active_frontend
1147 widget = self.active_frontend
992 widget._confirm_exit = False
1148 widget._confirm_exit = False
993 self.close_tab(widget)
1149 self.close_tab(widget)
994 event.accept()
1150 event.accept()
995
1151
@@ -1,378 +1,379 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
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 os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26
26
27 # If run on Windows, install an exception hook which pops up a
27 # If run on Windows, install an exception hook which pops up a
28 # message box. Pythonw.exe hides the console, so without this
28 # message box. Pythonw.exe hides the console, so without this
29 # the application silently fails to load.
29 # the application silently fails to load.
30 #
30 #
31 # We always install this handler, because the expectation is for
31 # We always install this handler, because the expectation is for
32 # qtconsole to bring up a GUI even if called from the console.
32 # qtconsole to bring up a GUI even if called from the console.
33 # The old handler is called, so the exception is printed as well.
33 # The old handler is called, so the exception is printed as well.
34 # If desired, check for pythonw with an additional condition
34 # If desired, check for pythonw with an additional condition
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 if os.name == 'nt':
36 if os.name == 'nt':
37 old_excepthook = sys.excepthook
37 old_excepthook = sys.excepthook
38
38
39 # Exclude this from our autogenerated API docs.
39 # Exclude this from our autogenerated API docs.
40 undoc = lambda func: func
40 undoc = lambda func: func
41
41
42 @undoc
42 @undoc
43 def gui_excepthook(exctype, value, tb):
43 def gui_excepthook(exctype, value, tb):
44 try:
44 try:
45 import ctypes, traceback
45 import ctypes, traceback
46 MB_ICONERROR = 0x00000010
46 MB_ICONERROR = 0x00000010
47 title = u'Error starting IPython QtConsole'
47 title = u'Error starting IPython QtConsole'
48 msg = u''.join(traceback.format_exception(exctype, value, tb))
48 msg = u''.join(traceback.format_exception(exctype, value, tb))
49 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
49 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
50 finally:
50 finally:
51 # Also call the old exception hook to let it do
51 # Also call the old exception hook to let it do
52 # its thing too.
52 # its thing too.
53 old_excepthook(exctype, value, tb)
53 old_excepthook(exctype, value, tb)
54
54
55 sys.excepthook = gui_excepthook
55 sys.excepthook = gui_excepthook
56
56
57 # System library imports
57 # System library imports
58 from IPython.external.qt import QtCore, QtGui
58 from IPython.external.qt import QtCore, QtGui
59
59
60 # Local imports
60 # Local imports
61 from IPython.config.application import catch_config_error
61 from IPython.config.application import catch_config_error
62 from IPython.core.application import BaseIPythonApplication
62 from IPython.core.application import BaseIPythonApplication
63 from IPython.qt.console.ipython_widget import IPythonWidget
63 from IPython.qt.console.ipython_widget import IPythonWidget
64 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
64 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.qt.console import styles
65 from IPython.qt.console import styles
66 from IPython.qt.console.mainwindow import MainWindow
66 from IPython.qt.console.mainwindow import MainWindow
67 from IPython.qt.client import QtKernelClient
67 from IPython.qt.client import QtKernelClient
68 from IPython.qt.manager import QtKernelManager
68 from IPython.qt.manager import QtKernelManager
69 from IPython.utils.traitlets import (
69 from IPython.utils.traitlets import (
70 Dict, Unicode, CBool, Any
70 Dict, Unicode, CBool, Any
71 )
71 )
72
72
73 from IPython.consoleapp import (
73 from IPython.consoleapp import (
74 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
74 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
75 )
75 )
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Network Constants
78 # Network Constants
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 from IPython.utils.localinterfaces import is_local_ip
81 from IPython.utils.localinterfaces import is_local_ip
82
82
83 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
84 # Globals
84 # Globals
85 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
86
86
87 _examples = """
87 _examples = """
88 ipython qtconsole # start the qtconsole
88 ipython qtconsole # start the qtconsole
89 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
89 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
90 """
90 """
91
91
92 #-----------------------------------------------------------------------------
92 #-----------------------------------------------------------------------------
93 # Aliases and Flags
93 # Aliases and Flags
94 #-----------------------------------------------------------------------------
94 #-----------------------------------------------------------------------------
95
95
96 # start with copy of flags
96 # start with copy of flags
97 flags = dict(flags)
97 flags = dict(flags)
98 qt_flags = {
98 qt_flags = {
99 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
99 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
100 "Disable rich text support."),
100 "Disable rich text support."),
101 }
101 }
102
102
103 # and app_flags from the Console Mixin
103 # and app_flags from the Console Mixin
104 qt_flags.update(app_flags)
104 qt_flags.update(app_flags)
105 # add frontend flags to the full set
105 # add frontend flags to the full set
106 flags.update(qt_flags)
106 flags.update(qt_flags)
107
107
108 # start with copy of front&backend aliases list
108 # start with copy of front&backend aliases list
109 aliases = dict(aliases)
109 aliases = dict(aliases)
110 qt_aliases = dict(
110 qt_aliases = dict(
111 style = 'IPythonWidget.syntax_style',
111 style = 'IPythonWidget.syntax_style',
112 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 stylesheet = 'IPythonQtConsoleApp.stylesheet',
113 colors = 'ZMQInteractiveShell.colors',
113 colors = 'ZMQInteractiveShell.colors',
114
114
115 editor = 'IPythonWidget.editor',
115 editor = 'IPythonWidget.editor',
116 paging = 'ConsoleWidget.paging',
116 paging = 'ConsoleWidget.paging',
117 )
117 )
118 # and app_aliases from the Console Mixin
118 # and app_aliases from the Console Mixin
119 qt_aliases.update(app_aliases)
119 qt_aliases.update(app_aliases)
120 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
120 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
121 # add frontend aliases to the full set
121 # add frontend aliases to the full set
122 aliases.update(qt_aliases)
122 aliases.update(qt_aliases)
123
123
124 # get flags&aliases into sets, and remove a couple that
124 # get flags&aliases into sets, and remove a couple that
125 # shouldn't be scrubbed from backend flags:
125 # shouldn't be scrubbed from backend flags:
126 qt_aliases = set(qt_aliases.keys())
126 qt_aliases = set(qt_aliases.keys())
127 qt_aliases.remove('colors')
127 qt_aliases.remove('colors')
128 qt_flags = set(qt_flags.keys())
128 qt_flags = set(qt_flags.keys())
129
129
130 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
131 # Classes
131 # Classes
132 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
133
133
134 #-----------------------------------------------------------------------------
134 #-----------------------------------------------------------------------------
135 # IPythonQtConsole
135 # IPythonQtConsole
136 #-----------------------------------------------------------------------------
136 #-----------------------------------------------------------------------------
137
137
138
138
139 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
139 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
140 name = 'ipython-qtconsole'
140 name = 'ipython-qtconsole'
141
141
142 description = """
142 description = """
143 The IPython QtConsole.
143 The IPython QtConsole.
144
144
145 This launches a Console-style application using Qt. It is not a full
145 This launches a Console-style application using Qt. It is not a full
146 console, in that launched terminal subprocesses will not be able to accept
146 console, in that launched terminal subprocesses will not be able to accept
147 input.
147 input.
148
148
149 The QtConsole supports various extra features beyond the Terminal IPython
149 The QtConsole supports various extra features beyond the Terminal IPython
150 shell, such as inline plotting with matplotlib, via:
150 shell, such as inline plotting with matplotlib, via:
151
151
152 ipython qtconsole --matplotlib=inline
152 ipython qtconsole --matplotlib=inline
153
153
154 as well as saving your session as HTML, and printing the output.
154 as well as saving your session as HTML, and printing the output.
155
155
156 """
156 """
157 examples = _examples
157 examples = _examples
158
158
159 classes = [IPythonWidget] + IPythonConsoleApp.classes
159 classes = [IPythonWidget] + IPythonConsoleApp.classes
160 flags = Dict(flags)
160 flags = Dict(flags)
161 aliases = Dict(aliases)
161 aliases = Dict(aliases)
162 frontend_flags = Any(qt_flags)
162 frontend_flags = Any(qt_flags)
163 frontend_aliases = Any(qt_aliases)
163 frontend_aliases = Any(qt_aliases)
164 kernel_client_class = QtKernelClient
164 kernel_client_class = QtKernelClient
165 kernel_manager_class = QtKernelManager
165 kernel_manager_class = QtKernelManager
166
166
167 stylesheet = Unicode('', config=True,
167 stylesheet = Unicode('', config=True,
168 help="path to a custom CSS stylesheet")
168 help="path to a custom CSS stylesheet")
169
169
170 hide_menubar = CBool(False, config=True,
170 hide_menubar = CBool(False, config=True,
171 help="Start the console window with the menu bar hidden.")
171 help="Start the console window with the menu bar hidden.")
172
172
173 maximize = CBool(False, config=True,
173 maximize = CBool(False, config=True,
174 help="Start the console window maximized.")
174 help="Start the console window maximized.")
175
175
176 plain = CBool(False, config=True,
176 plain = CBool(False, config=True,
177 help="Use a plaintext widget instead of rich text (plain can't print/save).")
177 help="Use a plaintext widget instead of rich text (plain can't print/save).")
178
178
179 def _plain_changed(self, name, old, new):
179 def _plain_changed(self, name, old, new):
180 kind = 'plain' if new else 'rich'
180 kind = 'plain' if new else 'rich'
181 self.config.ConsoleWidget.kind = kind
181 self.config.ConsoleWidget.kind = kind
182 if new:
182 if new:
183 self.widget_factory = IPythonWidget
183 self.widget_factory = IPythonWidget
184 else:
184 else:
185 self.widget_factory = RichIPythonWidget
185 self.widget_factory = RichIPythonWidget
186
186
187 # the factory for creating a widget
187 # the factory for creating a widget
188 widget_factory = Any(RichIPythonWidget)
188 widget_factory = Any(RichIPythonWidget)
189
189
190 def parse_command_line(self, argv=None):
190 def parse_command_line(self, argv=None):
191 super(IPythonQtConsoleApp, self).parse_command_line(argv)
191 super(IPythonQtConsoleApp, self).parse_command_line(argv)
192 self.build_kernel_argv(argv)
192 self.build_kernel_argv(argv)
193
193
194
194
195 def new_frontend_master(self):
195 def new_frontend_master(self):
196 """ Create and return new frontend attached to new kernel, launched on localhost.
196 """ Create and return new frontend attached to new kernel, launched on localhost.
197 """
197 """
198 kernel_manager = self.kernel_manager_class(
198 kernel_manager = self.kernel_manager_class(
199 connection_file=self._new_connection_file(),
199 connection_file=self._new_connection_file(),
200 parent=self,
200 parent=self,
201 autorestart=True,
201 autorestart=True,
202 )
202 )
203 # start the kernel
203 # start the kernel
204 kwargs = dict()
204 kwargs = dict()
205 kwargs['extra_arguments'] = self.kernel_argv
205 kwargs['extra_arguments'] = self.kernel_argv
206 kernel_manager.start_kernel(**kwargs)
206 kernel_manager.start_kernel(**kwargs)
207 kernel_manager.client_factory = self.kernel_client_class
207 kernel_manager.client_factory = self.kernel_client_class
208 kernel_client = kernel_manager.client()
208 kernel_client = kernel_manager.client()
209 kernel_client.start_channels(shell=True, iopub=True)
209 kernel_client.start_channels(shell=True, iopub=True)
210 widget = self.widget_factory(config=self.config,
210 widget = self.widget_factory(config=self.config,
211 local_kernel=True)
211 local_kernel=True)
212 self.init_colors(widget)
212 self.init_colors(widget)
213 widget.kernel_manager = kernel_manager
213 widget.kernel_manager = kernel_manager
214 widget.kernel_client = kernel_client
214 widget.kernel_client = kernel_client
215 widget._existing = False
215 widget._existing = False
216 widget._may_close = True
216 widget._may_close = True
217 widget._confirm_exit = self.confirm_exit
217 widget._confirm_exit = self.confirm_exit
218 return widget
218 return widget
219
219
220 def new_frontend_slave(self, current_widget):
220 def new_frontend_slave(self, current_widget):
221 """Create and return a new frontend attached to an existing kernel.
221 """Create and return a new frontend attached to an existing kernel.
222
222
223 Parameters
223 Parameters
224 ----------
224 ----------
225 current_widget : IPythonWidget
225 current_widget : IPythonWidget
226 The IPythonWidget whose kernel this frontend is to share
226 The IPythonWidget whose kernel this frontend is to share
227 """
227 """
228 kernel_client = self.kernel_client_class(
228 kernel_client = self.kernel_client_class(
229 connection_file=current_widget.kernel_client.connection_file,
229 connection_file=current_widget.kernel_client.connection_file,
230 config = self.config,
230 config = self.config,
231 )
231 )
232 kernel_client.load_connection_file()
232 kernel_client.load_connection_file()
233 kernel_client.start_channels()
233 kernel_client.start_channels()
234 widget = self.widget_factory(config=self.config,
234 widget = self.widget_factory(config=self.config,
235 local_kernel=False)
235 local_kernel=False)
236 self.init_colors(widget)
236 self.init_colors(widget)
237 widget._existing = True
237 widget._existing = True
238 widget._may_close = False
238 widget._may_close = False
239 widget._confirm_exit = False
239 widget._confirm_exit = False
240 widget.kernel_client = kernel_client
240 widget.kernel_client = kernel_client
241 widget.kernel_manager = current_widget.kernel_manager
241 widget.kernel_manager = current_widget.kernel_manager
242 return widget
242 return widget
243
243
244 def init_qt_app(self):
244 def init_qt_app(self):
245 # separate from qt_elements, because it must run first
245 # separate from qt_elements, because it must run first
246 self.app = QtGui.QApplication([])
246 self.app = QtGui.QApplication([])
247
247
248 def init_qt_elements(self):
248 def init_qt_elements(self):
249 # Create the widget.
249 # Create the widget.
250
250
251 base_path = os.path.abspath(os.path.dirname(__file__))
251 base_path = os.path.abspath(os.path.dirname(__file__))
252 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
252 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
253 self.app.icon = QtGui.QIcon(icon_path)
253 self.app.icon = QtGui.QIcon(icon_path)
254 QtGui.QApplication.setWindowIcon(self.app.icon)
254 QtGui.QApplication.setWindowIcon(self.app.icon)
255
255
256 ip = self.ip
256 ip = self.ip
257 local_kernel = (not self.existing) or is_local_ip(ip)
257 local_kernel = (not self.existing) or is_local_ip(ip)
258 self.widget = self.widget_factory(config=self.config,
258 self.widget = self.widget_factory(config=self.config,
259 local_kernel=local_kernel)
259 local_kernel=local_kernel)
260 self.init_colors(self.widget)
260 self.init_colors(self.widget)
261 self.widget._existing = self.existing
261 self.widget._existing = self.existing
262 self.widget._may_close = not self.existing
262 self.widget._may_close = not self.existing
263 self.widget._confirm_exit = self.confirm_exit
263 self.widget._confirm_exit = self.confirm_exit
264
264
265 self.widget.kernel_manager = self.kernel_manager
265 self.widget.kernel_manager = self.kernel_manager
266 self.widget.kernel_client = self.kernel_client
266 self.widget.kernel_client = self.kernel_client
267 self.window = MainWindow(self.app,
267 self.window = MainWindow(self.app,
268 confirm_exit=self.confirm_exit,
268 confirm_exit=self.confirm_exit,
269 new_frontend_factory=self.new_frontend_master,
269 new_frontend_factory=self.new_frontend_master,
270 slave_frontend_factory=self.new_frontend_slave,
270 slave_frontend_factory=self.new_frontend_slave,
271 )
271 )
272 self.window.log = self.log
272 self.window.log = self.log
273 self.window.add_tab_with_frontend(self.widget)
273 self.window.add_tab_with_frontend(self.widget)
274 self.window.init_menu_bar()
274 self.window.init_menu_bar()
275 self.window.init_magic_helper()
275
276
276 # Ignore on OSX, where there is always a menu bar
277 # Ignore on OSX, where there is always a menu bar
277 if sys.platform != 'darwin' and self.hide_menubar:
278 if sys.platform != 'darwin' and self.hide_menubar:
278 self.window.menuBar().setVisible(False)
279 self.window.menuBar().setVisible(False)
279
280
280 self.window.setWindowTitle('IPython')
281 self.window.setWindowTitle('IPython')
281
282
282 def init_colors(self, widget):
283 def init_colors(self, widget):
283 """Configure the coloring of the widget"""
284 """Configure the coloring of the widget"""
284 # Note: This will be dramatically simplified when colors
285 # Note: This will be dramatically simplified when colors
285 # are removed from the backend.
286 # are removed from the backend.
286
287
287 # parse the colors arg down to current known labels
288 # parse the colors arg down to current known labels
288 cfg = self.config
289 cfg = self.config
289 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
290 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
290 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
291 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
291 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
292 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
292
293
293 # find the value for colors:
294 # find the value for colors:
294 if colors:
295 if colors:
295 colors=colors.lower()
296 colors=colors.lower()
296 if colors in ('lightbg', 'light'):
297 if colors in ('lightbg', 'light'):
297 colors='lightbg'
298 colors='lightbg'
298 elif colors in ('dark', 'linux'):
299 elif colors in ('dark', 'linux'):
299 colors='linux'
300 colors='linux'
300 else:
301 else:
301 colors='nocolor'
302 colors='nocolor'
302 elif style:
303 elif style:
303 if style=='bw':
304 if style=='bw':
304 colors='nocolor'
305 colors='nocolor'
305 elif styles.dark_style(style):
306 elif styles.dark_style(style):
306 colors='linux'
307 colors='linux'
307 else:
308 else:
308 colors='lightbg'
309 colors='lightbg'
309 else:
310 else:
310 colors=None
311 colors=None
311
312
312 # Configure the style
313 # Configure the style
313 if style:
314 if style:
314 widget.style_sheet = styles.sheet_from_template(style, colors)
315 widget.style_sheet = styles.sheet_from_template(style, colors)
315 widget.syntax_style = style
316 widget.syntax_style = style
316 widget._syntax_style_changed()
317 widget._syntax_style_changed()
317 widget._style_sheet_changed()
318 widget._style_sheet_changed()
318 elif colors:
319 elif colors:
319 # use a default dark/light/bw style
320 # use a default dark/light/bw style
320 widget.set_default_style(colors=colors)
321 widget.set_default_style(colors=colors)
321
322
322 if self.stylesheet:
323 if self.stylesheet:
323 # we got an explicit stylesheet
324 # we got an explicit stylesheet
324 if os.path.isfile(self.stylesheet):
325 if os.path.isfile(self.stylesheet):
325 with open(self.stylesheet) as f:
326 with open(self.stylesheet) as f:
326 sheet = f.read()
327 sheet = f.read()
327 else:
328 else:
328 raise IOError("Stylesheet %r not found." % self.stylesheet)
329 raise IOError("Stylesheet %r not found." % self.stylesheet)
329 if sheet:
330 if sheet:
330 widget.style_sheet = sheet
331 widget.style_sheet = sheet
331 widget._style_sheet_changed()
332 widget._style_sheet_changed()
332
333
333
334
334 def init_signal(self):
335 def init_signal(self):
335 """allow clean shutdown on sigint"""
336 """allow clean shutdown on sigint"""
336 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
337 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
337 # need a timer, so that QApplication doesn't block until a real
338 # need a timer, so that QApplication doesn't block until a real
338 # Qt event fires (can require mouse movement)
339 # Qt event fires (can require mouse movement)
339 # timer trick from http://stackoverflow.com/q/4938723/938949
340 # timer trick from http://stackoverflow.com/q/4938723/938949
340 timer = QtCore.QTimer()
341 timer = QtCore.QTimer()
341 # Let the interpreter run each 200 ms:
342 # Let the interpreter run each 200 ms:
342 timer.timeout.connect(lambda: None)
343 timer.timeout.connect(lambda: None)
343 timer.start(200)
344 timer.start(200)
344 # hold onto ref, so the timer doesn't get cleaned up
345 # hold onto ref, so the timer doesn't get cleaned up
345 self._sigint_timer = timer
346 self._sigint_timer = timer
346
347
347 @catch_config_error
348 @catch_config_error
348 def initialize(self, argv=None):
349 def initialize(self, argv=None):
349 self.init_qt_app()
350 self.init_qt_app()
350 super(IPythonQtConsoleApp, self).initialize(argv)
351 super(IPythonQtConsoleApp, self).initialize(argv)
351 IPythonConsoleApp.initialize(self,argv)
352 IPythonConsoleApp.initialize(self,argv)
352 self.init_qt_elements()
353 self.init_qt_elements()
353 self.init_signal()
354 self.init_signal()
354
355
355 def start(self):
356 def start(self):
356
357
357 # draw the window
358 # draw the window
358 if self.maximize:
359 if self.maximize:
359 self.window.showMaximized()
360 self.window.showMaximized()
360 else:
361 else:
361 self.window.show()
362 self.window.show()
362 self.window.raise_()
363 self.window.raise_()
363
364
364 # Start the application main loop.
365 # Start the application main loop.
365 self.app.exec_()
366 self.app.exec_()
366
367
367 #-----------------------------------------------------------------------------
368 #-----------------------------------------------------------------------------
368 # Main entry point
369 # Main entry point
369 #-----------------------------------------------------------------------------
370 #-----------------------------------------------------------------------------
370
371
371 def main():
372 def main():
372 app = IPythonQtConsoleApp()
373 app = IPythonQtConsoleApp()
373 app.initialize()
374 app.initialize()
374 app.start()
375 app.start()
375
376
376
377
377 if __name__ == '__main__':
378 if __name__ == '__main__':
378 main()
379 main()
General Comments 0
You need to be logged in to leave comments. Login now