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