##// END OF EJS Templates
make lib.latextools accept unicode...
Min RK -
Show More
@@ -1,251 +1,190 b''
1 1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX.
2 """Tools for handling LaTeX."""
3 3
4 Authors:
5
6 * Brian Granger
7 """
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010 IPython Development Team.
10 #
4 # Copyright (c) IPython Development Team.
11 5 # Distributed under the terms of the Modified BSD License.
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19 6
20 from io import BytesIO
7 from io import BytesIO, open
21 8 from base64 import encodestring
22 9 import os
23 10 import tempfile
24 11 import shutil
25 12 import subprocess
26 13
27 14 from IPython.utils.process import find_cmd, FindCmdError
15 from IPython.config.application import Application
28 16 from IPython.config.configurable import SingletonConfigurable
29 from IPython.utils.traitlets import List, CBool, CUnicode
30 from IPython.utils.py3compat import bytes_to_str
31
32 #-----------------------------------------------------------------------------
33 # Tools
34 #-----------------------------------------------------------------------------
17 from IPython.utils.traitlets import List, Bool, Unicode
18 from IPython.utils.py3compat import cast_unicode, cast_unicode_py2 as u
35 19
36 20
37 21 class LaTeXTool(SingletonConfigurable):
38 22 """An object to store configuration of the LaTeX tool."""
39 23
40 24 backends = List(
41 CUnicode, ["matplotlib", "dvipng"],
25 Unicode, ["matplotlib", "dvipng"],
42 26 help="Preferred backend to draw LaTeX math equations. "
43 27 "Backends in the list are checked one by one and the first "
44 28 "usable one is used. Note that `matplotlib` backend "
45 29 "is usable only for inline style equations. To draw "
46 30 "display style equations, `dvipng` backend must be specified. ",
47 31 # It is a List instead of Enum, to make configuration more
48 32 # flexible. For example, to use matplotlib mainly but dvipng
49 33 # for display style, the default ["matplotlib", "dvipng"] can
50 34 # be used. To NOT use dvipng so that other repr such as
51 35 # unicode pretty printing is used, you can use ["matplotlib"].
52 36 config=True)
53 37
54 use_breqn = CBool(
38 use_breqn = Bool(
55 39 True,
56 40 help="Use breqn.sty to automatically break long equations. "
57 41 "This configuration takes effect only for dvipng backend.",
58 42 config=True)
59 43
60 44 packages = List(
61 45 ['amsmath', 'amsthm', 'amssymb', 'bm'],
62 46 help="A list of packages to use for dvipng backend. "
63 47 "'breqn' will be automatically appended when use_breqn=True.",
64 48 config=True)
65 49
66 preamble = CUnicode(
50 preamble = Unicode(
67 51 help="Additional preamble to use when generating LaTeX source "
68 52 "for dvipng backend.",
69 53 config=True)
70 54
71 55
72 56 def latex_to_png(s, encode=False, backend=None, wrap=False):
73 57 """Render a LaTeX string to PNG.
74 58
75 59 Parameters
76 60 ----------
77 s : str
61 s : text
78 62 The raw string containing valid inline LaTeX.
79 63 encode : bool, optional
80 Should the PNG data bebase64 encoded to make it JSON'able.
64 Should the PNG data base64 encoded to make it JSON'able.
81 65 backend : {matplotlib, dvipng}
82 66 Backend for producing PNG data.
83 67 wrap : bool
84 68 If true, Automatically wrap `s` as a LaTeX equation.
85 69
86 70 None is returned when the backend cannot be used.
87 71
88 72 """
73 s = cast_unicode(s)
89 74 allowed_backends = LaTeXTool.instance().backends
90 75 if backend is None:
91 76 backend = allowed_backends[0]
92 77 if backend not in allowed_backends:
93 78 return None
94 79 if backend == 'matplotlib':
95 80 f = latex_to_png_mpl
96 81 elif backend == 'dvipng':
97 82 f = latex_to_png_dvipng
98 83 else:
99 84 raise ValueError('No such backend {0}'.format(backend))
100 85 bin_data = f(s, wrap)
101 86 if encode and bin_data:
102 87 bin_data = encodestring(bin_data)
103 88 return bin_data
104 89
105 90
106 91 def latex_to_png_mpl(s, wrap):
107 92 try:
108 93 from matplotlib import mathtext
109 94 except ImportError:
110 95 return None
111 96
112 97 if wrap:
113 s = '${0}$'.format(s)
98 s = u'${0}$'.format(s)
114 99 mt = mathtext.MathTextParser('bitmap')
115 100 f = BytesIO()
116 101 mt.to_png(f, s, fontsize=12)
117 102 return f.getvalue()
118 103
119 104
120 105 def latex_to_png_dvipng(s, wrap):
121 106 try:
122 107 find_cmd('latex')
123 108 find_cmd('dvipng')
124 109 except FindCmdError:
125 110 return None
126 111 try:
127 112 workdir = tempfile.mkdtemp()
128 113 tmpfile = os.path.join(workdir, "tmp.tex")
129 114 dvifile = os.path.join(workdir, "tmp.dvi")
130 115 outfile = os.path.join(workdir, "tmp.png")
131 116
132 with open(tmpfile, "w") as f:
117 with open(tmpfile, "w", encoding='utf8') as f:
133 118 f.writelines(genelatex(s, wrap))
134 119
135 with open(os.devnull, 'w') as devnull:
120 with open(os.devnull, 'wb') as devnull:
136 121 subprocess.check_call(
137 122 ["latex", "-halt-on-error", "-interaction", "batchmode", tmpfile],
138 123 cwd=workdir, stdout=devnull, stderr=devnull)
139 124
140 125 subprocess.check_call(
141 126 ["dvipng", "-T", "tight", "-x", "1500", "-z", "9",
142 127 "-bg", "transparent", "-o", outfile, dvifile], cwd=workdir,
143 128 stdout=devnull, stderr=devnull)
144 129
145 130 with open(outfile, "rb") as f:
146 131 return f.read()
147 132 finally:
148 133 shutil.rmtree(workdir)
149 134
150 135
151 136 def kpsewhich(filename):
152 137 """Invoke kpsewhich command with an argument `filename`."""
153 138 try:
154 139 find_cmd("kpsewhich")
155 140 proc = subprocess.Popen(
156 141 ["kpsewhich", filename],
157 142 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
158 143 (stdout, stderr) = proc.communicate()
159 return stdout.strip()
144 return stdout.strip().decode('utf8', 'replace')
160 145 except FindCmdError:
161 146 pass
162 147
163 148
164 149 def genelatex(body, wrap):
165 150 """Generate LaTeX document for dvipng backend."""
166 151 lt = LaTeXTool.instance()
167 152 breqn = wrap and lt.use_breqn and kpsewhich("breqn.sty")
168 yield r'\documentclass{article}'
153 yield u(r'\documentclass{article}')
169 154 packages = lt.packages
170 155 if breqn:
171 156 packages = packages + ['breqn']
172 157 for pack in packages:
173 yield r'\usepackage{{{0}}}'.format(pack)
174 yield r'\pagestyle{empty}'
158 yield u(r'\usepackage{{{0}}}'.format(pack))
159 yield u(r'\pagestyle{empty}')
175 160 if lt.preamble:
176 161 yield lt.preamble
177 yield r'\begin{document}'
162 yield u(r'\begin{document}')
178 163 if breqn:
179 yield r'\begin{dmath*}'
164 yield u(r'\begin{dmath*}')
180 165 yield body
181 yield r'\end{dmath*}'
166 yield u(r'\end{dmath*}')
182 167 elif wrap:
183 yield '$${0}$$'.format(body)
168 yield u'$${0}$$'.format(body)
184 169 else:
185 170 yield body
186 yield r'\end{document}'
171 yield u'\end{document}'
187 172
188 173
189 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
174 _data_uri_template_png = u"""<img src="data:image/png;base64,%s" alt=%s />"""
190 175
191 176 def latex_to_html(s, alt='image'):
192 177 """Render LaTeX to HTML with embedded PNG data using data URIs.
193 178
194 179 Parameters
195 180 ----------
196 181 s : str
197 182 The raw string containing valid inline LateX.
198 183 alt : str
199 184 The alt text to use for the HTML.
200 185 """
201 base64_data = bytes_to_str(latex_to_png(s, encode=True), 'ascii')
186 base64_data = latex_to_png(s, encode=True).decode('ascii')
202 187 if base64_data:
203 188 return _data_uri_template_png % (base64_data, alt)
204 189
205 190
206 # From matplotlib, thanks to mdboom. Once this is in matplotlib releases, we
207 # will remove.
208 def math_to_image(s, filename_or_obj, prop=None, dpi=None, format=None):
209 """
210 Given a math expression, renders it in a closely-clipped bounding
211 box to an image file.
212
213 *s*
214 A math expression. The math portion should be enclosed in
215 dollar signs.
216
217 *filename_or_obj*
218 A filepath or writable file-like object to write the image data
219 to.
220
221 *prop*
222 If provided, a FontProperties() object describing the size and
223 style of the text.
224
225 *dpi*
226 Override the output dpi, otherwise use the default associated
227 with the output format.
228
229 *format*
230 The output format, eg. 'svg', 'pdf', 'ps' or 'png'. If not
231 provided, will be deduced from the filename.
232 """
233 from matplotlib import figure
234 # backend_agg supports all of the core output formats
235 from matplotlib.backends import backend_agg
236 from matplotlib.font_manager import FontProperties
237 from matplotlib.mathtext import MathTextParser
238
239 if prop is None:
240 prop = FontProperties()
241
242 parser = MathTextParser('path')
243 width, height, depth, _, _ = parser.parse(s, dpi=72, prop=prop)
244
245 fig = figure.Figure(figsize=(width / 72.0, height / 72.0))
246 fig.text(0, depth/height, s, fontproperties=prop)
247 backend_agg.FigureCanvasAgg(fig)
248 fig.savefig(filename_or_obj, dpi=dpi, format=format)
249
250 return depth
251
@@ -1,347 +1,346 b''
1 1 # Copyright (c) IPython Development Team.
2 2 # Distributed under the terms of the Modified BSD License.
3 3
4 4 from base64 import decodestring
5 5 import os
6 6 import re
7 7
8 8 from IPython.external.qt import QtCore, QtGui
9 9
10 10 from IPython.lib.latextools import latex_to_png
11 11 from IPython.utils.path import ensure_dir_exists
12 12 from IPython.utils.traitlets import Bool
13 13 from IPython.qt.svg import save_svg, svg_to_clipboard, svg_to_image
14 14 from .ipython_widget import IPythonWidget
15 15
16 16
17 17 class RichIPythonWidget(IPythonWidget):
18 18 """ An IPythonWidget that supports rich text, including lists, images, and
19 19 tables. Note that raw performance will be reduced compared to the plain
20 20 text version.
21 21 """
22 22
23 23 # RichIPythonWidget protected class variables.
24 24 _payload_source_plot = 'IPython.kernel.zmq.pylab.backend_payload.add_plot_payload'
25 25 _jpg_supported = Bool(False)
26 26
27 27 # Used to determine whether a given html export attempt has already
28 28 # displayed a warning about being unable to convert a png to svg.
29 29 _svg_warning_displayed = False
30 30
31 31 #---------------------------------------------------------------------------
32 32 # 'object' interface
33 33 #---------------------------------------------------------------------------
34 34
35 35 def __init__(self, *args, **kw):
36 36 """ Create a RichIPythonWidget.
37 37 """
38 38 kw['kind'] = 'rich'
39 39 super(RichIPythonWidget, self).__init__(*args, **kw)
40 40
41 41 # Configure the ConsoleWidget HTML exporter for our formats.
42 42 self._html_exporter.image_tag = self._get_image_tag
43 43
44 44 # Dictionary for resolving document resource names to SVG data.
45 45 self._name_to_svg_map = {}
46 46
47 47 # Do we support jpg ?
48 48 # it seems that sometime jpg support is a plugin of QT, so try to assume
49 49 # it is not always supported.
50 50 _supported_format = map(str, QtGui.QImageReader.supportedImageFormats())
51 51 self._jpg_supported = 'jpeg' in _supported_format
52 52
53 53
54 54 #---------------------------------------------------------------------------
55 55 # 'ConsoleWidget' public interface overides
56 56 #---------------------------------------------------------------------------
57 57
58 58 def export_html(self):
59 59 """ Shows a dialog to export HTML/XML in various formats.
60 60
61 61 Overridden in order to reset the _svg_warning_displayed flag prior
62 62 to the export running.
63 63 """
64 64 self._svg_warning_displayed = False
65 65 super(RichIPythonWidget, self).export_html()
66 66
67 67
68 68 #---------------------------------------------------------------------------
69 69 # 'ConsoleWidget' protected interface
70 70 #---------------------------------------------------------------------------
71 71
72 72 def _context_menu_make(self, pos):
73 73 """ Reimplemented to return a custom context menu for images.
74 74 """
75 75 format = self._control.cursorForPosition(pos).charFormat()
76 76 name = format.stringProperty(QtGui.QTextFormat.ImageName)
77 77 if name:
78 78 menu = QtGui.QMenu()
79 79
80 80 menu.addAction('Copy Image', lambda: self._copy_image(name))
81 81 menu.addAction('Save Image As...', lambda: self._save_image(name))
82 82 menu.addSeparator()
83 83
84 84 svg = self._name_to_svg_map.get(name, None)
85 85 if svg is not None:
86 86 menu.addSeparator()
87 87 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
88 88 menu.addAction('Save SVG As...',
89 89 lambda: save_svg(svg, self._control))
90 90 else:
91 91 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
92 92 return menu
93 93
94 94 #---------------------------------------------------------------------------
95 95 # 'BaseFrontendMixin' abstract interface
96 96 #---------------------------------------------------------------------------
97 97 def _pre_image_append(self, msg, prompt_number):
98 98 """ Append the Out[] prompt and make the output nicer
99 99
100 100 Shared code for some the following if statement
101 101 """
102 102 self.log.debug("execute_result: %s", msg.get('content', ''))
103 103 self._append_plain_text(self.output_sep, True)
104 104 self._append_html(self._make_out_prompt(prompt_number), True)
105 105 self._append_plain_text('\n', True)
106 106
107 107 def _handle_execute_result(self, msg):
108 108 """ Overridden to handle rich data types, like SVG.
109 109 """
110 110 if self.include_output(msg):
111 111 self.flush_clearoutput()
112 112 content = msg['content']
113 113 prompt_number = content.get('execution_count', 0)
114 114 data = content['data']
115 115 metadata = msg['content']['metadata']
116 116 if 'image/svg+xml' in data:
117 117 self._pre_image_append(msg, prompt_number)
118 118 self._append_svg(data['image/svg+xml'], True)
119 119 self._append_html(self.output_sep2, True)
120 120 elif 'image/png' in data:
121 121 self._pre_image_append(msg, prompt_number)
122 122 png = decodestring(data['image/png'].encode('ascii'))
123 123 self._append_png(png, True, metadata=metadata.get('image/png', None))
124 124 self._append_html(self.output_sep2, True)
125 125 elif 'image/jpeg' in data and self._jpg_supported:
126 126 self._pre_image_append(msg, prompt_number)
127 127 jpg = decodestring(data['image/jpeg'].encode('ascii'))
128 128 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
129 129 self._append_html(self.output_sep2, True)
130 130 elif 'text/latex' in data:
131 131 self._pre_image_append(msg, prompt_number)
132 latex = data['text/latex'].encode('ascii')
133 132 # latex_to_png takes care of handling $
134 133 latex = latex.strip('$')
135 134 png = latex_to_png(latex, wrap=True)
136 135 if png is not None:
137 136 self._append_png(png, True)
138 137 self._append_html(self.output_sep2, True)
139 138 else:
140 139 # Print plain text if png can't be generated
141 140 return super(RichIPythonWidget, self)._handle_execute_result(msg)
142 141 else:
143 142 # Default back to the plain text representation.
144 143 return super(RichIPythonWidget, self)._handle_execute_result(msg)
145 144
146 145 def _handle_display_data(self, msg):
147 146 """ Overridden to handle rich data types, like SVG.
148 147 """
149 148 if self.include_output(msg):
150 149 self.flush_clearoutput()
151 150 data = msg['content']['data']
152 151 metadata = msg['content']['metadata']
153 152 # Try to use the svg or html representations.
154 153 # FIXME: Is this the right ordering of things to try?
155 154 if 'image/svg+xml' in data:
156 155 self.log.debug("display: %s", msg.get('content', ''))
157 156 svg = data['image/svg+xml']
158 157 self._append_svg(svg, True)
159 158 elif 'image/png' in data:
160 159 self.log.debug("display: %s", msg.get('content', ''))
161 160 # PNG data is base64 encoded as it passes over the network
162 161 # in a JSON structure so we decode it.
163 162 png = decodestring(data['image/png'].encode('ascii'))
164 163 self._append_png(png, True, metadata=metadata.get('image/png', None))
165 164 elif 'image/jpeg' in data and self._jpg_supported:
166 165 self.log.debug("display: %s", msg.get('content', ''))
167 166 jpg = decodestring(data['image/jpeg'].encode('ascii'))
168 167 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
169 168 else:
170 169 # Default back to the plain text representation.
171 170 return super(RichIPythonWidget, self)._handle_display_data(msg)
172 171
173 172 #---------------------------------------------------------------------------
174 173 # 'RichIPythonWidget' protected interface
175 174 #---------------------------------------------------------------------------
176 175
177 176 def _append_jpg(self, jpg, before_prompt=False, metadata=None):
178 177 """ Append raw JPG data to the widget."""
179 178 self._append_custom(self._insert_jpg, jpg, before_prompt, metadata=metadata)
180 179
181 180 def _append_png(self, png, before_prompt=False, metadata=None):
182 181 """ Append raw PNG data to the widget.
183 182 """
184 183 self._append_custom(self._insert_png, png, before_prompt, metadata=metadata)
185 184
186 185 def _append_svg(self, svg, before_prompt=False):
187 186 """ Append raw SVG data to the widget.
188 187 """
189 188 self._append_custom(self._insert_svg, svg, before_prompt)
190 189
191 190 def _add_image(self, image):
192 191 """ Adds the specified QImage to the document and returns a
193 192 QTextImageFormat that references it.
194 193 """
195 194 document = self._control.document()
196 195 name = str(image.cacheKey())
197 196 document.addResource(QtGui.QTextDocument.ImageResource,
198 197 QtCore.QUrl(name), image)
199 198 format = QtGui.QTextImageFormat()
200 199 format.setName(name)
201 200 return format
202 201
203 202 def _copy_image(self, name):
204 203 """ Copies the ImageResource with 'name' to the clipboard.
205 204 """
206 205 image = self._get_image(name)
207 206 QtGui.QApplication.clipboard().setImage(image)
208 207
209 208 def _get_image(self, name):
210 209 """ Returns the QImage stored as the ImageResource with 'name'.
211 210 """
212 211 document = self._control.document()
213 212 image = document.resource(QtGui.QTextDocument.ImageResource,
214 213 QtCore.QUrl(name))
215 214 return image
216 215
217 216 def _get_image_tag(self, match, path = None, format = "png"):
218 217 """ Return (X)HTML mark-up for the image-tag given by match.
219 218
220 219 Parameters
221 220 ----------
222 221 match : re.SRE_Match
223 222 A match to an HTML image tag as exported by Qt, with
224 223 match.group("Name") containing the matched image ID.
225 224
226 225 path : string|None, optional [default None]
227 226 If not None, specifies a path to which supporting files may be
228 227 written (e.g., for linked images). If None, all images are to be
229 228 included inline.
230 229
231 230 format : "png"|"svg"|"jpg", optional [default "png"]
232 231 Format for returned or referenced images.
233 232 """
234 233 if format in ("png","jpg"):
235 234 try:
236 235 image = self._get_image(match.group("name"))
237 236 except KeyError:
238 237 return "<b>Couldn't find image %s</b>" % match.group("name")
239 238
240 239 if path is not None:
241 240 ensure_dir_exists(path)
242 241 relpath = os.path.basename(path)
243 242 if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
244 243 "PNG"):
245 244 return '<img src="%s/qt_img%s.%s">' % (relpath,
246 245 match.group("name"),format)
247 246 else:
248 247 return "<b>Couldn't save image!</b>"
249 248 else:
250 249 ba = QtCore.QByteArray()
251 250 buffer_ = QtCore.QBuffer(ba)
252 251 buffer_.open(QtCore.QIODevice.WriteOnly)
253 252 image.save(buffer_, format.upper())
254 253 buffer_.close()
255 254 return '<img src="data:image/%s;base64,\n%s\n" />' % (
256 255 format,re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
257 256
258 257 elif format == "svg":
259 258 try:
260 259 svg = str(self._name_to_svg_map[match.group("name")])
261 260 except KeyError:
262 261 if not self._svg_warning_displayed:
263 262 QtGui.QMessageBox.warning(self, 'Error converting PNG to SVG.',
264 263 'Cannot convert PNG images to SVG, export with PNG figures instead. '
265 264 'If you want to export matplotlib figures as SVG, add '
266 265 'to your ipython config:\n\n'
267 266 '\tc.InlineBackend.figure_format = \'svg\'\n\n'
268 267 'And regenerate the figures.',
269 268 QtGui.QMessageBox.Ok)
270 269 self._svg_warning_displayed = True
271 270 return ("<b>Cannot convert PNG images to SVG.</b> "
272 271 "You must export this session with PNG images. "
273 272 "If you want to export matplotlib figures as SVG, add to your config "
274 273 "<span>c.InlineBackend.figure_format = 'svg'</span> "
275 274 "and regenerate the figures.")
276 275
277 276 # Not currently checking path, because it's tricky to find a
278 277 # cross-browser way to embed external SVG images (e.g., via
279 278 # object or embed tags).
280 279
281 280 # Chop stand-alone header from matplotlib SVG
282 281 offset = svg.find("<svg")
283 282 assert(offset > -1)
284 283
285 284 return svg[offset:]
286 285
287 286 else:
288 287 return '<b>Unrecognized image format</b>'
289 288
290 289 def _insert_jpg(self, cursor, jpg, metadata=None):
291 290 """ Insert raw PNG data into the widget."""
292 291 self._insert_img(cursor, jpg, 'jpg', metadata=metadata)
293 292
294 293 def _insert_png(self, cursor, png, metadata=None):
295 294 """ Insert raw PNG data into the widget.
296 295 """
297 296 self._insert_img(cursor, png, 'png', metadata=metadata)
298 297
299 298 def _insert_img(self, cursor, img, fmt, metadata=None):
300 299 """ insert a raw image, jpg or png """
301 300 if metadata:
302 301 width = metadata.get('width', None)
303 302 height = metadata.get('height', None)
304 303 else:
305 304 width = height = None
306 305 try:
307 306 image = QtGui.QImage()
308 307 image.loadFromData(img, fmt.upper())
309 308 if width and height:
310 309 image = image.scaled(width, height, transformMode=QtCore.Qt.SmoothTransformation)
311 310 elif width and not height:
312 311 image = image.scaledToWidth(width, transformMode=QtCore.Qt.SmoothTransformation)
313 312 elif height and not width:
314 313 image = image.scaledToHeight(height, transformMode=QtCore.Qt.SmoothTransformation)
315 314 except ValueError:
316 315 self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
317 316 else:
318 317 format = self._add_image(image)
319 318 cursor.insertBlock()
320 319 cursor.insertImage(format)
321 320 cursor.insertBlock()
322 321
323 322 def _insert_svg(self, cursor, svg):
324 323 """ Insert raw SVG data into the widet.
325 324 """
326 325 try:
327 326 image = svg_to_image(svg)
328 327 except ValueError:
329 328 self._insert_plain_text(cursor, 'Received invalid SVG data.')
330 329 else:
331 330 format = self._add_image(image)
332 331 self._name_to_svg_map[format.name()] = svg
333 332 cursor.insertBlock()
334 333 cursor.insertImage(format)
335 334 cursor.insertBlock()
336 335
337 336 def _save_image(self, name, format='PNG'):
338 337 """ Shows a save dialog for the ImageResource with 'name'.
339 338 """
340 339 dialog = QtGui.QFileDialog(self._control, 'Save Image')
341 340 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
342 341 dialog.setDefaultSuffix(format.lower())
343 342 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
344 343 if dialog.exec_():
345 344 filename = dialog.selectedFiles()[0]
346 345 image = self._get_image(name)
347 346 image.save(filename, format)
General Comments 0
You need to be logged in to leave comments. Login now