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