##// END OF EJS Templates
Fixed bug introduced by the recent payload processing refactor.
epatters -
Show More
@@ -1,323 +1,325 b''
1 # Standard library imports
1 # Standard library imports
2 from subprocess import Popen
2 from subprocess import Popen
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from IPython.core.inputsplitter import IPythonInputSplitter
8 from IPython.core.inputsplitter import IPythonInputSplitter
9 from IPython.core.usage import default_banner
9 from IPython.core.usage import default_banner
10 from frontend_widget import FrontendWidget
10 from frontend_widget import FrontendWidget
11
11
12
12
13 class IPythonPromptBlock(object):
13 class IPythonPromptBlock(object):
14 """ An internal storage object for IPythonWidget.
14 """ An internal storage object for IPythonWidget.
15 """
15 """
16 def __init__(self, block, length, number):
16 def __init__(self, block, length, number):
17 self.block = block
17 self.block = block
18 self.length = length
18 self.length = length
19 self.number = number
19 self.number = number
20
20
21
21
22 class IPythonWidget(FrontendWidget):
22 class IPythonWidget(FrontendWidget):
23 """ A FrontendWidget for an IPython kernel.
23 """ A FrontendWidget for an IPython kernel.
24 """
24 """
25
25
26 # Signal emitted when an editor is needed for a file and the editor has been
26 # Signal emitted when an editor is needed for a file and the editor has been
27 # specified as 'custom'. See 'set_editor' for more information.
27 # specified as 'custom'. See 'set_editor' for more information.
28 custom_edit_requested = QtCore.pyqtSignal(object, object)
28 custom_edit_requested = QtCore.pyqtSignal(object, object)
29
29
30 # The default stylesheet: black text on a white background.
30 # The default stylesheet: black text on a white background.
31 default_stylesheet = """
31 default_stylesheet = """
32 .error { color: red; }
32 .error { color: red; }
33 .in-prompt { color: navy; }
33 .in-prompt { color: navy; }
34 .in-prompt-number { font-weight: bold; }
34 .in-prompt-number { font-weight: bold; }
35 .out-prompt { color: darkred; }
35 .out-prompt { color: darkred; }
36 .out-prompt-number { font-weight: bold; }
36 .out-prompt-number { font-weight: bold; }
37 """
37 """
38
38
39 # A dark stylesheet: white text on a black background.
39 # A dark stylesheet: white text on a black background.
40 dark_stylesheet = """
40 dark_stylesheet = """
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
41 QPlainTextEdit, QTextEdit { background-color: black; color: white }
42 QFrame { border: 1px solid grey; }
42 QFrame { border: 1px solid grey; }
43 .error { color: red; }
43 .error { color: red; }
44 .in-prompt { color: lime; }
44 .in-prompt { color: lime; }
45 .in-prompt-number { color: lime; font-weight: bold; }
45 .in-prompt-number { color: lime; font-weight: bold; }
46 .out-prompt { color: red; }
46 .out-prompt { color: red; }
47 .out-prompt-number { color: red; font-weight: bold; }
47 .out-prompt-number { color: red; font-weight: bold; }
48 """
48 """
49
49
50 # Default prompts.
50 # Default prompts.
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
51 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
52 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
53
53
54 # FrontendWidget protected class variables.
54 # FrontendWidget protected class variables.
55 #_input_splitter_class = IPythonInputSplitter
55 #_input_splitter_class = IPythonInputSplitter
56
56
57 # IPythonWidget protected class variables.
57 # IPythonWidget protected class variables.
58 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
58 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
59 _payload_source_page = 'IPython.zmq.page.page'
59 _payload_source_page = 'IPython.zmq.page.page'
60
60
61 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
62 # 'object' interface
62 # 'object' interface
63 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
64
64
65 def __init__(self, *args, **kw):
65 def __init__(self, *args, **kw):
66 super(IPythonWidget, self).__init__(*args, **kw)
66 super(IPythonWidget, self).__init__(*args, **kw)
67
67
68 # IPythonWidget protected variables.
68 # IPythonWidget protected variables.
69 self._previous_prompt_obj = None
69 self._previous_prompt_obj = None
70
70
71 # Set a default editor and stylesheet.
71 # Set a default editor and stylesheet.
72 self.set_editor('default')
72 self.set_editor('default')
73 self.reset_styling()
73 self.reset_styling()
74
74
75 #---------------------------------------------------------------------------
75 #---------------------------------------------------------------------------
76 # 'BaseFrontendMixin' abstract interface
76 # 'BaseFrontendMixin' abstract interface
77 #---------------------------------------------------------------------------
77 #---------------------------------------------------------------------------
78
78
79 def _handle_history_reply(self, msg):
79 def _handle_history_reply(self, msg):
80 """ Implemented to handle history replies, which are only supported by
80 """ Implemented to handle history replies, which are only supported by
81 the IPython kernel.
81 the IPython kernel.
82 """
82 """
83 history_dict = msg['content']['history']
83 history_dict = msg['content']['history']
84 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
84 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
85 self._set_history(items)
85 self._set_history(items)
86
86
87 def _handle_prompt_reply(self, msg):
87 def _handle_prompt_reply(self, msg):
88 """ Implemented to handle prompt number replies, which are only
88 """ Implemented to handle prompt number replies, which are only
89 supported by the IPython kernel.
89 supported by the IPython kernel.
90 """
90 """
91 content = msg['content']
91 content = msg['content']
92 self._show_interpreter_prompt(content['prompt_number'],
92 self._show_interpreter_prompt(content['prompt_number'],
93 content['input_sep'])
93 content['input_sep'])
94
94
95 def _handle_pyout(self, msg):
95 def _handle_pyout(self, msg):
96 """ Reimplemented for IPython-style "display hook".
96 """ Reimplemented for IPython-style "display hook".
97 """
97 """
98 if not self._hidden and self._is_from_this_session(msg):
98 if not self._hidden and self._is_from_this_session(msg):
99 content = msg['content']
99 content = msg['content']
100 prompt_number = content['prompt_number']
100 prompt_number = content['prompt_number']
101 self._append_plain_text(content['output_sep'])
101 self._append_plain_text(content['output_sep'])
102 self._append_html(self._make_out_prompt(prompt_number))
102 self._append_html(self._make_out_prompt(prompt_number))
103 self._append_plain_text(content['data'] + '\n' +
103 self._append_plain_text(content['data'] + '\n' +
104 content['output_sep2'])
104 content['output_sep2'])
105
105
106 def _started_channels(self):
106 def _started_channels(self):
107 """ Reimplemented to make a history request.
107 """ Reimplemented to make a history request.
108 """
108 """
109 super(IPythonWidget, self)._started_channels()
109 super(IPythonWidget, self)._started_channels()
110 # FIXME: Disabled until history requests are properly implemented.
110 # FIXME: Disabled until history requests are properly implemented.
111 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
111 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
112
112
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114 # 'FrontendWidget' interface
114 # 'FrontendWidget' interface
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116
116
117 def execute_file(self, path, hidden=False):
117 def execute_file(self, path, hidden=False):
118 """ Reimplemented to use the 'run' magic.
118 """ Reimplemented to use the 'run' magic.
119 """
119 """
120 self.execute('%%run %s' % path, hidden=hidden)
120 self.execute('%%run %s' % path, hidden=hidden)
121
121
122 #---------------------------------------------------------------------------
122 #---------------------------------------------------------------------------
123 # 'FrontendWidget' protected interface
123 # 'FrontendWidget' protected interface
124 #---------------------------------------------------------------------------
124 #---------------------------------------------------------------------------
125
125
126 def _get_banner(self):
126 def _get_banner(self):
127 """ Reimplemented to return IPython's default banner.
127 """ Reimplemented to return IPython's default banner.
128 """
128 """
129 return default_banner + '\n'
129 return default_banner + '\n'
130
130
131 def _process_execute_error(self, msg):
131 def _process_execute_error(self, msg):
132 """ Reimplemented for IPython-style traceback formatting.
132 """ Reimplemented for IPython-style traceback formatting.
133 """
133 """
134 content = msg['content']
134 content = msg['content']
135 traceback = '\n'.join(content['traceback']) + '\n'
135 traceback = '\n'.join(content['traceback']) + '\n'
136 if False:
136 if False:
137 # FIXME: For now, tracebacks come as plain text, so we can't use
137 # FIXME: For now, tracebacks come as plain text, so we can't use
138 # the html renderer yet. Once we refactor ultratb to produce
138 # the html renderer yet. Once we refactor ultratb to produce
139 # properly styled tracebacks, this branch should be the default
139 # properly styled tracebacks, this branch should be the default
140 traceback = traceback.replace(' ', '&nbsp;')
140 traceback = traceback.replace(' ', '&nbsp;')
141 traceback = traceback.replace('\n', '<br/>')
141 traceback = traceback.replace('\n', '<br/>')
142
142
143 ename = content['ename']
143 ename = content['ename']
144 ename_styled = '<span class="error">%s</span>' % ename
144 ename_styled = '<span class="error">%s</span>' % ename
145 traceback = traceback.replace(ename, ename_styled)
145 traceback = traceback.replace(ename, ename_styled)
146
146
147 self._append_html(traceback)
147 self._append_html(traceback)
148 else:
148 else:
149 # This is the fallback for now, using plain text with ansi escapes
149 # This is the fallback for now, using plain text with ansi escapes
150 self._append_plain_text(traceback)
150 self._append_plain_text(traceback)
151
151
152 def _process_execute_payload(self, item):
152 def _process_execute_payload(self, item):
153 """ Reimplemented to handle %edit and paging payloads.
153 """ Reimplemented to handle %edit and paging payloads.
154 """
154 """
155 if item['source'] == self._payload_source_edit:
155 if item['source'] == self._payload_source_edit:
156 self.edit(item['filename'], item['line_number'])
156 self.edit(item['filename'], item['line_number'])
157 return True
157 return True
158 elif item['source'] == self._payload_source_page:
158 elif item['source'] == self._payload_source_page:
159 self._page(item['data'])
159 self._page(item['data'])
160 return True
160 return True
161 else:
161 else:
162 return False
162 return False
163
163
164 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
164 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
165 """ Reimplemented for IPython-style prompts.
165 """ Reimplemented for IPython-style prompts.
166 """
166 """
167 # If a number was not specified, make a prompt number request.
167 # If a number was not specified, make a prompt number request.
168 if number is None:
168 if number is None:
169 self.kernel_manager.xreq_channel.prompt()
169 self.kernel_manager.xreq_channel.prompt()
170 return
170 return
171
171
172 # Show a new prompt and save information about it so that it can be
172 # Show a new prompt and save information about it so that it can be
173 # updated later if the prompt number turns out to be wrong.
173 # updated later if the prompt number turns out to be wrong.
174 self._append_plain_text(input_sep)
174 self._append_plain_text(input_sep)
175 self._show_prompt(self._make_in_prompt(number), html=True)
175 self._show_prompt(self._make_in_prompt(number), html=True)
176 block = self._control.document().lastBlock()
176 block = self._control.document().lastBlock()
177 length = len(self._prompt)
177 length = len(self._prompt)
178 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
178 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
179
179
180 # Update continuation prompt to reflect (possibly) new prompt length.
180 # Update continuation prompt to reflect (possibly) new prompt length.
181 self._set_continuation_prompt(
181 self._set_continuation_prompt(
182 self._make_continuation_prompt(self._prompt), html=True)
182 self._make_continuation_prompt(self._prompt), html=True)
183
183
184 def _show_interpreter_prompt_for_reply(self, msg):
184 def _show_interpreter_prompt_for_reply(self, msg):
185 """ Reimplemented for IPython-style prompts.
185 """ Reimplemented for IPython-style prompts.
186 """
186 """
187 # Update the old prompt number if necessary.
187 # Update the old prompt number if necessary.
188 content = msg['content']
188 content = msg['content']
189 previous_prompt_number = content['prompt_number']
189 previous_prompt_number = content['prompt_number']
190 if self._previous_prompt_obj and \
190 if self._previous_prompt_obj and \
191 self._previous_prompt_obj.number != previous_prompt_number:
191 self._previous_prompt_obj.number != previous_prompt_number:
192 block = self._previous_prompt_obj.block
192 block = self._previous_prompt_obj.block
193
194 # Make sure the prompt block has not been erased.
193 if block.isValid() and not block.text().isEmpty():
195 if block.isValid() and not block.text().isEmpty():
194
196
195 # Remove the old prompt and insert a new prompt.
197 # Remove the old prompt and insert a new prompt.
196 cursor = QtGui.QTextCursor(block)
198 cursor = QtGui.QTextCursor(block)
197 cursor.movePosition(QtGui.QTextCursor.Right,
199 cursor.movePosition(QtGui.QTextCursor.Right,
198 QtGui.QTextCursor.KeepAnchor,
200 QtGui.QTextCursor.KeepAnchor,
199 self._previous_prompt_obj.length)
201 self._previous_prompt_obj.length)
200 prompt = self._make_in_prompt(previous_prompt_number)
202 prompt = self._make_in_prompt(previous_prompt_number)
201 self._prompt = self._insert_html_fetching_plain_text(
203 self._prompt = self._insert_html_fetching_plain_text(
202 cursor, prompt)
204 cursor, prompt)
203
205
204 # When the HTML is inserted, Qt blows away the syntax
206 # When the HTML is inserted, Qt blows away the syntax
205 # highlighting for the line, so we need to rehighlight it.
207 # highlighting for the line, so we need to rehighlight it.
206 self._highlighter.rehighlightBlock(cursor.block())
208 self._highlighter.rehighlightBlock(cursor.block())
207
209
208 self._previous_prompt_obj = None
210 self._previous_prompt_obj = None
209
211
210 # Show a new prompt with the kernel's estimated prompt number.
212 # Show a new prompt with the kernel's estimated prompt number.
211 next_prompt = content['next_prompt']
213 next_prompt = content['next_prompt']
212 self._show_interpreter_prompt(next_prompt['prompt_number'],
214 self._show_interpreter_prompt(next_prompt['prompt_number'],
213 next_prompt['input_sep'])
215 next_prompt['input_sep'])
214
216
215 #---------------------------------------------------------------------------
217 #---------------------------------------------------------------------------
216 # 'IPythonWidget' interface
218 # 'IPythonWidget' interface
217 #---------------------------------------------------------------------------
219 #---------------------------------------------------------------------------
218
220
219 def edit(self, filename, line=None):
221 def edit(self, filename, line=None):
220 """ Opens a Python script for editing.
222 """ Opens a Python script for editing.
221
223
222 Parameters:
224 Parameters:
223 -----------
225 -----------
224 filename : str
226 filename : str
225 A path to a local system file.
227 A path to a local system file.
226
228
227 line : int, optional
229 line : int, optional
228 A line of interest in the file.
230 A line of interest in the file.
229
231
230 Raises:
232 Raises:
231 -------
233 -------
232 OSError
234 OSError
233 If the editor command cannot be executed.
235 If the editor command cannot be executed.
234 """
236 """
235 if self._editor == 'default':
237 if self._editor == 'default':
236 url = QtCore.QUrl.fromLocalFile(filename)
238 url = QtCore.QUrl.fromLocalFile(filename)
237 if not QtGui.QDesktopServices.openUrl(url):
239 if not QtGui.QDesktopServices.openUrl(url):
238 message = 'Failed to open %s with the default application'
240 message = 'Failed to open %s with the default application'
239 raise OSError(message % repr(filename))
241 raise OSError(message % repr(filename))
240 elif self._editor is None:
242 elif self._editor is None:
241 self.custom_edit_requested.emit(filename, line)
243 self.custom_edit_requested.emit(filename, line)
242 else:
244 else:
243 Popen(self._editor + [filename])
245 Popen(self._editor + [filename])
244
246
245 def reset_styling(self):
247 def reset_styling(self):
246 """ Restores the default IPythonWidget styling.
248 """ Restores the default IPythonWidget styling.
247 """
249 """
248 self.set_styling(self.default_stylesheet, syntax_style='default')
250 self.set_styling(self.default_stylesheet, syntax_style='default')
249 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
251 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
250
252
251 def set_editor(self, editor):
253 def set_editor(self, editor):
252 """ Sets the editor to use with the %edit magic.
254 """ Sets the editor to use with the %edit magic.
253
255
254 Parameters:
256 Parameters:
255 -----------
257 -----------
256 editor : str or sequence of str
258 editor : str or sequence of str
257 A command suitable for use with Popen. This command will be executed
259 A command suitable for use with Popen. This command will be executed
258 with a single argument--a filename--when editing is requested.
260 with a single argument--a filename--when editing is requested.
259
261
260 This parameter also takes two special values:
262 This parameter also takes two special values:
261 'default' : Files will be edited with the system default
263 'default' : Files will be edited with the system default
262 application for Python files.
264 application for Python files.
263 'custom' : Emit a 'custom_edit_requested(str, int)' signal
265 'custom' : Emit a 'custom_edit_requested(str, int)' signal
264 instead of opening an editor.
266 instead of opening an editor.
265 """
267 """
266 if editor == 'default':
268 if editor == 'default':
267 self._editor = 'default'
269 self._editor = 'default'
268 elif editor == 'custom':
270 elif editor == 'custom':
269 self._editor = None
271 self._editor = None
270 elif isinstance(editor, basestring):
272 elif isinstance(editor, basestring):
271 self._editor = [ editor ]
273 self._editor = [ editor ]
272 else:
274 else:
273 self._editor = list(editor)
275 self._editor = list(editor)
274
276
275 def set_styling(self, stylesheet, syntax_style=None):
277 def set_styling(self, stylesheet, syntax_style=None):
276 """ Sets the IPythonWidget styling.
278 """ Sets the IPythonWidget styling.
277
279
278 Parameters:
280 Parameters:
279 -----------
281 -----------
280 stylesheet : str
282 stylesheet : str
281 A CSS stylesheet. The stylesheet can contain classes for:
283 A CSS stylesheet. The stylesheet can contain classes for:
282 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
284 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
283 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
285 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
284 3. IPython: .error, .in-prompt, .out-prompt, etc.
286 3. IPython: .error, .in-prompt, .out-prompt, etc.
285
287
286 syntax_style : str or None [default None]
288 syntax_style : str or None [default None]
287 If specified, use the Pygments style with given name. Otherwise,
289 If specified, use the Pygments style with given name. Otherwise,
288 the stylesheet is queried for Pygments style information.
290 the stylesheet is queried for Pygments style information.
289 """
291 """
290 self.setStyleSheet(stylesheet)
292 self.setStyleSheet(stylesheet)
291 self._control.document().setDefaultStyleSheet(stylesheet)
293 self._control.document().setDefaultStyleSheet(stylesheet)
292 if self._page_control:
294 if self._page_control:
293 self._page_control.document().setDefaultStyleSheet(stylesheet)
295 self._page_control.document().setDefaultStyleSheet(stylesheet)
294
296
295 if syntax_style is None:
297 if syntax_style is None:
296 self._highlighter.set_style_sheet(stylesheet)
298 self._highlighter.set_style_sheet(stylesheet)
297 else:
299 else:
298 self._highlighter.set_style(syntax_style)
300 self._highlighter.set_style(syntax_style)
299
301
300 #---------------------------------------------------------------------------
302 #---------------------------------------------------------------------------
301 # 'IPythonWidget' protected interface
303 # 'IPythonWidget' protected interface
302 #---------------------------------------------------------------------------
304 #---------------------------------------------------------------------------
303
305
304 def _make_in_prompt(self, number):
306 def _make_in_prompt(self, number):
305 """ Given a prompt number, returns an HTML In prompt.
307 """ Given a prompt number, returns an HTML In prompt.
306 """
308 """
307 body = self.in_prompt % number
309 body = self.in_prompt % number
308 return '<span class="in-prompt">%s</span>' % body
310 return '<span class="in-prompt">%s</span>' % body
309
311
310 def _make_continuation_prompt(self, prompt):
312 def _make_continuation_prompt(self, prompt):
311 """ Given a plain text version of an In prompt, returns an HTML
313 """ Given a plain text version of an In prompt, returns an HTML
312 continuation prompt.
314 continuation prompt.
313 """
315 """
314 end_chars = '...: '
316 end_chars = '...: '
315 space_count = len(prompt.lstrip('\n')) - len(end_chars)
317 space_count = len(prompt.lstrip('\n')) - len(end_chars)
316 body = '&nbsp;' * space_count + end_chars
318 body = '&nbsp;' * space_count + end_chars
317 return '<span class="in-prompt">%s</span>' % body
319 return '<span class="in-prompt">%s</span>' % body
318
320
319 def _make_out_prompt(self, number):
321 def _make_out_prompt(self, number):
320 """ Given a prompt number, returns an HTML Out prompt.
322 """ Given a prompt number, returns an HTML Out prompt.
321 """
323 """
322 body = self.out_prompt % number
324 body = self.out_prompt % number
323 return '<span class="out-prompt">%s</span>' % body
325 return '<span class="out-prompt">%s</span>' % body
@@ -1,126 +1,126 b''
1 import os
1 import os
2
2
3 # System library imports
3 # System library imports
4 from PyQt4 import QtCore, QtGui
4 from PyQt4 import QtCore, QtGui
5
5
6 # Local imports
6 # Local imports
7 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
7 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
8 from ipython_widget import IPythonWidget
8 from ipython_widget import IPythonWidget
9
9
10
10
11 class RichIPythonWidget(IPythonWidget):
11 class RichIPythonWidget(IPythonWidget):
12 """ An IPythonWidget that supports rich text, including lists, images, and
12 """ An IPythonWidget that supports rich text, including lists, images, and
13 tables. Note that raw performance will be reduced compared to the plain
13 tables. Note that raw performance will be reduced compared to the plain
14 text version.
14 text version.
15 """
15 """
16
16
17 # RichIPythonWidget protected class variables.
17 # RichIPythonWidget protected class variables.
18 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
18 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
19 _svg_text_format_property = 1
19 _svg_text_format_property = 1
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'object' interface
22 # 'object' interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def __init__(self, *args, **kw):
25 def __init__(self, *args, **kw):
26 """ Create a RichIPythonWidget.
26 """ Create a RichIPythonWidget.
27 """
27 """
28 kw['kind'] = 'rich'
28 kw['kind'] = 'rich'
29 super(RichIPythonWidget, self).__init__(*args, **kw)
29 super(RichIPythonWidget, self).__init__(*args, **kw)
30
30
31 #---------------------------------------------------------------------------
31 #---------------------------------------------------------------------------
32 # 'ConsoleWidget' protected interface
32 # 'ConsoleWidget' protected interface
33 #---------------------------------------------------------------------------
33 #---------------------------------------------------------------------------
34
34
35 def _show_context_menu(self, pos):
35 def _show_context_menu(self, pos):
36 """ Reimplemented to show a custom context menu for images.
36 """ Reimplemented to show a custom context menu for images.
37 """
37 """
38 format = self._control.cursorForPosition(pos).charFormat()
38 format = self._control.cursorForPosition(pos).charFormat()
39 name = format.stringProperty(QtGui.QTextFormat.ImageName)
39 name = format.stringProperty(QtGui.QTextFormat.ImageName)
40 if name.isEmpty():
40 if name.isEmpty():
41 super(RichIPythonWidget, self)._show_context_menu(pos)
41 super(RichIPythonWidget, self)._show_context_menu(pos)
42 else:
42 else:
43 menu = QtGui.QMenu()
43 menu = QtGui.QMenu()
44
44
45 menu.addAction('Copy Image', lambda: self._copy_image(name))
45 menu.addAction('Copy Image', lambda: self._copy_image(name))
46 menu.addAction('Save Image As...', lambda: self._save_image(name))
46 menu.addAction('Save Image As...', lambda: self._save_image(name))
47 menu.addSeparator()
47 menu.addSeparator()
48
48
49 svg = format.stringProperty(self._svg_text_format_property)
49 svg = format.stringProperty(self._svg_text_format_property)
50 if not svg.isEmpty():
50 if not svg.isEmpty():
51 menu.addSeparator()
51 menu.addSeparator()
52 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
52 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
53 menu.addAction('Save SVG As...',
53 menu.addAction('Save SVG As...',
54 lambda: save_svg(svg, self._control))
54 lambda: save_svg(svg, self._control))
55
55
56 menu.exec_(self._control.mapToGlobal(pos))
56 menu.exec_(self._control.mapToGlobal(pos))
57
57
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59 # 'FrontendWidget' protected interface
59 # 'FrontendWidget' protected interface
60 #---------------------------------------------------------------------------
60 #---------------------------------------------------------------------------
61
61
62 def _process_execute_payload(self, item):
62 def _process_execute_payload(self, item):
63 """ Reimplemented to handle matplotlib plot payloads.
63 """ Reimplemented to handle matplotlib plot payloads.
64 """
64 """
65 if item['source'] == self._payload_source_plot:
65 if item['source'] == self._payload_source_plot:
66 if item['format'] == 'svg':
66 if item['format'] == 'svg':
67 svg = item['data']
67 svg = item['data']
68 try:
68 try:
69 image = svg_to_image(svg)
69 image = svg_to_image(svg)
70 except ValueError:
70 except ValueError:
71 self._append_plain_text('Received invalid plot data.')
71 self._append_plain_text('Received invalid plot data.')
72 else:
72 else:
73 format = self._add_image(image)
73 format = self._add_image(image)
74 format.setProperty(self._svg_text_format_property, svg)
74 format.setProperty(self._svg_text_format_property, svg)
75 cursor = self._get_end_cursor()
75 cursor = self._get_end_cursor()
76 cursor.insertBlock()
76 cursor.insertBlock()
77 cursor.insertImage(format)
77 cursor.insertImage(format)
78 cursor.insertBlock()
78 cursor.insertBlock()
79 return True
79 return True
80 else:
80 else:
81 # Add other plot formats here!
81 # Add other plot formats here!
82 return False
82 return False
83 else:
83 else:
84 return super(RichIPythonWidget, self)._process_execute_ok(msg)
84 return super(RichIPythonWidget, self)._process_execute_payload(item)
85
85
86 #---------------------------------------------------------------------------
86 #---------------------------------------------------------------------------
87 # 'RichIPythonWidget' protected interface
87 # 'RichIPythonWidget' protected interface
88 #---------------------------------------------------------------------------
88 #---------------------------------------------------------------------------
89
89
90 def _add_image(self, image):
90 def _add_image(self, image):
91 """ Adds the specified QImage to the document and returns a
91 """ Adds the specified QImage to the document and returns a
92 QTextImageFormat that references it.
92 QTextImageFormat that references it.
93 """
93 """
94 document = self._control.document()
94 document = self._control.document()
95 name = QtCore.QString.number(image.cacheKey())
95 name = QtCore.QString.number(image.cacheKey())
96 document.addResource(QtGui.QTextDocument.ImageResource,
96 document.addResource(QtGui.QTextDocument.ImageResource,
97 QtCore.QUrl(name), image)
97 QtCore.QUrl(name), image)
98 format = QtGui.QTextImageFormat()
98 format = QtGui.QTextImageFormat()
99 format.setName(name)
99 format.setName(name)
100 return format
100 return format
101
101
102 def _copy_image(self, name):
102 def _copy_image(self, name):
103 """ Copies the ImageResource with 'name' to the clipboard.
103 """ Copies the ImageResource with 'name' to the clipboard.
104 """
104 """
105 image = self._get_image(name)
105 image = self._get_image(name)
106 QtGui.QApplication.clipboard().setImage(image)
106 QtGui.QApplication.clipboard().setImage(image)
107
107
108 def _get_image(self, name):
108 def _get_image(self, name):
109 """ Returns the QImage stored as the ImageResource with 'name'.
109 """ Returns the QImage stored as the ImageResource with 'name'.
110 """
110 """
111 document = self._control.document()
111 document = self._control.document()
112 variant = document.resource(QtGui.QTextDocument.ImageResource,
112 variant = document.resource(QtGui.QTextDocument.ImageResource,
113 QtCore.QUrl(name))
113 QtCore.QUrl(name))
114 return variant.toPyObject()
114 return variant.toPyObject()
115
115
116 def _save_image(self, name, format='PNG'):
116 def _save_image(self, name, format='PNG'):
117 """ Shows a save dialog for the ImageResource with 'name'.
117 """ Shows a save dialog for the ImageResource with 'name'.
118 """
118 """
119 dialog = QtGui.QFileDialog(self._control, 'Save Image')
119 dialog = QtGui.QFileDialog(self._control, 'Save Image')
120 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
120 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
121 dialog.setDefaultSuffix(format.lower())
121 dialog.setDefaultSuffix(format.lower())
122 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
122 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
123 if dialog.exec_():
123 if dialog.exec_():
124 filename = dialog.selectedFiles()[0]
124 filename = dialog.selectedFiles()[0]
125 image = self._get_image(name)
125 image = self._get_image(name)
126 image.save(filename, format)
126 image.save(filename, format)
General Comments 0
You need to be logged in to leave comments. Login now