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