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