##// END OF EJS Templates
Add Math display support for qtconsole
Carlos Cordoba -
Show More
@@ -1,334 +1,347 b''
1 # Copyright (c) IPython Development Team.
1 # Copyright (c) IPython Development Team.
2 # Distributed under the terms of the Modified BSD License.
2 # Distributed under the terms of the Modified BSD License.
3
3
4 from base64 import decodestring
4 from base64 import decodestring
5 import os
5 import os
6 import re
6 import re
7
7
8 from IPython.external.qt import QtCore, QtGui
8 from IPython.external.qt import QtCore, QtGui
9
9
10 from IPython.lib.latextools import latex_to_png
10 from IPython.utils.path import ensure_dir_exists
11 from IPython.utils.path import ensure_dir_exists
11 from IPython.utils.traitlets import Bool
12 from IPython.utils.traitlets import Bool
12 from IPython.qt.svg import save_svg, svg_to_clipboard, svg_to_image
13 from IPython.qt.svg import save_svg, svg_to_clipboard, svg_to_image
13 from .ipython_widget import IPythonWidget
14 from .ipython_widget import IPythonWidget
14
15
15
16
16 class RichIPythonWidget(IPythonWidget):
17 class RichIPythonWidget(IPythonWidget):
17 """ An IPythonWidget that supports rich text, including lists, images, and
18 """ An IPythonWidget that supports rich text, including lists, images, and
18 tables. Note that raw performance will be reduced compared to the plain
19 tables. Note that raw performance will be reduced compared to the plain
19 text version.
20 text version.
20 """
21 """
21
22
22 # RichIPythonWidget protected class variables.
23 # RichIPythonWidget protected class variables.
23 _payload_source_plot = 'IPython.kernel.zmq.pylab.backend_payload.add_plot_payload'
24 _payload_source_plot = 'IPython.kernel.zmq.pylab.backend_payload.add_plot_payload'
24 _jpg_supported = Bool(False)
25 _jpg_supported = Bool(False)
25
26
26 # Used to determine whether a given html export attempt has already
27 # Used to determine whether a given html export attempt has already
27 # displayed a warning about being unable to convert a png to svg.
28 # displayed a warning about being unable to convert a png to svg.
28 _svg_warning_displayed = False
29 _svg_warning_displayed = False
29
30
30 #---------------------------------------------------------------------------
31 #---------------------------------------------------------------------------
31 # 'object' interface
32 # 'object' interface
32 #---------------------------------------------------------------------------
33 #---------------------------------------------------------------------------
33
34
34 def __init__(self, *args, **kw):
35 def __init__(self, *args, **kw):
35 """ Create a RichIPythonWidget.
36 """ Create a RichIPythonWidget.
36 """
37 """
37 kw['kind'] = 'rich'
38 kw['kind'] = 'rich'
38 super(RichIPythonWidget, self).__init__(*args, **kw)
39 super(RichIPythonWidget, self).__init__(*args, **kw)
39
40
40 # Configure the ConsoleWidget HTML exporter for our formats.
41 # Configure the ConsoleWidget HTML exporter for our formats.
41 self._html_exporter.image_tag = self._get_image_tag
42 self._html_exporter.image_tag = self._get_image_tag
42
43
43 # Dictionary for resolving document resource names to SVG data.
44 # Dictionary for resolving document resource names to SVG data.
44 self._name_to_svg_map = {}
45 self._name_to_svg_map = {}
45
46
46 # Do we support jpg ?
47 # Do we support jpg ?
47 # it seems that sometime jpg support is a plugin of QT, so try to assume
48 # it seems that sometime jpg support is a plugin of QT, so try to assume
48 # it is not always supported.
49 # it is not always supported.
49 _supported_format = map(str, QtGui.QImageReader.supportedImageFormats())
50 _supported_format = map(str, QtGui.QImageReader.supportedImageFormats())
50 self._jpg_supported = 'jpeg' in _supported_format
51 self._jpg_supported = 'jpeg' in _supported_format
51
52
52
53
53 #---------------------------------------------------------------------------
54 #---------------------------------------------------------------------------
54 # 'ConsoleWidget' public interface overides
55 # 'ConsoleWidget' public interface overides
55 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
56
57
57 def export_html(self):
58 def export_html(self):
58 """ Shows a dialog to export HTML/XML in various formats.
59 """ Shows a dialog to export HTML/XML in various formats.
59
60
60 Overridden in order to reset the _svg_warning_displayed flag prior
61 Overridden in order to reset the _svg_warning_displayed flag prior
61 to the export running.
62 to the export running.
62 """
63 """
63 self._svg_warning_displayed = False
64 self._svg_warning_displayed = False
64 super(RichIPythonWidget, self).export_html()
65 super(RichIPythonWidget, self).export_html()
65
66
66
67
67 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
68 # 'ConsoleWidget' protected interface
69 # 'ConsoleWidget' protected interface
69 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
70
71
71 def _context_menu_make(self, pos):
72 def _context_menu_make(self, pos):
72 """ Reimplemented to return a custom context menu for images.
73 """ Reimplemented to return a custom context menu for images.
73 """
74 """
74 format = self._control.cursorForPosition(pos).charFormat()
75 format = self._control.cursorForPosition(pos).charFormat()
75 name = format.stringProperty(QtGui.QTextFormat.ImageName)
76 name = format.stringProperty(QtGui.QTextFormat.ImageName)
76 if name:
77 if name:
77 menu = QtGui.QMenu()
78 menu = QtGui.QMenu()
78
79
79 menu.addAction('Copy Image', lambda: self._copy_image(name))
80 menu.addAction('Copy Image', lambda: self._copy_image(name))
80 menu.addAction('Save Image As...', lambda: self._save_image(name))
81 menu.addAction('Save Image As...', lambda: self._save_image(name))
81 menu.addSeparator()
82 menu.addSeparator()
82
83
83 svg = self._name_to_svg_map.get(name, None)
84 svg = self._name_to_svg_map.get(name, None)
84 if svg is not None:
85 if svg is not None:
85 menu.addSeparator()
86 menu.addSeparator()
86 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
87 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
87 menu.addAction('Save SVG As...',
88 menu.addAction('Save SVG As...',
88 lambda: save_svg(svg, self._control))
89 lambda: save_svg(svg, self._control))
89 else:
90 else:
90 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
91 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
91 return menu
92 return menu
92
93
93 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
94 # 'BaseFrontendMixin' abstract interface
95 # 'BaseFrontendMixin' abstract interface
95 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
96 def _pre_image_append(self, msg, prompt_number):
97 def _pre_image_append(self, msg, prompt_number):
97 """ Append the Out[] prompt and make the output nicer
98 """ Append the Out[] prompt and make the output nicer
98
99
99 Shared code for some the following if statement
100 Shared code for some the following if statement
100 """
101 """
101 self.log.debug("execute_result: %s", msg.get('content', ''))
102 self.log.debug("execute_result: %s", msg.get('content', ''))
102 self._append_plain_text(self.output_sep, True)
103 self._append_plain_text(self.output_sep, True)
103 self._append_html(self._make_out_prompt(prompt_number), True)
104 self._append_html(self._make_out_prompt(prompt_number), True)
104 self._append_plain_text('\n', True)
105 self._append_plain_text('\n', True)
105
106
106 def _handle_execute_result(self, msg):
107 def _handle_execute_result(self, msg):
107 """ Overridden to handle rich data types, like SVG.
108 """ Overridden to handle rich data types, like SVG.
108 """
109 """
109 if not self._hidden and self._is_from_this_session(msg):
110 if not self._hidden and self._is_from_this_session(msg):
110 self.flush_clearoutput()
111 self.flush_clearoutput()
111 content = msg['content']
112 content = msg['content']
112 prompt_number = content.get('execution_count', 0)
113 prompt_number = content.get('execution_count', 0)
113 data = content['data']
114 data = content['data']
114 metadata = msg['content']['metadata']
115 metadata = msg['content']['metadata']
115 if 'image/svg+xml' in data:
116 if 'image/svg+xml' in data:
116 self._pre_image_append(msg, prompt_number)
117 self._pre_image_append(msg, prompt_number)
117 self._append_svg(data['image/svg+xml'], True)
118 self._append_svg(data['image/svg+xml'], True)
118 self._append_html(self.output_sep2, True)
119 self._append_html(self.output_sep2, True)
119 elif 'image/png' in data:
120 elif 'image/png' in data:
120 self._pre_image_append(msg, prompt_number)
121 self._pre_image_append(msg, prompt_number)
121 png = decodestring(data['image/png'].encode('ascii'))
122 png = decodestring(data['image/png'].encode('ascii'))
122 self._append_png(png, True, metadata=metadata.get('image/png', None))
123 self._append_png(png, True, metadata=metadata.get('image/png', None))
123 self._append_html(self.output_sep2, True)
124 self._append_html(self.output_sep2, True)
124 elif 'image/jpeg' in data and self._jpg_supported:
125 elif 'image/jpeg' in data and self._jpg_supported:
125 self._pre_image_append(msg, prompt_number)
126 self._pre_image_append(msg, prompt_number)
126 jpg = decodestring(data['image/jpeg'].encode('ascii'))
127 jpg = decodestring(data['image/jpeg'].encode('ascii'))
127 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
128 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
128 self._append_html(self.output_sep2, True)
129 self._append_html(self.output_sep2, True)
130 elif 'text/latex' in data:
131 self._pre_image_append(msg, prompt_number)
132 latex = data['text/latex'].encode('ascii')
133 # latex_to_png takes care of handling $
134 latex = latex.strip('$')
135 png = latex_to_png(latex, wrap=True)
136 if png is not None:
137 self._append_png(png, True)
138 self._append_html(self.output_sep2, True)
139 else:
140 # Print plain text if png can't be generated
141 return super(RichIPythonWidget, self)._handle_execute_result(msg)
129 else:
142 else:
130 # Default back to the plain text representation.
143 # Default back to the plain text representation.
131 return super(RichIPythonWidget, self)._handle_execute_result(msg)
144 return super(RichIPythonWidget, self)._handle_execute_result(msg)
132
145
133 def _handle_display_data(self, msg):
146 def _handle_display_data(self, msg):
134 """ Overridden to handle rich data types, like SVG.
147 """ Overridden to handle rich data types, like SVG.
135 """
148 """
136 if not self._hidden and self._is_from_this_session(msg):
149 if not self._hidden and self._is_from_this_session(msg):
137 self.flush_clearoutput()
150 self.flush_clearoutput()
138 data = msg['content']['data']
151 data = msg['content']['data']
139 metadata = msg['content']['metadata']
152 metadata = msg['content']['metadata']
140 # Try to use the svg or html representations.
153 # Try to use the svg or html representations.
141 # FIXME: Is this the right ordering of things to try?
154 # FIXME: Is this the right ordering of things to try?
142 if 'image/svg+xml' in data:
155 if 'image/svg+xml' in data:
143 self.log.debug("display: %s", msg.get('content', ''))
156 self.log.debug("display: %s", msg.get('content', ''))
144 svg = data['image/svg+xml']
157 svg = data['image/svg+xml']
145 self._append_svg(svg, True)
158 self._append_svg(svg, True)
146 elif 'image/png' in data:
159 elif 'image/png' in data:
147 self.log.debug("display: %s", msg.get('content', ''))
160 self.log.debug("display: %s", msg.get('content', ''))
148 # PNG data is base64 encoded as it passes over the network
161 # PNG data is base64 encoded as it passes over the network
149 # in a JSON structure so we decode it.
162 # in a JSON structure so we decode it.
150 png = decodestring(data['image/png'].encode('ascii'))
163 png = decodestring(data['image/png'].encode('ascii'))
151 self._append_png(png, True, metadata=metadata.get('image/png', None))
164 self._append_png(png, True, metadata=metadata.get('image/png', None))
152 elif 'image/jpeg' in data and self._jpg_supported:
165 elif 'image/jpeg' in data and self._jpg_supported:
153 self.log.debug("display: %s", msg.get('content', ''))
166 self.log.debug("display: %s", msg.get('content', ''))
154 jpg = decodestring(data['image/jpeg'].encode('ascii'))
167 jpg = decodestring(data['image/jpeg'].encode('ascii'))
155 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
168 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
156 else:
169 else:
157 # Default back to the plain text representation.
170 # Default back to the plain text representation.
158 return super(RichIPythonWidget, self)._handle_display_data(msg)
171 return super(RichIPythonWidget, self)._handle_display_data(msg)
159
172
160 #---------------------------------------------------------------------------
173 #---------------------------------------------------------------------------
161 # 'RichIPythonWidget' protected interface
174 # 'RichIPythonWidget' protected interface
162 #---------------------------------------------------------------------------
175 #---------------------------------------------------------------------------
163
176
164 def _append_jpg(self, jpg, before_prompt=False, metadata=None):
177 def _append_jpg(self, jpg, before_prompt=False, metadata=None):
165 """ Append raw JPG data to the widget."""
178 """ Append raw JPG data to the widget."""
166 self._append_custom(self._insert_jpg, jpg, before_prompt, metadata=metadata)
179 self._append_custom(self._insert_jpg, jpg, before_prompt, metadata=metadata)
167
180
168 def _append_png(self, png, before_prompt=False, metadata=None):
181 def _append_png(self, png, before_prompt=False, metadata=None):
169 """ Append raw PNG data to the widget.
182 """ Append raw PNG data to the widget.
170 """
183 """
171 self._append_custom(self._insert_png, png, before_prompt, metadata=metadata)
184 self._append_custom(self._insert_png, png, before_prompt, metadata=metadata)
172
185
173 def _append_svg(self, svg, before_prompt=False):
186 def _append_svg(self, svg, before_prompt=False):
174 """ Append raw SVG data to the widget.
187 """ Append raw SVG data to the widget.
175 """
188 """
176 self._append_custom(self._insert_svg, svg, before_prompt)
189 self._append_custom(self._insert_svg, svg, before_prompt)
177
190
178 def _add_image(self, image):
191 def _add_image(self, image):
179 """ Adds the specified QImage to the document and returns a
192 """ Adds the specified QImage to the document and returns a
180 QTextImageFormat that references it.
193 QTextImageFormat that references it.
181 """
194 """
182 document = self._control.document()
195 document = self._control.document()
183 name = str(image.cacheKey())
196 name = str(image.cacheKey())
184 document.addResource(QtGui.QTextDocument.ImageResource,
197 document.addResource(QtGui.QTextDocument.ImageResource,
185 QtCore.QUrl(name), image)
198 QtCore.QUrl(name), image)
186 format = QtGui.QTextImageFormat()
199 format = QtGui.QTextImageFormat()
187 format.setName(name)
200 format.setName(name)
188 return format
201 return format
189
202
190 def _copy_image(self, name):
203 def _copy_image(self, name):
191 """ Copies the ImageResource with 'name' to the clipboard.
204 """ Copies the ImageResource with 'name' to the clipboard.
192 """
205 """
193 image = self._get_image(name)
206 image = self._get_image(name)
194 QtGui.QApplication.clipboard().setImage(image)
207 QtGui.QApplication.clipboard().setImage(image)
195
208
196 def _get_image(self, name):
209 def _get_image(self, name):
197 """ Returns the QImage stored as the ImageResource with 'name'.
210 """ Returns the QImage stored as the ImageResource with 'name'.
198 """
211 """
199 document = self._control.document()
212 document = self._control.document()
200 image = document.resource(QtGui.QTextDocument.ImageResource,
213 image = document.resource(QtGui.QTextDocument.ImageResource,
201 QtCore.QUrl(name))
214 QtCore.QUrl(name))
202 return image
215 return image
203
216
204 def _get_image_tag(self, match, path = None, format = "png"):
217 def _get_image_tag(self, match, path = None, format = "png"):
205 """ Return (X)HTML mark-up for the image-tag given by match.
218 """ Return (X)HTML mark-up for the image-tag given by match.
206
219
207 Parameters
220 Parameters
208 ----------
221 ----------
209 match : re.SRE_Match
222 match : re.SRE_Match
210 A match to an HTML image tag as exported by Qt, with
223 A match to an HTML image tag as exported by Qt, with
211 match.group("Name") containing the matched image ID.
224 match.group("Name") containing the matched image ID.
212
225
213 path : string|None, optional [default None]
226 path : string|None, optional [default None]
214 If not None, specifies a path to which supporting files may be
227 If not None, specifies a path to which supporting files may be
215 written (e.g., for linked images). If None, all images are to be
228 written (e.g., for linked images). If None, all images are to be
216 included inline.
229 included inline.
217
230
218 format : "png"|"svg"|"jpg", optional [default "png"]
231 format : "png"|"svg"|"jpg", optional [default "png"]
219 Format for returned or referenced images.
232 Format for returned or referenced images.
220 """
233 """
221 if format in ("png","jpg"):
234 if format in ("png","jpg"):
222 try:
235 try:
223 image = self._get_image(match.group("name"))
236 image = self._get_image(match.group("name"))
224 except KeyError:
237 except KeyError:
225 return "<b>Couldn't find image %s</b>" % match.group("name")
238 return "<b>Couldn't find image %s</b>" % match.group("name")
226
239
227 if path is not None:
240 if path is not None:
228 ensure_dir_exists(path)
241 ensure_dir_exists(path)
229 relpath = os.path.basename(path)
242 relpath = os.path.basename(path)
230 if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
243 if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
231 "PNG"):
244 "PNG"):
232 return '<img src="%s/qt_img%s.%s">' % (relpath,
245 return '<img src="%s/qt_img%s.%s">' % (relpath,
233 match.group("name"),format)
246 match.group("name"),format)
234 else:
247 else:
235 return "<b>Couldn't save image!</b>"
248 return "<b>Couldn't save image!</b>"
236 else:
249 else:
237 ba = QtCore.QByteArray()
250 ba = QtCore.QByteArray()
238 buffer_ = QtCore.QBuffer(ba)
251 buffer_ = QtCore.QBuffer(ba)
239 buffer_.open(QtCore.QIODevice.WriteOnly)
252 buffer_.open(QtCore.QIODevice.WriteOnly)
240 image.save(buffer_, format.upper())
253 image.save(buffer_, format.upper())
241 buffer_.close()
254 buffer_.close()
242 return '<img src="data:image/%s;base64,\n%s\n" />' % (
255 return '<img src="data:image/%s;base64,\n%s\n" />' % (
243 format,re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
256 format,re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
244
257
245 elif format == "svg":
258 elif format == "svg":
246 try:
259 try:
247 svg = str(self._name_to_svg_map[match.group("name")])
260 svg = str(self._name_to_svg_map[match.group("name")])
248 except KeyError:
261 except KeyError:
249 if not self._svg_warning_displayed:
262 if not self._svg_warning_displayed:
250 QtGui.QMessageBox.warning(self, 'Error converting PNG to SVG.',
263 QtGui.QMessageBox.warning(self, 'Error converting PNG to SVG.',
251 'Cannot convert PNG images to SVG, export with PNG figures instead. '
264 'Cannot convert PNG images to SVG, export with PNG figures instead. '
252 'If you want to export matplotlib figures as SVG, add '
265 'If you want to export matplotlib figures as SVG, add '
253 'to your ipython config:\n\n'
266 'to your ipython config:\n\n'
254 '\tc.InlineBackend.figure_format = \'svg\'\n\n'
267 '\tc.InlineBackend.figure_format = \'svg\'\n\n'
255 'And regenerate the figures.',
268 'And regenerate the figures.',
256 QtGui.QMessageBox.Ok)
269 QtGui.QMessageBox.Ok)
257 self._svg_warning_displayed = True
270 self._svg_warning_displayed = True
258 return ("<b>Cannot convert PNG images to SVG.</b> "
271 return ("<b>Cannot convert PNG images to SVG.</b> "
259 "You must export this session with PNG images. "
272 "You must export this session with PNG images. "
260 "If you want to export matplotlib figures as SVG, add to your config "
273 "If you want to export matplotlib figures as SVG, add to your config "
261 "<span>c.InlineBackend.figure_format = 'svg'</span> "
274 "<span>c.InlineBackend.figure_format = 'svg'</span> "
262 "and regenerate the figures.")
275 "and regenerate the figures.")
263
276
264 # Not currently checking path, because it's tricky to find a
277 # Not currently checking path, because it's tricky to find a
265 # cross-browser way to embed external SVG images (e.g., via
278 # cross-browser way to embed external SVG images (e.g., via
266 # object or embed tags).
279 # object or embed tags).
267
280
268 # Chop stand-alone header from matplotlib SVG
281 # Chop stand-alone header from matplotlib SVG
269 offset = svg.find("<svg")
282 offset = svg.find("<svg")
270 assert(offset > -1)
283 assert(offset > -1)
271
284
272 return svg[offset:]
285 return svg[offset:]
273
286
274 else:
287 else:
275 return '<b>Unrecognized image format</b>'
288 return '<b>Unrecognized image format</b>'
276
289
277 def _insert_jpg(self, cursor, jpg, metadata=None):
290 def _insert_jpg(self, cursor, jpg, metadata=None):
278 """ Insert raw PNG data into the widget."""
291 """ Insert raw PNG data into the widget."""
279 self._insert_img(cursor, jpg, 'jpg', metadata=metadata)
292 self._insert_img(cursor, jpg, 'jpg', metadata=metadata)
280
293
281 def _insert_png(self, cursor, png, metadata=None):
294 def _insert_png(self, cursor, png, metadata=None):
282 """ Insert raw PNG data into the widget.
295 """ Insert raw PNG data into the widget.
283 """
296 """
284 self._insert_img(cursor, png, 'png', metadata=metadata)
297 self._insert_img(cursor, png, 'png', metadata=metadata)
285
298
286 def _insert_img(self, cursor, img, fmt, metadata=None):
299 def _insert_img(self, cursor, img, fmt, metadata=None):
287 """ insert a raw image, jpg or png """
300 """ insert a raw image, jpg or png """
288 if metadata:
301 if metadata:
289 width = metadata.get('width', None)
302 width = metadata.get('width', None)
290 height = metadata.get('height', None)
303 height = metadata.get('height', None)
291 else:
304 else:
292 width = height = None
305 width = height = None
293 try:
306 try:
294 image = QtGui.QImage()
307 image = QtGui.QImage()
295 image.loadFromData(img, fmt.upper())
308 image.loadFromData(img, fmt.upper())
296 if width and height:
309 if width and height:
297 image = image.scaled(width, height, transformMode=QtCore.Qt.SmoothTransformation)
310 image = image.scaled(width, height, transformMode=QtCore.Qt.SmoothTransformation)
298 elif width and not height:
311 elif width and not height:
299 image = image.scaledToWidth(width, transformMode=QtCore.Qt.SmoothTransformation)
312 image = image.scaledToWidth(width, transformMode=QtCore.Qt.SmoothTransformation)
300 elif height and not width:
313 elif height and not width:
301 image = image.scaledToHeight(height, transformMode=QtCore.Qt.SmoothTransformation)
314 image = image.scaledToHeight(height, transformMode=QtCore.Qt.SmoothTransformation)
302 except ValueError:
315 except ValueError:
303 self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
316 self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
304 else:
317 else:
305 format = self._add_image(image)
318 format = self._add_image(image)
306 cursor.insertBlock()
319 cursor.insertBlock()
307 cursor.insertImage(format)
320 cursor.insertImage(format)
308 cursor.insertBlock()
321 cursor.insertBlock()
309
322
310 def _insert_svg(self, cursor, svg):
323 def _insert_svg(self, cursor, svg):
311 """ Insert raw SVG data into the widet.
324 """ Insert raw SVG data into the widet.
312 """
325 """
313 try:
326 try:
314 image = svg_to_image(svg)
327 image = svg_to_image(svg)
315 except ValueError:
328 except ValueError:
316 self._insert_plain_text(cursor, 'Received invalid SVG data.')
329 self._insert_plain_text(cursor, 'Received invalid SVG data.')
317 else:
330 else:
318 format = self._add_image(image)
331 format = self._add_image(image)
319 self._name_to_svg_map[format.name()] = svg
332 self._name_to_svg_map[format.name()] = svg
320 cursor.insertBlock()
333 cursor.insertBlock()
321 cursor.insertImage(format)
334 cursor.insertImage(format)
322 cursor.insertBlock()
335 cursor.insertBlock()
323
336
324 def _save_image(self, name, format='PNG'):
337 def _save_image(self, name, format='PNG'):
325 """ Shows a save dialog for the ImageResource with 'name'.
338 """ Shows a save dialog for the ImageResource with 'name'.
326 """
339 """
327 dialog = QtGui.QFileDialog(self._control, 'Save Image')
340 dialog = QtGui.QFileDialog(self._control, 'Save Image')
328 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
341 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
329 dialog.setDefaultSuffix(format.lower())
342 dialog.setDefaultSuffix(format.lower())
330 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
343 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
331 if dialog.exec_():
344 if dialog.exec_():
332 filename = dialog.selectedFiles()[0]
345 filename = dialog.selectedFiles()[0]
333 image = self._get_image(name)
346 image = self._get_image(name)
334 image.save(filename, format)
347 image.save(filename, format)
General Comments 0
You need to be logged in to leave comments. Login now