##// END OF EJS Templates
SVG scoping must be explicitly enabled by the user...
Pablo de Oliveira -
Show More
@@ -1,676 +1,708 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Top-level display functions for displaying object in different formats.
2 """Top-level display functions for displaying object in different formats.
3
3
4 Authors:
4 Authors:
5
5
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import os
22 import os
23 import struct
23 import struct
24
24
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
25 from IPython.utils.py3compat import (string_types, cast_bytes_py2, cast_unicode,
26 unicode_type)
26 unicode_type)
27
27
28 from .displaypub import publish_display_data
28 from .displaypub import publish_display_data
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # utility functions
31 # utility functions
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33
33
34 def _safe_exists(path):
34 def _safe_exists(path):
35 """Check path, but don't let exceptions raise"""
35 """Check path, but don't let exceptions raise"""
36 try:
36 try:
37 return os.path.exists(path)
37 return os.path.exists(path)
38 except Exception:
38 except Exception:
39 return False
39 return False
40
40
41 def _merge(d1, d2):
41 def _merge(d1, d2):
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
42 """Like update, but merges sub-dicts instead of clobbering at the top level.
43
43
44 Updates d1 in-place
44 Updates d1 in-place
45 """
45 """
46
46
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
47 if not isinstance(d2, dict) or not isinstance(d1, dict):
48 return d2
48 return d2
49 for key, value in d2.items():
49 for key, value in d2.items():
50 d1[key] = _merge(d1.get(key), value)
50 d1[key] = _merge(d1.get(key), value)
51 return d1
51 return d1
52
52
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
53 def _display_mimetype(mimetype, objs, raw=False, metadata=None):
54 """internal implementation of all display_foo methods
54 """internal implementation of all display_foo methods
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58 mimetype : str
58 mimetype : str
59 The mimetype to be published (e.g. 'image/png')
59 The mimetype to be published (e.g. 'image/png')
60 objs : tuple of objects
60 objs : tuple of objects
61 The Python objects to display, or if raw=True raw text data to
61 The Python objects to display, or if raw=True raw text data to
62 display.
62 display.
63 raw : bool
63 raw : bool
64 Are the data objects raw data or Python objects that need to be
64 Are the data objects raw data or Python objects that need to be
65 formatted before display? [default: False]
65 formatted before display? [default: False]
66 metadata : dict (optional)
66 metadata : dict (optional)
67 Metadata to be associated with the specific mimetype output.
67 Metadata to be associated with the specific mimetype output.
68 """
68 """
69 if metadata:
69 if metadata:
70 metadata = {mimetype: metadata}
70 metadata = {mimetype: metadata}
71 if raw:
71 if raw:
72 # turn list of pngdata into list of { 'image/png': pngdata }
72 # turn list of pngdata into list of { 'image/png': pngdata }
73 objs = [ {mimetype: obj} for obj in objs ]
73 objs = [ {mimetype: obj} for obj in objs ]
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
74 display(*objs, raw=raw, metadata=metadata, include=[mimetype])
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # Main functions
77 # Main functions
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80 def display(*objs, **kwargs):
80 def display(*objs, **kwargs):
81 """Display a Python object in all frontends.
81 """Display a Python object in all frontends.
82
82
83 By default all representations will be computed and sent to the frontends.
83 By default all representations will be computed and sent to the frontends.
84 Frontends can decide which representation is used and how.
84 Frontends can decide which representation is used and how.
85
85
86 Parameters
86 Parameters
87 ----------
87 ----------
88 objs : tuple of objects
88 objs : tuple of objects
89 The Python objects to display.
89 The Python objects to display.
90 raw : bool, optional
90 raw : bool, optional
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
91 Are the objects to be displayed already mimetype-keyed dicts of raw display data,
92 or Python objects that need to be formatted before display? [default: False]
92 or Python objects that need to be formatted before display? [default: False]
93 include : list or tuple, optional
93 include : list or tuple, optional
94 A list of format type strings (MIME types) to include in the
94 A list of format type strings (MIME types) to include in the
95 format data dict. If this is set *only* the format types included
95 format data dict. If this is set *only* the format types included
96 in this list will be computed.
96 in this list will be computed.
97 exclude : list or tuple, optional
97 exclude : list or tuple, optional
98 A list of format type strings (MIME types) to exclude in the format
98 A list of format type strings (MIME types) to exclude in the format
99 data dict. If this is set all format types will be computed,
99 data dict. If this is set all format types will be computed,
100 except for those included in this argument.
100 except for those included in this argument.
101 metadata : dict, optional
101 metadata : dict, optional
102 A dictionary of metadata to associate with the output.
102 A dictionary of metadata to associate with the output.
103 mime-type keys in this dictionary will be associated with the individual
103 mime-type keys in this dictionary will be associated with the individual
104 representation formats, if they exist.
104 representation formats, if they exist.
105 """
105 """
106 raw = kwargs.get('raw', False)
106 raw = kwargs.get('raw', False)
107 include = kwargs.get('include')
107 include = kwargs.get('include')
108 exclude = kwargs.get('exclude')
108 exclude = kwargs.get('exclude')
109 metadata = kwargs.get('metadata')
109 metadata = kwargs.get('metadata')
110
110
111 from IPython.core.interactiveshell import InteractiveShell
111 from IPython.core.interactiveshell import InteractiveShell
112
112
113 if raw:
113 if raw:
114 for obj in objs:
114 for obj in objs:
115 publish_display_data('display', obj, metadata)
115 publish_display_data('display', obj, metadata)
116 else:
116 else:
117 format = InteractiveShell.instance().display_formatter.format
117 format = InteractiveShell.instance().display_formatter.format
118 for obj in objs:
118 for obj in objs:
119 format_dict, md_dict = format(obj, include=include, exclude=exclude)
119 format_dict, md_dict = format(obj, include=include, exclude=exclude)
120 if metadata:
120 if metadata:
121 # kwarg-specified metadata gets precedence
121 # kwarg-specified metadata gets precedence
122 _merge(md_dict, metadata)
122 _merge(md_dict, metadata)
123 publish_display_data('display', format_dict, md_dict)
123 publish_display_data('display', format_dict, md_dict)
124
124
125
125
126 def display_pretty(*objs, **kwargs):
126 def display_pretty(*objs, **kwargs):
127 """Display the pretty (default) representation of an object.
127 """Display the pretty (default) representation of an object.
128
128
129 Parameters
129 Parameters
130 ----------
130 ----------
131 objs : tuple of objects
131 objs : tuple of objects
132 The Python objects to display, or if raw=True raw text data to
132 The Python objects to display, or if raw=True raw text data to
133 display.
133 display.
134 raw : bool
134 raw : bool
135 Are the data objects raw data or Python objects that need to be
135 Are the data objects raw data or Python objects that need to be
136 formatted before display? [default: False]
136 formatted before display? [default: False]
137 metadata : dict (optional)
137 metadata : dict (optional)
138 Metadata to be associated with the specific mimetype output.
138 Metadata to be associated with the specific mimetype output.
139 """
139 """
140 _display_mimetype('text/plain', objs, **kwargs)
140 _display_mimetype('text/plain', objs, **kwargs)
141
141
142
142
143 def display_html(*objs, **kwargs):
143 def display_html(*objs, **kwargs):
144 """Display the HTML representation of an object.
144 """Display the HTML representation of an object.
145
145
146 Parameters
146 Parameters
147 ----------
147 ----------
148 objs : tuple of objects
148 objs : tuple of objects
149 The Python objects to display, or if raw=True raw HTML data to
149 The Python objects to display, or if raw=True raw HTML data to
150 display.
150 display.
151 raw : bool
151 raw : bool
152 Are the data objects raw data or Python objects that need to be
152 Are the data objects raw data or Python objects that need to be
153 formatted before display? [default: False]
153 formatted before display? [default: False]
154 metadata : dict (optional)
154 metadata : dict (optional)
155 Metadata to be associated with the specific mimetype output.
155 Metadata to be associated with the specific mimetype output.
156 """
156 """
157 _display_mimetype('text/html', objs, **kwargs)
157 _display_mimetype('text/html', objs, **kwargs)
158
158
159
159
160 def display_svg(*objs, **kwargs):
160 def display_svg(*objs, **kwargs):
161 """Display the SVG representation of an object.
161 """Display the SVG representation of an object.
162
162
163 Parameters
163 Parameters
164 ----------
164 ----------
165 objs : tuple of objects
165 objs : tuple of objects
166 The Python objects to display, or if raw=True raw svg data to
166 The Python objects to display, or if raw=True raw svg data to
167 display.
167 display.
168 raw : bool
168 raw : bool
169 Are the data objects raw data or Python objects that need to be
169 Are the data objects raw data or Python objects that need to be
170 formatted before display? [default: False]
170 formatted before display? [default: False]
171 metadata : dict (optional)
171 metadata : dict (optional)
172 Metadata to be associated with the specific mimetype output.
172 Metadata to be associated with the specific mimetype output.
173 """
173 """
174 _display_mimetype('image/svg+xml', objs, **kwargs)
174 _display_mimetype('image/svg+xml', objs, **kwargs)
175
175
176
176
177 def display_png(*objs, **kwargs):
177 def display_png(*objs, **kwargs):
178 """Display the PNG representation of an object.
178 """Display the PNG representation of an object.
179
179
180 Parameters
180 Parameters
181 ----------
181 ----------
182 objs : tuple of objects
182 objs : tuple of objects
183 The Python objects to display, or if raw=True raw png data to
183 The Python objects to display, or if raw=True raw png data to
184 display.
184 display.
185 raw : bool
185 raw : bool
186 Are the data objects raw data or Python objects that need to be
186 Are the data objects raw data or Python objects that need to be
187 formatted before display? [default: False]
187 formatted before display? [default: False]
188 metadata : dict (optional)
188 metadata : dict (optional)
189 Metadata to be associated with the specific mimetype output.
189 Metadata to be associated with the specific mimetype output.
190 """
190 """
191 _display_mimetype('image/png', objs, **kwargs)
191 _display_mimetype('image/png', objs, **kwargs)
192
192
193
193
194 def display_jpeg(*objs, **kwargs):
194 def display_jpeg(*objs, **kwargs):
195 """Display the JPEG representation of an object.
195 """Display the JPEG representation of an object.
196
196
197 Parameters
197 Parameters
198 ----------
198 ----------
199 objs : tuple of objects
199 objs : tuple of objects
200 The Python objects to display, or if raw=True raw JPEG data to
200 The Python objects to display, or if raw=True raw JPEG data to
201 display.
201 display.
202 raw : bool
202 raw : bool
203 Are the data objects raw data or Python objects that need to be
203 Are the data objects raw data or Python objects that need to be
204 formatted before display? [default: False]
204 formatted before display? [default: False]
205 metadata : dict (optional)
205 metadata : dict (optional)
206 Metadata to be associated with the specific mimetype output.
206 Metadata to be associated with the specific mimetype output.
207 """
207 """
208 _display_mimetype('image/jpeg', objs, **kwargs)
208 _display_mimetype('image/jpeg', objs, **kwargs)
209
209
210
210
211 def display_latex(*objs, **kwargs):
211 def display_latex(*objs, **kwargs):
212 """Display the LaTeX representation of an object.
212 """Display the LaTeX representation of an object.
213
213
214 Parameters
214 Parameters
215 ----------
215 ----------
216 objs : tuple of objects
216 objs : tuple of objects
217 The Python objects to display, or if raw=True raw latex data to
217 The Python objects to display, or if raw=True raw latex data to
218 display.
218 display.
219 raw : bool
219 raw : bool
220 Are the data objects raw data or Python objects that need to be
220 Are the data objects raw data or Python objects that need to be
221 formatted before display? [default: False]
221 formatted before display? [default: False]
222 metadata : dict (optional)
222 metadata : dict (optional)
223 Metadata to be associated with the specific mimetype output.
223 Metadata to be associated with the specific mimetype output.
224 """
224 """
225 _display_mimetype('text/latex', objs, **kwargs)
225 _display_mimetype('text/latex', objs, **kwargs)
226
226
227
227
228 def display_json(*objs, **kwargs):
228 def display_json(*objs, **kwargs):
229 """Display the JSON representation of an object.
229 """Display the JSON representation of an object.
230
230
231 Note that not many frontends support displaying JSON.
231 Note that not many frontends support displaying JSON.
232
232
233 Parameters
233 Parameters
234 ----------
234 ----------
235 objs : tuple of objects
235 objs : tuple of objects
236 The Python objects to display, or if raw=True raw json data to
236 The Python objects to display, or if raw=True raw json data to
237 display.
237 display.
238 raw : bool
238 raw : bool
239 Are the data objects raw data or Python objects that need to be
239 Are the data objects raw data or Python objects that need to be
240 formatted before display? [default: False]
240 formatted before display? [default: False]
241 metadata : dict (optional)
241 metadata : dict (optional)
242 Metadata to be associated with the specific mimetype output.
242 Metadata to be associated with the specific mimetype output.
243 """
243 """
244 _display_mimetype('application/json', objs, **kwargs)
244 _display_mimetype('application/json', objs, **kwargs)
245
245
246
246
247 def display_javascript(*objs, **kwargs):
247 def display_javascript(*objs, **kwargs):
248 """Display the Javascript representation of an object.
248 """Display the Javascript representation of an object.
249
249
250 Parameters
250 Parameters
251 ----------
251 ----------
252 objs : tuple of objects
252 objs : tuple of objects
253 The Python objects to display, or if raw=True raw javascript data to
253 The Python objects to display, or if raw=True raw javascript data to
254 display.
254 display.
255 raw : bool
255 raw : bool
256 Are the data objects raw data or Python objects that need to be
256 Are the data objects raw data or Python objects that need to be
257 formatted before display? [default: False]
257 formatted before display? [default: False]
258 metadata : dict (optional)
258 metadata : dict (optional)
259 Metadata to be associated with the specific mimetype output.
259 Metadata to be associated with the specific mimetype output.
260 """
260 """
261 _display_mimetype('application/javascript', objs, **kwargs)
261 _display_mimetype('application/javascript', objs, **kwargs)
262
262
263 #-----------------------------------------------------------------------------
263 #-----------------------------------------------------------------------------
264 # Smart classes
264 # Smart classes
265 #-----------------------------------------------------------------------------
265 #-----------------------------------------------------------------------------
266
266
267
267
268 class DisplayObject(object):
268 class DisplayObject(object):
269 """An object that wraps data to be displayed."""
269 """An object that wraps data to be displayed."""
270
270
271 _read_flags = 'r'
271 _read_flags = 'r'
272
272
273 def __init__(self, data=None, url=None, filename=None):
273 def __init__(self, data=None, url=None, filename=None):
274 """Create a display object given raw data.
274 """Create a display object given raw data.
275
275
276 When this object is returned by an expression or passed to the
276 When this object is returned by an expression or passed to the
277 display function, it will result in the data being displayed
277 display function, it will result in the data being displayed
278 in the frontend. The MIME type of the data should match the
278 in the frontend. The MIME type of the data should match the
279 subclasses used, so the Png subclass should be used for 'image/png'
279 subclasses used, so the Png subclass should be used for 'image/png'
280 data. If the data is a URL, the data will first be downloaded
280 data. If the data is a URL, the data will first be downloaded
281 and then displayed. If
281 and then displayed. If
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 data : unicode, str or bytes
285 data : unicode, str or bytes
286 The raw data or a URL or file to load the data from
286 The raw data or a URL or file to load the data from
287 url : unicode
287 url : unicode
288 A URL to download the data from.
288 A URL to download the data from.
289 filename : unicode
289 filename : unicode
290 Path to a local file to load the data from.
290 Path to a local file to load the data from.
291 """
291 """
292 if data is not None and isinstance(data, string_types):
292 if data is not None and isinstance(data, string_types):
293 if data.startswith('http') and url is None:
293 if data.startswith('http') and url is None:
294 url = data
294 url = data
295 filename = None
295 filename = None
296 data = None
296 data = None
297 elif _safe_exists(data) and filename is None:
297 elif _safe_exists(data) and filename is None:
298 url = None
298 url = None
299 filename = data
299 filename = data
300 data = None
300 data = None
301
301
302 self.data = data
302 self.data = data
303 self.url = url
303 self.url = url
304 self.filename = None if filename is None else unicode_type(filename)
304 self.filename = None if filename is None else unicode_type(filename)
305
305
306 self.reload()
306 self.reload()
307
307
308 def reload(self):
308 def reload(self):
309 """Reload the raw data from file or URL."""
309 """Reload the raw data from file or URL."""
310 if self.filename is not None:
310 if self.filename is not None:
311 with open(self.filename, self._read_flags) as f:
311 with open(self.filename, self._read_flags) as f:
312 self.data = f.read()
312 self.data = f.read()
313 elif self.url is not None:
313 elif self.url is not None:
314 try:
314 try:
315 import urllib2
315 import urllib2
316 response = urllib2.urlopen(self.url)
316 response = urllib2.urlopen(self.url)
317 self.data = response.read()
317 self.data = response.read()
318 # extract encoding from header, if there is one:
318 # extract encoding from header, if there is one:
319 encoding = None
319 encoding = None
320 for sub in response.headers['content-type'].split(';'):
320 for sub in response.headers['content-type'].split(';'):
321 sub = sub.strip()
321 sub = sub.strip()
322 if sub.startswith('charset'):
322 if sub.startswith('charset'):
323 encoding = sub.split('=')[-1].strip()
323 encoding = sub.split('=')[-1].strip()
324 break
324 break
325 # decode data, if an encoding was specified
325 # decode data, if an encoding was specified
326 if encoding:
326 if encoding:
327 self.data = self.data.decode(encoding, 'replace')
327 self.data = self.data.decode(encoding, 'replace')
328 except:
328 except:
329 self.data = None
329 self.data = None
330
330
331 class Pretty(DisplayObject):
331 class Pretty(DisplayObject):
332
332
333 def _repr_pretty_(self):
333 def _repr_pretty_(self):
334 return self.data
334 return self.data
335
335
336
336
337 class HTML(DisplayObject):
337 class HTML(DisplayObject):
338
338
339 def _repr_html_(self):
339 def _repr_html_(self):
340 return self.data
340 return self.data
341
341
342 def __html__(self):
342 def __html__(self):
343 """
343 """
344 This method exists to inform other HTML-using modules (e.g. Markupsafe,
344 This method exists to inform other HTML-using modules (e.g. Markupsafe,
345 htmltag, etc) that this object is HTML and does not need things like
345 htmltag, etc) that this object is HTML and does not need things like
346 special characters (<>&) escaped.
346 special characters (<>&) escaped.
347 """
347 """
348 return self._repr_html_()
348 return self._repr_html_()
349
349
350
350
351 class Math(DisplayObject):
351 class Math(DisplayObject):
352
352
353 def _repr_latex_(self):
353 def _repr_latex_(self):
354 s = self.data.strip('$')
354 s = self.data.strip('$')
355 return "$$%s$$" % s
355 return "$$%s$$" % s
356
356
357
357
358 class Latex(DisplayObject):
358 class Latex(DisplayObject):
359
359
360 def _repr_latex_(self):
360 def _repr_latex_(self):
361 return self.data
361 return self.data
362
362
363
363
364 class SVG(DisplayObject):
364 class SVG(DisplayObject):
365
365
366 def __init__(self, data=None, url=None, filename=None, scoped=False):
367 """Create a SVG display object given raw data.
368
369 When this object is returned by an expression or passed to the
370 display function, it will result in the data being displayed
371 in the frontend. If the data is a URL, the data will first be
372 downloaded and then displayed.
373
374 Parameters
375 ----------
376 data : unicode, str or bytes
377 The Javascript source code or a URL to download it from.
378 url : unicode
379 A URL to download the data from.
380 filename : unicode
381 Path to a local file to load the data from.
382 scoped : bool
383 Should the SVG declarations be scoped.
384 """
385 if not isinstance(scoped, (bool)):
386 raise TypeError('expected bool, got: %r' % scoped)
387 self.scoped = scoped
388 super(SVG, self).__init__(data=data, url=url, filename=filename)
389
366 # wrap data in a property, which extracts the <svg> tag, discarding
390 # wrap data in a property, which extracts the <svg> tag, discarding
367 # document headers
391 # document headers
368 _data = None
392 _data = None
369
393
370 @property
394 @property
371 def data(self):
395 def data(self):
372 return self._data
396 return self._data
373
397
398 _scoped_class = "ipython-scoped"
399
374 @data.setter
400 @data.setter
375 def data(self, svg):
401 def data(self, svg):
376 if svg is None:
402 if svg is None:
377 self._data = None
403 self._data = None
378 return
404 return
379 # parse into dom object
405 # parse into dom object
380 from xml.dom import minidom
406 from xml.dom import minidom
381 svg = cast_bytes_py2(svg)
407 svg = cast_bytes_py2(svg)
382 x = minidom.parseString(svg)
408 x = minidom.parseString(svg)
383 # get svg tag (should be 1)
409 # get svg tag (should be 1)
384 found_svg = x.getElementsByTagName('svg')
410 found_svg = x.getElementsByTagName('svg')
385 if found_svg:
411 if found_svg:
412 # If the user request scoping, tag the svg with the
413 # ipython-scoped class
414 if self.scoped:
415 classes = (found_svg[0].getAttribute('class') +
416 " " + self._scoped_class)
417 found_svg[0].setAttribute('class', classes)
386 svg = found_svg[0].toxml()
418 svg = found_svg[0].toxml()
387 else:
419 else:
388 # fallback on the input, trust the user
420 # fallback on the input, trust the user
389 # but this is probably an error.
421 # but this is probably an error.
390 pass
422 pass
391 svg = cast_unicode(svg)
423 svg = cast_unicode(svg)
392 self._data = svg
424 self._data = svg
393
425
394 def _repr_svg_(self):
426 def _repr_svg_(self):
395 return self.data
427 return self.data
396
428
397
429
398 class JSON(DisplayObject):
430 class JSON(DisplayObject):
399
431
400 def _repr_json_(self):
432 def _repr_json_(self):
401 return self.data
433 return self.data
402
434
403 css_t = """$("head").append($("<link/>").attr({
435 css_t = """$("head").append($("<link/>").attr({
404 rel: "stylesheet",
436 rel: "stylesheet",
405 type: "text/css",
437 type: "text/css",
406 href: "%s"
438 href: "%s"
407 }));
439 }));
408 """
440 """
409
441
410 lib_t1 = """$.getScript("%s", function () {
442 lib_t1 = """$.getScript("%s", function () {
411 """
443 """
412 lib_t2 = """});
444 lib_t2 = """});
413 """
445 """
414
446
415 class Javascript(DisplayObject):
447 class Javascript(DisplayObject):
416
448
417 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
449 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
418 """Create a Javascript display object given raw data.
450 """Create a Javascript display object given raw data.
419
451
420 When this object is returned by an expression or passed to the
452 When this object is returned by an expression or passed to the
421 display function, it will result in the data being displayed
453 display function, it will result in the data being displayed
422 in the frontend. If the data is a URL, the data will first be
454 in the frontend. If the data is a URL, the data will first be
423 downloaded and then displayed.
455 downloaded and then displayed.
424
456
425 In the Notebook, the containing element will be available as `element`,
457 In the Notebook, the containing element will be available as `element`,
426 and jQuery will be available. The output area starts hidden, so if
458 and jQuery will be available. The output area starts hidden, so if
427 the js appends content to `element` that should be visible, then
459 the js appends content to `element` that should be visible, then
428 it must call `container.show()` to unhide the area.
460 it must call `container.show()` to unhide the area.
429
461
430 Parameters
462 Parameters
431 ----------
463 ----------
432 data : unicode, str or bytes
464 data : unicode, str or bytes
433 The Javascript source code or a URL to download it from.
465 The Javascript source code or a URL to download it from.
434 url : unicode
466 url : unicode
435 A URL to download the data from.
467 A URL to download the data from.
436 filename : unicode
468 filename : unicode
437 Path to a local file to load the data from.
469 Path to a local file to load the data from.
438 lib : list or str
470 lib : list or str
439 A sequence of Javascript library URLs to load asynchronously before
471 A sequence of Javascript library URLs to load asynchronously before
440 running the source code. The full URLs of the libraries should
472 running the source code. The full URLs of the libraries should
441 be given. A single Javascript library URL can also be given as a
473 be given. A single Javascript library URL can also be given as a
442 string.
474 string.
443 css: : list or str
475 css: : list or str
444 A sequence of css files to load before running the source code.
476 A sequence of css files to load before running the source code.
445 The full URLs of the css files should be given. A single css URL
477 The full URLs of the css files should be given. A single css URL
446 can also be given as a string.
478 can also be given as a string.
447 """
479 """
448 if isinstance(lib, string_types):
480 if isinstance(lib, string_types):
449 lib = [lib]
481 lib = [lib]
450 elif lib is None:
482 elif lib is None:
451 lib = []
483 lib = []
452 if isinstance(css, string_types):
484 if isinstance(css, string_types):
453 css = [css]
485 css = [css]
454 elif css is None:
486 elif css is None:
455 css = []
487 css = []
456 if not isinstance(lib, (list,tuple)):
488 if not isinstance(lib, (list,tuple)):
457 raise TypeError('expected sequence, got: %r' % lib)
489 raise TypeError('expected sequence, got: %r' % lib)
458 if not isinstance(css, (list,tuple)):
490 if not isinstance(css, (list,tuple)):
459 raise TypeError('expected sequence, got: %r' % css)
491 raise TypeError('expected sequence, got: %r' % css)
460 self.lib = lib
492 self.lib = lib
461 self.css = css
493 self.css = css
462 super(Javascript, self).__init__(data=data, url=url, filename=filename)
494 super(Javascript, self).__init__(data=data, url=url, filename=filename)
463
495
464 def _repr_javascript_(self):
496 def _repr_javascript_(self):
465 r = ''
497 r = ''
466 for c in self.css:
498 for c in self.css:
467 r += css_t % c
499 r += css_t % c
468 for l in self.lib:
500 for l in self.lib:
469 r += lib_t1 % l
501 r += lib_t1 % l
470 r += self.data
502 r += self.data
471 r += lib_t2*len(self.lib)
503 r += lib_t2*len(self.lib)
472 return r
504 return r
473
505
474 # constants for identifying png/jpeg data
506 # constants for identifying png/jpeg data
475 _PNG = b'\x89PNG\r\n\x1a\n'
507 _PNG = b'\x89PNG\r\n\x1a\n'
476 _JPEG = b'\xff\xd8'
508 _JPEG = b'\xff\xd8'
477
509
478 def _pngxy(data):
510 def _pngxy(data):
479 """read the (width, height) from a PNG header"""
511 """read the (width, height) from a PNG header"""
480 ihdr = data.index(b'IHDR')
512 ihdr = data.index(b'IHDR')
481 # next 8 bytes are width/height
513 # next 8 bytes are width/height
482 w4h4 = data[ihdr+4:ihdr+12]
514 w4h4 = data[ihdr+4:ihdr+12]
483 return struct.unpack('>ii', w4h4)
515 return struct.unpack('>ii', w4h4)
484
516
485 def _jpegxy(data):
517 def _jpegxy(data):
486 """read the (width, height) from a JPEG header"""
518 """read the (width, height) from a JPEG header"""
487 # adapted from http://www.64lines.com/jpeg-width-height
519 # adapted from http://www.64lines.com/jpeg-width-height
488
520
489 idx = 4
521 idx = 4
490 while True:
522 while True:
491 block_size = struct.unpack('>H', data[idx:idx+2])[0]
523 block_size = struct.unpack('>H', data[idx:idx+2])[0]
492 idx = idx + block_size
524 idx = idx + block_size
493 if data[idx:idx+2] == b'\xFF\xC0':
525 if data[idx:idx+2] == b'\xFF\xC0':
494 # found Start of Frame
526 # found Start of Frame
495 iSOF = idx
527 iSOF = idx
496 break
528 break
497 else:
529 else:
498 # read another block
530 # read another block
499 idx += 2
531 idx += 2
500
532
501 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
533 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
502 return w, h
534 return w, h
503
535
504 class Image(DisplayObject):
536 class Image(DisplayObject):
505
537
506 _read_flags = 'rb'
538 _read_flags = 'rb'
507 _FMT_JPEG = u'jpeg'
539 _FMT_JPEG = u'jpeg'
508 _FMT_PNG = u'png'
540 _FMT_PNG = u'png'
509 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
541 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
510
542
511 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
543 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
512 """Create a PNG/JPEG image object given raw data.
544 """Create a PNG/JPEG image object given raw data.
513
545
514 When this object is returned by an input cell or passed to the
546 When this object is returned by an input cell or passed to the
515 display function, it will result in the image being displayed
547 display function, it will result in the image being displayed
516 in the frontend.
548 in the frontend.
517
549
518 Parameters
550 Parameters
519 ----------
551 ----------
520 data : unicode, str or bytes
552 data : unicode, str or bytes
521 The raw image data or a URL or filename to load the data from.
553 The raw image data or a URL or filename to load the data from.
522 This always results in embedded image data.
554 This always results in embedded image data.
523 url : unicode
555 url : unicode
524 A URL to download the data from. If you specify `url=`,
556 A URL to download the data from. If you specify `url=`,
525 the image data will not be embedded unless you also specify `embed=True`.
557 the image data will not be embedded unless you also specify `embed=True`.
526 filename : unicode
558 filename : unicode
527 Path to a local file to load the data from.
559 Path to a local file to load the data from.
528 Images from a file are always embedded.
560 Images from a file are always embedded.
529 format : unicode
561 format : unicode
530 The format of the image data (png/jpeg/jpg). If a filename or URL is given
562 The format of the image data (png/jpeg/jpg). If a filename or URL is given
531 for format will be inferred from the filename extension.
563 for format will be inferred from the filename extension.
532 embed : bool
564 embed : bool
533 Should the image data be embedded using a data URI (True) or be
565 Should the image data be embedded using a data URI (True) or be
534 loaded using an <img> tag. Set this to True if you want the image
566 loaded using an <img> tag. Set this to True if you want the image
535 to be viewable later with no internet connection in the notebook.
567 to be viewable later with no internet connection in the notebook.
536
568
537 Default is `True`, unless the keyword argument `url` is set, then
569 Default is `True`, unless the keyword argument `url` is set, then
538 default value is `False`.
570 default value is `False`.
539
571
540 Note that QtConsole is not able to display images if `embed` is set to `False`
572 Note that QtConsole is not able to display images if `embed` is set to `False`
541 width : int
573 width : int
542 Width to which to constrain the image in html
574 Width to which to constrain the image in html
543 height : int
575 height : int
544 Height to which to constrain the image in html
576 Height to which to constrain the image in html
545 retina : bool
577 retina : bool
546 Automatically set the width and height to half of the measured
578 Automatically set the width and height to half of the measured
547 width and height.
579 width and height.
548 This only works for embedded images because it reads the width/height
580 This only works for embedded images because it reads the width/height
549 from image data.
581 from image data.
550 For non-embedded images, you can just set the desired display width
582 For non-embedded images, you can just set the desired display width
551 and height directly.
583 and height directly.
552
584
553 Examples
585 Examples
554 --------
586 --------
555 # embedded image data, works in qtconsole and notebook
587 # embedded image data, works in qtconsole and notebook
556 # when passed positionally, the first arg can be any of raw image data,
588 # when passed positionally, the first arg can be any of raw image data,
557 # a URL, or a filename from which to load image data.
589 # a URL, or a filename from which to load image data.
558 # The result is always embedding image data for inline images.
590 # The result is always embedding image data for inline images.
559 Image('http://www.google.fr/images/srpr/logo3w.png')
591 Image('http://www.google.fr/images/srpr/logo3w.png')
560 Image('/path/to/image.jpg')
592 Image('/path/to/image.jpg')
561 Image(b'RAW_PNG_DATA...')
593 Image(b'RAW_PNG_DATA...')
562
594
563 # Specifying Image(url=...) does not embed the image data,
595 # Specifying Image(url=...) does not embed the image data,
564 # it only generates `<img>` tag with a link to the source.
596 # it only generates `<img>` tag with a link to the source.
565 # This will not work in the qtconsole or offline.
597 # This will not work in the qtconsole or offline.
566 Image(url='http://www.google.fr/images/srpr/logo3w.png')
598 Image(url='http://www.google.fr/images/srpr/logo3w.png')
567
599
568 """
600 """
569 if filename is not None:
601 if filename is not None:
570 ext = self._find_ext(filename)
602 ext = self._find_ext(filename)
571 elif url is not None:
603 elif url is not None:
572 ext = self._find_ext(url)
604 ext = self._find_ext(url)
573 elif data is None:
605 elif data is None:
574 raise ValueError("No image data found. Expecting filename, url, or data.")
606 raise ValueError("No image data found. Expecting filename, url, or data.")
575 elif isinstance(data, string_types) and (
607 elif isinstance(data, string_types) and (
576 data.startswith('http') or _safe_exists(data)
608 data.startswith('http') or _safe_exists(data)
577 ):
609 ):
578 ext = self._find_ext(data)
610 ext = self._find_ext(data)
579 else:
611 else:
580 ext = None
612 ext = None
581
613
582 if ext is not None:
614 if ext is not None:
583 format = ext.lower()
615 format = ext.lower()
584 if ext == u'jpg' or ext == u'jpeg':
616 if ext == u'jpg' or ext == u'jpeg':
585 format = self._FMT_JPEG
617 format = self._FMT_JPEG
586 if ext == u'png':
618 if ext == u'png':
587 format = self._FMT_PNG
619 format = self._FMT_PNG
588 elif isinstance(data, bytes) and format == 'png':
620 elif isinstance(data, bytes) and format == 'png':
589 # infer image type from image data header,
621 # infer image type from image data header,
590 # only if format might not have been specified.
622 # only if format might not have been specified.
591 if data[:2] == _JPEG:
623 if data[:2] == _JPEG:
592 format = 'jpeg'
624 format = 'jpeg'
593
625
594 self.format = unicode_type(format).lower()
626 self.format = unicode_type(format).lower()
595 self.embed = embed if embed is not None else (url is None)
627 self.embed = embed if embed is not None else (url is None)
596
628
597 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
629 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
598 raise ValueError("Cannot embed the '%s' image format" % (self.format))
630 raise ValueError("Cannot embed the '%s' image format" % (self.format))
599 self.width = width
631 self.width = width
600 self.height = height
632 self.height = height
601 self.retina = retina
633 self.retina = retina
602 super(Image, self).__init__(data=data, url=url, filename=filename)
634 super(Image, self).__init__(data=data, url=url, filename=filename)
603
635
604 if retina:
636 if retina:
605 self._retina_shape()
637 self._retina_shape()
606
638
607 def _retina_shape(self):
639 def _retina_shape(self):
608 """load pixel-doubled width and height from image data"""
640 """load pixel-doubled width and height from image data"""
609 if not self.embed:
641 if not self.embed:
610 return
642 return
611 if self.format == 'png':
643 if self.format == 'png':
612 w, h = _pngxy(self.data)
644 w, h = _pngxy(self.data)
613 elif self.format == 'jpeg':
645 elif self.format == 'jpeg':
614 w, h = _jpegxy(self.data)
646 w, h = _jpegxy(self.data)
615 else:
647 else:
616 # retina only supports png
648 # retina only supports png
617 return
649 return
618 self.width = w // 2
650 self.width = w // 2
619 self.height = h // 2
651 self.height = h // 2
620
652
621 def reload(self):
653 def reload(self):
622 """Reload the raw data from file or URL."""
654 """Reload the raw data from file or URL."""
623 if self.embed:
655 if self.embed:
624 super(Image,self).reload()
656 super(Image,self).reload()
625 if self.retina:
657 if self.retina:
626 self._retina_shape()
658 self._retina_shape()
627
659
628 def _repr_html_(self):
660 def _repr_html_(self):
629 if not self.embed:
661 if not self.embed:
630 width = height = ''
662 width = height = ''
631 if self.width:
663 if self.width:
632 width = ' width="%d"' % self.width
664 width = ' width="%d"' % self.width
633 if self.height:
665 if self.height:
634 height = ' height="%d"' % self.height
666 height = ' height="%d"' % self.height
635 return u'<img src="%s"%s%s/>' % (self.url, width, height)
667 return u'<img src="%s"%s%s/>' % (self.url, width, height)
636
668
637 def _data_and_metadata(self):
669 def _data_and_metadata(self):
638 """shortcut for returning metadata with shape information, if defined"""
670 """shortcut for returning metadata with shape information, if defined"""
639 md = {}
671 md = {}
640 if self.width:
672 if self.width:
641 md['width'] = self.width
673 md['width'] = self.width
642 if self.height:
674 if self.height:
643 md['height'] = self.height
675 md['height'] = self.height
644 if md:
676 if md:
645 return self.data, md
677 return self.data, md
646 else:
678 else:
647 return self.data
679 return self.data
648
680
649 def _repr_png_(self):
681 def _repr_png_(self):
650 if self.embed and self.format == u'png':
682 if self.embed and self.format == u'png':
651 return self._data_and_metadata()
683 return self._data_and_metadata()
652
684
653 def _repr_jpeg_(self):
685 def _repr_jpeg_(self):
654 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
686 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
655 return self._data_and_metadata()
687 return self._data_and_metadata()
656
688
657 def _find_ext(self, s):
689 def _find_ext(self, s):
658 return unicode_type(s.split('.')[-1].lower())
690 return unicode_type(s.split('.')[-1].lower())
659
691
660
692
661 def clear_output(wait=False):
693 def clear_output(wait=False):
662 """Clear the output of the current cell receiving output.
694 """Clear the output of the current cell receiving output.
663
695
664 Parameters
696 Parameters
665 ----------
697 ----------
666 wait : bool [default: false]
698 wait : bool [default: false]
667 Wait to clear the output until new output is available to replace it."""
699 Wait to clear the output until new output is available to replace it."""
668 from IPython.core.interactiveshell import InteractiveShell
700 from IPython.core.interactiveshell import InteractiveShell
669 if InteractiveShell.initialized():
701 if InteractiveShell.initialized():
670 InteractiveShell.instance().display_pub.clear_output(wait)
702 InteractiveShell.instance().display_pub.clear_output(wait)
671 else:
703 else:
672 from IPython.utils import io
704 from IPython.utils import io
673 print('\033[2K\r', file=io.stdout, end='')
705 print('\033[2K\r', file=io.stdout, end='')
674 io.stdout.flush()
706 io.stdout.flush()
675 print('\033[2K\r', file=io.stderr, end='')
707 print('\033[2K\r', file=io.stderr, end='')
676 io.stderr.flush()
708 io.stderr.flush()
@@ -1,708 +1,712 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2008 The IPython Development Team
2 // Copyright (C) 2008 The IPython Development Team
3 //
3 //
4 // Distributed under the terms of the BSD License. The full license is in
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
6 //----------------------------------------------------------------------------
7
7
8 //============================================================================
8 //============================================================================
9 // OutputArea
9 // OutputArea
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 * @submodule OutputArea
15 * @submodule OutputArea
16 */
16 */
17 var IPython = (function (IPython) {
17 var IPython = (function (IPython) {
18 "use strict";
18 "use strict";
19
19
20 var utils = IPython.utils;
20 var utils = IPython.utils;
21
21
22 /**
22 /**
23 * @class OutputArea
23 * @class OutputArea
24 *
24 *
25 * @constructor
25 * @constructor
26 */
26 */
27
27
28 var OutputArea = function (selector, prompt_area) {
28 var OutputArea = function (selector, prompt_area) {
29 this.selector = selector;
29 this.selector = selector;
30 this.wrapper = $(selector);
30 this.wrapper = $(selector);
31 this.outputs = [];
31 this.outputs = [];
32 this.collapsed = false;
32 this.collapsed = false;
33 this.scrolled = false;
33 this.scrolled = false;
34 this.clear_queued = null;
34 this.clear_queued = null;
35 if (prompt_area === undefined) {
35 if (prompt_area === undefined) {
36 this.prompt_area = true;
36 this.prompt_area = true;
37 } else {
37 } else {
38 this.prompt_area = prompt_area;
38 this.prompt_area = prompt_area;
39 }
39 }
40 this.create_elements();
40 this.create_elements();
41 this.style();
41 this.style();
42 this.bind_events();
42 this.bind_events();
43 };
43 };
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output vbox');
60 this.element.addClass('output vbox');
61
61
62 this.collapse_button.addClass("btn output_collapsed");
62 this.collapse_button.addClass("btn output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.html('. . .');
64 this.collapse_button.html('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( IPython.utils.browser[0] === "Firefox" ) {
102 if ( IPython.utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 * Threshold to trigger autoscroll when the OutputArea is resized,
162 * Threshold to trigger autoscroll when the OutputArea is resized,
163 * typically when new outputs are added.
163 * typically when new outputs are added.
164 *
164 *
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
166 * unless it is < 0, in which case autoscroll will never be triggered
166 * unless it is < 0, in which case autoscroll will never be triggered
167 *
167 *
168 * @property auto_scroll_threshold
168 * @property auto_scroll_threshold
169 * @type Number
169 * @type Number
170 * @default 100
170 * @default 100
171 *
171 *
172 **/
172 **/
173 OutputArea.auto_scroll_threshold = 100;
173 OutputArea.auto_scroll_threshold = 100;
174
174
175
175
176 /**
176 /**
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
178 * shorter than this are never scrolled.
178 * shorter than this are never scrolled.
179 *
179 *
180 * @property minimum_scroll_threshold
180 * @property minimum_scroll_threshold
181 * @type Number
181 * @type Number
182 * @default 20
182 * @default 20
183 *
183 *
184 **/
184 **/
185 OutputArea.minimum_scroll_threshold = 20;
185 OutputArea.minimum_scroll_threshold = 20;
186
186
187
187
188 /**
188 /**
189 *
189 *
190 * Scroll OutputArea if height supperior than a threshold (in lines).
190 * Scroll OutputArea if height supperior than a threshold (in lines).
191 *
191 *
192 * Threshold is a maximum number of lines. If unspecified, defaults to
192 * Threshold is a maximum number of lines. If unspecified, defaults to
193 * OutputArea.minimum_scroll_threshold.
193 * OutputArea.minimum_scroll_threshold.
194 *
194 *
195 * Negative threshold will prevent the OutputArea from ever scrolling.
195 * Negative threshold will prevent the OutputArea from ever scrolling.
196 *
196 *
197 * @method scroll_if_long
197 * @method scroll_if_long
198 *
198 *
199 * @param [lines=20]{Number} Default to 20 if not set,
199 * @param [lines=20]{Number} Default to 20 if not set,
200 * behavior undefined for value of `0`.
200 * behavior undefined for value of `0`.
201 *
201 *
202 **/
202 **/
203 OutputArea.prototype.scroll_if_long = function (lines) {
203 OutputArea.prototype.scroll_if_long = function (lines) {
204 var n = lines | OutputArea.minimum_scroll_threshold;
204 var n = lines | OutputArea.minimum_scroll_threshold;
205 if(n <= 0){
205 if(n <= 0){
206 return
206 return
207 }
207 }
208
208
209 if (this._should_scroll(n)) {
209 if (this._should_scroll(n)) {
210 // only allow scrolling long-enough output
210 // only allow scrolling long-enough output
211 this.scroll_area();
211 this.scroll_area();
212 }
212 }
213 };
213 };
214
214
215
215
216 OutputArea.prototype.toggle_scroll = function () {
216 OutputArea.prototype.toggle_scroll = function () {
217 if (this.scrolled) {
217 if (this.scrolled) {
218 this.unscroll_area();
218 this.unscroll_area();
219 } else {
219 } else {
220 // only allow scrolling long-enough output
220 // only allow scrolling long-enough output
221 this.scroll_if_long();
221 this.scroll_if_long();
222 }
222 }
223 };
223 };
224
224
225
225
226 // typeset with MathJax if MathJax is available
226 // typeset with MathJax if MathJax is available
227 OutputArea.prototype.typeset = function () {
227 OutputArea.prototype.typeset = function () {
228 if (window.MathJax){
228 if (window.MathJax){
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
230 }
230 }
231 };
231 };
232
232
233
233
234 OutputArea.prototype.handle_output = function (msg) {
234 OutputArea.prototype.handle_output = function (msg) {
235 var json = {};
235 var json = {};
236 var msg_type = json.output_type = msg.header.msg_type;
236 var msg_type = json.output_type = msg.header.msg_type;
237 var content = msg.content;
237 var content = msg.content;
238 if (msg_type === "stream") {
238 if (msg_type === "stream") {
239 json.text = content.data;
239 json.text = content.data;
240 json.stream = content.name;
240 json.stream = content.name;
241 } else if (msg_type === "display_data") {
241 } else if (msg_type === "display_data") {
242 json = this.convert_mime_types(json, content.data);
242 json = this.convert_mime_types(json, content.data);
243 json.metadata = this.convert_mime_types({}, content.metadata);
243 json.metadata = this.convert_mime_types({}, content.metadata);
244 } else if (msg_type === "pyout") {
244 } else if (msg_type === "pyout") {
245 json.prompt_number = content.execution_count;
245 json.prompt_number = content.execution_count;
246 json = this.convert_mime_types(json, content.data);
246 json = this.convert_mime_types(json, content.data);
247 json.metadata = this.convert_mime_types({}, content.metadata);
247 json.metadata = this.convert_mime_types({}, content.metadata);
248 } else if (msg_type === "pyerr") {
248 } else if (msg_type === "pyerr") {
249 json.ename = content.ename;
249 json.ename = content.ename;
250 json.evalue = content.evalue;
250 json.evalue = content.evalue;
251 json.traceback = content.traceback;
251 json.traceback = content.traceback;
252 }
252 }
253 // append with dynamic=true
253 // append with dynamic=true
254 this.append_output(json, true);
254 this.append_output(json, true);
255 };
255 };
256
256
257
257
258 OutputArea.prototype.convert_mime_types = function (json, data) {
258 OutputArea.prototype.convert_mime_types = function (json, data) {
259 if (data === undefined) {
259 if (data === undefined) {
260 return json;
260 return json;
261 }
261 }
262 if (data['text/plain'] !== undefined) {
262 if (data['text/plain'] !== undefined) {
263 json.text = data['text/plain'];
263 json.text = data['text/plain'];
264 }
264 }
265 if (data['text/html'] !== undefined) {
265 if (data['text/html'] !== undefined) {
266 json.html = data['text/html'];
266 json.html = data['text/html'];
267 }
267 }
268 if (data['image/svg+xml'] !== undefined) {
268 if (data['image/svg+xml'] !== undefined) {
269 json.svg = data['image/svg+xml'];
269 json.svg = data['image/svg+xml'];
270 }
270 }
271 if (data['image/png'] !== undefined) {
271 if (data['image/png'] !== undefined) {
272 json.png = data['image/png'];
272 json.png = data['image/png'];
273 }
273 }
274 if (data['image/jpeg'] !== undefined) {
274 if (data['image/jpeg'] !== undefined) {
275 json.jpeg = data['image/jpeg'];
275 json.jpeg = data['image/jpeg'];
276 }
276 }
277 if (data['text/latex'] !== undefined) {
277 if (data['text/latex'] !== undefined) {
278 json.latex = data['text/latex'];
278 json.latex = data['text/latex'];
279 }
279 }
280 if (data['application/json'] !== undefined) {
280 if (data['application/json'] !== undefined) {
281 json.json = data['application/json'];
281 json.json = data['application/json'];
282 }
282 }
283 if (data['application/javascript'] !== undefined) {
283 if (data['application/javascript'] !== undefined) {
284 json.javascript = data['application/javascript'];
284 json.javascript = data['application/javascript'];
285 }
285 }
286 return json;
286 return json;
287 };
287 };
288
288
289
289
290 OutputArea.prototype.append_output = function (json, dynamic) {
290 OutputArea.prototype.append_output = function (json, dynamic) {
291 // If dynamic is true, javascript output will be eval'd.
291 // If dynamic is true, javascript output will be eval'd.
292 this.expand();
292 this.expand();
293
293
294 // Clear the output if clear is queued.
294 // Clear the output if clear is queued.
295 var needs_height_reset = false;
295 var needs_height_reset = false;
296 if (this.clear_queued) {
296 if (this.clear_queued) {
297 this.clear_output(false);
297 this.clear_output(false);
298 needs_height_reset = true;
298 needs_height_reset = true;
299 }
299 }
300
300
301 if (json.output_type === 'pyout') {
301 if (json.output_type === 'pyout') {
302 this.append_pyout(json, dynamic);
302 this.append_pyout(json, dynamic);
303 } else if (json.output_type === 'pyerr') {
303 } else if (json.output_type === 'pyerr') {
304 this.append_pyerr(json);
304 this.append_pyerr(json);
305 } else if (json.output_type === 'display_data') {
305 } else if (json.output_type === 'display_data') {
306 this.append_display_data(json, dynamic);
306 this.append_display_data(json, dynamic);
307 } else if (json.output_type === 'stream') {
307 } else if (json.output_type === 'stream') {
308 this.append_stream(json);
308 this.append_stream(json);
309 }
309 }
310 this.outputs.push(json);
310 this.outputs.push(json);
311
311
312 // Only reset the height to automatic if the height is currently
312 // Only reset the height to automatic if the height is currently
313 // fixed (done by wait=True flag on clear_output).
313 // fixed (done by wait=True flag on clear_output).
314 if (needs_height_reset) {
314 if (needs_height_reset) {
315 this.element.height('');
315 this.element.height('');
316 }
316 }
317
317
318 var that = this;
318 var that = this;
319 setTimeout(function(){that.element.trigger('resize');}, 100);
319 setTimeout(function(){that.element.trigger('resize');}, 100);
320 };
320 };
321
321
322
322
323 OutputArea.prototype.create_output_area = function () {
323 OutputArea.prototype.create_output_area = function () {
324 var oa = $("<div/>").addClass("output_area");
324 var oa = $("<div/>").addClass("output_area");
325 if (this.prompt_area) {
325 if (this.prompt_area) {
326 oa.append($('<div/>').addClass('prompt'));
326 oa.append($('<div/>').addClass('prompt'));
327 }
327 }
328 return oa;
328 return oa;
329 };
329 };
330
330
331 OutputArea.prototype._append_javascript_error = function (err, container) {
331 OutputArea.prototype._append_javascript_error = function (err, container) {
332 // display a message when a javascript error occurs in display output
332 // display a message when a javascript error occurs in display output
333 var msg = "Javascript error adding output!"
333 var msg = "Javascript error adding output!"
334 console.log(msg, err);
334 console.log(msg, err);
335 if ( container === undefined ) return;
335 if ( container === undefined ) return;
336 container.append(
336 container.append(
337 $('<div/>').html(msg + "<br/>" +
337 $('<div/>').html(msg + "<br/>" +
338 err.toString() +
338 err.toString() +
339 '<br/>See your browser Javascript console for more details.'
339 '<br/>See your browser Javascript console for more details.'
340 ).addClass('js-error')
340 ).addClass('js-error')
341 );
341 );
342 container.show();
342 container.show();
343 };
343 };
344
344
345 OutputArea.prototype._safe_append = function (toinsert) {
345 OutputArea.prototype._safe_append = function (toinsert) {
346 // safely append an item to the document
346 // safely append an item to the document
347 // this is an object created by user code,
347 // this is an object created by user code,
348 // and may have errors, which should not be raised
348 // and may have errors, which should not be raised
349 // under any circumstances.
349 // under any circumstances.
350 try {
350 try {
351 this.element.append(toinsert);
351 this.element.append(toinsert);
352 } catch(err) {
352 } catch(err) {
353 console.log(err);
353 console.log(err);
354 this._append_javascript_error(err, this.element);
354 this._append_javascript_error(err, this.element);
355 }
355 }
356 };
356 };
357
357
358
358
359 OutputArea.prototype.append_pyout = function (json, dynamic) {
359 OutputArea.prototype.append_pyout = function (json, dynamic) {
360 var n = json.prompt_number || ' ';
360 var n = json.prompt_number || ' ';
361 var toinsert = this.create_output_area();
361 var toinsert = this.create_output_area();
362 if (this.prompt_area) {
362 if (this.prompt_area) {
363 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
363 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
364 }
364 }
365 this.append_mime_type(json, toinsert, dynamic);
365 this.append_mime_type(json, toinsert, dynamic);
366 this._safe_append(toinsert);
366 this._safe_append(toinsert);
367 // If we just output latex, typeset it.
367 // If we just output latex, typeset it.
368 if ((json.latex !== undefined) || (json.html !== undefined)) {
368 if ((json.latex !== undefined) || (json.html !== undefined)) {
369 this.typeset();
369 this.typeset();
370 }
370 }
371 };
371 };
372
372
373
373
374 OutputArea.prototype.append_pyerr = function (json) {
374 OutputArea.prototype.append_pyerr = function (json) {
375 var tb = json.traceback;
375 var tb = json.traceback;
376 if (tb !== undefined && tb.length > 0) {
376 if (tb !== undefined && tb.length > 0) {
377 var s = '';
377 var s = '';
378 var len = tb.length;
378 var len = tb.length;
379 for (var i=0; i<len; i++) {
379 for (var i=0; i<len; i++) {
380 s = s + tb[i] + '\n';
380 s = s + tb[i] + '\n';
381 }
381 }
382 s = s + '\n';
382 s = s + '\n';
383 var toinsert = this.create_output_area();
383 var toinsert = this.create_output_area();
384 this.append_text(s, {}, toinsert);
384 this.append_text(s, {}, toinsert);
385 this._safe_append(toinsert);
385 this._safe_append(toinsert);
386 }
386 }
387 };
387 };
388
388
389
389
390 OutputArea.prototype.append_stream = function (json) {
390 OutputArea.prototype.append_stream = function (json) {
391 // temporary fix: if stream undefined (json file written prior to this patch),
391 // temporary fix: if stream undefined (json file written prior to this patch),
392 // default to most likely stdout:
392 // default to most likely stdout:
393 if (json.stream == undefined){
393 if (json.stream == undefined){
394 json.stream = 'stdout';
394 json.stream = 'stdout';
395 }
395 }
396 var text = json.text;
396 var text = json.text;
397 var subclass = "output_"+json.stream;
397 var subclass = "output_"+json.stream;
398 if (this.outputs.length > 0){
398 if (this.outputs.length > 0){
399 // have at least one output to consider
399 // have at least one output to consider
400 var last = this.outputs[this.outputs.length-1];
400 var last = this.outputs[this.outputs.length-1];
401 if (last.output_type == 'stream' && json.stream == last.stream){
401 if (last.output_type == 'stream' && json.stream == last.stream){
402 // latest output was in the same stream,
402 // latest output was in the same stream,
403 // so append directly into its pre tag
403 // so append directly into its pre tag
404 // escape ANSI & HTML specials:
404 // escape ANSI & HTML specials:
405 var pre = this.element.find('div.'+subclass).last().find('pre');
405 var pre = this.element.find('div.'+subclass).last().find('pre');
406 var html = utils.fixCarriageReturn(
406 var html = utils.fixCarriageReturn(
407 pre.html() + utils.fixConsole(text));
407 pre.html() + utils.fixConsole(text));
408 pre.html(html);
408 pre.html(html);
409 return;
409 return;
410 }
410 }
411 }
411 }
412
412
413 if (!text.replace("\r", "")) {
413 if (!text.replace("\r", "")) {
414 // text is nothing (empty string, \r, etc.)
414 // text is nothing (empty string, \r, etc.)
415 // so don't append any elements, which might add undesirable space
415 // so don't append any elements, which might add undesirable space
416 return;
416 return;
417 }
417 }
418
418
419 // If we got here, attach a new div
419 // If we got here, attach a new div
420 var toinsert = this.create_output_area();
420 var toinsert = this.create_output_area();
421 this.append_text(text, {}, toinsert, "output_stream "+subclass);
421 this.append_text(text, {}, toinsert, "output_stream "+subclass);
422 this._safe_append(toinsert);
422 this._safe_append(toinsert);
423 };
423 };
424
424
425
425
426 OutputArea.prototype.append_display_data = function (json, dynamic) {
426 OutputArea.prototype.append_display_data = function (json, dynamic) {
427 var toinsert = this.create_output_area();
427 var toinsert = this.create_output_area();
428 this.append_mime_type(json, toinsert, dynamic);
428 this.append_mime_type(json, toinsert, dynamic);
429 this._safe_append(toinsert);
429 this._safe_append(toinsert);
430 // If we just output latex, typeset it.
430 // If we just output latex, typeset it.
431 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
431 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
432 this.typeset();
432 this.typeset();
433 }
433 }
434 };
434 };
435
435
436 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
436 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
437
437
438 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
438 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
439 for(var type_i in OutputArea.display_order){
439 for(var type_i in OutputArea.display_order){
440 var type = OutputArea.display_order[type_i];
440 var type = OutputArea.display_order[type_i];
441 if(json[type] != undefined ){
441 if(json[type] != undefined ){
442 var md = {};
442 var md = {};
443 if (json.metadata && json.metadata[type]) {
443 if (json.metadata && json.metadata[type]) {
444 md = json.metadata[type];
444 md = json.metadata[type];
445 };
445 };
446 if(type == 'javascript'){
446 if(type == 'javascript'){
447 if (dynamic) {
447 if (dynamic) {
448 this.append_javascript(json.javascript, md, element, dynamic);
448 this.append_javascript(json.javascript, md, element, dynamic);
449 }
449 }
450 } else {
450 } else {
451 this['append_'+type](json[type], md, element);
451 this['append_'+type](json[type], md, element);
452 }
452 }
453 return;
453 return;
454 }
454 }
455 }
455 }
456 };
456 };
457
457
458
458
459 OutputArea.prototype.append_html = function (html, md, element) {
459 OutputArea.prototype.append_html = function (html, md, element) {
460 var toinsert = $("<div/>").addClass("output_subarea output_html rendered_html");
460 var toinsert = $("<div/>").addClass("output_subarea output_html rendered_html");
461 toinsert.append(html);
461 toinsert.append(html);
462 element.append(toinsert);
462 element.append(toinsert);
463 };
463 };
464
464
465
465
466 OutputArea.prototype.append_javascript = function (js, md, container) {
466 OutputArea.prototype.append_javascript = function (js, md, container) {
467 // We just eval the JS code, element appears in the local scope.
467 // We just eval the JS code, element appears in the local scope.
468 var element = $("<div/>").addClass("output_subarea");
468 var element = $("<div/>").addClass("output_subarea");
469 container.append(element);
469 container.append(element);
470 // Div for js shouldn't be drawn, as it will add empty height to the area.
470 // Div for js shouldn't be drawn, as it will add empty height to the area.
471 container.hide();
471 container.hide();
472 // If the Javascript appends content to `element` that should be drawn, then
472 // If the Javascript appends content to `element` that should be drawn, then
473 // it must also call `container.show()`.
473 // it must also call `container.show()`.
474 try {
474 try {
475 eval(js);
475 eval(js);
476 } catch(err) {
476 } catch(err) {
477 this._append_javascript_error(err, container);
477 this._append_javascript_error(err, container);
478 }
478 }
479 };
479 };
480
480
481
481
482 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
482 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
483 var toinsert = $("<div/>").addClass("output_subarea output_text");
483 var toinsert = $("<div/>").addClass("output_subarea output_text");
484 // escape ANSI & HTML specials in plaintext:
484 // escape ANSI & HTML specials in plaintext:
485 data = utils.fixConsole(data);
485 data = utils.fixConsole(data);
486 data = utils.fixCarriageReturn(data);
486 data = utils.fixCarriageReturn(data);
487 data = utils.autoLinkUrls(data);
487 data = utils.autoLinkUrls(data);
488 if (extra_class){
488 if (extra_class){
489 toinsert.addClass(extra_class);
489 toinsert.addClass(extra_class);
490 }
490 }
491 toinsert.append($("<pre/>").html(data));
491 toinsert.append($("<pre/>").html(data));
492 element.append(toinsert);
492 element.append(toinsert);
493 };
493 };
494
494
495
495
496 OutputArea.prototype.append_svg = function (svg, md, element) {
496 OutputArea.prototype.append_svg = function (svg, md, element) {
497 var wrapper = $('<div/>').addClass('output_subarea output_svg');
498 wrapper.append(svg);
499 var svg_element = wrapper.children()[0];
500
501 if (svg_element.classList.contains('ipython-scoped')) {
497 // To avoid style or use collisions between multiple svg figures,
502 // To avoid style or use collisions between multiple svg figures,
498 // svg figures are wrapped inside an iframe.
503 // svg figures are wrapped inside an iframe.
499
500 var iframe = $('<iframe/>')
504 var iframe = $('<iframe/>')
501 iframe.attr('frameborder', 0);
505 iframe.attr('frameborder', 0);
502 iframe.attr('scrolling', 'no');
506 iframe.attr('scrolling', 'no');
503
507
504 var wrapper = $("<div/>").addClass("output_subarea output_svg");
505 wrapper.append(svg);
506
507 // Once the iframe is loaded, the svg is dynamically inserted
508 // Once the iframe is loaded, the svg is dynamically inserted
508 iframe.on('load', function() {
509 iframe.on('load', function() {
509 // Set the iframe height and width to fit the svg
510 // Set the iframe height and width to fit the svg
510 // (the +10 pixel offset handles the default body margins
511 // (the +10 pixel offset handles the default body margins
511 // in Chrome)
512 // in Chrome)
512 var svg = wrapper.children()[0];
513 iframe.width(svg_element.width.baseVal.value + 10);
513 iframe.width(svg.width.baseVal.value + 10);
514 iframe.height(svg_element.height.baseVal.value + 10);
514 iframe.height(svg.height.baseVal.value + 10);
515
515
516 // Workaround needed by Firefox, to properly render svg inside iframes,
516 // Workaround needed by Firefox, to properly render svg inside
517 // see http://stackoverflow.com/questions/10177190/svg-dynamically-added-to-iframe-does-not-render-correctly
517 // iframes, see http://stackoverflow.com/questions/10177190/
518 // svg-dynamically-added-to-iframe-does-not-render-correctly
518 iframe.contents()[0].open();
519 iframe.contents()[0].open();
519 iframe.contents()[0].close();
520 iframe.contents()[0].close();
520
521
521 // Insert the svg inside the iframe
522 // Insert the svg inside the iframe
522 var body = iframe.contents().find('body');
523 var body = iframe.contents().find('body');
523 body.html(wrapper.html());
524 body.html(wrapper.html());
524 });
525 });
525
526
526 element.append(iframe);
527 element.append(iframe);
528 } else {
529 element.append(wrapper);
530 }
527 };
531 };
528
532
529
533
530 OutputArea.prototype._dblclick_to_reset_size = function (img) {
534 OutputArea.prototype._dblclick_to_reset_size = function (img) {
531 // schedule wrapping image in resizable after a delay,
535 // schedule wrapping image in resizable after a delay,
532 // so we don't end up calling resize on a zero-size object
536 // so we don't end up calling resize on a zero-size object
533 var that = this;
537 var that = this;
534 setTimeout(function () {
538 setTimeout(function () {
535 var h0 = img.height();
539 var h0 = img.height();
536 var w0 = img.width();
540 var w0 = img.width();
537 if (!(h0 && w0)) {
541 if (!(h0 && w0)) {
538 // zero size, schedule another timeout
542 // zero size, schedule another timeout
539 that._dblclick_to_reset_size(img);
543 that._dblclick_to_reset_size(img);
540 return;
544 return;
541 }
545 }
542 img.resizable({
546 img.resizable({
543 aspectRatio: true,
547 aspectRatio: true,
544 autoHide: true
548 autoHide: true
545 });
549 });
546 img.dblclick(function () {
550 img.dblclick(function () {
547 // resize wrapper & image together for some reason:
551 // resize wrapper & image together for some reason:
548 img.parent().height(h0);
552 img.parent().height(h0);
549 img.height(h0);
553 img.height(h0);
550 img.parent().width(w0);
554 img.parent().width(w0);
551 img.width(w0);
555 img.width(w0);
552 });
556 });
553 }, 250);
557 }, 250);
554 };
558 };
555
559
556
560
557 OutputArea.prototype.append_png = function (png, md, element) {
561 OutputArea.prototype.append_png = function (png, md, element) {
558 var toinsert = $("<div/>").addClass("output_subarea output_png");
562 var toinsert = $("<div/>").addClass("output_subarea output_png");
559 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
563 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
560 if (md['height']) {
564 if (md['height']) {
561 img.attr('height', md['height']);
565 img.attr('height', md['height']);
562 }
566 }
563 if (md['width']) {
567 if (md['width']) {
564 img.attr('width', md['width']);
568 img.attr('width', md['width']);
565 }
569 }
566 this._dblclick_to_reset_size(img);
570 this._dblclick_to_reset_size(img);
567 toinsert.append(img);
571 toinsert.append(img);
568 element.append(toinsert);
572 element.append(toinsert);
569 };
573 };
570
574
571
575
572 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
576 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
573 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
577 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
574 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
578 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
575 if (md['height']) {
579 if (md['height']) {
576 img.attr('height', md['height']);
580 img.attr('height', md['height']);
577 }
581 }
578 if (md['width']) {
582 if (md['width']) {
579 img.attr('width', md['width']);
583 img.attr('width', md['width']);
580 }
584 }
581 this._dblclick_to_reset_size(img);
585 this._dblclick_to_reset_size(img);
582 toinsert.append(img);
586 toinsert.append(img);
583 element.append(toinsert);
587 element.append(toinsert);
584 };
588 };
585
589
586
590
587 OutputArea.prototype.append_latex = function (latex, md, element) {
591 OutputArea.prototype.append_latex = function (latex, md, element) {
588 // This method cannot do the typesetting because the latex first has to
592 // This method cannot do the typesetting because the latex first has to
589 // be on the page.
593 // be on the page.
590 var toinsert = $("<div/>").addClass("output_subarea output_latex");
594 var toinsert = $("<div/>").addClass("output_subarea output_latex");
591 toinsert.append(latex);
595 toinsert.append(latex);
592 element.append(toinsert);
596 element.append(toinsert);
593 };
597 };
594
598
595 OutputArea.prototype.append_raw_input = function (msg) {
599 OutputArea.prototype.append_raw_input = function (msg) {
596 var that = this;
600 var that = this;
597 this.expand();
601 this.expand();
598 var content = msg.content;
602 var content = msg.content;
599 var area = this.create_output_area();
603 var area = this.create_output_area();
600
604
601 // disable any other raw_inputs, if they are left around
605 // disable any other raw_inputs, if they are left around
602 $("div.output_subarea.raw_input").remove();
606 $("div.output_subarea.raw_input").remove();
603
607
604 area.append(
608 area.append(
605 $("<div/>")
609 $("<div/>")
606 .addClass("box-flex1 output_subarea raw_input")
610 .addClass("box-flex1 output_subarea raw_input")
607 .append(
611 .append(
608 $("<span/>")
612 $("<span/>")
609 .addClass("input_prompt")
613 .addClass("input_prompt")
610 .text(content.prompt)
614 .text(content.prompt)
611 )
615 )
612 .append(
616 .append(
613 $("<input/>")
617 $("<input/>")
614 .addClass("raw_input")
618 .addClass("raw_input")
615 .attr('type', 'text')
619 .attr('type', 'text')
616 .attr("size", 47)
620 .attr("size", 47)
617 .keydown(function (event, ui) {
621 .keydown(function (event, ui) {
618 // make sure we submit on enter,
622 // make sure we submit on enter,
619 // and don't re-execute the *cell* on shift-enter
623 // and don't re-execute the *cell* on shift-enter
620 if (event.which === utils.keycodes.ENTER) {
624 if (event.which === utils.keycodes.ENTER) {
621 that._submit_raw_input();
625 that._submit_raw_input();
622 return false;
626 return false;
623 }
627 }
624 })
628 })
625 )
629 )
626 );
630 );
627 this.element.append(area);
631 this.element.append(area);
628 // weirdly need double-focus now,
632 // weirdly need double-focus now,
629 // otherwise only the cell will be focused
633 // otherwise only the cell will be focused
630 area.find("input.raw_input").focus().focus();
634 area.find("input.raw_input").focus().focus();
631 }
635 }
632 OutputArea.prototype._submit_raw_input = function (evt) {
636 OutputArea.prototype._submit_raw_input = function (evt) {
633 var container = this.element.find("div.raw_input");
637 var container = this.element.find("div.raw_input");
634 var theprompt = container.find("span.input_prompt");
638 var theprompt = container.find("span.input_prompt");
635 var theinput = container.find("input.raw_input");
639 var theinput = container.find("input.raw_input");
636 var value = theinput.val();
640 var value = theinput.val();
637 var content = {
641 var content = {
638 output_type : 'stream',
642 output_type : 'stream',
639 name : 'stdout',
643 name : 'stdout',
640 text : theprompt.text() + value + '\n'
644 text : theprompt.text() + value + '\n'
641 }
645 }
642 // remove form container
646 // remove form container
643 container.parent().remove();
647 container.parent().remove();
644 // replace with plaintext version in stdout
648 // replace with plaintext version in stdout
645 this.append_output(content, false);
649 this.append_output(content, false);
646 $([IPython.events]).trigger('send_input_reply.Kernel', value);
650 $([IPython.events]).trigger('send_input_reply.Kernel', value);
647 }
651 }
648
652
649
653
650 OutputArea.prototype.handle_clear_output = function (msg) {
654 OutputArea.prototype.handle_clear_output = function (msg) {
651 this.clear_output(msg.content.wait);
655 this.clear_output(msg.content.wait);
652 };
656 };
653
657
654
658
655 OutputArea.prototype.clear_output = function(wait) {
659 OutputArea.prototype.clear_output = function(wait) {
656 if (wait) {
660 if (wait) {
657
661
658 // If a clear is queued, clear before adding another to the queue.
662 // If a clear is queued, clear before adding another to the queue.
659 if (this.clear_queued) {
663 if (this.clear_queued) {
660 this.clear_output(false);
664 this.clear_output(false);
661 };
665 };
662
666
663 this.clear_queued = true;
667 this.clear_queued = true;
664 } else {
668 } else {
665
669
666 // Fix the output div's height if the clear_output is waiting for
670 // Fix the output div's height if the clear_output is waiting for
667 // new output (it is being used in an animation).
671 // new output (it is being used in an animation).
668 if (this.clear_queued) {
672 if (this.clear_queued) {
669 var height = this.element.height();
673 var height = this.element.height();
670 this.element.height(height);
674 this.element.height(height);
671 this.clear_queued = false;
675 this.clear_queued = false;
672 }
676 }
673
677
674 // clear all, no need for logic
678 // clear all, no need for logic
675 this.element.html("");
679 this.element.html("");
676 this.outputs = [];
680 this.outputs = [];
677 this.unscroll_area();
681 this.unscroll_area();
678 return;
682 return;
679 };
683 };
680 };
684 };
681
685
682
686
683 // JSON serialization
687 // JSON serialization
684
688
685 OutputArea.prototype.fromJSON = function (outputs) {
689 OutputArea.prototype.fromJSON = function (outputs) {
686 var len = outputs.length;
690 var len = outputs.length;
687 for (var i=0; i<len; i++) {
691 for (var i=0; i<len; i++) {
688 // append with dynamic=false.
692 // append with dynamic=false.
689 this.append_output(outputs[i], false);
693 this.append_output(outputs[i], false);
690 }
694 }
691 };
695 };
692
696
693
697
694 OutputArea.prototype.toJSON = function () {
698 OutputArea.prototype.toJSON = function () {
695 var outputs = [];
699 var outputs = [];
696 var len = this.outputs.length;
700 var len = this.outputs.length;
697 for (var i=0; i<len; i++) {
701 for (var i=0; i<len; i++) {
698 outputs[i] = this.outputs[i];
702 outputs[i] = this.outputs[i];
699 }
703 }
700 return outputs;
704 return outputs;
701 };
705 };
702
706
703
707
704 IPython.OutputArea = OutputArea;
708 IPython.OutputArea = OutputArea;
705
709
706 return IPython;
710 return IPython;
707
711
708 }(IPython));
712 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now