##// END OF EJS Templates
Merge pull request #956 from Carreau/all-magic-menu-live...
Fernando Perez -
r5419:8fa65442 merge
parent child Browse files
Show More
@@ -1,667 +1,728
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7
8
8 # System library imports
9 # System library imports
9 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
10 from IPython.external import qt
11 from IPython.external import qt
11 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
12
13
13 # Local imports
14 # Local imports
14 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
15 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
15 from IPython.core.oinspect import call_tip
16 from IPython.core.oinspect import call_tip
16 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
17 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
17 from IPython.utils.traitlets import Bool, Instance, Unicode
18 from IPython.utils.traitlets import Bool, Instance, Unicode
18 from bracket_matcher import BracketMatcher
19 from bracket_matcher import BracketMatcher
19 from call_tip_widget import CallTipWidget
20 from call_tip_widget import CallTipWidget
20 from completion_lexer import CompletionLexer
21 from completion_lexer import CompletionLexer
21 from history_console_widget import HistoryConsoleWidget
22 from history_console_widget import HistoryConsoleWidget
22 from pygments_highlighter import PygmentsHighlighter
23 from pygments_highlighter import PygmentsHighlighter
23
24
24
25
25 class FrontendHighlighter(PygmentsHighlighter):
26 class FrontendHighlighter(PygmentsHighlighter):
26 """ A PygmentsHighlighter that understands and ignores prompts.
27 """ A PygmentsHighlighter that understands and ignores prompts.
27 """
28 """
28
29
29 def __init__(self, frontend):
30 def __init__(self, frontend):
30 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 super(FrontendHighlighter, self).__init__(frontend._control.document())
31 self._current_offset = 0
32 self._current_offset = 0
32 self._frontend = frontend
33 self._frontend = frontend
33 self.highlighting_on = False
34 self.highlighting_on = False
34
35
35 def highlightBlock(self, string):
36 def highlightBlock(self, string):
36 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """ Highlight a block of text. Reimplemented to highlight selectively.
37 """
38 """
38 if not self.highlighting_on:
39 if not self.highlighting_on:
39 return
40 return
40
41
41 # The input to this function is a unicode string that may contain
42 # The input to this function is a unicode string that may contain
42 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
43 # the string as plain text so we can compare it.
44 # the string as plain text so we can compare it.
44 current_block = self.currentBlock()
45 current_block = self.currentBlock()
45 string = self._frontend._get_block_plain_text(current_block)
46 string = self._frontend._get_block_plain_text(current_block)
46
47
47 # Decide whether to check for the regular or continuation prompt.
48 # Decide whether to check for the regular or continuation prompt.
48 if current_block.contains(self._frontend._prompt_pos):
49 if current_block.contains(self._frontend._prompt_pos):
49 prompt = self._frontend._prompt
50 prompt = self._frontend._prompt
50 else:
51 else:
51 prompt = self._frontend._continuation_prompt
52 prompt = self._frontend._continuation_prompt
52
53
53 # Only highlight if we can identify a prompt, but make sure not to
54 # Only highlight if we can identify a prompt, but make sure not to
54 # highlight the prompt.
55 # highlight the prompt.
55 if string.startswith(prompt):
56 if string.startswith(prompt):
56 self._current_offset = len(prompt)
57 self._current_offset = len(prompt)
57 string = string[len(prompt):]
58 string = string[len(prompt):]
58 super(FrontendHighlighter, self).highlightBlock(string)
59 super(FrontendHighlighter, self).highlightBlock(string)
59
60
60 def rehighlightBlock(self, block):
61 def rehighlightBlock(self, block):
61 """ Reimplemented to temporarily enable highlighting if disabled.
62 """ Reimplemented to temporarily enable highlighting if disabled.
62 """
63 """
63 old = self.highlighting_on
64 old = self.highlighting_on
64 self.highlighting_on = True
65 self.highlighting_on = True
65 super(FrontendHighlighter, self).rehighlightBlock(block)
66 super(FrontendHighlighter, self).rehighlightBlock(block)
66 self.highlighting_on = old
67 self.highlighting_on = old
67
68
68 def setFormat(self, start, count, format):
69 def setFormat(self, start, count, format):
69 """ Reimplemented to highlight selectively.
70 """ Reimplemented to highlight selectively.
70 """
71 """
71 start += self._current_offset
72 start += self._current_offset
72 super(FrontendHighlighter, self).setFormat(start, count, format)
73 super(FrontendHighlighter, self).setFormat(start, count, format)
73
74
74
75
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 """ A Qt frontend for a generic Python kernel.
77 """ A Qt frontend for a generic Python kernel.
77 """
78 """
78
79
79 # The text to show when the kernel is (re)started.
80 # The text to show when the kernel is (re)started.
80 banner = Unicode()
81 banner = Unicode()
81
82
82 # An option and corresponding signal for overriding the default kernel
83 # An option and corresponding signal for overriding the default kernel
83 # interrupt behavior.
84 # interrupt behavior.
84 custom_interrupt = Bool(False)
85 custom_interrupt = Bool(False)
85 custom_interrupt_requested = QtCore.Signal()
86 custom_interrupt_requested = QtCore.Signal()
86
87
87 # An option and corresponding signals for overriding the default kernel
88 # An option and corresponding signals for overriding the default kernel
88 # restart behavior.
89 # restart behavior.
89 custom_restart = Bool(False)
90 custom_restart = Bool(False)
90 custom_restart_kernel_died = QtCore.Signal(float)
91 custom_restart_kernel_died = QtCore.Signal(float)
91 custom_restart_requested = QtCore.Signal()
92 custom_restart_requested = QtCore.Signal()
92
93
93 # Whether to automatically show calltips on open-parentheses.
94 # Whether to automatically show calltips on open-parentheses.
94 enable_calltips = Bool(True, config=True,
95 enable_calltips = Bool(True, config=True,
95 help="Whether to draw information calltips on open-parentheses.")
96 help="Whether to draw information calltips on open-parentheses.")
96
97
97 # Emitted when a user visible 'execute_request' has been submitted to the
98 # Emitted when a user visible 'execute_request' has been submitted to the
98 # kernel from the FrontendWidget. Contains the code to be executed.
99 # kernel from the FrontendWidget. Contains the code to be executed.
99 executing = QtCore.Signal(object)
100 executing = QtCore.Signal(object)
100
101
101 # Emitted when a user-visible 'execute_reply' has been received from the
102 # Emitted when a user-visible 'execute_reply' has been received from the
102 # kernel and processed by the FrontendWidget. Contains the response message.
103 # kernel and processed by the FrontendWidget. Contains the response message.
103 executed = QtCore.Signal(object)
104 executed = QtCore.Signal(object)
104
105
105 # Emitted when an exit request has been received from the kernel.
106 # Emitted when an exit request has been received from the kernel.
106 exit_requested = QtCore.Signal(object)
107 exit_requested = QtCore.Signal(object)
107
108
108 # Protected class variables.
109 # Protected class variables.
109 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
110 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
110 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
111 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
111 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
112 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
112 _input_splitter_class = InputSplitter
113 _input_splitter_class = InputSplitter
113 _local_kernel = False
114 _local_kernel = False
114 _highlighter = Instance(FrontendHighlighter)
115 _highlighter = Instance(FrontendHighlighter)
115
116
116 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
117 # 'object' interface
118 # 'object' interface
118 #---------------------------------------------------------------------------
119 #---------------------------------------------------------------------------
119
120
120 def __init__(self, *args, **kw):
121 def __init__(self, *args, **kw):
121 super(FrontendWidget, self).__init__(*args, **kw)
122 super(FrontendWidget, self).__init__(*args, **kw)
122 # FIXME: remove this when PySide min version is updated past 1.0.7
123 # FIXME: remove this when PySide min version is updated past 1.0.7
123 # forcefully disable calltips if PySide is < 1.0.7, because they crash
124 # forcefully disable calltips if PySide is < 1.0.7, because they crash
124 if qt.QT_API == qt.QT_API_PYSIDE:
125 if qt.QT_API == qt.QT_API_PYSIDE:
125 import PySide
126 import PySide
126 if PySide.__version_info__ < (1,0,7):
127 if PySide.__version_info__ < (1,0,7):
127 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
128 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
128 self.enable_calltips = False
129 self.enable_calltips = False
129
130
130 # FrontendWidget protected variables.
131 # FrontendWidget protected variables.
131 self._bracket_matcher = BracketMatcher(self._control)
132 self._bracket_matcher = BracketMatcher(self._control)
132 self._call_tip_widget = CallTipWidget(self._control)
133 self._call_tip_widget = CallTipWidget(self._control)
133 self._completion_lexer = CompletionLexer(PythonLexer())
134 self._completion_lexer = CompletionLexer(PythonLexer())
134 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
135 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
135 self._hidden = False
136 self._hidden = False
136 self._highlighter = FrontendHighlighter(self)
137 self._highlighter = FrontendHighlighter(self)
137 self._input_splitter = self._input_splitter_class(input_mode='cell')
138 self._input_splitter = self._input_splitter_class(input_mode='cell')
138 self._kernel_manager = None
139 self._kernel_manager = None
139 self._request_info = {}
140 self._request_info = {}
141 self._callback_dict = {}
140
142
141 # Configure the ConsoleWidget.
143 # Configure the ConsoleWidget.
142 self.tab_width = 4
144 self.tab_width = 4
143 self._set_continuation_prompt('... ')
145 self._set_continuation_prompt('... ')
144
146
145 # Configure the CallTipWidget.
147 # Configure the CallTipWidget.
146 self._call_tip_widget.setFont(self.font)
148 self._call_tip_widget.setFont(self.font)
147 self.font_changed.connect(self._call_tip_widget.setFont)
149 self.font_changed.connect(self._call_tip_widget.setFont)
148
150
149 # Configure actions.
151 # Configure actions.
150 action = self._copy_raw_action
152 action = self._copy_raw_action
151 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
153 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
152 action.setEnabled(False)
154 action.setEnabled(False)
153 action.setShortcut(QtGui.QKeySequence(key))
155 action.setShortcut(QtGui.QKeySequence(key))
154 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
156 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
155 action.triggered.connect(self.copy_raw)
157 action.triggered.connect(self.copy_raw)
156 self.copy_available.connect(action.setEnabled)
158 self.copy_available.connect(action.setEnabled)
157 self.addAction(action)
159 self.addAction(action)
158
160
159 # Connect signal handlers.
161 # Connect signal handlers.
160 document = self._control.document()
162 document = self._control.document()
161 document.contentsChange.connect(self._document_contents_change)
163 document.contentsChange.connect(self._document_contents_change)
162
164
163 # Set flag for whether we are connected via localhost.
165 # Set flag for whether we are connected via localhost.
164 self._local_kernel = kw.get('local_kernel',
166 self._local_kernel = kw.get('local_kernel',
165 FrontendWidget._local_kernel)
167 FrontendWidget._local_kernel)
166
168
167 #---------------------------------------------------------------------------
169 #---------------------------------------------------------------------------
168 # 'ConsoleWidget' public interface
170 # 'ConsoleWidget' public interface
169 #---------------------------------------------------------------------------
171 #---------------------------------------------------------------------------
170
172
171 def copy(self):
173 def copy(self):
172 """ Copy the currently selected text to the clipboard, removing prompts.
174 """ Copy the currently selected text to the clipboard, removing prompts.
173 """
175 """
174 text = self._control.textCursor().selection().toPlainText()
176 text = self._control.textCursor().selection().toPlainText()
175 if text:
177 if text:
176 lines = map(transform_classic_prompt, text.splitlines())
178 lines = map(transform_classic_prompt, text.splitlines())
177 text = '\n'.join(lines)
179 text = '\n'.join(lines)
178 QtGui.QApplication.clipboard().setText(text)
180 QtGui.QApplication.clipboard().setText(text)
179
181
180 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
181 # 'ConsoleWidget' abstract interface
183 # 'ConsoleWidget' abstract interface
182 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
183
185
184 def _is_complete(self, source, interactive):
186 def _is_complete(self, source, interactive):
185 """ Returns whether 'source' can be completely processed and a new
187 """ Returns whether 'source' can be completely processed and a new
186 prompt created. When triggered by an Enter/Return key press,
188 prompt created. When triggered by an Enter/Return key press,
187 'interactive' is True; otherwise, it is False.
189 'interactive' is True; otherwise, it is False.
188 """
190 """
189 complete = self._input_splitter.push(source)
191 complete = self._input_splitter.push(source)
190 if interactive:
192 if interactive:
191 complete = not self._input_splitter.push_accepts_more()
193 complete = not self._input_splitter.push_accepts_more()
192 return complete
194 return complete
193
195
194 def _execute(self, source, hidden):
196 def _execute(self, source, hidden):
195 """ Execute 'source'. If 'hidden', do not show any output.
197 """ Execute 'source'. If 'hidden', do not show any output.
196
198
197 See parent class :meth:`execute` docstring for full details.
199 See parent class :meth:`execute` docstring for full details.
198 """
200 """
199 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
201 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
200 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
202 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
201 self._hidden = hidden
203 self._hidden = hidden
202 if not hidden:
204 if not hidden:
203 self.executing.emit(source)
205 self.executing.emit(source)
204
206
205 def _prompt_started_hook(self):
207 def _prompt_started_hook(self):
206 """ Called immediately after a new prompt is displayed.
208 """ Called immediately after a new prompt is displayed.
207 """
209 """
208 if not self._reading:
210 if not self._reading:
209 self._highlighter.highlighting_on = True
211 self._highlighter.highlighting_on = True
210
212
211 def _prompt_finished_hook(self):
213 def _prompt_finished_hook(self):
212 """ Called immediately after a prompt is finished, i.e. when some input
214 """ Called immediately after a prompt is finished, i.e. when some input
213 will be processed and a new prompt displayed.
215 will be processed and a new prompt displayed.
214 """
216 """
215 # Flush all state from the input splitter so the next round of
217 # Flush all state from the input splitter so the next round of
216 # reading input starts with a clean buffer.
218 # reading input starts with a clean buffer.
217 self._input_splitter.reset()
219 self._input_splitter.reset()
218
220
219 if not self._reading:
221 if not self._reading:
220 self._highlighter.highlighting_on = False
222 self._highlighter.highlighting_on = False
221
223
222 def _tab_pressed(self):
224 def _tab_pressed(self):
223 """ Called when the tab key is pressed. Returns whether to continue
225 """ Called when the tab key is pressed. Returns whether to continue
224 processing the event.
226 processing the event.
225 """
227 """
226 # Perform tab completion if:
228 # Perform tab completion if:
227 # 1) The cursor is in the input buffer.
229 # 1) The cursor is in the input buffer.
228 # 2) There is a non-whitespace character before the cursor.
230 # 2) There is a non-whitespace character before the cursor.
229 text = self._get_input_buffer_cursor_line()
231 text = self._get_input_buffer_cursor_line()
230 if text is None:
232 if text is None:
231 return False
233 return False
232 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
234 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
233 if complete:
235 if complete:
234 self._complete()
236 self._complete()
235 return not complete
237 return not complete
236
238
237 #---------------------------------------------------------------------------
239 #---------------------------------------------------------------------------
238 # 'ConsoleWidget' protected interface
240 # 'ConsoleWidget' protected interface
239 #---------------------------------------------------------------------------
241 #---------------------------------------------------------------------------
240
242
241 def _context_menu_make(self, pos):
243 def _context_menu_make(self, pos):
242 """ Reimplemented to add an action for raw copy.
244 """ Reimplemented to add an action for raw copy.
243 """
245 """
244 menu = super(FrontendWidget, self)._context_menu_make(pos)
246 menu = super(FrontendWidget, self)._context_menu_make(pos)
245 for before_action in menu.actions():
247 for before_action in menu.actions():
246 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
248 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
247 QtGui.QKeySequence.ExactMatch:
249 QtGui.QKeySequence.ExactMatch:
248 menu.insertAction(before_action, self._copy_raw_action)
250 menu.insertAction(before_action, self._copy_raw_action)
249 break
251 break
250 return menu
252 return menu
251
253
252 def request_interrupt_kernel(self):
254 def request_interrupt_kernel(self):
253 if self._executing:
255 if self._executing:
254 self.interrupt_kernel()
256 self.interrupt_kernel()
255
257
256 def request_restart_kernel(self):
258 def request_restart_kernel(self):
257 message = 'Are you sure you want to restart the kernel?'
259 message = 'Are you sure you want to restart the kernel?'
258 self.restart_kernel(message, now=False)
260 self.restart_kernel(message, now=False)
259
261
260 def _event_filter_console_keypress(self, event):
262 def _event_filter_console_keypress(self, event):
261 """ Reimplemented for execution interruption and smart backspace.
263 """ Reimplemented for execution interruption and smart backspace.
262 """
264 """
263 key = event.key()
265 key = event.key()
264 if self._control_key_down(event.modifiers(), include_command=False):
266 if self._control_key_down(event.modifiers(), include_command=False):
265
267
266 if key == QtCore.Qt.Key_C and self._executing:
268 if key == QtCore.Qt.Key_C and self._executing:
267 self.request_interrupt_kernel()
269 self.request_interrupt_kernel()
268 return True
270 return True
269
271
270 elif key == QtCore.Qt.Key_Period:
272 elif key == QtCore.Qt.Key_Period:
271 self.request_restart_kernel()
273 self.request_restart_kernel()
272 return True
274 return True
273
275
274 elif not event.modifiers() & QtCore.Qt.AltModifier:
276 elif not event.modifiers() & QtCore.Qt.AltModifier:
275
277
276 # Smart backspace: remove four characters in one backspace if:
278 # Smart backspace: remove four characters in one backspace if:
277 # 1) everything left of the cursor is whitespace
279 # 1) everything left of the cursor is whitespace
278 # 2) the four characters immediately left of the cursor are spaces
280 # 2) the four characters immediately left of the cursor are spaces
279 if key == QtCore.Qt.Key_Backspace:
281 if key == QtCore.Qt.Key_Backspace:
280 col = self._get_input_buffer_cursor_column()
282 col = self._get_input_buffer_cursor_column()
281 cursor = self._control.textCursor()
283 cursor = self._control.textCursor()
282 if col > 3 and not cursor.hasSelection():
284 if col > 3 and not cursor.hasSelection():
283 text = self._get_input_buffer_cursor_line()[:col]
285 text = self._get_input_buffer_cursor_line()[:col]
284 if text.endswith(' ') and not text.strip():
286 if text.endswith(' ') and not text.strip():
285 cursor.movePosition(QtGui.QTextCursor.Left,
287 cursor.movePosition(QtGui.QTextCursor.Left,
286 QtGui.QTextCursor.KeepAnchor, 4)
288 QtGui.QTextCursor.KeepAnchor, 4)
287 cursor.removeSelectedText()
289 cursor.removeSelectedText()
288 return True
290 return True
289
291
290 return super(FrontendWidget, self)._event_filter_console_keypress(event)
292 return super(FrontendWidget, self)._event_filter_console_keypress(event)
291
293
292 def _insert_continuation_prompt(self, cursor):
294 def _insert_continuation_prompt(self, cursor):
293 """ Reimplemented for auto-indentation.
295 """ Reimplemented for auto-indentation.
294 """
296 """
295 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
297 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
296 cursor.insertText(' ' * self._input_splitter.indent_spaces)
298 cursor.insertText(' ' * self._input_splitter.indent_spaces)
297
299
298 #---------------------------------------------------------------------------
300 #---------------------------------------------------------------------------
299 # 'BaseFrontendMixin' abstract interface
301 # 'BaseFrontendMixin' abstract interface
300 #---------------------------------------------------------------------------
302 #---------------------------------------------------------------------------
301
303
302 def _handle_complete_reply(self, rep):
304 def _handle_complete_reply(self, rep):
303 """ Handle replies for tab completion.
305 """ Handle replies for tab completion.
304 """
306 """
305 self.log.debug("complete: %s", rep.get('content', ''))
307 self.log.debug("complete: %s", rep.get('content', ''))
306 cursor = self._get_cursor()
308 cursor = self._get_cursor()
307 info = self._request_info.get('complete')
309 info = self._request_info.get('complete')
308 if info and info.id == rep['parent_header']['msg_id'] and \
310 if info and info.id == rep['parent_header']['msg_id'] and \
309 info.pos == cursor.position():
311 info.pos == cursor.position():
310 text = '.'.join(self._get_context())
312 text = '.'.join(self._get_context())
311 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
313 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
312 self._complete_with_items(cursor, rep['content']['matches'])
314 self._complete_with_items(cursor, rep['content']['matches'])
313
315
316 def _silent_exec_callback(self, expr, callback):
317 """Silently execute `expr` in the kernel and call `callback` with reply
318
319 the `expr` is evaluated silently in the kernel (without) output in
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`
330
331 The `callback` is called with the 'repr()' of the result of `expr` as
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
338 """
339
340 # generate uuid, which would be used as a indication of wether or not
341 # the unique request originate from here (can use msg id ?)
342 local_uuid = str(uuid.uuid1())
343 msg_id = self.kernel_manager.shell_channel.execute('',
344 silent=True, user_expressions={ local_uuid:expr })
345 self._callback_dict[local_uuid] = callback
346 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
347
348 def _handle_exec_callback(self, msg):
349 """Execute `callback` corresonding to `msg` reply, after ``_silent_exec_callback``
350
351 Parameters
352 ----------
353 msg : raw message send by the kernel containing an `user_expressions`
354 and having a 'silent_exec_callback' kind.
355
356 Notes
357 -----
358 This fonction will look for a `callback` associated with the
359 corresponding message id. Association has been made by
360 `_silent_exec_callback`. `callback` is then called with the `repr()`
361 of the value of corresponding `user_expressions` as argument.
362 `callback` is then removed from the known list so that any message
363 coming again with the same id won't trigger it.
364
365 """
366
367 user_exp = msg['content']['user_expressions']
368 for expression in user_exp:
369 if expression in self._callback_dict:
370 self._callback_dict.pop(expression)(user_exp[expression])
371
314 def _handle_execute_reply(self, msg):
372 def _handle_execute_reply(self, msg):
315 """ Handles replies for code execution.
373 """ Handles replies for code execution.
316 """
374 """
317 self.log.debug("execute: %s", msg.get('content', ''))
375 self.log.debug("execute: %s", msg.get('content', ''))
318 info = self._request_info.get('execute')
376 info = self._request_info.get('execute')
319 # unset reading flag, because if execute finished, raw_input can't
377 # unset reading flag, because if execute finished, raw_input can't
320 # still be pending.
378 # still be pending.
321 self._reading = False
379 self._reading = False
322 if info and info.id == msg['parent_header']['msg_id'] and \
380 if info and info.id == msg['parent_header']['msg_id'] and \
323 info.kind == 'user' and not self._hidden:
381 info.kind == 'user' and not self._hidden:
324 # 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
325 # before writing a new prompt.
383 # before writing a new prompt.
326 self.kernel_manager.sub_channel.flush()
384 self.kernel_manager.sub_channel.flush()
327
385
328 # Reset the ANSI style information to prevent bad text in stdout
386 # Reset the ANSI style information to prevent bad text in stdout
329 # from messing up our colors. We're not a true terminal so we're
387 # from messing up our colors. We're not a true terminal so we're
330 # allowed to do this.
388 # allowed to do this.
331 if self.ansi_codes:
389 if self.ansi_codes:
332 self._ansi_processor.reset_sgr()
390 self._ansi_processor.reset_sgr()
333
391
334 content = msg['content']
392 content = msg['content']
335 status = content['status']
393 status = content['status']
336 if status == 'ok':
394 if status == 'ok':
337 self._process_execute_ok(msg)
395 self._process_execute_ok(msg)
338 elif status == 'error':
396 elif status == 'error':
339 self._process_execute_error(msg)
397 self._process_execute_error(msg)
340 elif status == 'aborted':
398 elif status == 'aborted':
341 self._process_execute_abort(msg)
399 self._process_execute_abort(msg)
342
400
343 self._show_interpreter_prompt_for_reply(msg)
401 self._show_interpreter_prompt_for_reply(msg)
344 self.executed.emit(msg)
402 self.executed.emit(msg)
403 elif info and info.id == msg['parent_header']['msg_id'] and \
404 info.kind == 'silent_exec_callback' and not self._hidden:
405 self._handle_exec_callback(msg)
345 else:
406 else:
346 super(FrontendWidget, self)._handle_execute_reply(msg)
407 super(FrontendWidget, self)._handle_execute_reply(msg)
347
408
348 def _handle_input_request(self, msg):
409 def _handle_input_request(self, msg):
349 """ Handle requests for raw_input.
410 """ Handle requests for raw_input.
350 """
411 """
351 self.log.debug("input: %s", msg.get('content', ''))
412 self.log.debug("input: %s", msg.get('content', ''))
352 if self._hidden:
413 if self._hidden:
353 raise RuntimeError('Request for raw input during hidden execution.')
414 raise RuntimeError('Request for raw input during hidden execution.')
354
415
355 # 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
356 # before entering readline mode.
417 # before entering readline mode.
357 self.kernel_manager.sub_channel.flush()
418 self.kernel_manager.sub_channel.flush()
358
419
359 def callback(line):
420 def callback(line):
360 self.kernel_manager.stdin_channel.input(line)
421 self.kernel_manager.stdin_channel.input(line)
361 if self._reading:
422 if self._reading:
362 self.log.debug("Got second input request, assuming first was interrupted.")
423 self.log.debug("Got second input request, assuming first was interrupted.")
363 self._reading = False
424 self._reading = False
364 self._readline(msg['content']['prompt'], callback=callback)
425 self._readline(msg['content']['prompt'], callback=callback)
365
426
366 def _handle_kernel_died(self, since_last_heartbeat):
427 def _handle_kernel_died(self, since_last_heartbeat):
367 """ 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.
368 """
429 """
369 self.log.debug("kernel died: %s", since_last_heartbeat)
430 self.log.debug("kernel died: %s", since_last_heartbeat)
370 if self.custom_restart:
431 if self.custom_restart:
371 self.custom_restart_kernel_died.emit(since_last_heartbeat)
432 self.custom_restart_kernel_died.emit(since_last_heartbeat)
372 else:
433 else:
373 message = 'The kernel heartbeat has been inactive for %.2f ' \
434 message = 'The kernel heartbeat has been inactive for %.2f ' \
374 'seconds. Do you want to restart the kernel? You may ' \
435 'seconds. Do you want to restart the kernel? You may ' \
375 'first want to check the network connection.' % \
436 'first want to check the network connection.' % \
376 since_last_heartbeat
437 since_last_heartbeat
377 self.restart_kernel(message, now=True)
438 self.restart_kernel(message, now=True)
378
439
379 def _handle_object_info_reply(self, rep):
440 def _handle_object_info_reply(self, rep):
380 """ Handle replies for call tips.
441 """ Handle replies for call tips.
381 """
442 """
382 self.log.debug("oinfo: %s", rep.get('content', ''))
443 self.log.debug("oinfo: %s", rep.get('content', ''))
383 cursor = self._get_cursor()
444 cursor = self._get_cursor()
384 info = self._request_info.get('call_tip')
445 info = self._request_info.get('call_tip')
385 if info and info.id == rep['parent_header']['msg_id'] and \
446 if info and info.id == rep['parent_header']['msg_id'] and \
386 info.pos == cursor.position():
447 info.pos == cursor.position():
387 # 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
388 # 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
389 # syntax-highlight it ourselves for nicer formatting in the
450 # syntax-highlight it ourselves for nicer formatting in the
390 # calltip.
451 # calltip.
391 content = rep['content']
452 content = rep['content']
392 # if this is from pykernel, 'docstring' will be the only key
453 # if this is from pykernel, 'docstring' will be the only key
393 if content.get('ismagic', False):
454 if content.get('ismagic', False):
394 # Don't generate a call-tip for magics. Ideally, we should
455 # Don't generate a call-tip for magics. Ideally, we should
395 # generate a tooltip, but not on ( like we do for actual
456 # generate a tooltip, but not on ( like we do for actual
396 # callables.
457 # callables.
397 call_info, doc = None, None
458 call_info, doc = None, None
398 else:
459 else:
399 call_info, doc = call_tip(content, format_call=True)
460 call_info, doc = call_tip(content, format_call=True)
400 if call_info or doc:
461 if call_info or doc:
401 self._call_tip_widget.show_call_info(call_info, doc)
462 self._call_tip_widget.show_call_info(call_info, doc)
402
463
403 def _handle_pyout(self, msg):
464 def _handle_pyout(self, msg):
404 """ Handle display hook output.
465 """ Handle display hook output.
405 """
466 """
406 self.log.debug("pyout: %s", msg.get('content', ''))
467 self.log.debug("pyout: %s", msg.get('content', ''))
407 if not self._hidden and self._is_from_this_session(msg):
468 if not self._hidden and self._is_from_this_session(msg):
408 text = msg['content']['data']
469 text = msg['content']['data']
409 self._append_plain_text(text + '\n', before_prompt=True)
470 self._append_plain_text(text + '\n', before_prompt=True)
410
471
411 def _handle_stream(self, msg):
472 def _handle_stream(self, msg):
412 """ Handle stdout, stderr, and stdin.
473 """ Handle stdout, stderr, and stdin.
413 """
474 """
414 self.log.debug("stream: %s", msg.get('content', ''))
475 self.log.debug("stream: %s", msg.get('content', ''))
415 if not self._hidden and self._is_from_this_session(msg):
476 if not self._hidden and self._is_from_this_session(msg):
416 # Most consoles treat tabs as being 8 space characters. Convert tabs
477 # Most consoles treat tabs as being 8 space characters. Convert tabs
417 # to spaces so that output looks as expected regardless of this
478 # to spaces so that output looks as expected regardless of this
418 # widget's tab width.
479 # widget's tab width.
419 text = msg['content']['data'].expandtabs(8)
480 text = msg['content']['data'].expandtabs(8)
420
481
421 self._append_plain_text(text, before_prompt=True)
482 self._append_plain_text(text, before_prompt=True)
422 self._control.moveCursor(QtGui.QTextCursor.End)
483 self._control.moveCursor(QtGui.QTextCursor.End)
423
484
424 def _handle_shutdown_reply(self, msg):
485 def _handle_shutdown_reply(self, msg):
425 """ Handle shutdown signal, only if from other console.
486 """ Handle shutdown signal, only if from other console.
426 """
487 """
427 self.log.debug("shutdown: %s", msg.get('content', ''))
488 self.log.debug("shutdown: %s", msg.get('content', ''))
428 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):
429 if self._local_kernel:
490 if self._local_kernel:
430 if not msg['content']['restart']:
491 if not msg['content']['restart']:
431 self.exit_requested.emit(self)
492 self.exit_requested.emit(self)
432 else:
493 else:
433 # we just got notified of a restart!
494 # we just got notified of a restart!
434 time.sleep(0.25) # wait 1/4 sec to reset
495 time.sleep(0.25) # wait 1/4 sec to reset
435 # lest the request for a new prompt
496 # lest the request for a new prompt
436 # goes to the old kernel
497 # goes to the old kernel
437 self.reset()
498 self.reset()
438 else: # remote kernel, prompt on Kernel shutdown/reset
499 else: # remote kernel, prompt on Kernel shutdown/reset
439 title = self.window().windowTitle()
500 title = self.window().windowTitle()
440 if not msg['content']['restart']:
501 if not msg['content']['restart']:
441 reply = QtGui.QMessageBox.question(self, title,
502 reply = QtGui.QMessageBox.question(self, title,
442 "Kernel has been shutdown permanently. "
503 "Kernel has been shutdown permanently. "
443 "Close the Console?",
504 "Close the Console?",
444 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
505 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
445 if reply == QtGui.QMessageBox.Yes:
506 if reply == QtGui.QMessageBox.Yes:
446 self.exit_requested.emit(self)
507 self.exit_requested.emit(self)
447 else:
508 else:
448 reply = QtGui.QMessageBox.question(self, title,
509 reply = QtGui.QMessageBox.question(self, title,
449 "Kernel has been reset. Clear the Console?",
510 "Kernel has been reset. Clear the Console?",
450 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
511 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
451 if reply == QtGui.QMessageBox.Yes:
512 if reply == QtGui.QMessageBox.Yes:
452 time.sleep(0.25) # wait 1/4 sec to reset
513 time.sleep(0.25) # wait 1/4 sec to reset
453 # lest the request for a new prompt
514 # lest the request for a new prompt
454 # goes to the old kernel
515 # goes to the old kernel
455 self.reset()
516 self.reset()
456
517
457 def _started_channels(self):
518 def _started_channels(self):
458 """ Called when the KernelManager channels have started listening or
519 """ Called when the KernelManager channels have started listening or
459 when the frontend is assigned an already listening KernelManager.
520 when the frontend is assigned an already listening KernelManager.
460 """
521 """
461 self.reset()
522 self.reset()
462
523
463 #---------------------------------------------------------------------------
524 #---------------------------------------------------------------------------
464 # 'FrontendWidget' public interface
525 # 'FrontendWidget' public interface
465 #---------------------------------------------------------------------------
526 #---------------------------------------------------------------------------
466
527
467 def copy_raw(self):
528 def copy_raw(self):
468 """ Copy the currently selected text to the clipboard without attempting
529 """ Copy the currently selected text to the clipboard without attempting
469 to remove prompts or otherwise alter the text.
530 to remove prompts or otherwise alter the text.
470 """
531 """
471 self._control.copy()
532 self._control.copy()
472
533
473 def execute_file(self, path, hidden=False):
534 def execute_file(self, path, hidden=False):
474 """ Attempts to execute file with 'path'. If 'hidden', no output is
535 """ Attempts to execute file with 'path'. If 'hidden', no output is
475 shown.
536 shown.
476 """
537 """
477 self.execute('execfile(%r)' % path, hidden=hidden)
538 self.execute('execfile(%r)' % path, hidden=hidden)
478
539
479 def interrupt_kernel(self):
540 def interrupt_kernel(self):
480 """ Attempts to interrupt the running kernel.
541 """ Attempts to interrupt the running kernel.
481
542
482 Also unsets _reading flag, to avoid runtime errors
543 Also unsets _reading flag, to avoid runtime errors
483 if raw_input is called again.
544 if raw_input is called again.
484 """
545 """
485 if self.custom_interrupt:
546 if self.custom_interrupt:
486 self._reading = False
547 self._reading = False
487 self.custom_interrupt_requested.emit()
548 self.custom_interrupt_requested.emit()
488 elif self.kernel_manager.has_kernel:
549 elif self.kernel_manager.has_kernel:
489 self._reading = False
550 self._reading = False
490 self.kernel_manager.interrupt_kernel()
551 self.kernel_manager.interrupt_kernel()
491 else:
552 else:
492 self._append_plain_text('Kernel process is either remote or '
553 self._append_plain_text('Kernel process is either remote or '
493 'unspecified. Cannot interrupt.\n')
554 'unspecified. Cannot interrupt.\n')
494
555
495 def reset(self):
556 def reset(self):
496 """ Resets the widget to its initial state. Similar to ``clear``, but
557 """ Resets the widget to its initial state. Similar to ``clear``, but
497 also re-writes the banner and aborts execution if necessary.
558 also re-writes the banner and aborts execution if necessary.
498 """
559 """
499 if self._executing:
560 if self._executing:
500 self._executing = False
561 self._executing = False
501 self._request_info['execute'] = None
562 self._request_info['execute'] = None
502 self._reading = False
563 self._reading = False
503 self._highlighter.highlighting_on = False
564 self._highlighter.highlighting_on = False
504
565
505 self._control.clear()
566 self._control.clear()
506 self._append_plain_text(self.banner)
567 self._append_plain_text(self.banner)
507 # update output marker for stdout/stderr, so that startup
568 # update output marker for stdout/stderr, so that startup
508 # messages appear after banner:
569 # messages appear after banner:
509 self._append_before_prompt_pos = self._get_cursor().position()
570 self._append_before_prompt_pos = self._get_cursor().position()
510 self._show_interpreter_prompt()
571 self._show_interpreter_prompt()
511
572
512 def restart_kernel(self, message, now=False):
573 def restart_kernel(self, message, now=False):
513 """ Attempts to restart the running kernel.
574 """ Attempts to restart the running kernel.
514 """
575 """
515 # 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
516 # 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
517 # 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
518 # 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
519 # enough Qt to go implementing the checkbox now.
580 # enough Qt to go implementing the checkbox now.
520
581
521 if self.custom_restart:
582 if self.custom_restart:
522 self.custom_restart_requested.emit()
583 self.custom_restart_requested.emit()
523
584
524 elif self.kernel_manager.has_kernel:
585 elif self.kernel_manager.has_kernel:
525 # Pause the heart beat channel to prevent further warnings.
586 # Pause the heart beat channel to prevent further warnings.
526 self.kernel_manager.hb_channel.pause()
587 self.kernel_manager.hb_channel.pause()
527
588
528 # 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
529 # they decline. (If they accept, the heartbeat will be un-paused
590 # they decline. (If they accept, the heartbeat will be un-paused
530 # automatically when the kernel is restarted.)
591 # automatically when the kernel is restarted.)
531 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
592 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
532 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
593 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
533 message, buttons)
594 message, buttons)
534 if result == QtGui.QMessageBox.Yes:
595 if result == QtGui.QMessageBox.Yes:
535 try:
596 try:
536 self.kernel_manager.restart_kernel(now=now)
597 self.kernel_manager.restart_kernel(now=now)
537 except RuntimeError:
598 except RuntimeError:
538 self._append_plain_text('Kernel started externally. '
599 self._append_plain_text('Kernel started externally. '
539 'Cannot restart.\n')
600 'Cannot restart.\n')
540 else:
601 else:
541 self.reset()
602 self.reset()
542 else:
603 else:
543 self.kernel_manager.hb_channel.unpause()
604 self.kernel_manager.hb_channel.unpause()
544
605
545 else:
606 else:
546 self._append_plain_text('Kernel process is either remote or '
607 self._append_plain_text('Kernel process is either remote or '
547 'unspecified. Cannot restart.\n')
608 'unspecified. Cannot restart.\n')
548
609
549 #---------------------------------------------------------------------------
610 #---------------------------------------------------------------------------
550 # 'FrontendWidget' protected interface
611 # 'FrontendWidget' protected interface
551 #---------------------------------------------------------------------------
612 #---------------------------------------------------------------------------
552
613
553 def _call_tip(self):
614 def _call_tip(self):
554 """ Shows a call tip, if appropriate, at the current cursor location.
615 """ Shows a call tip, if appropriate, at the current cursor location.
555 """
616 """
556 # Decide if it makes sense to show a call tip
617 # Decide if it makes sense to show a call tip
557 if not self.enable_calltips:
618 if not self.enable_calltips:
558 return False
619 return False
559 cursor = self._get_cursor()
620 cursor = self._get_cursor()
560 cursor.movePosition(QtGui.QTextCursor.Left)
621 cursor.movePosition(QtGui.QTextCursor.Left)
561 if cursor.document().characterAt(cursor.position()) != '(':
622 if cursor.document().characterAt(cursor.position()) != '(':
562 return False
623 return False
563 context = self._get_context(cursor)
624 context = self._get_context(cursor)
564 if not context:
625 if not context:
565 return False
626 return False
566
627
567 # Send the metadata request to the kernel
628 # Send the metadata request to the kernel
568 name = '.'.join(context)
629 name = '.'.join(context)
569 msg_id = self.kernel_manager.shell_channel.object_info(name)
630 msg_id = self.kernel_manager.shell_channel.object_info(name)
570 pos = self._get_cursor().position()
631 pos = self._get_cursor().position()
571 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
632 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
572 return True
633 return True
573
634
574 def _complete(self):
635 def _complete(self):
575 """ Performs completion at the current cursor location.
636 """ Performs completion at the current cursor location.
576 """
637 """
577 context = self._get_context()
638 context = self._get_context()
578 if context:
639 if context:
579 # Send the completion request to the kernel
640 # Send the completion request to the kernel
580 msg_id = self.kernel_manager.shell_channel.complete(
641 msg_id = self.kernel_manager.shell_channel.complete(
581 '.'.join(context), # text
642 '.'.join(context), # text
582 self._get_input_buffer_cursor_line(), # line
643 self._get_input_buffer_cursor_line(), # line
583 self._get_input_buffer_cursor_column(), # cursor_pos
644 self._get_input_buffer_cursor_column(), # cursor_pos
584 self.input_buffer) # block
645 self.input_buffer) # block
585 pos = self._get_cursor().position()
646 pos = self._get_cursor().position()
586 info = self._CompletionRequest(msg_id, pos)
647 info = self._CompletionRequest(msg_id, pos)
587 self._request_info['complete'] = info
648 self._request_info['complete'] = info
588
649
589 def _get_context(self, cursor=None):
650 def _get_context(self, cursor=None):
590 """ Gets the context for the specified cursor (or the current cursor
651 """ Gets the context for the specified cursor (or the current cursor
591 if none is specified).
652 if none is specified).
592 """
653 """
593 if cursor is None:
654 if cursor is None:
594 cursor = self._get_cursor()
655 cursor = self._get_cursor()
595 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
656 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
596 QtGui.QTextCursor.KeepAnchor)
657 QtGui.QTextCursor.KeepAnchor)
597 text = cursor.selection().toPlainText()
658 text = cursor.selection().toPlainText()
598 return self._completion_lexer.get_context(text)
659 return self._completion_lexer.get_context(text)
599
660
600 def _process_execute_abort(self, msg):
661 def _process_execute_abort(self, msg):
601 """ Process a reply for an aborted execution request.
662 """ Process a reply for an aborted execution request.
602 """
663 """
603 self._append_plain_text("ERROR: execution aborted\n")
664 self._append_plain_text("ERROR: execution aborted\n")
604
665
605 def _process_execute_error(self, msg):
666 def _process_execute_error(self, msg):
606 """ 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.
607 """
668 """
608 content = msg['content']
669 content = msg['content']
609 # 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
610 # 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
611 # the kernel running
672 # the kernel running
612 if content['ename']=='SystemExit':
673 if content['ename']=='SystemExit':
613 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
674 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
614 self._keep_kernel_on_exit = keepkernel
675 self._keep_kernel_on_exit = keepkernel
615 self.exit_requested.emit(self)
676 self.exit_requested.emit(self)
616 else:
677 else:
617 traceback = ''.join(content['traceback'])
678 traceback = ''.join(content['traceback'])
618 self._append_plain_text(traceback)
679 self._append_plain_text(traceback)
619
680
620 def _process_execute_ok(self, msg):
681 def _process_execute_ok(self, msg):
621 """ Process a reply for a successful execution equest.
682 """ Process a reply for a successful execution equest.
622 """
683 """
623 payload = msg['content']['payload']
684 payload = msg['content']['payload']
624 for item in payload:
685 for item in payload:
625 if not self._process_execute_payload(item):
686 if not self._process_execute_payload(item):
626 warning = 'Warning: received unknown payload of type %s'
687 warning = 'Warning: received unknown payload of type %s'
627 print(warning % repr(item['source']))
688 print(warning % repr(item['source']))
628
689
629 def _process_execute_payload(self, item):
690 def _process_execute_payload(self, item):
630 """ 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
631 execution reply. Returns whether the payload was handled.
692 execution reply. Returns whether the payload was handled.
632 """
693 """
633 # The basic FrontendWidget doesn't handle payloads, as they are a
694 # The basic FrontendWidget doesn't handle payloads, as they are a
634 # mechanism for going beyond the standard Python interpreter model.
695 # mechanism for going beyond the standard Python interpreter model.
635 return False
696 return False
636
697
637 def _show_interpreter_prompt(self):
698 def _show_interpreter_prompt(self):
638 """ Shows a prompt for the interpreter.
699 """ Shows a prompt for the interpreter.
639 """
700 """
640 self._show_prompt('>>> ')
701 self._show_prompt('>>> ')
641
702
642 def _show_interpreter_prompt_for_reply(self, msg):
703 def _show_interpreter_prompt_for_reply(self, msg):
643 """ Shows a prompt for the interpreter given an 'execute_reply' message.
704 """ Shows a prompt for the interpreter given an 'execute_reply' message.
644 """
705 """
645 self._show_interpreter_prompt()
706 self._show_interpreter_prompt()
646
707
647 #------ Signal handlers ----------------------------------------------------
708 #------ Signal handlers ----------------------------------------------------
648
709
649 def _document_contents_change(self, position, removed, added):
710 def _document_contents_change(self, position, removed, added):
650 """ Called whenever the document's content changes. Display a call tip
711 """ Called whenever the document's content changes. Display a call tip
651 if appropriate.
712 if appropriate.
652 """
713 """
653 # Calculate where the cursor should be *after* the change:
714 # Calculate where the cursor should be *after* the change:
654 position += added
715 position += added
655
716
656 document = self._control.document()
717 document = self._control.document()
657 if position == self._get_cursor().position():
718 if position == self._get_cursor().position():
658 self._call_tip()
719 self._call_tip()
659
720
660 #------ Trait default initializers -----------------------------------------
721 #------ Trait default initializers -----------------------------------------
661
722
662 def _banner_default(self):
723 def _banner_default(self):
663 """ Returns the standard Python banner.
724 """ Returns the standard Python banner.
664 """
725 """
665 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
726 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
666 '"license" for more information.'
727 '"license" for more information.'
667 return banner % (sys.version, sys.platform)
728 return banner % (sys.version, sys.platform)
@@ -1,851 +1,904
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
548 def _make_dynamic_magic(self,magic):
549 """Return a function `fun` that will execute `magic` on active frontend.
550
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
561
562 See Also
563 --------
564 populate_all_magic_menu : generate the "All Magics..." menu
565
566 Notes
567 -----
568 `fun` execute `magic` an active frontend at the moment it is triggerd,
569 not the active frontend at the moment it has been created.
570
571 This function is mostly used to create the "All Magics..." Menu at run time.
572 """
573 # need to level nested function to be sure to past magic
574 # on active frontend **at run time**.
575 def inner_dynamic_magic():
576 self.active_frontend.execute(magic)
577 inner_dynamic_magic.__name__ = "dynamics_magic_s"
578 return inner_dynamic_magic
579
580 def populate_all_magic_menu(self, listofmagic=None):
581 """Clean "All Magics..." menu and repopulate it with `listofmagic`
582
583 Parameters
584 ----------
585 listofmagic : string,
586 repr() of a list of strings, send back by the kernel
587
588 Notes
589 -----
590 `listofmagic`is a repr() of list because it is fed with the result of
591 a 'user_expression'
592 """
593 alm_magic_menu = self.all_magic_menu
594 alm_magic_menu.clear()
595
596 # list of protected magic that don't like to be called without argument
597 # append '?' to the end to print the docstring when called from the menu
598 protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
599 magics=re.findall('\w+', listofmagic)
600 for magic in magics:
601 if magic in protected_magic:
602 pmagic = '%s%s%s'%('%',magic,'?')
603 else:
604 pmagic = '%s%s'%('%',magic)
605 xaction = QtGui.QAction(pmagic,
606 self,
607 triggered=self._make_dynamic_magic(pmagic)
608 )
609 alm_magic_menu.addAction(xaction)
610
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 """
618 # first define a callback which will get the list of all magic and put it in the menu.
619 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
620
547 def init_magic_menu(self):
621 def init_magic_menu(self):
548 self.magic_menu = self.menuBar().addMenu("&Magic")
622 self.magic_menu = self.menuBar().addMenu("&Magic")
549 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
623 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
550
624
625 # this action should not appear as it will be cleard when menu
626 # will be updated at first kernel response.
627 self.pop = QtGui.QAction("&Update All Magic Menu ",
628 self, triggered=self.update_all_magic_menu)
629 self.add_menu_action(self.all_magic_menu, self.pop)
630
551 self.reset_action = QtGui.QAction("&Reset",
631 self.reset_action = QtGui.QAction("&Reset",
552 self,
632 self,
553 statusTip="Clear all varible from workspace",
633 statusTip="Clear all varible from workspace",
554 triggered=self.reset_magic_active_frontend)
634 triggered=self.reset_magic_active_frontend)
555 self.add_menu_action(self.magic_menu, self.reset_action)
635 self.add_menu_action(self.magic_menu, self.reset_action)
556
636
557 self.history_action = QtGui.QAction("&History",
637 self.history_action = QtGui.QAction("&History",
558 self,
638 self,
559 statusTip="show command history",
639 statusTip="show command history",
560 triggered=self.history_magic_active_frontend)
640 triggered=self.history_magic_active_frontend)
561 self.add_menu_action(self.magic_menu, self.history_action)
641 self.add_menu_action(self.magic_menu, self.history_action)
562
642
563 self.save_action = QtGui.QAction("E&xport History ",
643 self.save_action = QtGui.QAction("E&xport History ",
564 self,
644 self,
565 statusTip="Export History as Python File",
645 statusTip="Export History as Python File",
566 triggered=self.save_magic_active_frontend)
646 triggered=self.save_magic_active_frontend)
567 self.add_menu_action(self.magic_menu, self.save_action)
647 self.add_menu_action(self.magic_menu, self.save_action)
568
648
569 self.who_action = QtGui.QAction("&Who",
649 self.who_action = QtGui.QAction("&Who",
570 self,
650 self,
571 statusTip="List interactive variable",
651 statusTip="List interactive variable",
572 triggered=self.who_magic_active_frontend)
652 triggered=self.who_magic_active_frontend)
573 self.add_menu_action(self.magic_menu, self.who_action)
653 self.add_menu_action(self.magic_menu, self.who_action)
574
654
575 self.who_ls_action = QtGui.QAction("Wh&o ls",
655 self.who_ls_action = QtGui.QAction("Wh&o ls",
576 self,
656 self,
577 statusTip="Return a list of interactive variable",
657 statusTip="Return a list of interactive variable",
578 triggered=self.who_ls_magic_active_frontend)
658 triggered=self.who_ls_magic_active_frontend)
579 self.add_menu_action(self.magic_menu, self.who_ls_action)
659 self.add_menu_action(self.magic_menu, self.who_ls_action)
580
660
581 self.whos_action = QtGui.QAction("Who&s",
661 self.whos_action = QtGui.QAction("Who&s",
582 self,
662 self,
583 statusTip="List interactive variable with detail",
663 statusTip="List interactive variable with detail",
584 triggered=self.whos_magic_active_frontend)
664 triggered=self.whos_magic_active_frontend)
585 self.add_menu_action(self.magic_menu, self.whos_action)
665 self.add_menu_action(self.magic_menu, self.whos_action)
586
666
587 # allmagics submenu:
588
589 #for now this is just a copy and paste, but we should get this dynamically
590 magiclist=["%alias", "%autocall", "%automagic", "%bookmark", "%cd", "%clear",
591 "%colors", "%debug", "%dhist", "%dirs", "%doctest_mode", "%ed", "%edit", "%env", "%gui",
592 "%guiref", "%hist", "%history", "%install_default_config", "%install_profiles",
593 "%less", "%load_ext", "%loadpy", "%logoff", "%logon", "%logstart", "%logstate",
594 "%logstop", "%lsmagic", "%macro", "%magic", "%man", "%more", "%notebook", "%page",
595 "%pastebin", "%pdb", "%pdef", "%pdoc", "%pfile", "%pinfo", "%pinfo2", "%popd", "%pprint",
596 "%precision", "%profile", "%prun", "%psearch", "%psource", "%pushd", "%pwd", "%pycat",
597 "%pylab", "%quickref", "%recall", "%rehashx", "%reload_ext", "%rep", "%rerun",
598 "%reset", "%reset_selective", "%run", "%save", "%sc", "%sx", "%tb", "%time", "%timeit",
599 "%unalias", "%unload_ext", "%who", "%who_ls", "%whos", "%xdel", "%xmode"]
600
601 def make_dynamic_magic(i):
602 def inner_dynamic_magic():
603 self.active_frontend.execute(i)
604 inner_dynamic_magic.__name__ = "dynamics_magic_%s" % i
605 return inner_dynamic_magic
606
607 for magic in magiclist:
608 xaction = QtGui.QAction(magic,
609 self,
610 triggered=make_dynamic_magic(magic)
611 )
612 self.all_magic_menu.addAction(xaction)
613
614 def init_window_menu(self):
667 def init_window_menu(self):
615 self.window_menu = self.menuBar().addMenu("&Window")
668 self.window_menu = self.menuBar().addMenu("&Window")
616 if sys.platform == 'darwin':
669 if sys.platform == 'darwin':
617 # add min/maximize actions to OSX, which lacks default bindings.
670 # add min/maximize actions to OSX, which lacks default bindings.
618 self.minimizeAct = QtGui.QAction("Mini&mize",
671 self.minimizeAct = QtGui.QAction("Mini&mize",
619 self,
672 self,
620 shortcut="Ctrl+m",
673 shortcut="Ctrl+m",
621 statusTip="Minimize the window/Restore Normal Size",
674 statusTip="Minimize the window/Restore Normal Size",
622 triggered=self.toggleMinimized)
675 triggered=self.toggleMinimized)
623 # maximize is called 'Zoom' on OSX for some reason
676 # maximize is called 'Zoom' on OSX for some reason
624 self.maximizeAct = QtGui.QAction("&Zoom",
677 self.maximizeAct = QtGui.QAction("&Zoom",
625 self,
678 self,
626 shortcut="Ctrl+Shift+M",
679 shortcut="Ctrl+Shift+M",
627 statusTip="Maximize the window/Restore Normal Size",
680 statusTip="Maximize the window/Restore Normal Size",
628 triggered=self.toggleMaximized)
681 triggered=self.toggleMaximized)
629
682
630 self.add_menu_action(self.window_menu, self.minimizeAct)
683 self.add_menu_action(self.window_menu, self.minimizeAct)
631 self.add_menu_action(self.window_menu, self.maximizeAct)
684 self.add_menu_action(self.window_menu, self.maximizeAct)
632 self.window_menu.addSeparator()
685 self.window_menu.addSeparator()
633
686
634 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"
635 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
688 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
636 self,
689 self,
637 shortcut=prev_key,
690 shortcut=prev_key,
638 statusTip="Select previous tab",
691 statusTip="Select previous tab",
639 triggered=self.prev_tab)
692 triggered=self.prev_tab)
640 self.add_menu_action(self.window_menu, self.prev_tab_act)
693 self.add_menu_action(self.window_menu, self.prev_tab_act)
641
694
642 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"
643 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
696 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
644 self,
697 self,
645 shortcut=next_key,
698 shortcut=next_key,
646 statusTip="Select next tab",
699 statusTip="Select next tab",
647 triggered=self.next_tab)
700 triggered=self.next_tab)
648 self.add_menu_action(self.window_menu, self.next_tab_act)
701 self.add_menu_action(self.window_menu, self.next_tab_act)
649
702
650 def init_help_menu(self):
703 def init_help_menu(self):
651 # 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
652 # automatically contain a search field to search inside menus and
705 # automatically contain a search field to search inside menus and
653 # 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
654 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
707 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
655 # this search field fonctionality
708 # this search field fonctionality
656
709
657 self.help_menu = self.menuBar().addMenu("&Help")
710 self.help_menu = self.menuBar().addMenu("&Help")
658
711
659
712
660 # Help Menu
713 # Help Menu
661
714
662 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
715 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
663 self,
716 self,
664 triggered=self.intro_active_frontend
717 triggered=self.intro_active_frontend
665 )
718 )
666 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)
667
720
668 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
721 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
669 self,
722 self,
670 triggered=self.quickref_active_frontend
723 triggered=self.quickref_active_frontend
671 )
724 )
672 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)
673
726
674 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
727 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
675 self,
728 self,
676 triggered=self.guiref_active_frontend
729 triggered=self.guiref_active_frontend
677 )
730 )
678 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)
679
732
680 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
733 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
681 self,
734 self,
682 triggered=self._open_online_help)
735 triggered=self._open_online_help)
683 self.add_menu_action(self.help_menu, self.onlineHelpAct)
736 self.add_menu_action(self.help_menu, self.onlineHelpAct)
684
737
685 # minimize/maximize/fullscreen actions:
738 # minimize/maximize/fullscreen actions:
686
739
687 def toggle_menu_bar(self):
740 def toggle_menu_bar(self):
688 menu_bar = self.menuBar()
741 menu_bar = self.menuBar()
689 if menu_bar.isVisible():
742 if menu_bar.isVisible():
690 menu_bar.setVisible(False)
743 menu_bar.setVisible(False)
691 else:
744 else:
692 menu_bar.setVisible(True)
745 menu_bar.setVisible(True)
693
746
694 def toggleMinimized(self):
747 def toggleMinimized(self):
695 if not self.isMinimized():
748 if not self.isMinimized():
696 self.showMinimized()
749 self.showMinimized()
697 else:
750 else:
698 self.showNormal()
751 self.showNormal()
699
752
700 def _open_online_help(self):
753 def _open_online_help(self):
701 filename="http://ipython.org/ipython-doc/stable/index.html"
754 filename="http://ipython.org/ipython-doc/stable/index.html"
702 webbrowser.open(filename, new=1, autoraise=True)
755 webbrowser.open(filename, new=1, autoraise=True)
703
756
704 def toggleMaximized(self):
757 def toggleMaximized(self):
705 if not self.isMaximized():
758 if not self.isMaximized():
706 self.showMaximized()
759 self.showMaximized()
707 else:
760 else:
708 self.showNormal()
761 self.showNormal()
709
762
710 # Min/Max imizing while in full screen give a bug
763 # Min/Max imizing while in full screen give a bug
711 # when going out of full screen, at least on OSX
764 # when going out of full screen, at least on OSX
712 def toggleFullScreen(self):
765 def toggleFullScreen(self):
713 if not self.isFullScreen():
766 if not self.isFullScreen():
714 self.showFullScreen()
767 self.showFullScreen()
715 if sys.platform == 'darwin':
768 if sys.platform == 'darwin':
716 self.maximizeAct.setEnabled(False)
769 self.maximizeAct.setEnabled(False)
717 self.minimizeAct.setEnabled(False)
770 self.minimizeAct.setEnabled(False)
718 else:
771 else:
719 self.showNormal()
772 self.showNormal()
720 if sys.platform == 'darwin':
773 if sys.platform == 'darwin':
721 self.maximizeAct.setEnabled(True)
774 self.maximizeAct.setEnabled(True)
722 self.minimizeAct.setEnabled(True)
775 self.minimizeAct.setEnabled(True)
723
776
724 def close_active_frontend(self):
777 def close_active_frontend(self):
725 self.close_tab(self.active_frontend)
778 self.close_tab(self.active_frontend)
726
779
727 def restart_kernel_active_frontend(self):
780 def restart_kernel_active_frontend(self):
728 self.active_frontend.request_restart_kernel()
781 self.active_frontend.request_restart_kernel()
729
782
730 def interrupt_kernel_active_frontend(self):
783 def interrupt_kernel_active_frontend(self):
731 self.active_frontend.request_interrupt_kernel()
784 self.active_frontend.request_interrupt_kernel()
732
785
733 def cut_active_frontend(self):
786 def cut_active_frontend(self):
734 widget = self.active_frontend
787 widget = self.active_frontend
735 if widget.can_cut():
788 if widget.can_cut():
736 widget.cut()
789 widget.cut()
737
790
738 def copy_active_frontend(self):
791 def copy_active_frontend(self):
739 widget = self.active_frontend
792 widget = self.active_frontend
740 if widget.can_copy():
793 if widget.can_copy():
741 widget.copy()
794 widget.copy()
742
795
743 def copy_raw_active_frontend(self):
796 def copy_raw_active_frontend(self):
744 self.active_frontend._copy_raw_action.trigger()
797 self.active_frontend._copy_raw_action.trigger()
745
798
746 def paste_active_frontend(self):
799 def paste_active_frontend(self):
747 widget = self.active_frontend
800 widget = self.active_frontend
748 if widget.can_paste():
801 if widget.can_paste():
749 widget.paste()
802 widget.paste()
750
803
751 def undo_active_frontend(self):
804 def undo_active_frontend(self):
752 self.active_frontend.undo()
805 self.active_frontend.undo()
753
806
754 def redo_active_frontend(self):
807 def redo_active_frontend(self):
755 self.active_frontend.redo()
808 self.active_frontend.redo()
756
809
757 def reset_magic_active_frontend(self):
810 def reset_magic_active_frontend(self):
758 self.active_frontend.execute("%reset")
811 self.active_frontend.execute("%reset")
759
812
760 def history_magic_active_frontend(self):
813 def history_magic_active_frontend(self):
761 self.active_frontend.execute("%history")
814 self.active_frontend.execute("%history")
762
815
763 def save_magic_active_frontend(self):
816 def save_magic_active_frontend(self):
764 self.active_frontend.save_magic()
817 self.active_frontend.save_magic()
765
818
766 def clear_magic_active_frontend(self):
819 def clear_magic_active_frontend(self):
767 self.active_frontend.execute("%clear")
820 self.active_frontend.execute("%clear")
768
821
769 def who_magic_active_frontend(self):
822 def who_magic_active_frontend(self):
770 self.active_frontend.execute("%who")
823 self.active_frontend.execute("%who")
771
824
772 def who_ls_magic_active_frontend(self):
825 def who_ls_magic_active_frontend(self):
773 self.active_frontend.execute("%who_ls")
826 self.active_frontend.execute("%who_ls")
774
827
775 def whos_magic_active_frontend(self):
828 def whos_magic_active_frontend(self):
776 self.active_frontend.execute("%whos")
829 self.active_frontend.execute("%whos")
777
830
778 def print_action_active_frontend(self):
831 def print_action_active_frontend(self):
779 self.active_frontend.print_action.trigger()
832 self.active_frontend.print_action.trigger()
780
833
781 def export_action_active_frontend(self):
834 def export_action_active_frontend(self):
782 self.active_frontend.export_action.trigger()
835 self.active_frontend.export_action.trigger()
783
836
784 def select_all_active_frontend(self):
837 def select_all_active_frontend(self):
785 self.active_frontend.select_all_action.trigger()
838 self.active_frontend.select_all_action.trigger()
786
839
787 def increase_font_size_active_frontend(self):
840 def increase_font_size_active_frontend(self):
788 self.active_frontend.increase_font_size.trigger()
841 self.active_frontend.increase_font_size.trigger()
789
842
790 def decrease_font_size_active_frontend(self):
843 def decrease_font_size_active_frontend(self):
791 self.active_frontend.decrease_font_size.trigger()
844 self.active_frontend.decrease_font_size.trigger()
792
845
793 def reset_font_size_active_frontend(self):
846 def reset_font_size_active_frontend(self):
794 self.active_frontend.reset_font_size.trigger()
847 self.active_frontend.reset_font_size.trigger()
795
848
796 def guiref_active_frontend(self):
849 def guiref_active_frontend(self):
797 self.active_frontend.execute("%guiref")
850 self.active_frontend.execute("%guiref")
798
851
799 def intro_active_frontend(self):
852 def intro_active_frontend(self):
800 self.active_frontend.execute("?")
853 self.active_frontend.execute("?")
801
854
802 def quickref_active_frontend(self):
855 def quickref_active_frontend(self):
803 self.active_frontend.execute("%quickref")
856 self.active_frontend.execute("%quickref")
804 #---------------------------------------------------------------------------
857 #---------------------------------------------------------------------------
805 # QWidget interface
858 # QWidget interface
806 #---------------------------------------------------------------------------
859 #---------------------------------------------------------------------------
807
860
808 def closeEvent(self, event):
861 def closeEvent(self, event):
809 """ Forward the close event to every tabs contained by the windows
862 """ Forward the close event to every tabs contained by the windows
810 """
863 """
811 if self.tab_widget.count() == 0:
864 if self.tab_widget.count() == 0:
812 # no tabs, just close
865 # no tabs, just close
813 event.accept()
866 event.accept()
814 return
867 return
815 # 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
816 title = self.window().windowTitle()
869 title = self.window().windowTitle()
817 cancel = QtGui.QMessageBox.Cancel
870 cancel = QtGui.QMessageBox.Cancel
818 okay = QtGui.QMessageBox.Ok
871 okay = QtGui.QMessageBox.Ok
819
872
820 if self.confirm_exit:
873 if self.confirm_exit:
821 if self.tab_widget.count() > 1:
874 if self.tab_widget.count() > 1:
822 msg = "Close all tabs, stop all kernels, and Quit?"
875 msg = "Close all tabs, stop all kernels, and Quit?"
823 else:
876 else:
824 msg = "Close console, stop kernel, and Quit?"
877 msg = "Close console, stop kernel, and Quit?"
825 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."
826 closeall = QtGui.QPushButton("&Yes, quit everything", self)
879 closeall = QtGui.QPushButton("&Yes, quit everything", self)
827 closeall.setShortcut('Y')
880 closeall.setShortcut('Y')
828 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
881 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
829 title, msg)
882 title, msg)
830 box.setInformativeText(info)
883 box.setInformativeText(info)
831 box.addButton(cancel)
884 box.addButton(cancel)
832 box.addButton(closeall, QtGui.QMessageBox.YesRole)
885 box.addButton(closeall, QtGui.QMessageBox.YesRole)
833 box.setDefaultButton(closeall)
886 box.setDefaultButton(closeall)
834 box.setEscapeButton(cancel)
887 box.setEscapeButton(cancel)
835 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
888 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
836 box.setIconPixmap(pixmap)
889 box.setIconPixmap(pixmap)
837 reply = box.exec_()
890 reply = box.exec_()
838 else:
891 else:
839 reply = okay
892 reply = okay
840
893
841 if reply == cancel:
894 if reply == cancel:
842 event.ignore()
895 event.ignore()
843 return
896 return
844 if reply == okay:
897 if reply == okay:
845 while self.tab_widget.count() >= 1:
898 while self.tab_widget.count() >= 1:
846 # prevent further confirmations:
899 # prevent further confirmations:
847 widget = self.active_frontend
900 widget = self.active_frontend
848 widget._confirm_exit = False
901 widget._confirm_exit = False
849 self.close_tab(widget)
902 self.close_tab(widget)
850 event.accept()
903 event.accept()
851
904
@@ -1,548 +1,552
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import json
22 import json
23 import os
23 import os
24 import signal
24 import signal
25 import sys
25 import sys
26 import uuid
26 import uuid
27
27
28 # System library imports
28 # System library imports
29 from IPython.external.qt import QtGui
29 from IPython.external.qt import QtGui
30
30
31 # Local imports
31 # Local imports
32 from IPython.config.application import boolean_flag, catch_config_error
32 from IPython.config.application import boolean_flag, catch_config_error
33 from IPython.core.application import BaseIPythonApplication
33 from IPython.core.application import BaseIPythonApplication
34 from IPython.core.profiledir import ProfileDir
34 from IPython.core.profiledir import ProfileDir
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
35 from IPython.lib.kernel import tunnel_to_kernel, find_connection_file
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
36 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
37 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
38 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
39 from IPython.frontend.qt.console import styles
39 from IPython.frontend.qt.console import styles
40 from IPython.frontend.qt.console.mainwindow import MainWindow
40 from IPython.frontend.qt.console.mainwindow import MainWindow
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
41 from IPython.frontend.qt.kernelmanager import QtKernelManager
42 from IPython.utils.path import filefind
42 from IPython.utils.path import filefind
43 from IPython.utils.py3compat import str_to_bytes
43 from IPython.utils.py3compat import str_to_bytes
44 from IPython.utils.traitlets import (
44 from IPython.utils.traitlets import (
45 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
45 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
46 )
46 )
47 from IPython.zmq.ipkernel import (
47 from IPython.zmq.ipkernel import (
48 flags as ipkernel_flags,
48 flags as ipkernel_flags,
49 aliases as ipkernel_aliases,
49 aliases as ipkernel_aliases,
50 IPKernelApp
50 IPKernelApp
51 )
51 )
52 from IPython.zmq.session import Session, default_secure
52 from IPython.zmq.session import Session, default_secure
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
53 from IPython.zmq.zmqshell import ZMQInteractiveShell
54
54
55 #-----------------------------------------------------------------------------
55 #-----------------------------------------------------------------------------
56 # Network Constants
56 # Network Constants
57 #-----------------------------------------------------------------------------
57 #-----------------------------------------------------------------------------
58
58
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
59 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Globals
62 # Globals
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65 _examples = """
65 _examples = """
66 ipython qtconsole # start the qtconsole
66 ipython qtconsole # start the qtconsole
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
67 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
68 """
68 """
69
69
70 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
71 # Aliases and Flags
71 # Aliases and Flags
72 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
73
73
74 flags = dict(ipkernel_flags)
74 flags = dict(ipkernel_flags)
75 qt_flags = {
75 qt_flags = {
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
76 'existing' : ({'IPythonQtConsoleApp' : {'existing' : 'kernel*.json'}},
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
77 "Connect to an existing kernel. If no argument specified, guess most recent"),
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
78 'pure' : ({'IPythonQtConsoleApp' : {'pure' : True}},
79 "Use a pure Python kernel instead of an IPython kernel."),
79 "Use a pure Python kernel instead of an IPython kernel."),
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
80 'plain' : ({'ConsoleWidget' : {'kind' : 'plain'}},
81 "Disable rich text support."),
81 "Disable rich text support."),
82 }
82 }
83 qt_flags.update(boolean_flag(
83 qt_flags.update(boolean_flag(
84 'gui-completion', 'ConsoleWidget.gui_completion',
84 'gui-completion', 'ConsoleWidget.gui_completion',
85 "use a GUI widget for tab completion",
85 "use a GUI widget for tab completion",
86 "use plaintext output for completion"
86 "use plaintext output for completion"
87 ))
87 ))
88 qt_flags.update(boolean_flag(
88 qt_flags.update(boolean_flag(
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
89 'confirm-exit', 'IPythonQtConsoleApp.confirm_exit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
90 """Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
91 to force a direct exit without any confirmation.
91 to force a direct exit without any confirmation.
92 """,
92 """,
93 """Don't prompt the user when exiting. This will terminate the kernel
93 """Don't prompt the user when exiting. This will terminate the kernel
94 if it is owned by the frontend, and leave it alive if it is external.
94 if it is owned by the frontend, and leave it alive if it is external.
95 """
95 """
96 ))
96 ))
97 flags.update(qt_flags)
97 flags.update(qt_flags)
98
98
99 aliases = dict(ipkernel_aliases)
99 aliases = dict(ipkernel_aliases)
100
100
101 qt_aliases = dict(
101 qt_aliases = dict(
102 hb = 'IPythonQtConsoleApp.hb_port',
102 hb = 'IPythonQtConsoleApp.hb_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
103 shell = 'IPythonQtConsoleApp.shell_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
104 iopub = 'IPythonQtConsoleApp.iopub_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
105 stdin = 'IPythonQtConsoleApp.stdin_port',
106 ip = 'IPythonQtConsoleApp.ip',
106 ip = 'IPythonQtConsoleApp.ip',
107 existing = 'IPythonQtConsoleApp.existing',
107 existing = 'IPythonQtConsoleApp.existing',
108 f = 'IPythonQtConsoleApp.connection_file',
108 f = 'IPythonQtConsoleApp.connection_file',
109
109
110 style = 'IPythonWidget.syntax_style',
110 style = 'IPythonWidget.syntax_style',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
111 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 colors = 'ZMQInteractiveShell.colors',
112 colors = 'ZMQInteractiveShell.colors',
113
113
114 editor = 'IPythonWidget.editor',
114 editor = 'IPythonWidget.editor',
115 paging = 'ConsoleWidget.paging',
115 paging = 'ConsoleWidget.paging',
116 ssh = 'IPythonQtConsoleApp.sshserver',
116 ssh = 'IPythonQtConsoleApp.sshserver',
117 )
117 )
118 aliases.update(qt_aliases)
118 aliases.update(qt_aliases)
119
119
120 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
121 # Classes
121 # Classes
122 #-----------------------------------------------------------------------------
122 #-----------------------------------------------------------------------------
123
123
124 #-----------------------------------------------------------------------------
124 #-----------------------------------------------------------------------------
125 # IPythonQtConsole
125 # IPythonQtConsole
126 #-----------------------------------------------------------------------------
126 #-----------------------------------------------------------------------------
127
127
128
128
129 class IPythonQtConsoleApp(BaseIPythonApplication):
129 class IPythonQtConsoleApp(BaseIPythonApplication):
130 name = 'ipython-qtconsole'
130 name = 'ipython-qtconsole'
131 default_config_file_name='ipython_config.py'
131 default_config_file_name='ipython_config.py'
132
132
133 description = """
133 description = """
134 The IPython QtConsole.
134 The IPython QtConsole.
135
135
136 This launches a Console-style application using Qt. It is not a full
136 This launches a Console-style application using Qt. It is not a full
137 console, in that launched terminal subprocesses will not be able to accept
137 console, in that launched terminal subprocesses will not be able to accept
138 input.
138 input.
139
139
140 The QtConsole supports various extra features beyond the Terminal IPython
140 The QtConsole supports various extra features beyond the Terminal IPython
141 shell, such as inline plotting with matplotlib, via:
141 shell, such as inline plotting with matplotlib, via:
142
142
143 ipython qtconsole --pylab=inline
143 ipython qtconsole --pylab=inline
144
144
145 as well as saving your session as HTML, and printing the output.
145 as well as saving your session as HTML, and printing the output.
146
146
147 """
147 """
148 examples = _examples
148 examples = _examples
149
149
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
150 classes = [IPKernelApp, IPythonWidget, ZMQInteractiveShell, ProfileDir, Session]
151 flags = Dict(flags)
151 flags = Dict(flags)
152 aliases = Dict(aliases)
152 aliases = Dict(aliases)
153
153
154 kernel_argv = List(Unicode)
154 kernel_argv = List(Unicode)
155
155
156 # create requested profiles by default, if they don't exist:
156 # create requested profiles by default, if they don't exist:
157 auto_create = CBool(True)
157 auto_create = CBool(True)
158 # connection info:
158 # connection info:
159 ip = Unicode(LOCALHOST, config=True,
159 ip = Unicode(LOCALHOST, config=True,
160 help="""Set the kernel\'s IP address [default localhost].
160 help="""Set the kernel\'s IP address [default localhost].
161 If the IP address is something other than localhost, then
161 If the IP address is something other than localhost, then
162 Consoles on other machines will be able to connect
162 Consoles on other machines will be able to connect
163 to the Kernel, so be careful!"""
163 to the Kernel, so be careful!"""
164 )
164 )
165
165
166 sshserver = Unicode('', config=True,
166 sshserver = Unicode('', config=True,
167 help="""The SSH server to use to connect to the kernel.""")
167 help="""The SSH server to use to connect to the kernel.""")
168 sshkey = Unicode('', config=True,
168 sshkey = Unicode('', config=True,
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
169 help="""Path to the ssh key to use for logging in to the ssh server.""")
170
170
171 hb_port = Integer(0, config=True,
171 hb_port = Integer(0, config=True,
172 help="set the heartbeat port [default: random]")
172 help="set the heartbeat port [default: random]")
173 shell_port = Integer(0, config=True,
173 shell_port = Integer(0, config=True,
174 help="set the shell (XREP) port [default: random]")
174 help="set the shell (XREP) port [default: random]")
175 iopub_port = Integer(0, config=True,
175 iopub_port = Integer(0, config=True,
176 help="set the iopub (PUB) port [default: random]")
176 help="set the iopub (PUB) port [default: random]")
177 stdin_port = Integer(0, config=True,
177 stdin_port = Integer(0, config=True,
178 help="set the stdin (XREQ) port [default: random]")
178 help="set the stdin (XREQ) port [default: random]")
179 connection_file = Unicode('', config=True,
179 connection_file = Unicode('', config=True,
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
180 help="""JSON file in which to store connection info [default: kernel-<pid>.json]
181
181
182 This file will contain the IP, ports, and authentication key needed to connect
182 This file will contain the IP, ports, and authentication key needed to connect
183 clients to this kernel. By default, this file will be created in the security-dir
183 clients to this kernel. By default, this file will be created in the security-dir
184 of the current profile, but can be specified by absolute path.
184 of the current profile, but can be specified by absolute path.
185 """)
185 """)
186 def _connection_file_default(self):
186 def _connection_file_default(self):
187 return 'kernel-%i.json' % os.getpid()
187 return 'kernel-%i.json' % os.getpid()
188
188
189 existing = Unicode('', config=True,
189 existing = Unicode('', config=True,
190 help="""Connect to an already running kernel""")
190 help="""Connect to an already running kernel""")
191
191
192 stylesheet = Unicode('', config=True,
192 stylesheet = Unicode('', config=True,
193 help="path to a custom CSS stylesheet")
193 help="path to a custom CSS stylesheet")
194
194
195 pure = CBool(False, config=True,
195 pure = CBool(False, config=True,
196 help="Use a pure Python kernel instead of an IPython kernel.")
196 help="Use a pure Python kernel instead of an IPython kernel.")
197 plain = CBool(False, config=True,
197 plain = CBool(False, config=True,
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
198 help="Use a plaintext widget instead of rich text (plain can't print/save).")
199
199
200 def _pure_changed(self, name, old, new):
200 def _pure_changed(self, name, old, new):
201 kind = 'plain' if self.plain else 'rich'
201 kind = 'plain' if self.plain else 'rich'
202 self.config.ConsoleWidget.kind = kind
202 self.config.ConsoleWidget.kind = kind
203 if self.pure:
203 if self.pure:
204 self.widget_factory = FrontendWidget
204 self.widget_factory = FrontendWidget
205 elif self.plain:
205 elif self.plain:
206 self.widget_factory = IPythonWidget
206 self.widget_factory = IPythonWidget
207 else:
207 else:
208 self.widget_factory = RichIPythonWidget
208 self.widget_factory = RichIPythonWidget
209
209
210 _plain_changed = _pure_changed
210 _plain_changed = _pure_changed
211
211
212 confirm_exit = CBool(True, config=True,
212 confirm_exit = CBool(True, config=True,
213 help="""
213 help="""
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
214 Set to display confirmation dialog on exit. You can always use 'exit' or 'quit',
215 to force a direct exit without any confirmation.""",
215 to force a direct exit without any confirmation.""",
216 )
216 )
217
217
218 # the factory for creating a widget
218 # the factory for creating a widget
219 widget_factory = Any(RichIPythonWidget)
219 widget_factory = Any(RichIPythonWidget)
220
220
221 def parse_command_line(self, argv=None):
221 def parse_command_line(self, argv=None):
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
222 super(IPythonQtConsoleApp, self).parse_command_line(argv)
223 if argv is None:
223 if argv is None:
224 argv = sys.argv[1:]
224 argv = sys.argv[1:]
225 self.kernel_argv = list(argv) # copy
225 self.kernel_argv = list(argv) # copy
226 # kernel should inherit default config file from frontend
226 # kernel should inherit default config file from frontend
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
227 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
228 # Scrub frontend-specific flags
228 # Scrub frontend-specific flags
229 swallow_next = False
229 swallow_next = False
230 was_flag = False
230 was_flag = False
231 # copy again, in case some aliases have the same name as a flag
231 # copy again, in case some aliases have the same name as a flag
232 # argv = list(self.kernel_argv)
232 # argv = list(self.kernel_argv)
233 for a in argv:
233 for a in argv:
234 if swallow_next:
234 if swallow_next:
235 swallow_next = False
235 swallow_next = False
236 # last arg was an alias, remove the next one
236 # last arg was an alias, remove the next one
237 # *unless* the last alias has a no-arg flag version, in which
237 # *unless* the last alias has a no-arg flag version, in which
238 # case, don't swallow the next arg if it's also a flag:
238 # case, don't swallow the next arg if it's also a flag:
239 if not (was_flag and a.startswith('-')):
239 if not (was_flag and a.startswith('-')):
240 self.kernel_argv.remove(a)
240 self.kernel_argv.remove(a)
241 continue
241 continue
242 if a.startswith('-'):
242 if a.startswith('-'):
243 split = a.lstrip('-').split('=')
243 split = a.lstrip('-').split('=')
244 alias = split[0]
244 alias = split[0]
245 if alias in qt_aliases:
245 if alias in qt_aliases:
246 self.kernel_argv.remove(a)
246 self.kernel_argv.remove(a)
247 if len(split) == 1:
247 if len(split) == 1:
248 # alias passed with arg via space
248 # alias passed with arg via space
249 swallow_next = True
249 swallow_next = True
250 # could have been a flag that matches an alias, e.g. `existing`
250 # could have been a flag that matches an alias, e.g. `existing`
251 # in which case, we might not swallow the next arg
251 # in which case, we might not swallow the next arg
252 was_flag = alias in qt_flags
252 was_flag = alias in qt_flags
253 elif alias in qt_flags:
253 elif alias in qt_flags:
254 # strip flag, but don't swallow next, as flags don't take args
254 # strip flag, but don't swallow next, as flags don't take args
255 self.kernel_argv.remove(a)
255 self.kernel_argv.remove(a)
256
256
257 def init_connection_file(self):
257 def init_connection_file(self):
258 """find the connection file, and load the info if found.
258 """find the connection file, and load the info if found.
259
259
260 The current working directory and the current profile's security
260 The current working directory and the current profile's security
261 directory will be searched for the file if it is not given by
261 directory will be searched for the file if it is not given by
262 absolute path.
262 absolute path.
263
263
264 When attempting to connect to an existing kernel and the `--existing`
264 When attempting to connect to an existing kernel and the `--existing`
265 argument does not match an existing file, it will be interpreted as a
265 argument does not match an existing file, it will be interpreted as a
266 fileglob, and the matching file in the current profile's security dir
266 fileglob, and the matching file in the current profile's security dir
267 with the latest access time will be used.
267 with the latest access time will be used.
268 """
268 """
269 if self.existing:
269 if self.existing:
270 try:
270 try:
271 cf = find_connection_file(self.existing)
271 cf = find_connection_file(self.existing)
272 except Exception:
272 except Exception:
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
273 self.log.critical("Could not find existing kernel connection file %s", self.existing)
274 self.exit(1)
274 self.exit(1)
275 self.log.info("Connecting to existing kernel: %s" % cf)
275 self.log.info("Connecting to existing kernel: %s" % cf)
276 self.connection_file = cf
276 self.connection_file = cf
277 # should load_connection_file only be used for existing?
277 # should load_connection_file only be used for existing?
278 # as it is now, this allows reusing ports if an existing
278 # as it is now, this allows reusing ports if an existing
279 # file is requested
279 # file is requested
280 try:
280 try:
281 self.load_connection_file()
281 self.load_connection_file()
282 except Exception:
282 except Exception:
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
283 self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True)
284 self.exit(1)
284 self.exit(1)
285
285
286 def load_connection_file(self):
286 def load_connection_file(self):
287 """load ip/port/hmac config from JSON connection file"""
287 """load ip/port/hmac config from JSON connection file"""
288 # this is identical to KernelApp.load_connection_file
288 # this is identical to KernelApp.load_connection_file
289 # perhaps it can be centralized somewhere?
289 # perhaps it can be centralized somewhere?
290 try:
290 try:
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
291 fname = filefind(self.connection_file, ['.', self.profile_dir.security_dir])
292 except IOError:
292 except IOError:
293 self.log.debug("Connection File not found: %s", self.connection_file)
293 self.log.debug("Connection File not found: %s", self.connection_file)
294 return
294 return
295 self.log.debug(u"Loading connection file %s", fname)
295 self.log.debug(u"Loading connection file %s", fname)
296 with open(fname) as f:
296 with open(fname) as f:
297 s = f.read()
297 s = f.read()
298 cfg = json.loads(s)
298 cfg = json.loads(s)
299 if self.ip == LOCALHOST and 'ip' in cfg:
299 if self.ip == LOCALHOST and 'ip' in cfg:
300 # not overridden by config or cl_args
300 # not overridden by config or cl_args
301 self.ip = cfg['ip']
301 self.ip = cfg['ip']
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
302 for channel in ('hb', 'shell', 'iopub', 'stdin'):
303 name = channel + '_port'
303 name = channel + '_port'
304 if getattr(self, name) == 0 and name in cfg:
304 if getattr(self, name) == 0 and name in cfg:
305 # not overridden by config or cl_args
305 # not overridden by config or cl_args
306 setattr(self, name, cfg[name])
306 setattr(self, name, cfg[name])
307 if 'key' in cfg:
307 if 'key' in cfg:
308 self.config.Session.key = str_to_bytes(cfg['key'])
308 self.config.Session.key = str_to_bytes(cfg['key'])
309
309
310 def init_ssh(self):
310 def init_ssh(self):
311 """set up ssh tunnels, if needed."""
311 """set up ssh tunnels, if needed."""
312 if not self.sshserver and not self.sshkey:
312 if not self.sshserver and not self.sshkey:
313 return
313 return
314
314
315 if self.sshkey and not self.sshserver:
315 if self.sshkey and not self.sshserver:
316 # specifying just the key implies that we are connecting directly
316 # specifying just the key implies that we are connecting directly
317 self.sshserver = self.ip
317 self.sshserver = self.ip
318 self.ip = LOCALHOST
318 self.ip = LOCALHOST
319
319
320 # build connection dict for tunnels:
320 # build connection dict for tunnels:
321 info = dict(ip=self.ip,
321 info = dict(ip=self.ip,
322 shell_port=self.shell_port,
322 shell_port=self.shell_port,
323 iopub_port=self.iopub_port,
323 iopub_port=self.iopub_port,
324 stdin_port=self.stdin_port,
324 stdin_port=self.stdin_port,
325 hb_port=self.hb_port
325 hb_port=self.hb_port
326 )
326 )
327
327
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
328 self.log.info("Forwarding connections to %s via %s"%(self.ip, self.sshserver))
329
329
330 # tunnels return a new set of ports, which will be on localhost:
330 # tunnels return a new set of ports, which will be on localhost:
331 self.ip = LOCALHOST
331 self.ip = LOCALHOST
332 try:
332 try:
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
333 newports = tunnel_to_kernel(info, self.sshserver, self.sshkey)
334 except:
334 except:
335 # even catch KeyboardInterrupt
335 # even catch KeyboardInterrupt
336 self.log.error("Could not setup tunnels", exc_info=True)
336 self.log.error("Could not setup tunnels", exc_info=True)
337 self.exit(1)
337 self.exit(1)
338
338
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
339 self.shell_port, self.iopub_port, self.stdin_port, self.hb_port = newports
340
340
341 cf = self.connection_file
341 cf = self.connection_file
342 base,ext = os.path.splitext(cf)
342 base,ext = os.path.splitext(cf)
343 base = os.path.basename(base)
343 base = os.path.basename(base)
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
344 self.connection_file = os.path.basename(base)+'-ssh'+ext
345 self.log.critical("To connect another client via this tunnel, use:")
345 self.log.critical("To connect another client via this tunnel, use:")
346 self.log.critical("--existing %s" % self.connection_file)
346 self.log.critical("--existing %s" % self.connection_file)
347
347
348 def _new_connection_file(self):
348 def _new_connection_file(self):
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
349 return os.path.join(self.profile_dir.security_dir, 'kernel-%s.json' % uuid.uuid4())
350
350
351 def init_kernel_manager(self):
351 def init_kernel_manager(self):
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
352 # Don't let Qt or ZMQ swallow KeyboardInterupts.
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
353 signal.signal(signal.SIGINT, signal.SIG_DFL)
354 sec = self.profile_dir.security_dir
354 sec = self.profile_dir.security_dir
355 try:
355 try:
356 cf = filefind(self.connection_file, ['.', sec])
356 cf = filefind(self.connection_file, ['.', sec])
357 except IOError:
357 except IOError:
358 # file might not exist
358 # file might not exist
359 if self.connection_file == os.path.basename(self.connection_file):
359 if self.connection_file == os.path.basename(self.connection_file):
360 # just shortname, put it in security dir
360 # just shortname, put it in security dir
361 cf = os.path.join(sec, self.connection_file)
361 cf = os.path.join(sec, self.connection_file)
362 else:
362 else:
363 cf = self.connection_file
363 cf = self.connection_file
364
364
365 # Create a KernelManager and start a kernel.
365 # Create a KernelManager and start a kernel.
366 self.kernel_manager = QtKernelManager(
366 self.kernel_manager = QtKernelManager(
367 ip=self.ip,
367 ip=self.ip,
368 shell_port=self.shell_port,
368 shell_port=self.shell_port,
369 iopub_port=self.iopub_port,
369 iopub_port=self.iopub_port,
370 stdin_port=self.stdin_port,
370 stdin_port=self.stdin_port,
371 hb_port=self.hb_port,
371 hb_port=self.hb_port,
372 connection_file=cf,
372 connection_file=cf,
373 config=self.config,
373 config=self.config,
374 )
374 )
375 # start the kernel
375 # start the kernel
376 if not self.existing:
376 if not self.existing:
377 kwargs = dict(ipython=not self.pure)
377 kwargs = dict(ipython=not self.pure)
378 kwargs['extra_arguments'] = self.kernel_argv
378 kwargs['extra_arguments'] = self.kernel_argv
379 self.kernel_manager.start_kernel(**kwargs)
379 self.kernel_manager.start_kernel(**kwargs)
380 elif self.sshserver:
380 elif self.sshserver:
381 # ssh, write new connection file
381 # ssh, write new connection file
382 self.kernel_manager.write_connection_file()
382 self.kernel_manager.write_connection_file()
383 self.kernel_manager.start_channels()
383 self.kernel_manager.start_channels()
384
384
385 def new_frontend_master(self):
385 def new_frontend_master(self):
386 """ Create and return new frontend attached to new kernel, launched on localhost.
386 """ Create and return new frontend attached to new kernel, launched on localhost.
387 """
387 """
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
388 ip = self.ip if self.ip in LOCAL_IPS else LOCALHOST
389 kernel_manager = QtKernelManager(
389 kernel_manager = QtKernelManager(
390 ip=ip,
390 ip=ip,
391 connection_file=self._new_connection_file(),
391 connection_file=self._new_connection_file(),
392 config=self.config,
392 config=self.config,
393 )
393 )
394 # start the kernel
394 # start the kernel
395 kwargs = dict(ipython=not self.pure)
395 kwargs = dict(ipython=not self.pure)
396 kwargs['extra_arguments'] = self.kernel_argv
396 kwargs['extra_arguments'] = self.kernel_argv
397 kernel_manager.start_kernel(**kwargs)
397 kernel_manager.start_kernel(**kwargs)
398 kernel_manager.start_channels()
398 kernel_manager.start_channels()
399 widget = self.widget_factory(config=self.config,
399 widget = self.widget_factory(config=self.config,
400 local_kernel=True)
400 local_kernel=True)
401 widget.kernel_manager = kernel_manager
401 widget.kernel_manager = kernel_manager
402 widget._existing = False
402 widget._existing = False
403 widget._may_close = True
403 widget._may_close = True
404 widget._confirm_exit = self.confirm_exit
404 widget._confirm_exit = self.confirm_exit
405 return widget
405 return widget
406
406
407 def new_frontend_slave(self, current_widget):
407 def new_frontend_slave(self, current_widget):
408 """Create and return a new frontend attached to an existing kernel.
408 """Create and return a new frontend attached to an existing kernel.
409
409
410 Parameters
410 Parameters
411 ----------
411 ----------
412 current_widget : IPythonWidget
412 current_widget : IPythonWidget
413 The IPythonWidget whose kernel this frontend is to share
413 The IPythonWidget whose kernel this frontend is to share
414 """
414 """
415 kernel_manager = QtKernelManager(
415 kernel_manager = QtKernelManager(
416 connection_file=current_widget.kernel_manager.connection_file,
416 connection_file=current_widget.kernel_manager.connection_file,
417 config = self.config,
417 config = self.config,
418 )
418 )
419 kernel_manager.load_connection_file()
419 kernel_manager.load_connection_file()
420 kernel_manager.start_channels()
420 kernel_manager.start_channels()
421 widget = self.widget_factory(config=self.config,
421 widget = self.widget_factory(config=self.config,
422 local_kernel=False)
422 local_kernel=False)
423 widget._existing = True
423 widget._existing = True
424 widget._may_close = False
424 widget._may_close = False
425 widget._confirm_exit = False
425 widget._confirm_exit = False
426 widget.kernel_manager = kernel_manager
426 widget.kernel_manager = kernel_manager
427 return widget
427 return widget
428
428
429 def init_qt_elements(self):
429 def init_qt_elements(self):
430 # Create the widget.
430 # Create the widget.
431 self.app = QtGui.QApplication([])
431 self.app = QtGui.QApplication([])
432
432
433 base_path = os.path.abspath(os.path.dirname(__file__))
433 base_path = os.path.abspath(os.path.dirname(__file__))
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
434 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
435 self.app.icon = QtGui.QIcon(icon_path)
435 self.app.icon = QtGui.QIcon(icon_path)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
436 QtGui.QApplication.setWindowIcon(self.app.icon)
437
437
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
438 local_kernel = (not self.existing) or self.ip in LOCAL_IPS
439 self.widget = self.widget_factory(config=self.config,
439 self.widget = self.widget_factory(config=self.config,
440 local_kernel=local_kernel)
440 local_kernel=local_kernel)
441 self.widget._existing = self.existing
441 self.widget._existing = self.existing
442 self.widget._may_close = not self.existing
442 self.widget._may_close = not self.existing
443 self.widget._confirm_exit = self.confirm_exit
443 self.widget._confirm_exit = self.confirm_exit
444
444
445 self.widget.kernel_manager = self.kernel_manager
445 self.widget.kernel_manager = self.kernel_manager
446 self.window = MainWindow(self.app,
446 self.window = MainWindow(self.app,
447 confirm_exit=self.confirm_exit,
447 confirm_exit=self.confirm_exit,
448 new_frontend_factory=self.new_frontend_master,
448 new_frontend_factory=self.new_frontend_master,
449 slave_frontend_factory=self.new_frontend_slave,
449 slave_frontend_factory=self.new_frontend_slave,
450 )
450 )
451 self.window.log = self.log
451 self.window.log = self.log
452 self.window.add_tab_with_frontend(self.widget)
452 self.window.add_tab_with_frontend(self.widget)
453 self.window.init_menu_bar()
453 self.window.init_menu_bar()
454
455 # we need to populate the 'Magic Menu' once the kernel has answer at least once
456 self.kernel_manager.shell_channel.first_reply.connect(self.window.pop.trigger)
457
454 self.window.setWindowTitle('Python' if self.pure else 'IPython')
458 self.window.setWindowTitle('Python' if self.pure else 'IPython')
455
459
456 def init_colors(self):
460 def init_colors(self):
457 """Configure the coloring of the widget"""
461 """Configure the coloring of the widget"""
458 # Note: This will be dramatically simplified when colors
462 # Note: This will be dramatically simplified when colors
459 # are removed from the backend.
463 # are removed from the backend.
460
464
461 if self.pure:
465 if self.pure:
462 # only IPythonWidget supports styling
466 # only IPythonWidget supports styling
463 return
467 return
464
468
465 # parse the colors arg down to current known labels
469 # parse the colors arg down to current known labels
466 try:
470 try:
467 colors = self.config.ZMQInteractiveShell.colors
471 colors = self.config.ZMQInteractiveShell.colors
468 except AttributeError:
472 except AttributeError:
469 colors = None
473 colors = None
470 try:
474 try:
471 style = self.config.IPythonWidget.syntax_style
475 style = self.config.IPythonWidget.syntax_style
472 except AttributeError:
476 except AttributeError:
473 style = None
477 style = None
474
478
475 # find the value for colors:
479 # find the value for colors:
476 if colors:
480 if colors:
477 colors=colors.lower()
481 colors=colors.lower()
478 if colors in ('lightbg', 'light'):
482 if colors in ('lightbg', 'light'):
479 colors='lightbg'
483 colors='lightbg'
480 elif colors in ('dark', 'linux'):
484 elif colors in ('dark', 'linux'):
481 colors='linux'
485 colors='linux'
482 else:
486 else:
483 colors='nocolor'
487 colors='nocolor'
484 elif style:
488 elif style:
485 if style=='bw':
489 if style=='bw':
486 colors='nocolor'
490 colors='nocolor'
487 elif styles.dark_style(style):
491 elif styles.dark_style(style):
488 colors='linux'
492 colors='linux'
489 else:
493 else:
490 colors='lightbg'
494 colors='lightbg'
491 else:
495 else:
492 colors=None
496 colors=None
493
497
494 # Configure the style.
498 # Configure the style.
495 widget = self.widget
499 widget = self.widget
496 if style:
500 if style:
497 widget.style_sheet = styles.sheet_from_template(style, colors)
501 widget.style_sheet = styles.sheet_from_template(style, colors)
498 widget.syntax_style = style
502 widget.syntax_style = style
499 widget._syntax_style_changed()
503 widget._syntax_style_changed()
500 widget._style_sheet_changed()
504 widget._style_sheet_changed()
501 elif colors:
505 elif colors:
502 # use a default style
506 # use a default style
503 widget.set_default_style(colors=colors)
507 widget.set_default_style(colors=colors)
504 else:
508 else:
505 # this is redundant for now, but allows the widget's
509 # this is redundant for now, but allows the widget's
506 # defaults to change
510 # defaults to change
507 widget.set_default_style()
511 widget.set_default_style()
508
512
509 if self.stylesheet:
513 if self.stylesheet:
510 # we got an expicit stylesheet
514 # we got an expicit stylesheet
511 if os.path.isfile(self.stylesheet):
515 if os.path.isfile(self.stylesheet):
512 with open(self.stylesheet) as f:
516 with open(self.stylesheet) as f:
513 sheet = f.read()
517 sheet = f.read()
514 widget.style_sheet = sheet
518 widget.style_sheet = sheet
515 widget._style_sheet_changed()
519 widget._style_sheet_changed()
516 else:
520 else:
517 raise IOError("Stylesheet %r not found."%self.stylesheet)
521 raise IOError("Stylesheet %r not found."%self.stylesheet)
518
522
519 @catch_config_error
523 @catch_config_error
520 def initialize(self, argv=None):
524 def initialize(self, argv=None):
521 super(IPythonQtConsoleApp, self).initialize(argv)
525 super(IPythonQtConsoleApp, self).initialize(argv)
522 self.init_connection_file()
526 self.init_connection_file()
523 default_secure(self.config)
527 default_secure(self.config)
524 self.init_ssh()
528 self.init_ssh()
525 self.init_kernel_manager()
529 self.init_kernel_manager()
526 self.init_qt_elements()
530 self.init_qt_elements()
527 self.init_colors()
531 self.init_colors()
528
532
529 def start(self):
533 def start(self):
530
534
531 # draw the window
535 # draw the window
532 self.window.show()
536 self.window.show()
533
537
534 # Start the application main loop.
538 # Start the application main loop.
535 self.app.exec_()
539 self.app.exec_()
536
540
537 #-----------------------------------------------------------------------------
541 #-----------------------------------------------------------------------------
538 # Main entry point
542 # Main entry point
539 #-----------------------------------------------------------------------------
543 #-----------------------------------------------------------------------------
540
544
541 def main():
545 def main():
542 app = IPythonQtConsoleApp()
546 app = IPythonQtConsoleApp()
543 app.initialize()
547 app.initialize()
544 app.start()
548 app.start()
545
549
546
550
547 if __name__ == '__main__':
551 if __name__ == '__main__':
548 main()
552 main()
General Comments 0
You need to be logged in to leave comments. Login now