##// END OF EJS Templates
Generate "All magics..." menu live...
Matthias BUSSONNIER -
Show More
@@ -1,667 +1,708 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7
8
8 # System library imports
9 # System library imports
9 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
10 from IPython.external import qt
11 from IPython.external import qt
11 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
12
13
13 # Local imports
14 # Local imports
14 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
15 from IPython.core.oinspect import call_tip
16 from IPython.core.oinspect import call_tip
16 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
17 from IPython.utils.traitlets import Bool, Instance, Unicode
18 from IPython.utils.traitlets import Bool, Instance, Unicode
18 from bracket_matcher import BracketMatcher
19 from bracket_matcher import BracketMatcher
19 from call_tip_widget import CallTipWidget
20 from call_tip_widget import CallTipWidget
20 from completion_lexer import CompletionLexer
21 from completion_lexer import CompletionLexer
21 from history_console_widget import HistoryConsoleWidget
22 from history_console_widget import HistoryConsoleWidget
22 from pygments_highlighter import PygmentsHighlighter
23 from pygments_highlighter import PygmentsHighlighter
23
24
24
25
25 class FrontendHighlighter(PygmentsHighlighter):
26 class FrontendHighlighter(PygmentsHighlighter):
26 """ A PygmentsHighlighter that understands and ignores prompts.
27 """ A PygmentsHighlighter that understands and ignores prompts.
27 """
28 """
28
29
29 def __init__(self, frontend):
30 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
32 self._current_offset = 0
32 self._frontend = frontend
33 self._frontend = frontend
33 self.highlighting_on = False
34 self.highlighting_on = False
34
35
35 def highlightBlock(self, string):
36 def highlightBlock(self, string):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
38 """
38 if not self.highlighting_on:
39 if not self.highlighting_on:
39 return
40 return
40
41
41 # The input to this function is a unicode string that may contain
42 # The input to this function is a unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
44 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
45 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
46 string = self._frontend._get_block_plain_text(current_block)
46
47
47 # Decide whether to check for the regular or continuation prompt.
48 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
49 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
50 prompt = self._frontend._prompt
50 else:
51 else:
51 prompt = self._frontend._continuation_prompt
52 prompt = self._frontend._continuation_prompt
52
53
53 # Only highlight if we can identify a prompt, but make sure not to
54 # Only highlight if we can identify a prompt, but make sure not to
54 # highlight the prompt.
55 # highlight the prompt.
55 if string.startswith(prompt):
56 if string.startswith(prompt):
56 self._current_offset = len(prompt)
57 self._current_offset = len(prompt)
57 string = string[len(prompt):]
58 string = string[len(prompt):]
58 super(FrontendHighlighter, self).highlightBlock(string)
59 super(FrontendHighlighter, self).highlightBlock(string)
59
60
60 def rehighlightBlock(self, block):
61 def rehighlightBlock(self, block):
61 """ Reimplemented to temporarily enable highlighting if disabled.
62 """ Reimplemented to temporarily enable highlighting if disabled.
62 """
63 """
63 old = self.highlighting_on
64 old = self.highlighting_on
64 self.highlighting_on = True
65 self.highlighting_on = True
65 super(FrontendHighlighter, self).rehighlightBlock(block)
66 super(FrontendHighlighter, self).rehighlightBlock(block)
66 self.highlighting_on = old
67 self.highlighting_on = old
67
68
68 def setFormat(self, start, count, format):
69 def setFormat(self, start, count, format):
69 """ Reimplemented to highlight selectively.
70 """ Reimplemented to highlight selectively.
70 """
71 """
71 start += self._current_offset
72 start += self._current_offset
72 super(FrontendHighlighter, self).setFormat(start, count, format)
73 super(FrontendHighlighter, self).setFormat(start, count, format)
73
74
74
75
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 """ A Qt frontend for a generic Python kernel.
77 """ A Qt frontend for a generic Python kernel.
77 """
78 """
78
79
79 # The text to show when the kernel is (re)started.
80 # The text to show when the kernel is (re)started.
80 banner = Unicode()
81 banner = Unicode()
81
82
82 # An option and corresponding signal for overriding the default kernel
83 # An option and corresponding signal for overriding the default kernel
83 # interrupt behavior.
84 # interrupt behavior.
84 custom_interrupt = Bool(False)
85 custom_interrupt = Bool(False)
85 custom_interrupt_requested = QtCore.Signal()
86 custom_interrupt_requested = QtCore.Signal()
86
87
87 # An option and corresponding signals for overriding the default kernel
88 # An option and corresponding signals for overriding the default kernel
88 # restart behavior.
89 # restart behavior.
89 custom_restart = Bool(False)
90 custom_restart = Bool(False)
90 custom_restart_kernel_died = QtCore.Signal(float)
91 custom_restart_kernel_died = QtCore.Signal(float)
91 custom_restart_requested = QtCore.Signal()
92 custom_restart_requested = QtCore.Signal()
92
93
93 # Whether to automatically show calltips on open-parentheses.
94 # Whether to automatically show calltips on open-parentheses.
94 enable_calltips = Bool(True, config=True,
95 enable_calltips = Bool(True, config=True,
95 help="Whether to draw information calltips on open-parentheses.")
96 help="Whether to draw information calltips on open-parentheses.")
96
97
97 # Emitted when a user visible 'execute_request' has been submitted to the
98 # Emitted when a user visible 'execute_request' has been submitted to the
98 # kernel from the FrontendWidget. Contains the code to be executed.
99 # kernel from the FrontendWidget. Contains the code to be executed.
99 executing = QtCore.Signal(object)
100 executing = QtCore.Signal(object)
100
101
101 # Emitted when a user-visible 'execute_reply' has been received from the
102 # Emitted when a user-visible 'execute_reply' has been received from the
102 # kernel and processed by the FrontendWidget. Contains the response message.
103 # kernel and processed by the FrontendWidget. Contains the response message.
103 executed = QtCore.Signal(object)
104 executed = QtCore.Signal(object)
104
105
105 # Emitted when an exit request has been received from the kernel.
106 # Emitted when an exit request has been received from the kernel.
106 exit_requested = QtCore.Signal(object)
107 exit_requested = QtCore.Signal(object)
107
108
108 # Protected class variables.
109 # Protected class variables.
109 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
110 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
110 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
111 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
111 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
112 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
112 _input_splitter_class = InputSplitter
113 _input_splitter_class = InputSplitter
113 _local_kernel = False
114 _local_kernel = False
114 _highlighter = Instance(FrontendHighlighter)
115 _highlighter = Instance(FrontendHighlighter)
115
116
116 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
117 # 'object' interface
118 # 'object' interface
118 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
119
120
120 def __init__(self, *args, **kw):
121 def __init__(self, *args, **kw):
121 super(FrontendWidget, self).__init__(*args, **kw)
122 super(FrontendWidget, self).__init__(*args, **kw)
122 # FIXME: remove this when PySide min version is updated past 1.0.7
123 # FIXME: remove this when PySide min version is updated past 1.0.7
123 # forcefully disable calltips if PySide is < 1.0.7, because they crash
124 # forcefully disable calltips if PySide is < 1.0.7, because they crash
124 if qt.QT_API == qt.QT_API_PYSIDE:
125 if qt.QT_API == qt.QT_API_PYSIDE:
125 import PySide
126 import PySide
126 if PySide.__version_info__ < (1,0,7):
127 if PySide.__version_info__ < (1,0,7):
127 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
128 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
128 self.enable_calltips = False
129 self.enable_calltips = False
129
130
130 # FrontendWidget protected variables.
131 # FrontendWidget protected variables.
131 self._bracket_matcher = BracketMatcher(self._control)
132 self._bracket_matcher = BracketMatcher(self._control)
132 self._call_tip_widget = CallTipWidget(self._control)
133 self._call_tip_widget = CallTipWidget(self._control)
133 self._completion_lexer = CompletionLexer(PythonLexer())
134 self._completion_lexer = CompletionLexer(PythonLexer())
134 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
135 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
135 self._hidden = False
136 self._hidden = False
136 self._highlighter = FrontendHighlighter(self)
137 self._highlighter = FrontendHighlighter(self)
137 self._input_splitter = self._input_splitter_class(input_mode='cell')
138 self._input_splitter = self._input_splitter_class(input_mode='cell')
138 self._kernel_manager = None
139 self._kernel_manager = None
139 self._request_info = {}
140 self._request_info = {}
141 self._callback_dict=dict([])
140
142
141 # Configure the ConsoleWidget.
143 # Configure the ConsoleWidget.
142 self.tab_width = 4
144 self.tab_width = 4
143 self._set_continuation_prompt('... ')
145 self._set_continuation_prompt('... ')
144
146
145 # Configure the CallTipWidget.
147 # Configure the CallTipWidget.
146 self._call_tip_widget.setFont(self.font)
148 self._call_tip_widget.setFont(self.font)
147 self.font_changed.connect(self._call_tip_widget.setFont)
149 self.font_changed.connect(self._call_tip_widget.setFont)
148
150
149 # Configure actions.
151 # Configure actions.
150 action = self._copy_raw_action
152 action = self._copy_raw_action
151 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
153 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
152 action.setEnabled(False)
154 action.setEnabled(False)
153 action.setShortcut(QtGui.QKeySequence(key))
155 action.setShortcut(QtGui.QKeySequence(key))
154 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
156 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
155 action.triggered.connect(self.copy_raw)
157 action.triggered.connect(self.copy_raw)
156 self.copy_available.connect(action.setEnabled)
158 self.copy_available.connect(action.setEnabled)
157 self.addAction(action)
159 self.addAction(action)
158
160
159 # Connect signal handlers.
161 # Connect signal handlers.
160 document = self._control.document()
162 document = self._control.document()
161 document.contentsChange.connect(self._document_contents_change)
163 document.contentsChange.connect(self._document_contents_change)
162
164
163 # Set flag for whether we are connected via localhost.
165 # Set flag for whether we are connected via localhost.
164 self._local_kernel = kw.get('local_kernel',
166 self._local_kernel = kw.get('local_kernel',
165 FrontendWidget._local_kernel)
167 FrontendWidget._local_kernel)
166
168
167 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
168 # 'ConsoleWidget' public interface
170 # 'ConsoleWidget' public interface
169 #---------------------------------------------------------------------------
171 #---------------------------------------------------------------------------
170
172
171 def copy(self):
173 def copy(self):
172 """ Copy the currently selected text to the clipboard, removing prompts.
174 """ Copy the currently selected text to the clipboard, removing prompts.
173 """
175 """
174 text = self._control.textCursor().selection().toPlainText()
176 text = self._control.textCursor().selection().toPlainText()
175 if text:
177 if text:
176 lines = map(transform_classic_prompt, text.splitlines())
178 lines = map(transform_classic_prompt, text.splitlines())
177 text = '\n'.join(lines)
179 text = '\n'.join(lines)
178 QtGui.QApplication.clipboard().setText(text)
180 QtGui.QApplication.clipboard().setText(text)
179
181
180 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
181 # 'ConsoleWidget' abstract interface
183 # 'ConsoleWidget' abstract interface
182 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
183
185
184 def _is_complete(self, source, interactive):
186 def _is_complete(self, source, interactive):
185 """ Returns whether 'source' can be completely processed and a new
187 """ Returns whether 'source' can be completely processed and a new
186 prompt created. When triggered by an Enter/Return key press,
188 prompt created. When triggered by an Enter/Return key press,
187 'interactive' is True; otherwise, it is False.
189 'interactive' is True; otherwise, it is False.
188 """
190 """
189 complete = self._input_splitter.push(source)
191 complete = self._input_splitter.push(source)
190 if interactive:
192 if interactive:
191 complete = not self._input_splitter.push_accepts_more()
193 complete = not self._input_splitter.push_accepts_more()
192 return complete
194 return complete
193
195
194 def _execute(self, source, hidden):
196 def _execute(self, source, hidden):
195 """ Execute 'source'. If 'hidden', do not show any output.
197 """ Execute 'source'. If 'hidden', do not show any output.
196
198
197 See parent class :meth:`execute` docstring for full details.
199 See parent class :meth:`execute` docstring for full details.
198 """
200 """
199 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
201 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
200 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
202 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
201 self._hidden = hidden
203 self._hidden = hidden
202 if not hidden:
204 if not hidden:
203 self.executing.emit(source)
205 self.executing.emit(source)
204
206
205 def _prompt_started_hook(self):
207 def _prompt_started_hook(self):
206 """ Called immediately after a new prompt is displayed.
208 """ Called immediately after a new prompt is displayed.
207 """
209 """
208 if not self._reading:
210 if not self._reading:
209 self._highlighter.highlighting_on = True
211 self._highlighter.highlighting_on = True
210
212
211 def _prompt_finished_hook(self):
213 def _prompt_finished_hook(self):
212 """ Called immediately after a prompt is finished, i.e. when some input
214 """ Called immediately after a prompt is finished, i.e. when some input
213 will be processed and a new prompt displayed.
215 will be processed and a new prompt displayed.
214 """
216 """
215 # Flush all state from the input splitter so the next round of
217 # Flush all state from the input splitter so the next round of
216 # reading input starts with a clean buffer.
218 # reading input starts with a clean buffer.
217 self._input_splitter.reset()
219 self._input_splitter.reset()
218
220
219 if not self._reading:
221 if not self._reading:
220 self._highlighter.highlighting_on = False
222 self._highlighter.highlighting_on = False
221
223
222 def _tab_pressed(self):
224 def _tab_pressed(self):
223 """ Called when the tab key is pressed. Returns whether to continue
225 """ Called when the tab key is pressed. Returns whether to continue
224 processing the event.
226 processing the event.
225 """
227 """
226 # Perform tab completion if:
228 # Perform tab completion if:
227 # 1) The cursor is in the input buffer.
229 # 1) The cursor is in the input buffer.
228 # 2) There is a non-whitespace character before the cursor.
230 # 2) There is a non-whitespace character before the cursor.
229 text = self._get_input_buffer_cursor_line()
231 text = self._get_input_buffer_cursor_line()
230 if text is None:
232 if text is None:
231 return False
233 return False
232 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
234 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
233 if complete:
235 if complete:
234 self._complete()
236 self._complete()
235 return not complete
237 return not complete
236
238
237 #---------------------------------------------------------------------------
239 #---------------------------------------------------------------------------
238 # 'ConsoleWidget' protected interface
240 # 'ConsoleWidget' protected interface
239 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
240
242
241 def _context_menu_make(self, pos):
243 def _context_menu_make(self, pos):
242 """ Reimplemented to add an action for raw copy.
244 """ Reimplemented to add an action for raw copy.
243 """
245 """
244 menu = super(FrontendWidget, self)._context_menu_make(pos)
246 menu = super(FrontendWidget, self)._context_menu_make(pos)
245 for before_action in menu.actions():
247 for before_action in menu.actions():
246 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
248 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
247 QtGui.QKeySequence.ExactMatch:
249 QtGui.QKeySequence.ExactMatch:
248 menu.insertAction(before_action, self._copy_raw_action)
250 menu.insertAction(before_action, self._copy_raw_action)
249 break
251 break
250 return menu
252 return menu
251
253
252 def request_interrupt_kernel(self):
254 def request_interrupt_kernel(self):
253 if self._executing:
255 if self._executing:
254 self.interrupt_kernel()
256 self.interrupt_kernel()
255
257
256 def request_restart_kernel(self):
258 def request_restart_kernel(self):
257 message = 'Are you sure you want to restart the kernel?'
259 message = 'Are you sure you want to restart the kernel?'
258 self.restart_kernel(message, now=False)
260 self.restart_kernel(message, now=False)
259
261
260 def _event_filter_console_keypress(self, event):
262 def _event_filter_console_keypress(self, event):
261 """ Reimplemented for execution interruption and smart backspace.
263 """ Reimplemented for execution interruption and smart backspace.
262 """
264 """
263 key = event.key()
265 key = event.key()
264 if self._control_key_down(event.modifiers(), include_command=False):
266 if self._control_key_down(event.modifiers(), include_command=False):
265
267
266 if key == QtCore.Qt.Key_C and self._executing:
268 if key == QtCore.Qt.Key_C and self._executing:
267 self.request_interrupt_kernel()
269 self.request_interrupt_kernel()
268 return True
270 return True
269
271
270 elif key == QtCore.Qt.Key_Period:
272 elif key == QtCore.Qt.Key_Period:
271 self.request_restart_kernel()
273 self.request_restart_kernel()
272 return True
274 return True
273
275
274 elif not event.modifiers() & QtCore.Qt.AltModifier:
276 elif not event.modifiers() & QtCore.Qt.AltModifier:
275
277
276 # Smart backspace: remove four characters in one backspace if:
278 # Smart backspace: remove four characters in one backspace if:
277 # 1) everything left of the cursor is whitespace
279 # 1) everything left of the cursor is whitespace
278 # 2) the four characters immediately left of the cursor are spaces
280 # 2) the four characters immediately left of the cursor are spaces
279 if key == QtCore.Qt.Key_Backspace:
281 if key == QtCore.Qt.Key_Backspace:
280 col = self._get_input_buffer_cursor_column()
282 col = self._get_input_buffer_cursor_column()
281 cursor = self._control.textCursor()
283 cursor = self._control.textCursor()
282 if col > 3 and not cursor.hasSelection():
284 if col > 3 and not cursor.hasSelection():
283 text = self._get_input_buffer_cursor_line()[:col]
285 text = self._get_input_buffer_cursor_line()[:col]
284 if text.endswith(' ') and not text.strip():
286 if text.endswith(' ') and not text.strip():
285 cursor.movePosition(QtGui.QTextCursor.Left,
287 cursor.movePosition(QtGui.QTextCursor.Left,
286 QtGui.QTextCursor.KeepAnchor, 4)
288 QtGui.QTextCursor.KeepAnchor, 4)
287 cursor.removeSelectedText()
289 cursor.removeSelectedText()
288 return True
290 return True
289
291
290 return super(FrontendWidget, self)._event_filter_console_keypress(event)
292 return super(FrontendWidget, self)._event_filter_console_keypress(event)
291
293
292 def _insert_continuation_prompt(self, cursor):
294 def _insert_continuation_prompt(self, cursor):
293 """ Reimplemented for auto-indentation.
295 """ Reimplemented for auto-indentation.
294 """
296 """
295 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
297 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
296 cursor.insertText(' ' * self._input_splitter.indent_spaces)
298 cursor.insertText(' ' * self._input_splitter.indent_spaces)
297
299
298 #---------------------------------------------------------------------------
300 #---------------------------------------------------------------------------
299 # 'BaseFrontendMixin' abstract interface
301 # 'BaseFrontendMixin' abstract interface
300 #---------------------------------------------------------------------------
302 #---------------------------------------------------------------------------
301
303
302 def _handle_complete_reply(self, rep):
304 def _handle_complete_reply(self, rep):
303 """ Handle replies for tab completion.
305 """ Handle replies for tab completion.
304 """
306 """
305 self.log.debug("complete: %s", rep.get('content', ''))
307 self.log.debug("complete: %s", rep.get('content', ''))
306 cursor = self._get_cursor()
308 cursor = self._get_cursor()
307 info = self._request_info.get('complete')
309 info = self._request_info.get('complete')
308 if info and info.id == rep['parent_header']['msg_id'] and \
310 if info and info.id == rep['parent_header']['msg_id'] and \
309 info.pos == cursor.position():
311 info.pos == cursor.position():
310 text = '.'.join(self._get_context())
312 text = '.'.join(self._get_context())
311 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
313 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 self._complete_with_items(cursor, rep['content']['matches'])
314 self._complete_with_items(cursor, rep['content']['matches'])
313
315
316 def _silent_exec_callback(self,expr,callback):
317 """ silently execute a function in the kernel and send the reply to the callback
318
319 expr should be a valid string to be executed by the kernel.
320
321 callback a function accepting one argument (str)
322
323 the callback is called with the 'repr()' of the result as first argument.
324 to get the object, do 'eval()' on the passed value.
325 """
326
327 # generate uuid, which would be used as a indication of wether or not
328 # the unique request originate from here (can use msg id ?)
329 local_uuid = str(uuid.uuid1())
330 msg_id = self.kernel_manager.shell_channel.execute('',
331 silent=True,
332 user_expressions={ local_uuid:expr,
333 }
334 )
335 self._callback_dict[local_uuid]=callback
336 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
337
338 def _handle_exec_callback(self,msg):
339 """ Called when _silent_exec_callback message comme back from the kernel.
340
341 Strip the message comming back from the kernel and send it to the
342 corresponding callback function.
343 """
344 cnt=msg['content']
345 ue=cnt['user_expressions']
346 for i in ue.keys():
347 if i in self._callback_dict.keys():
348 f= self._callback_dict[i]
349 f(ue[i])
350 self._callback_dict.pop(i)
351
314 def _handle_execute_reply(self, msg):
352 def _handle_execute_reply(self, msg):
315 """ Handles replies for code execution.
353 """ Handles replies for code execution.
316 """
354 """
317 self.log.debug("execute: %s", msg.get('content', ''))
355 self.log.debug("execute: %s", msg.get('content', ''))
318 info = self._request_info.get('execute')
356 info = self._request_info.get('execute')
319 # unset reading flag, because if execute finished, raw_input can't
357 # unset reading flag, because if execute finished, raw_input can't
320 # still be pending.
358 # still be pending.
321 self._reading = False
359 self._reading = False
322 if info and info.id == msg['parent_header']['msg_id'] and \
360 if info and info.id == msg['parent_header']['msg_id'] and \
323 info.kind == 'user' and not self._hidden:
361 info.kind == 'user' and not self._hidden:
324 # Make sure that all output from the SUB channel has been processed
362 # Make sure that all output from the SUB channel has been processed
325 # before writing a new prompt.
363 # before writing a new prompt.
326 self.kernel_manager.sub_channel.flush()
364 self.kernel_manager.sub_channel.flush()
327
365
328 # Reset the ANSI style information to prevent bad text in stdout
366 # Reset the ANSI style information to prevent bad text in stdout
329 # from messing up our colors. We're not a true terminal so we're
367 # from messing up our colors. We're not a true terminal so we're
330 # allowed to do this.
368 # allowed to do this.
331 if self.ansi_codes:
369 if self.ansi_codes:
332 self._ansi_processor.reset_sgr()
370 self._ansi_processor.reset_sgr()
333
371
334 content = msg['content']
372 content = msg['content']
335 status = content['status']
373 status = content['status']
336 if status == 'ok':
374 if status == 'ok':
337 self._process_execute_ok(msg)
375 self._process_execute_ok(msg)
338 elif status == 'error':
376 elif status == 'error':
339 self._process_execute_error(msg)
377 self._process_execute_error(msg)
340 elif status == 'aborted':
378 elif status == 'aborted':
341 self._process_execute_abort(msg)
379 self._process_execute_abort(msg)
342
380
343 self._show_interpreter_prompt_for_reply(msg)
381 self._show_interpreter_prompt_for_reply(msg)
344 self.executed.emit(msg)
382 self.executed.emit(msg)
383 elif info and info.id == msg['parent_header']['msg_id'] and \
384 info.kind == 'silent_exec_callback' and not self._hidden:
385 self._handle_exec_callback(msg)
345 else:
386 else:
346 super(FrontendWidget, self)._handle_execute_reply(msg)
387 super(FrontendWidget, self)._handle_execute_reply(msg)
347
388
348 def _handle_input_request(self, msg):
389 def _handle_input_request(self, msg):
349 """ Handle requests for raw_input.
390 """ Handle requests for raw_input.
350 """
391 """
351 self.log.debug("input: %s", msg.get('content', ''))
392 self.log.debug("input: %s", msg.get('content', ''))
352 if self._hidden:
393 if self._hidden:
353 raise RuntimeError('Request for raw input during hidden execution.')
394 raise RuntimeError('Request for raw input during hidden execution.')
354
395
355 # Make sure that all output from the SUB channel has been processed
396 # Make sure that all output from the SUB channel has been processed
356 # before entering readline mode.
397 # before entering readline mode.
357 self.kernel_manager.sub_channel.flush()
398 self.kernel_manager.sub_channel.flush()
358
399
359 def callback(line):
400 def callback(line):
360 self.kernel_manager.stdin_channel.input(line)
401 self.kernel_manager.stdin_channel.input(line)
361 if self._reading:
402 if self._reading:
362 self.log.debug("Got second input request, assuming first was interrupted.")
403 self.log.debug("Got second input request, assuming first was interrupted.")
363 self._reading = False
404 self._reading = False
364 self._readline(msg['content']['prompt'], callback=callback)
405 self._readline(msg['content']['prompt'], callback=callback)
365
406
366 def _handle_kernel_died(self, since_last_heartbeat):
407 def _handle_kernel_died(self, since_last_heartbeat):
367 """ Handle the kernel's death by asking if the user wants to restart.
408 """ Handle the kernel's death by asking if the user wants to restart.
368 """
409 """
369 self.log.debug("kernel died: %s", since_last_heartbeat)
410 self.log.debug("kernel died: %s", since_last_heartbeat)
370 if self.custom_restart:
411 if self.custom_restart:
371 self.custom_restart_kernel_died.emit(since_last_heartbeat)
412 self.custom_restart_kernel_died.emit(since_last_heartbeat)
372 else:
413 else:
373 message = 'The kernel heartbeat has been inactive for %.2f ' \
414 message = 'The kernel heartbeat has been inactive for %.2f ' \
374 'seconds. Do you want to restart the kernel? You may ' \
415 'seconds. Do you want to restart the kernel? You may ' \
375 'first want to check the network connection.' % \
416 'first want to check the network connection.' % \
376 since_last_heartbeat
417 since_last_heartbeat
377 self.restart_kernel(message, now=True)
418 self.restart_kernel(message, now=True)
378
419
379 def _handle_object_info_reply(self, rep):
420 def _handle_object_info_reply(self, rep):
380 """ Handle replies for call tips.
421 """ Handle replies for call tips.
381 """
422 """
382 self.log.debug("oinfo: %s", rep.get('content', ''))
423 self.log.debug("oinfo: %s", rep.get('content', ''))
383 cursor = self._get_cursor()
424 cursor = self._get_cursor()
384 info = self._request_info.get('call_tip')
425 info = self._request_info.get('call_tip')
385 if info and info.id == rep['parent_header']['msg_id'] and \
426 if info and info.id == rep['parent_header']['msg_id'] and \
386 info.pos == cursor.position():
427 info.pos == cursor.position():
387 # Get the information for a call tip. For now we format the call
428 # Get the information for a call tip. For now we format the call
388 # line as string, later we can pass False to format_call and
429 # line as string, later we can pass False to format_call and
389 # syntax-highlight it ourselves for nicer formatting in the
430 # syntax-highlight it ourselves for nicer formatting in the
390 # calltip.
431 # calltip.
391 content = rep['content']
432 content = rep['content']
392 # if this is from pykernel, 'docstring' will be the only key
433 # if this is from pykernel, 'docstring' will be the only key
393 if content.get('ismagic', False):
434 if content.get('ismagic', False):
394 # Don't generate a call-tip for magics. Ideally, we should
435 # Don't generate a call-tip for magics. Ideally, we should
395 # generate a tooltip, but not on ( like we do for actual
436 # generate a tooltip, but not on ( like we do for actual
396 # callables.
437 # callables.
397 call_info, doc = None, None
438 call_info, doc = None, None
398 else:
439 else:
399 call_info, doc = call_tip(content, format_call=True)
440 call_info, doc = call_tip(content, format_call=True)
400 if call_info or doc:
441 if call_info or doc:
401 self._call_tip_widget.show_call_info(call_info, doc)
442 self._call_tip_widget.show_call_info(call_info, doc)
402
443
403 def _handle_pyout(self, msg):
444 def _handle_pyout(self, msg):
404 """ Handle display hook output.
445 """ Handle display hook output.
405 """
446 """
406 self.log.debug("pyout: %s", msg.get('content', ''))
447 self.log.debug("pyout: %s", msg.get('content', ''))
407 if not self._hidden and self._is_from_this_session(msg):
448 if not self._hidden and self._is_from_this_session(msg):
408 text = msg['content']['data']
449 text = msg['content']['data']
409 self._append_plain_text(text + '\n', before_prompt=True)
450 self._append_plain_text(text + '\n', before_prompt=True)
410
451
411 def _handle_stream(self, msg):
452 def _handle_stream(self, msg):
412 """ Handle stdout, stderr, and stdin.
453 """ Handle stdout, stderr, and stdin.
413 """
454 """
414 self.log.debug("stream: %s", msg.get('content', ''))
455 self.log.debug("stream: %s", msg.get('content', ''))
415 if not self._hidden and self._is_from_this_session(msg):
456 if not self._hidden and self._is_from_this_session(msg):
416 # Most consoles treat tabs as being 8 space characters. Convert tabs
457 # Most consoles treat tabs as being 8 space characters. Convert tabs
417 # to spaces so that output looks as expected regardless of this
458 # to spaces so that output looks as expected regardless of this
418 # widget's tab width.
459 # widget's tab width.
419 text = msg['content']['data'].expandtabs(8)
460 text = msg['content']['data'].expandtabs(8)
420
461
421 self._append_plain_text(text, before_prompt=True)
462 self._append_plain_text(text, before_prompt=True)
422 self._control.moveCursor(QtGui.QTextCursor.End)
463 self._control.moveCursor(QtGui.QTextCursor.End)
423
464
424 def _handle_shutdown_reply(self, msg):
465 def _handle_shutdown_reply(self, msg):
425 """ Handle shutdown signal, only if from other console.
466 """ Handle shutdown signal, only if from other console.
426 """
467 """
427 self.log.debug("shutdown: %s", msg.get('content', ''))
468 self.log.debug("shutdown: %s", msg.get('content', ''))
428 if not self._hidden and not self._is_from_this_session(msg):
469 if not self._hidden and not self._is_from_this_session(msg):
429 if self._local_kernel:
470 if self._local_kernel:
430 if not msg['content']['restart']:
471 if not msg['content']['restart']:
431 self.exit_requested.emit(self)
472 self.exit_requested.emit(self)
432 else:
473 else:
433 # we just got notified of a restart!
474 # we just got notified of a restart!
434 time.sleep(0.25) # wait 1/4 sec to reset
475 time.sleep(0.25) # wait 1/4 sec to reset
435 # lest the request for a new prompt
476 # lest the request for a new prompt
436 # goes to the old kernel
477 # goes to the old kernel
437 self.reset()
478 self.reset()
438 else: # remote kernel, prompt on Kernel shutdown/reset
479 else: # remote kernel, prompt on Kernel shutdown/reset
439 title = self.window().windowTitle()
480 title = self.window().windowTitle()
440 if not msg['content']['restart']:
481 if not msg['content']['restart']:
441 reply = QtGui.QMessageBox.question(self, title,
482 reply = QtGui.QMessageBox.question(self, title,
442 "Kernel has been shutdown permanently. "
483 "Kernel has been shutdown permanently. "
443 "Close the Console?",
484 "Close the Console?",
444 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
485 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
445 if reply == QtGui.QMessageBox.Yes:
486 if reply == QtGui.QMessageBox.Yes:
446 self.exit_requested.emit(self)
487 self.exit_requested.emit(self)
447 else:
488 else:
448 reply = QtGui.QMessageBox.question(self, title,
489 reply = QtGui.QMessageBox.question(self, title,
449 "Kernel has been reset. Clear the Console?",
490 "Kernel has been reset. Clear the Console?",
450 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
491 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
451 if reply == QtGui.QMessageBox.Yes:
492 if reply == QtGui.QMessageBox.Yes:
452 time.sleep(0.25) # wait 1/4 sec to reset
493 time.sleep(0.25) # wait 1/4 sec to reset
453 # lest the request for a new prompt
494 # lest the request for a new prompt
454 # goes to the old kernel
495 # goes to the old kernel
455 self.reset()
496 self.reset()
456
497
457 def _started_channels(self):
498 def _started_channels(self):
458 """ Called when the KernelManager channels have started listening or
499 """ Called when the KernelManager channels have started listening or
459 when the frontend is assigned an already listening KernelManager.
500 when the frontend is assigned an already listening KernelManager.
460 """
501 """
461 self.reset()
502 self.reset()
462
503
463 #---------------------------------------------------------------------------
504 #---------------------------------------------------------------------------
464 # 'FrontendWidget' public interface
505 # 'FrontendWidget' public interface
465 #---------------------------------------------------------------------------
506 #---------------------------------------------------------------------------
466
507
467 def copy_raw(self):
508 def copy_raw(self):
468 """ Copy the currently selected text to the clipboard without attempting
509 """ Copy the currently selected text to the clipboard without attempting
469 to remove prompts or otherwise alter the text.
510 to remove prompts or otherwise alter the text.
470 """
511 """
471 self._control.copy()
512 self._control.copy()
472
513
473 def execute_file(self, path, hidden=False):
514 def execute_file(self, path, hidden=False):
474 """ Attempts to execute file with 'path'. If 'hidden', no output is
515 """ Attempts to execute file with 'path'. If 'hidden', no output is
475 shown.
516 shown.
476 """
517 """
477 self.execute('execfile(%r)' % path, hidden=hidden)
518 self.execute('execfile(%r)' % path, hidden=hidden)
478
519
479 def interrupt_kernel(self):
520 def interrupt_kernel(self):
480 """ Attempts to interrupt the running kernel.
521 """ Attempts to interrupt the running kernel.
481
522
482 Also unsets _reading flag, to avoid runtime errors
523 Also unsets _reading flag, to avoid runtime errors
483 if raw_input is called again.
524 if raw_input is called again.
484 """
525 """
485 if self.custom_interrupt:
526 if self.custom_interrupt:
486 self._reading = False
527 self._reading = False
487 self.custom_interrupt_requested.emit()
528 self.custom_interrupt_requested.emit()
488 elif self.kernel_manager.has_kernel:
529 elif self.kernel_manager.has_kernel:
489 self._reading = False
530 self._reading = False
490 self.kernel_manager.interrupt_kernel()
531 self.kernel_manager.interrupt_kernel()
491 else:
532 else:
492 self._append_plain_text('Kernel process is either remote or '
533 self._append_plain_text('Kernel process is either remote or '
493 'unspecified. Cannot interrupt.\n')
534 'unspecified. Cannot interrupt.\n')
494
535
495 def reset(self):
536 def reset(self):
496 """ Resets the widget to its initial state. Similar to ``clear``, but
537 """ Resets the widget to its initial state. Similar to ``clear``, but
497 also re-writes the banner and aborts execution if necessary.
538 also re-writes the banner and aborts execution if necessary.
498 """
539 """
499 if self._executing:
540 if self._executing:
500 self._executing = False
541 self._executing = False
501 self._request_info['execute'] = None
542 self._request_info['execute'] = None
502 self._reading = False
543 self._reading = False
503 self._highlighter.highlighting_on = False
544 self._highlighter.highlighting_on = False
504
545
505 self._control.clear()
546 self._control.clear()
506 self._append_plain_text(self.banner)
547 self._append_plain_text(self.banner)
507 # update output marker for stdout/stderr, so that startup
548 # update output marker for stdout/stderr, so that startup
508 # messages appear after banner:
549 # messages appear after banner:
509 self._append_before_prompt_pos = self._get_cursor().position()
550 self._append_before_prompt_pos = self._get_cursor().position()
510 self._show_interpreter_prompt()
551 self._show_interpreter_prompt()
511
552
512 def restart_kernel(self, message, now=False):
553 def restart_kernel(self, message, now=False):
513 """ Attempts to restart the running kernel.
554 """ Attempts to restart the running kernel.
514 """
555 """
515 # FIXME: now should be configurable via a checkbox in the dialog. Right
556 # FIXME: now should be configurable via a checkbox in the dialog. Right
516 # now at least the heartbeat path sets it to True and the manual restart
557 # now at least the heartbeat path sets it to True and the manual restart
517 # to False. But those should just be the pre-selected states of a
558 # to False. But those should just be the pre-selected states of a
518 # checkbox that the user could override if so desired. But I don't know
559 # checkbox that the user could override if so desired. But I don't know
519 # enough Qt to go implementing the checkbox now.
560 # enough Qt to go implementing the checkbox now.
520
561
521 if self.custom_restart:
562 if self.custom_restart:
522 self.custom_restart_requested.emit()
563 self.custom_restart_requested.emit()
523
564
524 elif self.kernel_manager.has_kernel:
565 elif self.kernel_manager.has_kernel:
525 # Pause the heart beat channel to prevent further warnings.
566 # Pause the heart beat channel to prevent further warnings.
526 self.kernel_manager.hb_channel.pause()
567 self.kernel_manager.hb_channel.pause()
527
568
528 # Prompt the user to restart the kernel. Un-pause the heartbeat if
569 # Prompt the user to restart the kernel. Un-pause the heartbeat if
529 # they decline. (If they accept, the heartbeat will be un-paused
570 # they decline. (If they accept, the heartbeat will be un-paused
530 # automatically when the kernel is restarted.)
571 # automatically when the kernel is restarted.)
531 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
572 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
532 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
573 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
533 message, buttons)
574 message, buttons)
534 if result == QtGui.QMessageBox.Yes:
575 if result == QtGui.QMessageBox.Yes:
535 try:
576 try:
536 self.kernel_manager.restart_kernel(now=now)
577 self.kernel_manager.restart_kernel(now=now)
537 except RuntimeError:
578 except RuntimeError:
538 self._append_plain_text('Kernel started externally. '
579 self._append_plain_text('Kernel started externally. '
539 'Cannot restart.\n')
580 'Cannot restart.\n')
540 else:
581 else:
541 self.reset()
582 self.reset()
542 else:
583 else:
543 self.kernel_manager.hb_channel.unpause()
584 self.kernel_manager.hb_channel.unpause()
544
585
545 else:
586 else:
546 self._append_plain_text('Kernel process is either remote or '
587 self._append_plain_text('Kernel process is either remote or '
547 'unspecified. Cannot restart.\n')
588 'unspecified. Cannot restart.\n')
548
589
549 #---------------------------------------------------------------------------
590 #---------------------------------------------------------------------------
550 # 'FrontendWidget' protected interface
591 # 'FrontendWidget' protected interface
551 #---------------------------------------------------------------------------
592 #---------------------------------------------------------------------------
552
593
553 def _call_tip(self):
594 def _call_tip(self):
554 """ Shows a call tip, if appropriate, at the current cursor location.
595 """ Shows a call tip, if appropriate, at the current cursor location.
555 """
596 """
556 # Decide if it makes sense to show a call tip
597 # Decide if it makes sense to show a call tip
557 if not self.enable_calltips:
598 if not self.enable_calltips:
558 return False
599 return False
559 cursor = self._get_cursor()
600 cursor = self._get_cursor()
560 cursor.movePosition(QtGui.QTextCursor.Left)
601 cursor.movePosition(QtGui.QTextCursor.Left)
561 if cursor.document().characterAt(cursor.position()) != '(':
602 if cursor.document().characterAt(cursor.position()) != '(':
562 return False
603 return False
563 context = self._get_context(cursor)
604 context = self._get_context(cursor)
564 if not context:
605 if not context:
565 return False
606 return False
566
607
567 # Send the metadata request to the kernel
608 # Send the metadata request to the kernel
568 name = '.'.join(context)
609 name = '.'.join(context)
569 msg_id = self.kernel_manager.shell_channel.object_info(name)
610 msg_id = self.kernel_manager.shell_channel.object_info(name)
570 pos = self._get_cursor().position()
611 pos = self._get_cursor().position()
571 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
612 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
572 return True
613 return True
573
614
574 def _complete(self):
615 def _complete(self):
575 """ Performs completion at the current cursor location.
616 """ Performs completion at the current cursor location.
576 """
617 """
577 context = self._get_context()
618 context = self._get_context()
578 if context:
619 if context:
579 # Send the completion request to the kernel
620 # Send the completion request to the kernel
580 msg_id = self.kernel_manager.shell_channel.complete(
621 msg_id = self.kernel_manager.shell_channel.complete(
581 '.'.join(context), # text
622 '.'.join(context), # text
582 self._get_input_buffer_cursor_line(), # line
623 self._get_input_buffer_cursor_line(), # line
583 self._get_input_buffer_cursor_column(), # cursor_pos
624 self._get_input_buffer_cursor_column(), # cursor_pos
584 self.input_buffer) # block
625 self.input_buffer) # block
585 pos = self._get_cursor().position()
626 pos = self._get_cursor().position()
586 info = self._CompletionRequest(msg_id, pos)
627 info = self._CompletionRequest(msg_id, pos)
587 self._request_info['complete'] = info
628 self._request_info['complete'] = info
588
629
589 def _get_context(self, cursor=None):
630 def _get_context(self, cursor=None):
590 """ Gets the context for the specified cursor (or the current cursor
631 """ Gets the context for the specified cursor (or the current cursor
591 if none is specified).
632 if none is specified).
592 """
633 """
593 if cursor is None:
634 if cursor is None:
594 cursor = self._get_cursor()
635 cursor = self._get_cursor()
595 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
636 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
596 QtGui.QTextCursor.KeepAnchor)
637 QtGui.QTextCursor.KeepAnchor)
597 text = cursor.selection().toPlainText()
638 text = cursor.selection().toPlainText()
598 return self._completion_lexer.get_context(text)
639 return self._completion_lexer.get_context(text)
599
640
600 def _process_execute_abort(self, msg):
641 def _process_execute_abort(self, msg):
601 """ Process a reply for an aborted execution request.
642 """ Process a reply for an aborted execution request.
602 """
643 """
603 self._append_plain_text("ERROR: execution aborted\n")
644 self._append_plain_text("ERROR: execution aborted\n")
604
645
605 def _process_execute_error(self, msg):
646 def _process_execute_error(self, msg):
606 """ Process a reply for an execution request that resulted in an error.
647 """ Process a reply for an execution request that resulted in an error.
607 """
648 """
608 content = msg['content']
649 content = msg['content']
609 # If a SystemExit is passed along, this means exit() was called - also
650 # If a SystemExit is passed along, this means exit() was called - also
610 # all the ipython %exit magic syntax of '-k' to be used to keep
651 # all the ipython %exit magic syntax of '-k' to be used to keep
611 # the kernel running
652 # the kernel running
612 if content['ename']=='SystemExit':
653 if content['ename']=='SystemExit':
613 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
654 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
614 self._keep_kernel_on_exit = keepkernel
655 self._keep_kernel_on_exit = keepkernel
615 self.exit_requested.emit(self)
656 self.exit_requested.emit(self)
616 else:
657 else:
617 traceback = ''.join(content['traceback'])
658 traceback = ''.join(content['traceback'])
618 self._append_plain_text(traceback)
659 self._append_plain_text(traceback)
619
660
620 def _process_execute_ok(self, msg):
661 def _process_execute_ok(self, msg):
621 """ Process a reply for a successful execution equest.
662 """ Process a reply for a successful execution equest.
622 """
663 """
623 payload = msg['content']['payload']
664 payload = msg['content']['payload']
624 for item in payload:
665 for item in payload:
625 if not self._process_execute_payload(item):
666 if not self._process_execute_payload(item):
626 warning = 'Warning: received unknown payload of type %s'
667 warning = 'Warning: received unknown payload of type %s'
627 print(warning % repr(item['source']))
668 print(warning % repr(item['source']))
628
669
629 def _process_execute_payload(self, item):
670 def _process_execute_payload(self, item):
630 """ Process a single payload item from the list of payload items in an
671 """ Process a single payload item from the list of payload items in an
631 execution reply. Returns whether the payload was handled.
672 execution reply. Returns whether the payload was handled.
632 """
673 """
633 # The basic FrontendWidget doesn't handle payloads, as they are a
674 # The basic FrontendWidget doesn't handle payloads, as they are a
634 # mechanism for going beyond the standard Python interpreter model.
675 # mechanism for going beyond the standard Python interpreter model.
635 return False
676 return False
636
677
637 def _show_interpreter_prompt(self):
678 def _show_interpreter_prompt(self):
638 """ Shows a prompt for the interpreter.
679 """ Shows a prompt for the interpreter.
639 """
680 """
640 self._show_prompt('>>> ')
681 self._show_prompt('>>> ')
641
682
642 def _show_interpreter_prompt_for_reply(self, msg):
683 def _show_interpreter_prompt_for_reply(self, msg):
643 """ Shows a prompt for the interpreter given an 'execute_reply' message.
684 """ Shows a prompt for the interpreter given an 'execute_reply' message.
644 """
685 """
645 self._show_interpreter_prompt()
686 self._show_interpreter_prompt()
646
687
647 #------ Signal handlers ----------------------------------------------------
688 #------ Signal handlers ----------------------------------------------------
648
689
649 def _document_contents_change(self, position, removed, added):
690 def _document_contents_change(self, position, removed, added):
650 """ Called whenever the document's content changes. Display a call tip
691 """ Called whenever the document's content changes. Display a call tip
651 if appropriate.
692 if appropriate.
652 """
693 """
653 # Calculate where the cursor should be *after* the change:
694 # Calculate where the cursor should be *after* the change:
654 position += added
695 position += added
655
696
656 document = self._control.document()
697 document = self._control.document()
657 if position == self._get_cursor().position():
698 if position == self._get_cursor().position():
658 self._call_tip()
699 self._call_tip()
659
700
660 #------ Trait default initializers -----------------------------------------
701 #------ Trait default initializers -----------------------------------------
661
702
662 def _banner_default(self):
703 def _banner_default(self):
663 """ Returns the standard Python banner.
704 """ Returns the standard Python banner.
664 """
705 """
665 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
706 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
666 '"license" for more information.'
707 '"license" for more information.'
667 return banner % (sys.version, sys.platform)
708 return banner % (sys.version, sys.platform)
@@ -1,851 +1,876 b''
1 """The Qt MainWindow for the QtConsole
1 """The Qt MainWindow for the QtConsole
2
2
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 common actions.
4 common actions.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import sys
22 import sys
23 import webbrowser
23 import webbrowser
24 from threading import Thread
24 from threading import Thread
25
25
26 # System library imports
26 # System library imports
27 from IPython.external.qt import QtGui,QtCore
27 from IPython.external.qt import QtGui,QtCore
28
28
29 def background(f):
29 def background(f):
30 """call a function in a simple thread, to prevent blocking"""
30 """call a function in a simple thread, to prevent blocking"""
31 t = Thread(target=f)
31 t = Thread(target=f)
32 t.start()
32 t.start()
33 return t
33 return t
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Classes
36 # Classes
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 class MainWindow(QtGui.QMainWindow):
39 class MainWindow(QtGui.QMainWindow):
40
40
41 #---------------------------------------------------------------------------
41 #---------------------------------------------------------------------------
42 # 'object' interface
42 # 'object' interface
43 #---------------------------------------------------------------------------
43 #---------------------------------------------------------------------------
44
44
45 def __init__(self, app,
45 def __init__(self, app,
46 confirm_exit=True,
46 confirm_exit=True,
47 new_frontend_factory=None, slave_frontend_factory=None,
47 new_frontend_factory=None, slave_frontend_factory=None,
48 ):
48 ):
49 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
49 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
50
50
51 Parameters
51 Parameters
52 ----------
52 ----------
53
53
54 app : reference to QApplication parent
54 app : reference to QApplication parent
55 confirm_exit : bool, optional
55 confirm_exit : bool, optional
56 Whether we should prompt on close of tabs
56 Whether we should prompt on close of tabs
57 new_frontend_factory : callable
57 new_frontend_factory : callable
58 A callable that returns a new IPythonWidget instance, attached to
58 A callable that returns a new IPythonWidget instance, attached to
59 its own running kernel.
59 its own running kernel.
60 slave_frontend_factory : callable
60 slave_frontend_factory : callable
61 A callable that takes an existing IPythonWidget, and returns a new
61 A callable that takes an existing IPythonWidget, and returns a new
62 IPythonWidget instance, attached to the same kernel.
62 IPythonWidget instance, attached to the same kernel.
63 """
63 """
64
64
65 super(MainWindow, self).__init__()
65 super(MainWindow, self).__init__()
66 self._kernel_counter = 0
66 self._kernel_counter = 0
67 self._app = app
67 self._app = app
68 self.confirm_exit = confirm_exit
68 self.confirm_exit = confirm_exit
69 self.new_frontend_factory = new_frontend_factory
69 self.new_frontend_factory = new_frontend_factory
70 self.slave_frontend_factory = slave_frontend_factory
70 self.slave_frontend_factory = slave_frontend_factory
71
71
72 self.tab_widget = QtGui.QTabWidget(self)
72 self.tab_widget = QtGui.QTabWidget(self)
73 self.tab_widget.setDocumentMode(True)
73 self.tab_widget.setDocumentMode(True)
74 self.tab_widget.setTabsClosable(True)
74 self.tab_widget.setTabsClosable(True)
75 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
75 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
76
76
77 self.setCentralWidget(self.tab_widget)
77 self.setCentralWidget(self.tab_widget)
78 # hide tab bar at first, since we have no tabs:
78 # hide tab bar at first, since we have no tabs:
79 self.tab_widget.tabBar().setVisible(False)
79 self.tab_widget.tabBar().setVisible(False)
80 # prevent focus in tab bar
80 # prevent focus in tab bar
81 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
81 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
82
82
83 def update_tab_bar_visibility(self):
83 def update_tab_bar_visibility(self):
84 """ update visibility of the tabBar depending of the number of tab
84 """ update visibility of the tabBar depending of the number of tab
85
85
86 0 or 1 tab, tabBar hidden
86 0 or 1 tab, tabBar hidden
87 2+ tabs, tabBar visible
87 2+ tabs, tabBar visible
88
88
89 send a self.close if number of tab ==0
89 send a self.close if number of tab ==0
90
90
91 need to be called explicitely, or be connected to tabInserted/tabRemoved
91 need to be called explicitely, or be connected to tabInserted/tabRemoved
92 """
92 """
93 if self.tab_widget.count() <= 1:
93 if self.tab_widget.count() <= 1:
94 self.tab_widget.tabBar().setVisible(False)
94 self.tab_widget.tabBar().setVisible(False)
95 else:
95 else:
96 self.tab_widget.tabBar().setVisible(True)
96 self.tab_widget.tabBar().setVisible(True)
97 if self.tab_widget.count()==0 :
97 if self.tab_widget.count()==0 :
98 self.close()
98 self.close()
99
99
100 @property
100 @property
101 def next_kernel_id(self):
101 def next_kernel_id(self):
102 """constantly increasing counter for kernel IDs"""
102 """constantly increasing counter for kernel IDs"""
103 c = self._kernel_counter
103 c = self._kernel_counter
104 self._kernel_counter += 1
104 self._kernel_counter += 1
105 return c
105 return c
106
106
107 @property
107 @property
108 def active_frontend(self):
108 def active_frontend(self):
109 return self.tab_widget.currentWidget()
109 return self.tab_widget.currentWidget()
110
110
111 def create_tab_with_new_frontend(self):
111 def create_tab_with_new_frontend(self):
112 """create a new frontend and attach it to a new tab"""
112 """create a new frontend and attach it to a new tab"""
113 widget = self.new_frontend_factory()
113 widget = self.new_frontend_factory()
114 self.add_tab_with_frontend(widget)
114 self.add_tab_with_frontend(widget)
115
115
116 def create_tab_with_current_kernel(self):
116 def create_tab_with_current_kernel(self):
117 """create a new frontend attached to the same kernel as the current tab"""
117 """create a new frontend attached to the same kernel as the current tab"""
118 current_widget = self.tab_widget.currentWidget()
118 current_widget = self.tab_widget.currentWidget()
119 current_widget_index = self.tab_widget.indexOf(current_widget)
119 current_widget_index = self.tab_widget.indexOf(current_widget)
120 current_widget_name = self.tab_widget.tabText(current_widget_index)
120 current_widget_name = self.tab_widget.tabText(current_widget_index)
121 widget = self.slave_frontend_factory(current_widget)
121 widget = self.slave_frontend_factory(current_widget)
122 if 'slave' in current_widget_name:
122 if 'slave' in current_widget_name:
123 # don't keep stacking slaves
123 # don't keep stacking slaves
124 name = current_widget_name
124 name = current_widget_name
125 else:
125 else:
126 name = '(%s) slave' % current_widget_name
126 name = '(%s) slave' % current_widget_name
127 self.add_tab_with_frontend(widget,name=name)
127 self.add_tab_with_frontend(widget,name=name)
128
128
129 def close_tab(self,current_tab):
129 def close_tab(self,current_tab):
130 """ Called when you need to try to close a tab.
130 """ Called when you need to try to close a tab.
131
131
132 It takes the number of the tab to be closed as argument, or a referece
132 It takes the number of the tab to be closed as argument, or a referece
133 to the wiget insite this tab
133 to the wiget insite this tab
134 """
134 """
135
135
136 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
136 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
137 # and a reference to the trontend to close
137 # and a reference to the trontend to close
138 if type(current_tab) is not int :
138 if type(current_tab) is not int :
139 current_tab = self.tab_widget.indexOf(current_tab)
139 current_tab = self.tab_widget.indexOf(current_tab)
140 closing_widget=self.tab_widget.widget(current_tab)
140 closing_widget=self.tab_widget.widget(current_tab)
141
141
142
142
143 # when trying to be closed, widget might re-send a request to be closed again, but will
143 # when trying to be closed, widget might re-send a request to be closed again, but will
144 # be deleted when event will be processed. So need to check that widget still exist and
144 # be deleted when event will be processed. So need to check that widget still exist and
145 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
145 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
146 # re-send by this fonction on the master widget, which ask all slaves widget to exit
146 # re-send by this fonction on the master widget, which ask all slaves widget to exit
147 if closing_widget==None:
147 if closing_widget==None:
148 return
148 return
149
149
150 #get a list of all slave widgets on the same kernel.
150 #get a list of all slave widgets on the same kernel.
151 slave_tabs = self.find_slave_widgets(closing_widget)
151 slave_tabs = self.find_slave_widgets(closing_widget)
152
152
153 keepkernel = None #Use the prompt by default
153 keepkernel = None #Use the prompt by default
154 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
154 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
155 keepkernel = closing_widget._keep_kernel_on_exit
155 keepkernel = closing_widget._keep_kernel_on_exit
156 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
156 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
157 # we set local slave tabs._hidden to True to avoid prompting for kernel
157 # we set local slave tabs._hidden to True to avoid prompting for kernel
158 # restart when they get the signal. and then "forward" the 'exit'
158 # restart when they get the signal. and then "forward" the 'exit'
159 # to the main window
159 # to the main window
160 if keepkernel is not None:
160 if keepkernel is not None:
161 for tab in slave_tabs:
161 for tab in slave_tabs:
162 tab._hidden = True
162 tab._hidden = True
163 if closing_widget in slave_tabs:
163 if closing_widget in slave_tabs:
164 try :
164 try :
165 self.find_master_tab(closing_widget).execute('exit')
165 self.find_master_tab(closing_widget).execute('exit')
166 except AttributeError:
166 except AttributeError:
167 self.log.info("Master already closed or not local, closing only current tab")
167 self.log.info("Master already closed or not local, closing only current tab")
168 self.tab_widget.removeTab(current_tab)
168 self.tab_widget.removeTab(current_tab)
169 self.update_tab_bar_visibility()
169 self.update_tab_bar_visibility()
170 return
170 return
171
171
172 kernel_manager = closing_widget.kernel_manager
172 kernel_manager = closing_widget.kernel_manager
173
173
174 if keepkernel is None and not closing_widget._confirm_exit:
174 if keepkernel is None and not closing_widget._confirm_exit:
175 # don't prompt, just terminate the kernel if we own it
175 # don't prompt, just terminate the kernel if we own it
176 # or leave it alone if we don't
176 # or leave it alone if we don't
177 keepkernel = closing_widget._existing
177 keepkernel = closing_widget._existing
178 if keepkernel is None: #show prompt
178 if keepkernel is None: #show prompt
179 if kernel_manager and kernel_manager.channels_running:
179 if kernel_manager and kernel_manager.channels_running:
180 title = self.window().windowTitle()
180 title = self.window().windowTitle()
181 cancel = QtGui.QMessageBox.Cancel
181 cancel = QtGui.QMessageBox.Cancel
182 okay = QtGui.QMessageBox.Ok
182 okay = QtGui.QMessageBox.Ok
183 if closing_widget._may_close:
183 if closing_widget._may_close:
184 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
184 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
185 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
185 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
186 justthis = QtGui.QPushButton("&No, just this Tab", self)
186 justthis = QtGui.QPushButton("&No, just this Tab", self)
187 justthis.setShortcut('N')
187 justthis.setShortcut('N')
188 closeall = QtGui.QPushButton("&Yes, close all", self)
188 closeall = QtGui.QPushButton("&Yes, close all", self)
189 closeall.setShortcut('Y')
189 closeall.setShortcut('Y')
190 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
190 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
191 title, msg)
191 title, msg)
192 box.setInformativeText(info)
192 box.setInformativeText(info)
193 box.addButton(cancel)
193 box.addButton(cancel)
194 box.addButton(justthis, QtGui.QMessageBox.NoRole)
194 box.addButton(justthis, QtGui.QMessageBox.NoRole)
195 box.addButton(closeall, QtGui.QMessageBox.YesRole)
195 box.addButton(closeall, QtGui.QMessageBox.YesRole)
196 box.setDefaultButton(closeall)
196 box.setDefaultButton(closeall)
197 box.setEscapeButton(cancel)
197 box.setEscapeButton(cancel)
198 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
198 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
199 box.setIconPixmap(pixmap)
199 box.setIconPixmap(pixmap)
200 reply = box.exec_()
200 reply = box.exec_()
201 if reply == 1: # close All
201 if reply == 1: # close All
202 for slave in slave_tabs:
202 for slave in slave_tabs:
203 background(slave.kernel_manager.stop_channels)
203 background(slave.kernel_manager.stop_channels)
204 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
204 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
205 closing_widget.execute("exit")
205 closing_widget.execute("exit")
206 self.tab_widget.removeTab(current_tab)
206 self.tab_widget.removeTab(current_tab)
207 background(kernel_manager.stop_channels)
207 background(kernel_manager.stop_channels)
208 elif reply == 0: # close Console
208 elif reply == 0: # close Console
209 if not closing_widget._existing:
209 if not closing_widget._existing:
210 # Have kernel: don't quit, just close the tab
210 # Have kernel: don't quit, just close the tab
211 closing_widget.execute("exit True")
211 closing_widget.execute("exit True")
212 self.tab_widget.removeTab(current_tab)
212 self.tab_widget.removeTab(current_tab)
213 background(kernel_manager.stop_channels)
213 background(kernel_manager.stop_channels)
214 else:
214 else:
215 reply = QtGui.QMessageBox.question(self, title,
215 reply = QtGui.QMessageBox.question(self, title,
216 "Are you sure you want to close this Console?"+
216 "Are you sure you want to close this Console?"+
217 "\nThe Kernel and other Consoles will remain active.",
217 "\nThe Kernel and other Consoles will remain active.",
218 okay|cancel,
218 okay|cancel,
219 defaultButton=okay
219 defaultButton=okay
220 )
220 )
221 if reply == okay:
221 if reply == okay:
222 self.tab_widget.removeTab(current_tab)
222 self.tab_widget.removeTab(current_tab)
223 elif keepkernel: #close console but leave kernel running (no prompt)
223 elif keepkernel: #close console but leave kernel running (no prompt)
224 self.tab_widget.removeTab(current_tab)
224 self.tab_widget.removeTab(current_tab)
225 background(kernel_manager.stop_channels)
225 background(kernel_manager.stop_channels)
226 else: #close console and kernel (no prompt)
226 else: #close console and kernel (no prompt)
227 self.tab_widget.removeTab(current_tab)
227 self.tab_widget.removeTab(current_tab)
228 if kernel_manager and kernel_manager.channels_running:
228 if kernel_manager and kernel_manager.channels_running:
229 for slave in slave_tabs:
229 for slave in slave_tabs:
230 background(slave.kernel_manager.stop_channels)
230 background(slave.kernel_manager.stop_channels)
231 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
231 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
232 kernel_manager.shutdown_kernel()
232 kernel_manager.shutdown_kernel()
233 background(kernel_manager.stop_channels)
233 background(kernel_manager.stop_channels)
234
234
235 self.update_tab_bar_visibility()
235 self.update_tab_bar_visibility()
236
236
237 def add_tab_with_frontend(self,frontend,name=None):
237 def add_tab_with_frontend(self,frontend,name=None):
238 """ insert a tab with a given frontend in the tab bar, and give it a name
238 """ insert a tab with a given frontend in the tab bar, and give it a name
239
239
240 """
240 """
241 if not name:
241 if not name:
242 name = 'kernel %i' % self.next_kernel_id
242 name = 'kernel %i' % self.next_kernel_id
243 self.tab_widget.addTab(frontend,name)
243 self.tab_widget.addTab(frontend,name)
244 self.update_tab_bar_visibility()
244 self.update_tab_bar_visibility()
245 self.make_frontend_visible(frontend)
245 self.make_frontend_visible(frontend)
246 frontend.exit_requested.connect(self.close_tab)
246 frontend.exit_requested.connect(self.close_tab)
247
247
248 def next_tab(self):
248 def next_tab(self):
249 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
249 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
250
250
251 def prev_tab(self):
251 def prev_tab(self):
252 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
252 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
253
253
254 def make_frontend_visible(self,frontend):
254 def make_frontend_visible(self,frontend):
255 widget_index=self.tab_widget.indexOf(frontend)
255 widget_index=self.tab_widget.indexOf(frontend)
256 if widget_index > 0 :
256 if widget_index > 0 :
257 self.tab_widget.setCurrentIndex(widget_index)
257 self.tab_widget.setCurrentIndex(widget_index)
258
258
259 def find_master_tab(self,tab,as_list=False):
259 def find_master_tab(self,tab,as_list=False):
260 """
260 """
261 Try to return the frontend that own the kernel attached to the given widget/tab.
261 Try to return the frontend that own the kernel attached to the given widget/tab.
262
262
263 Only find frontend owed by the current application. Selection
263 Only find frontend owed by the current application. Selection
264 based on port of the kernel, might be inacurate if several kernel
264 based on port of the kernel, might be inacurate if several kernel
265 on different ip use same port number.
265 on different ip use same port number.
266
266
267 This fonction does the conversion tabNumber/widget if needed.
267 This fonction does the conversion tabNumber/widget if needed.
268 Might return None if no master widget (non local kernel)
268 Might return None if no master widget (non local kernel)
269 Will crash IPython if more than 1 masterWidget
269 Will crash IPython if more than 1 masterWidget
270
270
271 When asList set to True, always return a list of widget(s) owning
271 When asList set to True, always return a list of widget(s) owning
272 the kernel. The list might be empty or containing several Widget.
272 the kernel. The list might be empty or containing several Widget.
273 """
273 """
274
274
275 #convert from/to int/richIpythonWidget if needed
275 #convert from/to int/richIpythonWidget if needed
276 if isinstance(tab, int):
276 if isinstance(tab, int):
277 tab = self.tab_widget.widget(tab)
277 tab = self.tab_widget.widget(tab)
278 km=tab.kernel_manager
278 km=tab.kernel_manager
279
279
280 #build list of all widgets
280 #build list of all widgets
281 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
281 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
282
282
283 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
283 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
284 # And should have a _may_close attribute
284 # And should have a _may_close attribute
285 filtered_widget_list = [ widget for widget in widget_list if
285 filtered_widget_list = [ widget for widget in widget_list if
286 widget.kernel_manager.connection_file == km.connection_file and
286 widget.kernel_manager.connection_file == km.connection_file and
287 hasattr(widget,'_may_close') ]
287 hasattr(widget,'_may_close') ]
288 # the master widget is the one that may close the kernel
288 # the master widget is the one that may close the kernel
289 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
289 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
290 if as_list:
290 if as_list:
291 return master_widget
291 return master_widget
292 assert(len(master_widget)<=1 )
292 assert(len(master_widget)<=1 )
293 if len(master_widget)==0:
293 if len(master_widget)==0:
294 return None
294 return None
295
295
296 return master_widget[0]
296 return master_widget[0]
297
297
298 def find_slave_widgets(self,tab):
298 def find_slave_widgets(self,tab):
299 """return all the frontends that do not own the kernel attached to the given widget/tab.
299 """return all the frontends that do not own the kernel attached to the given widget/tab.
300
300
301 Only find frontends owned by the current application. Selection
301 Only find frontends owned by the current application. Selection
302 based on connection file of the kernel.
302 based on connection file of the kernel.
303
303
304 This function does the conversion tabNumber/widget if needed.
304 This function does the conversion tabNumber/widget if needed.
305 """
305 """
306 #convert from/to int/richIpythonWidget if needed
306 #convert from/to int/richIpythonWidget if needed
307 if isinstance(tab, int):
307 if isinstance(tab, int):
308 tab = self.tab_widget.widget(tab)
308 tab = self.tab_widget.widget(tab)
309 km=tab.kernel_manager
309 km=tab.kernel_manager
310
310
311 #build list of all widgets
311 #build list of all widgets
312 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
312 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
313
313
314 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
314 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
315 filtered_widget_list = ( widget for widget in widget_list if
315 filtered_widget_list = ( widget for widget in widget_list if
316 widget.kernel_manager.connection_file == km.connection_file)
316 widget.kernel_manager.connection_file == km.connection_file)
317 # Get a list of all widget owning the same kernel and removed it from
317 # Get a list of all widget owning the same kernel and removed it from
318 # the previous cadidate. (better using sets ?)
318 # the previous cadidate. (better using sets ?)
319 master_widget_list = self.find_master_tab(tab, as_list=True)
319 master_widget_list = self.find_master_tab(tab, as_list=True)
320 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
320 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
321
321
322 return slave_list
322 return slave_list
323
323
324 # Populate the menu bar with common actions and shortcuts
324 # Populate the menu bar with common actions and shortcuts
325 def add_menu_action(self, menu, action, defer_shortcut=False):
325 def add_menu_action(self, menu, action, defer_shortcut=False):
326 """Add action to menu as well as self
326 """Add action to menu as well as self
327
327
328 So that when the menu bar is invisible, its actions are still available.
328 So that when the menu bar is invisible, its actions are still available.
329
329
330 If defer_shortcut is True, set the shortcut context to widget-only,
330 If defer_shortcut is True, set the shortcut context to widget-only,
331 where it will avoid conflict with shortcuts already bound to the
331 where it will avoid conflict with shortcuts already bound to the
332 widgets themselves.
332 widgets themselves.
333 """
333 """
334 menu.addAction(action)
334 menu.addAction(action)
335 self.addAction(action)
335 self.addAction(action)
336
336
337 if defer_shortcut:
337 if defer_shortcut:
338 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
338 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
339
339
340 def init_menu_bar(self):
340 def init_menu_bar(self):
341 #create menu in the order they should appear in the menu bar
341 #create menu in the order they should appear in the menu bar
342 self.init_file_menu()
342 self.init_file_menu()
343 self.init_edit_menu()
343 self.init_edit_menu()
344 self.init_view_menu()
344 self.init_view_menu()
345 self.init_kernel_menu()
345 self.init_kernel_menu()
346 self.init_magic_menu()
346 self.init_magic_menu()
347 self.init_window_menu()
347 self.init_window_menu()
348 self.init_help_menu()
348 self.init_help_menu()
349
349
350 def init_file_menu(self):
350 def init_file_menu(self):
351 self.file_menu = self.menuBar().addMenu("&File")
351 self.file_menu = self.menuBar().addMenu("&File")
352
352
353 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
353 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
354 self,
354 self,
355 shortcut="Ctrl+T",
355 shortcut="Ctrl+T",
356 triggered=self.create_tab_with_new_frontend)
356 triggered=self.create_tab_with_new_frontend)
357 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
357 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
358
358
359 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
359 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
360 self,
360 self,
361 shortcut="Ctrl+Shift+T",
361 shortcut="Ctrl+Shift+T",
362 triggered=self.create_tab_with_current_kernel)
362 triggered=self.create_tab_with_current_kernel)
363 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
363 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
364
364
365 self.file_menu.addSeparator()
365 self.file_menu.addSeparator()
366
366
367 self.close_action=QtGui.QAction("&Close Tab",
367 self.close_action=QtGui.QAction("&Close Tab",
368 self,
368 self,
369 shortcut=QtGui.QKeySequence.Close,
369 shortcut=QtGui.QKeySequence.Close,
370 triggered=self.close_active_frontend
370 triggered=self.close_active_frontend
371 )
371 )
372 self.add_menu_action(self.file_menu, self.close_action)
372 self.add_menu_action(self.file_menu, self.close_action)
373
373
374 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
374 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
375 self,
375 self,
376 shortcut=QtGui.QKeySequence.Save,
376 shortcut=QtGui.QKeySequence.Save,
377 triggered=self.export_action_active_frontend
377 triggered=self.export_action_active_frontend
378 )
378 )
379 self.add_menu_action(self.file_menu, self.export_action, True)
379 self.add_menu_action(self.file_menu, self.export_action, True)
380
380
381 self.file_menu.addSeparator()
381 self.file_menu.addSeparator()
382
382
383 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
383 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
384 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
384 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
385 # Only override the default if there is a collision.
385 # Only override the default if there is a collision.
386 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
386 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
387 printkey = "Ctrl+Shift+P"
387 printkey = "Ctrl+Shift+P"
388 self.print_action = QtGui.QAction("&Print",
388 self.print_action = QtGui.QAction("&Print",
389 self,
389 self,
390 shortcut=printkey,
390 shortcut=printkey,
391 triggered=self.print_action_active_frontend)
391 triggered=self.print_action_active_frontend)
392 self.add_menu_action(self.file_menu, self.print_action, True)
392 self.add_menu_action(self.file_menu, self.print_action, True)
393
393
394 if sys.platform != 'darwin':
394 if sys.platform != 'darwin':
395 # OSX always has Quit in the Application menu, only add it
395 # OSX always has Quit in the Application menu, only add it
396 # to the File menu elsewhere.
396 # to the File menu elsewhere.
397
397
398 self.file_menu.addSeparator()
398 self.file_menu.addSeparator()
399
399
400 self.quit_action = QtGui.QAction("&Quit",
400 self.quit_action = QtGui.QAction("&Quit",
401 self,
401 self,
402 shortcut=QtGui.QKeySequence.Quit,
402 shortcut=QtGui.QKeySequence.Quit,
403 triggered=self.close,
403 triggered=self.close,
404 )
404 )
405 self.add_menu_action(self.file_menu, self.quit_action)
405 self.add_menu_action(self.file_menu, self.quit_action)
406
406
407
407
408 def init_edit_menu(self):
408 def init_edit_menu(self):
409 self.edit_menu = self.menuBar().addMenu("&Edit")
409 self.edit_menu = self.menuBar().addMenu("&Edit")
410
410
411 self.undo_action = QtGui.QAction("&Undo",
411 self.undo_action = QtGui.QAction("&Undo",
412 self,
412 self,
413 shortcut=QtGui.QKeySequence.Undo,
413 shortcut=QtGui.QKeySequence.Undo,
414 statusTip="Undo last action if possible",
414 statusTip="Undo last action if possible",
415 triggered=self.undo_active_frontend
415 triggered=self.undo_active_frontend
416 )
416 )
417 self.add_menu_action(self.edit_menu, self.undo_action)
417 self.add_menu_action(self.edit_menu, self.undo_action)
418
418
419 self.redo_action = QtGui.QAction("&Redo",
419 self.redo_action = QtGui.QAction("&Redo",
420 self,
420 self,
421 shortcut=QtGui.QKeySequence.Redo,
421 shortcut=QtGui.QKeySequence.Redo,
422 statusTip="Redo last action if possible",
422 statusTip="Redo last action if possible",
423 triggered=self.redo_active_frontend)
423 triggered=self.redo_active_frontend)
424 self.add_menu_action(self.edit_menu, self.redo_action)
424 self.add_menu_action(self.edit_menu, self.redo_action)
425
425
426 self.edit_menu.addSeparator()
426 self.edit_menu.addSeparator()
427
427
428 self.cut_action = QtGui.QAction("&Cut",
428 self.cut_action = QtGui.QAction("&Cut",
429 self,
429 self,
430 shortcut=QtGui.QKeySequence.Cut,
430 shortcut=QtGui.QKeySequence.Cut,
431 triggered=self.cut_active_frontend
431 triggered=self.cut_active_frontend
432 )
432 )
433 self.add_menu_action(self.edit_menu, self.cut_action, True)
433 self.add_menu_action(self.edit_menu, self.cut_action, True)
434
434
435 self.copy_action = QtGui.QAction("&Copy",
435 self.copy_action = QtGui.QAction("&Copy",
436 self,
436 self,
437 shortcut=QtGui.QKeySequence.Copy,
437 shortcut=QtGui.QKeySequence.Copy,
438 triggered=self.copy_active_frontend
438 triggered=self.copy_active_frontend
439 )
439 )
440 self.add_menu_action(self.edit_menu, self.copy_action, True)
440 self.add_menu_action(self.edit_menu, self.copy_action, True)
441
441
442 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
442 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
443 self,
443 self,
444 shortcut="Ctrl+Shift+C",
444 shortcut="Ctrl+Shift+C",
445 triggered=self.copy_raw_active_frontend
445 triggered=self.copy_raw_active_frontend
446 )
446 )
447 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
447 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
448
448
449 self.paste_action = QtGui.QAction("&Paste",
449 self.paste_action = QtGui.QAction("&Paste",
450 self,
450 self,
451 shortcut=QtGui.QKeySequence.Paste,
451 shortcut=QtGui.QKeySequence.Paste,
452 triggered=self.paste_active_frontend
452 triggered=self.paste_active_frontend
453 )
453 )
454 self.add_menu_action(self.edit_menu, self.paste_action, True)
454 self.add_menu_action(self.edit_menu, self.paste_action, True)
455
455
456 self.edit_menu.addSeparator()
456 self.edit_menu.addSeparator()
457
457
458 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
458 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
459 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
459 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
460 # Only override the default if there is a collision.
460 # Only override the default if there is a collision.
461 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
461 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
462 selectall = "Ctrl+Shift+A"
462 selectall = "Ctrl+Shift+A"
463 self.select_all_action = QtGui.QAction("Select &All",
463 self.select_all_action = QtGui.QAction("Select &All",
464 self,
464 self,
465 shortcut=selectall,
465 shortcut=selectall,
466 triggered=self.select_all_active_frontend
466 triggered=self.select_all_active_frontend
467 )
467 )
468 self.add_menu_action(self.edit_menu, self.select_all_action, True)
468 self.add_menu_action(self.edit_menu, self.select_all_action, True)
469
469
470
470
471 def init_view_menu(self):
471 def init_view_menu(self):
472 self.view_menu = self.menuBar().addMenu("&View")
472 self.view_menu = self.menuBar().addMenu("&View")
473
473
474 if sys.platform != 'darwin':
474 if sys.platform != 'darwin':
475 # disable on OSX, where there is always a menu bar
475 # disable on OSX, where there is always a menu bar
476 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
476 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
477 self,
477 self,
478 shortcut="Ctrl+Shift+M",
478 shortcut="Ctrl+Shift+M",
479 statusTip="Toggle visibility of menubar",
479 statusTip="Toggle visibility of menubar",
480 triggered=self.toggle_menu_bar)
480 triggered=self.toggle_menu_bar)
481 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
481 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
482
482
483 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
483 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
484 self.full_screen_act = QtGui.QAction("&Full Screen",
484 self.full_screen_act = QtGui.QAction("&Full Screen",
485 self,
485 self,
486 shortcut=fs_key,
486 shortcut=fs_key,
487 statusTip="Toggle between Fullscreen and Normal Size",
487 statusTip="Toggle between Fullscreen and Normal Size",
488 triggered=self.toggleFullScreen)
488 triggered=self.toggleFullScreen)
489 self.add_menu_action(self.view_menu, self.full_screen_act)
489 self.add_menu_action(self.view_menu, self.full_screen_act)
490
490
491 self.view_menu.addSeparator()
491 self.view_menu.addSeparator()
492
492
493 self.increase_font_size = QtGui.QAction("Zoom &In",
493 self.increase_font_size = QtGui.QAction("Zoom &In",
494 self,
494 self,
495 shortcut=QtGui.QKeySequence.ZoomIn,
495 shortcut=QtGui.QKeySequence.ZoomIn,
496 triggered=self.increase_font_size_active_frontend
496 triggered=self.increase_font_size_active_frontend
497 )
497 )
498 self.add_menu_action(self.view_menu, self.increase_font_size, True)
498 self.add_menu_action(self.view_menu, self.increase_font_size, True)
499
499
500 self.decrease_font_size = QtGui.QAction("Zoom &Out",
500 self.decrease_font_size = QtGui.QAction("Zoom &Out",
501 self,
501 self,
502 shortcut=QtGui.QKeySequence.ZoomOut,
502 shortcut=QtGui.QKeySequence.ZoomOut,
503 triggered=self.decrease_font_size_active_frontend
503 triggered=self.decrease_font_size_active_frontend
504 )
504 )
505 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
505 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
506
506
507 self.reset_font_size = QtGui.QAction("Zoom &Reset",
507 self.reset_font_size = QtGui.QAction("Zoom &Reset",
508 self,
508 self,
509 shortcut="Ctrl+0",
509 shortcut="Ctrl+0",
510 triggered=self.reset_font_size_active_frontend
510 triggered=self.reset_font_size_active_frontend
511 )
511 )
512 self.add_menu_action(self.view_menu, self.reset_font_size, True)
512 self.add_menu_action(self.view_menu, self.reset_font_size, True)
513
513
514 self.view_menu.addSeparator()
514 self.view_menu.addSeparator()
515
515
516 self.clear_action = QtGui.QAction("&Clear Screen",
516 self.clear_action = QtGui.QAction("&Clear Screen",
517 self,
517 self,
518 shortcut='Ctrl+L',
518 shortcut='Ctrl+L',
519 statusTip="Clear the console",
519 statusTip="Clear the console",
520 triggered=self.clear_magic_active_frontend)
520 triggered=self.clear_magic_active_frontend)
521 self.add_menu_action(self.view_menu, self.clear_action)
521 self.add_menu_action(self.view_menu, self.clear_action)
522
522
523 def init_kernel_menu(self):
523 def init_kernel_menu(self):
524 self.kernel_menu = self.menuBar().addMenu("&Kernel")
524 self.kernel_menu = self.menuBar().addMenu("&Kernel")
525 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
525 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
526 # keep the signal shortcuts to ctrl, rather than
526 # keep the signal shortcuts to ctrl, rather than
527 # platform-default like we do elsewhere.
527 # platform-default like we do elsewhere.
528
528
529 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
529 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
530
530
531 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
531 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
532 self,
532 self,
533 triggered=self.interrupt_kernel_active_frontend,
533 triggered=self.interrupt_kernel_active_frontend,
534 shortcut=ctrl+"+C",
534 shortcut=ctrl+"+C",
535 )
535 )
536 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
536 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
537
537
538 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
538 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
539 self,
539 self,
540 triggered=self.restart_kernel_active_frontend,
540 triggered=self.restart_kernel_active_frontend,
541 shortcut=ctrl+"+.",
541 shortcut=ctrl+"+.",
542 )
542 )
543 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
543 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
544
544
545 self.kernel_menu.addSeparator()
545 self.kernel_menu.addSeparator()
546
546
547 def update_all_magic_menu(self):
548 # first define a callback which will get the list of all magic and put it in the menu.
549 def populate_all_magic_menu(val=None):
550 alm_magic_menu = self.magic_menu.addMenu("&All Magics...")
551 def make_dynamic_magic(i):
552 def inner_dynamic_magic():
553 self.active_frontend.execute(i)
554 inner_dynamic_magic.__name__ = "dynamics_magic_s"
555 return inner_dynamic_magic
556
557 for magic in eval(val):
558 pmagic = '%s%s'%('%',magic)
559 xaction = QtGui.QAction(pmagic,
560 self,
561 triggered=make_dynamic_magic(pmagic)
562 )
563 alm_magic_menu.addAction(xaction)
564 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()',populate_all_magic_menu)
565
547 def init_magic_menu(self):
566 def init_magic_menu(self):
548 self.magic_menu = self.menuBar().addMenu("&Magic")
567 self.magic_menu = self.menuBar().addMenu("&Magic")
549 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
568 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
550
569
570 self.pop = QtGui.QAction("&Populate All Magic Menu",
571 self,
572 statusTip="Clear all varible from workspace",
573 triggered=self.update_all_magic_menu)
574 self.add_menu_action(self.magic_menu, self.pop)
575
551 self.reset_action = QtGui.QAction("&Reset",
576 self.reset_action = QtGui.QAction("&Reset",
552 self,
577 self,
553 statusTip="Clear all varible from workspace",
578 statusTip="Clear all varible from workspace",
554 triggered=self.reset_magic_active_frontend)
579 triggered=self.reset_magic_active_frontend)
555 self.add_menu_action(self.magic_menu, self.reset_action)
580 self.add_menu_action(self.magic_menu, self.reset_action)
556
581
557 self.history_action = QtGui.QAction("&History",
582 self.history_action = QtGui.QAction("&History",
558 self,
583 self,
559 statusTip="show command history",
584 statusTip="show command history",
560 triggered=self.history_magic_active_frontend)
585 triggered=self.history_magic_active_frontend)
561 self.add_menu_action(self.magic_menu, self.history_action)
586 self.add_menu_action(self.magic_menu, self.history_action)
562
587
563 self.save_action = QtGui.QAction("E&xport History ",
588 self.save_action = QtGui.QAction("E&xport History ",
564 self,
589 self,
565 statusTip="Export History as Python File",
590 statusTip="Export History as Python File",
566 triggered=self.save_magic_active_frontend)
591 triggered=self.save_magic_active_frontend)
567 self.add_menu_action(self.magic_menu, self.save_action)
592 self.add_menu_action(self.magic_menu, self.save_action)
568
593
569 self.who_action = QtGui.QAction("&Who",
594 self.who_action = QtGui.QAction("&Who",
570 self,
595 self,
571 statusTip="List interactive variable",
596 statusTip="List interactive variable",
572 triggered=self.who_magic_active_frontend)
597 triggered=self.who_magic_active_frontend)
573 self.add_menu_action(self.magic_menu, self.who_action)
598 self.add_menu_action(self.magic_menu, self.who_action)
574
599
575 self.who_ls_action = QtGui.QAction("Wh&o ls",
600 self.who_ls_action = QtGui.QAction("Wh&o ls",
576 self,
601 self,
577 statusTip="Return a list of interactive variable",
602 statusTip="Return a list of interactive variable",
578 triggered=self.who_ls_magic_active_frontend)
603 triggered=self.who_ls_magic_active_frontend)
579 self.add_menu_action(self.magic_menu, self.who_ls_action)
604 self.add_menu_action(self.magic_menu, self.who_ls_action)
580
605
581 self.whos_action = QtGui.QAction("Who&s",
606 self.whos_action = QtGui.QAction("Who&s",
582 self,
607 self,
583 statusTip="List interactive variable with detail",
608 statusTip="List interactive variable with detail",
584 triggered=self.whos_magic_active_frontend)
609 triggered=self.whos_magic_active_frontend)
585 self.add_menu_action(self.magic_menu, self.whos_action)
610 self.add_menu_action(self.magic_menu, self.whos_action)
586
611
587 # allmagics submenu:
612 # allmagics submenu:
588
613
589 #for now this is just a copy and paste, but we should get this dynamically
614 #for now this is just a copy and paste, but we should get this dynamically
590 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
615 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
591 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
616 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
592 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
617 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
593 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
618 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
594 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
619 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
595 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
620 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
596 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
621 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
597 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
622 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
598 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
623 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
599 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
624 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
600
625
601 def make_dynamic_magic(i):
626 def make_dynamic_magic(i):
602 def inner_dynamic_magic():
627 def inner_dynamic_magic():
603 self.active_frontend.execute(i)
628 self.active_frontend.execute(i)
604 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
629 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
605 return inner_dynamic_magic
630 return inner_dynamic_magic
606
631
607 for magic in magiclist:
632 for magic in magiclist:
608 xaction = QtGui.QAction(magic,
633 xaction = QtGui.QAction(magic,
609 self,
634 self,
610 triggered=make_dynamic_magic(magic)
635 triggered=make_dynamic_magic(magic)
611 )
636 )
612 self.all_magic_menu.addAction(xaction)
637 self.all_magic_menu.addAction(xaction)
613
638
614 def init_window_menu(self):
639 def init_window_menu(self):
615 self.window_menu = self.menuBar().addMenu("&Window")
640 self.window_menu = self.menuBar().addMenu("&Window")
616 if sys.platform == 'darwin':
641 if sys.platform == 'darwin':
617 # add min/maximize actions to OSX, which lacks default bindings.
642 # add min/maximize actions to OSX, which lacks default bindings.
618 self.minimizeAct = QtGui.QAction("Mini&mize",
643 self.minimizeAct = QtGui.QAction("Mini&mize",
619 self,
644 self,
620 shortcut="Ctrl+m",
645 shortcut="Ctrl+m",
621 statusTip="Minimize the window/Restore Normal Size",
646 statusTip="Minimize the window/Restore Normal Size",
622 triggered=self.toggleMinimized)
647 triggered=self.toggleMinimized)
623 # maximize is called 'Zoom' on OSX for some reason
648 # maximize is called 'Zoom' on OSX for some reason
624 self.maximizeAct = QtGui.QAction("&Zoom",
649 self.maximizeAct = QtGui.QAction("&Zoom",
625 self,
650 self,
626 shortcut="Ctrl+Shift+M",
651 shortcut="Ctrl+Shift+M",
627 statusTip="Maximize the window/Restore Normal Size",
652 statusTip="Maximize the window/Restore Normal Size",
628 triggered=self.toggleMaximized)
653 triggered=self.toggleMaximized)
629
654
630 self.add_menu_action(self.window_menu, self.minimizeAct)
655 self.add_menu_action(self.window_menu, self.minimizeAct)
631 self.add_menu_action(self.window_menu, self.maximizeAct)
656 self.add_menu_action(self.window_menu, self.maximizeAct)
632 self.window_menu.addSeparator()
657 self.window_menu.addSeparator()
633
658
634 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
659 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
635 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
660 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
636 self,
661 self,
637 shortcut=prev_key,
662 shortcut=prev_key,
638 statusTip="Select previous tab",
663 statusTip="Select previous tab",
639 triggered=self.prev_tab)
664 triggered=self.prev_tab)
640 self.add_menu_action(self.window_menu, self.prev_tab_act)
665 self.add_menu_action(self.window_menu, self.prev_tab_act)
641
666
642 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
667 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
643 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
668 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
644 self,
669 self,
645 shortcut=next_key,
670 shortcut=next_key,
646 statusTip="Select next tab",
671 statusTip="Select next tab",
647 triggered=self.next_tab)
672 triggered=self.next_tab)
648 self.add_menu_action(self.window_menu, self.next_tab_act)
673 self.add_menu_action(self.window_menu, self.next_tab_act)
649
674
650 def init_help_menu(self):
675 def init_help_menu(self):
651 # please keep the Help menu in Mac Os even if empty. It will
676 # please keep the Help menu in Mac Os even if empty. It will
652 # automatically contain a search field to search inside menus and
677 # automatically contain a search field to search inside menus and
653 # please keep it spelled in English, as long as Qt Doesn't support
678 # please keep it spelled in English, as long as Qt Doesn't support
654 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
679 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
655 # this search field fonctionality
680 # this search field fonctionality
656
681
657 self.help_menu = self.menuBar().addMenu("&Help")
682 self.help_menu = self.menuBar().addMenu("&Help")
658
683
659
684
660 # Help Menu
685 # Help Menu
661
686
662 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
687 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
663 self,
688 self,
664 triggered=self.intro_active_frontend
689 triggered=self.intro_active_frontend
665 )
690 )
666 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
691 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
667
692
668 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
693 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
669 self,
694 self,
670 triggered=self.quickref_active_frontend
695 triggered=self.quickref_active_frontend
671 )
696 )
672 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
697 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
673
698
674 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
699 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
675 self,
700 self,
676 triggered=self.guiref_active_frontend
701 triggered=self.guiref_active_frontend
677 )
702 )
678 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
703 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
679
704
680 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
705 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
681 self,
706 self,
682 triggered=self._open_online_help)
707 triggered=self._open_online_help)
683 self.add_menu_action(self.help_menu, self.onlineHelpAct)
708 self.add_menu_action(self.help_menu, self.onlineHelpAct)
684
709
685 # minimize/maximize/fullscreen actions:
710 # minimize/maximize/fullscreen actions:
686
711
687 def toggle_menu_bar(self):
712 def toggle_menu_bar(self):
688 menu_bar = self.menuBar()
713 menu_bar = self.menuBar()
689 if menu_bar.isVisible():
714 if menu_bar.isVisible():
690 menu_bar.setVisible(False)
715 menu_bar.setVisible(False)
691 else:
716 else:
692 menu_bar.setVisible(True)
717 menu_bar.setVisible(True)
693
718
694 def toggleMinimized(self):
719 def toggleMinimized(self):
695 if not self.isMinimized():
720 if not self.isMinimized():
696 self.showMinimized()
721 self.showMinimized()
697 else:
722 else:
698 self.showNormal()
723 self.showNormal()
699
724
700 def _open_online_help(self):
725 def _open_online_help(self):
701 filename="http://ipython.org/ipython-doc/stable/index.html"
726 filename="http://ipython.org/ipython-doc/stable/index.html"
702 webbrowser.open(filename, new=1, autoraise=True)
727 webbrowser.open(filename, new=1, autoraise=True)
703
728
704 def toggleMaximized(self):
729 def toggleMaximized(self):
705 if not self.isMaximized():
730 if not self.isMaximized():
706 self.showMaximized()
731 self.showMaximized()
707 else:
732 else:
708 self.showNormal()
733 self.showNormal()
709
734
710 # Min/Max imizing while in full screen give a bug
735 # Min/Max imizing while in full screen give a bug
711 # when going out of full screen, at least on OSX
736 # when going out of full screen, at least on OSX
712 def toggleFullScreen(self):
737 def toggleFullScreen(self):
713 if not self.isFullScreen():
738 if not self.isFullScreen():
714 self.showFullScreen()
739 self.showFullScreen()
715 if sys.platform == 'darwin':
740 if sys.platform == 'darwin':
716 self.maximizeAct.setEnabled(False)
741 self.maximizeAct.setEnabled(False)
717 self.minimizeAct.setEnabled(False)
742 self.minimizeAct.setEnabled(False)
718 else:
743 else:
719 self.showNormal()
744 self.showNormal()
720 if sys.platform == 'darwin':
745 if sys.platform == 'darwin':
721 self.maximizeAct.setEnabled(True)
746 self.maximizeAct.setEnabled(True)
722 self.minimizeAct.setEnabled(True)
747 self.minimizeAct.setEnabled(True)
723
748
724 def close_active_frontend(self):
749 def close_active_frontend(self):
725 self.close_tab(self.active_frontend)
750 self.close_tab(self.active_frontend)
726
751
727 def restart_kernel_active_frontend(self):
752 def restart_kernel_active_frontend(self):
728 self.active_frontend.request_restart_kernel()
753 self.active_frontend.request_restart_kernel()
729
754
730 def interrupt_kernel_active_frontend(self):
755 def interrupt_kernel_active_frontend(self):
731 self.active_frontend.request_interrupt_kernel()
756 self.active_frontend.request_interrupt_kernel()
732
757
733 def cut_active_frontend(self):
758 def cut_active_frontend(self):
734 widget = self.active_frontend
759 widget = self.active_frontend
735 if widget.can_cut():
760 if widget.can_cut():
736 widget.cut()
761 widget.cut()
737
762
738 def copy_active_frontend(self):
763 def copy_active_frontend(self):
739 widget = self.active_frontend
764 widget = self.active_frontend
740 if widget.can_copy():
765 if widget.can_copy():
741 widget.copy()
766 widget.copy()
742
767
743 def copy_raw_active_frontend(self):
768 def copy_raw_active_frontend(self):
744 self.active_frontend._copy_raw_action.trigger()
769 self.active_frontend._copy_raw_action.trigger()
745
770
746 def paste_active_frontend(self):
771 def paste_active_frontend(self):
747 widget = self.active_frontend
772 widget = self.active_frontend
748 if widget.can_paste():
773 if widget.can_paste():
749 widget.paste()
774 widget.paste()
750
775
751 def undo_active_frontend(self):
776 def undo_active_frontend(self):
752 self.active_frontend.undo()
777 self.active_frontend.undo()
753
778
754 def redo_active_frontend(self):
779 def redo_active_frontend(self):
755 self.active_frontend.redo()
780 self.active_frontend.redo()
756
781
757 def reset_magic_active_frontend(self):
782 def reset_magic_active_frontend(self):
758 self.active_frontend.execute("%reset")
783 self.active_frontend.execute("%reset")
759
784
760 def history_magic_active_frontend(self):
785 def history_magic_active_frontend(self):
761 self.active_frontend.execute("%history")
786 self.active_frontend.execute("%history")
762
787
763 def save_magic_active_frontend(self):
788 def save_magic_active_frontend(self):
764 self.active_frontend.save_magic()
789 self.active_frontend.save_magic()
765
790
766 def clear_magic_active_frontend(self):
791 def clear_magic_active_frontend(self):
767 self.active_frontend.execute("%clear")
792 self.active_frontend.execute("%clear")
768
793
769 def who_magic_active_frontend(self):
794 def who_magic_active_frontend(self):
770 self.active_frontend.execute("%who")
795 self.active_frontend.execute("%who")
771
796
772 def who_ls_magic_active_frontend(self):
797 def who_ls_magic_active_frontend(self):
773 self.active_frontend.execute("%who_ls")
798 self.active_frontend.execute("%who_ls")
774
799
775 def whos_magic_active_frontend(self):
800 def whos_magic_active_frontend(self):
776 self.active_frontend.execute("%whos")
801 self.active_frontend.execute("%whos")
777
802
778 def print_action_active_frontend(self):
803 def print_action_active_frontend(self):
779 self.active_frontend.print_action.trigger()
804 self.active_frontend.print_action.trigger()
780
805
781 def export_action_active_frontend(self):
806 def export_action_active_frontend(self):
782 self.active_frontend.export_action.trigger()
807 self.active_frontend.export_action.trigger()
783
808
784 def select_all_active_frontend(self):
809 def select_all_active_frontend(self):
785 self.active_frontend.select_all_action.trigger()
810 self.active_frontend.select_all_action.trigger()
786
811
787 def increase_font_size_active_frontend(self):
812 def increase_font_size_active_frontend(self):
788 self.active_frontend.increase_font_size.trigger()
813 self.active_frontend.increase_font_size.trigger()
789
814
790 def decrease_font_size_active_frontend(self):
815 def decrease_font_size_active_frontend(self):
791 self.active_frontend.decrease_font_size.trigger()
816 self.active_frontend.decrease_font_size.trigger()
792
817
793 def reset_font_size_active_frontend(self):
818 def reset_font_size_active_frontend(self):
794 self.active_frontend.reset_font_size.trigger()
819 self.active_frontend.reset_font_size.trigger()
795
820
796 def guiref_active_frontend(self):
821 def guiref_active_frontend(self):
797 self.active_frontend.execute("%guiref")
822 self.active_frontend.execute("%guiref")
798
823
799 def intro_active_frontend(self):
824 def intro_active_frontend(self):
800 self.active_frontend.execute("?")
825 self.active_frontend.execute("?")
801
826
802 def quickref_active_frontend(self):
827 def quickref_active_frontend(self):
803 self.active_frontend.execute("%quickref")
828 self.active_frontend.execute("%quickref")
804 #---------------------------------------------------------------------------
829 #---------------------------------------------------------------------------
805 # QWidget interface
830 # QWidget interface
806 #---------------------------------------------------------------------------
831 #---------------------------------------------------------------------------
807
832
808 def closeEvent(self, event):
833 def closeEvent(self, event):
809 """ Forward the close event to every tabs contained by the windows
834 """ Forward the close event to every tabs contained by the windows
810 """
835 """
811 if self.tab_widget.count() == 0:
836 if self.tab_widget.count() == 0:
812 # no tabs, just close
837 # no tabs, just close
813 event.accept()
838 event.accept()
814 return
839 return
815 # Do Not loop on the widget count as it change while closing
840 # Do Not loop on the widget count as it change while closing
816 title = self.window().windowTitle()
841 title = self.window().windowTitle()
817 cancel = QtGui.QMessageBox.Cancel
842 cancel = QtGui.QMessageBox.Cancel
818 okay = QtGui.QMessageBox.Ok
843 okay = QtGui.QMessageBox.Ok
819
844
820 if self.confirm_exit:
845 if self.confirm_exit:
821 if self.tab_widget.count() > 1:
846 if self.tab_widget.count() > 1:
822 msg = "Close all tabs, stop all kernels, and Quit?"
847 msg = "Close all tabs, stop all kernels, and Quit?"
823 else:
848 else:
824 msg = "Close console, stop kernel, and Quit?"
849 msg = "Close console, stop kernel, and Quit?"
825 info = "Kernels not started here (e.g. notebooks) will be left alone."
850 info = "Kernels not started here (e.g. notebooks) will be left alone."
826 closeall = QtGui.QPushButton("&Yes, quit everything", self)
851 closeall = QtGui.QPushButton("&Yes, quit everything", self)
827 closeall.setShortcut('Y')
852 closeall.setShortcut('Y')
828 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
853 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
829 title, msg)
854 title, msg)
830 box.setInformativeText(info)
855 box.setInformativeText(info)
831 box.addButton(cancel)
856 box.addButton(cancel)
832 box.addButton(closeall, QtGui.QMessageBox.YesRole)
857 box.addButton(closeall, QtGui.QMessageBox.YesRole)
833 box.setDefaultButton(closeall)
858 box.setDefaultButton(closeall)
834 box.setEscapeButton(cancel)
859 box.setEscapeButton(cancel)
835 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
860 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
836 box.setIconPixmap(pixmap)
861 box.setIconPixmap(pixmap)
837 reply = box.exec_()
862 reply = box.exec_()
838 else:
863 else:
839 reply = okay
864 reply = okay
840
865
841 if reply == cancel:
866 if reply == cancel:
842 event.ignore()
867 event.ignore()
843 return
868 return
844 if reply == okay:
869 if reply == okay:
845 while self.tab_widget.count() >= 1:
870 while self.tab_widget.count() >= 1:
846 # prevent further confirmations:
871 # prevent further confirmations:
847 widget = self.active_frontend
872 widget = self.active_frontend
848 widget._confirm_exit = False
873 widget._confirm_exit = False
849 self.close_tab(widget)
874 self.close_tab(widget)
850 event.accept()
875 event.accept()
851
876
General Comments 0
You need to be logged in to leave comments. Login now