##// END OF EJS Templates
SVG: scoped is passed as metadata...
Pablo de Oliveira -
Show More
@@ -1,708 +1,704 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):
366 def __init__(self, data=None, url=None, filename=None, scoped=False):
367 """Create a SVG display object given raw data.
367 """Create a SVG display object given raw data.
368
368
369 When this object is returned by an expression or passed to the
369 When this object is returned by an expression or passed to the
370 display function, it will result in the data being displayed
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
371 in the frontend. If the data is a URL, the data will first be
372 downloaded and then displayed.
372 downloaded and then displayed.
373
373
374 Parameters
374 Parameters
375 ----------
375 ----------
376 data : unicode, str or bytes
376 data : unicode, str or bytes
377 The Javascript source code or a URL to download it from.
377 The Javascript source code or a URL to download it from.
378 url : unicode
378 url : unicode
379 A URL to download the data from.
379 A URL to download the data from.
380 filename : unicode
380 filename : unicode
381 Path to a local file to load the data from.
381 Path to a local file to load the data from.
382 scoped : bool
382 scoped : bool
383 Should the SVG declarations be scoped.
383 Should the SVG declarations be scoped.
384 """
384 """
385 if not isinstance(scoped, (bool)):
385 if not isinstance(scoped, (bool)):
386 raise TypeError('expected bool, got: %r' % scoped)
386 raise TypeError('expected bool, got: %r' % scoped)
387 self.scoped = scoped
387 self.scoped = scoped
388 super(SVG, self).__init__(data=data, url=url, filename=filename)
388 super(SVG, self).__init__(data=data, url=url, filename=filename)
389
389
390 # wrap data in a property, which extracts the <svg> tag, discarding
390 # wrap data in a property, which extracts the <svg> tag, discarding
391 # document headers
391 # document headers
392 _data = None
392 _data = None
393
393
394 @property
394 @property
395 def data(self):
395 def data(self):
396 return self._data
396 return self._data
397
397
398 _scoped_class = "ipython-scoped"
399
400 @data.setter
398 @data.setter
401 def data(self, svg):
399 def data(self, svg):
402 if svg is None:
400 if svg is None:
403 self._data = None
401 self._data = None
404 return
402 return
405 # parse into dom object
403 # parse into dom object
406 from xml.dom import minidom
404 from xml.dom import minidom
407 svg = cast_bytes_py2(svg)
405 svg = cast_bytes_py2(svg)
408 x = minidom.parseString(svg)
406 x = minidom.parseString(svg)
409 # get svg tag (should be 1)
407 # get svg tag (should be 1)
410 found_svg = x.getElementsByTagName('svg')
408 found_svg = x.getElementsByTagName('svg')
411 if found_svg:
409 if found_svg:
412 # If the user requests 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)
418 svg = found_svg[0].toxml()
410 svg = found_svg[0].toxml()
419 else:
411 else:
420 # fallback on the input, trust the user
412 # fallback on the input, trust the user
421 # but this is probably an error.
413 # but this is probably an error.
422 pass
414 pass
423 svg = cast_unicode(svg)
415 svg = cast_unicode(svg)
424 self._data = svg
416 self._data = svg
425
417
426 def _repr_svg_(self):
418 def _repr_svg_(self):
427 return self.data
419 if self.scoped:
420 metadata = dict(scoped=True)
421 return self.data, metadata
422 else:
423 return self.data
428
424
429
425
430 class JSON(DisplayObject):
426 class JSON(DisplayObject):
431
427
432 def _repr_json_(self):
428 def _repr_json_(self):
433 return self.data
429 return self.data
434
430
435 css_t = """$("head").append($("<link/>").attr({
431 css_t = """$("head").append($("<link/>").attr({
436 rel: "stylesheet",
432 rel: "stylesheet",
437 type: "text/css",
433 type: "text/css",
438 href: "%s"
434 href: "%s"
439 }));
435 }));
440 """
436 """
441
437
442 lib_t1 = """$.getScript("%s", function () {
438 lib_t1 = """$.getScript("%s", function () {
443 """
439 """
444 lib_t2 = """});
440 lib_t2 = """});
445 """
441 """
446
442
447 class Javascript(DisplayObject):
443 class Javascript(DisplayObject):
448
444
449 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
445 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
450 """Create a Javascript display object given raw data.
446 """Create a Javascript display object given raw data.
451
447
452 When this object is returned by an expression or passed to the
448 When this object is returned by an expression or passed to the
453 display function, it will result in the data being displayed
449 display function, it will result in the data being displayed
454 in the frontend. If the data is a URL, the data will first be
450 in the frontend. If the data is a URL, the data will first be
455 downloaded and then displayed.
451 downloaded and then displayed.
456
452
457 In the Notebook, the containing element will be available as `element`,
453 In the Notebook, the containing element will be available as `element`,
458 and jQuery will be available. The output area starts hidden, so if
454 and jQuery will be available. The output area starts hidden, so if
459 the js appends content to `element` that should be visible, then
455 the js appends content to `element` that should be visible, then
460 it must call `container.show()` to unhide the area.
456 it must call `container.show()` to unhide the area.
461
457
462 Parameters
458 Parameters
463 ----------
459 ----------
464 data : unicode, str or bytes
460 data : unicode, str or bytes
465 The Javascript source code or a URL to download it from.
461 The Javascript source code or a URL to download it from.
466 url : unicode
462 url : unicode
467 A URL to download the data from.
463 A URL to download the data from.
468 filename : unicode
464 filename : unicode
469 Path to a local file to load the data from.
465 Path to a local file to load the data from.
470 lib : list or str
466 lib : list or str
471 A sequence of Javascript library URLs to load asynchronously before
467 A sequence of Javascript library URLs to load asynchronously before
472 running the source code. The full URLs of the libraries should
468 running the source code. The full URLs of the libraries should
473 be given. A single Javascript library URL can also be given as a
469 be given. A single Javascript library URL can also be given as a
474 string.
470 string.
475 css: : list or str
471 css: : list or str
476 A sequence of css files to load before running the source code.
472 A sequence of css files to load before running the source code.
477 The full URLs of the css files should be given. A single css URL
473 The full URLs of the css files should be given. A single css URL
478 can also be given as a string.
474 can also be given as a string.
479 """
475 """
480 if isinstance(lib, string_types):
476 if isinstance(lib, string_types):
481 lib = [lib]
477 lib = [lib]
482 elif lib is None:
478 elif lib is None:
483 lib = []
479 lib = []
484 if isinstance(css, string_types):
480 if isinstance(css, string_types):
485 css = [css]
481 css = [css]
486 elif css is None:
482 elif css is None:
487 css = []
483 css = []
488 if not isinstance(lib, (list,tuple)):
484 if not isinstance(lib, (list,tuple)):
489 raise TypeError('expected sequence, got: %r' % lib)
485 raise TypeError('expected sequence, got: %r' % lib)
490 if not isinstance(css, (list,tuple)):
486 if not isinstance(css, (list,tuple)):
491 raise TypeError('expected sequence, got: %r' % css)
487 raise TypeError('expected sequence, got: %r' % css)
492 self.lib = lib
488 self.lib = lib
493 self.css = css
489 self.css = css
494 super(Javascript, self).__init__(data=data, url=url, filename=filename)
490 super(Javascript, self).__init__(data=data, url=url, filename=filename)
495
491
496 def _repr_javascript_(self):
492 def _repr_javascript_(self):
497 r = ''
493 r = ''
498 for c in self.css:
494 for c in self.css:
499 r += css_t % c
495 r += css_t % c
500 for l in self.lib:
496 for l in self.lib:
501 r += lib_t1 % l
497 r += lib_t1 % l
502 r += self.data
498 r += self.data
503 r += lib_t2*len(self.lib)
499 r += lib_t2*len(self.lib)
504 return r
500 return r
505
501
506 # constants for identifying png/jpeg data
502 # constants for identifying png/jpeg data
507 _PNG = b'\x89PNG\r\n\x1a\n'
503 _PNG = b'\x89PNG\r\n\x1a\n'
508 _JPEG = b'\xff\xd8'
504 _JPEG = b'\xff\xd8'
509
505
510 def _pngxy(data):
506 def _pngxy(data):
511 """read the (width, height) from a PNG header"""
507 """read the (width, height) from a PNG header"""
512 ihdr = data.index(b'IHDR')
508 ihdr = data.index(b'IHDR')
513 # next 8 bytes are width/height
509 # next 8 bytes are width/height
514 w4h4 = data[ihdr+4:ihdr+12]
510 w4h4 = data[ihdr+4:ihdr+12]
515 return struct.unpack('>ii', w4h4)
511 return struct.unpack('>ii', w4h4)
516
512
517 def _jpegxy(data):
513 def _jpegxy(data):
518 """read the (width, height) from a JPEG header"""
514 """read the (width, height) from a JPEG header"""
519 # adapted from http://www.64lines.com/jpeg-width-height
515 # adapted from http://www.64lines.com/jpeg-width-height
520
516
521 idx = 4
517 idx = 4
522 while True:
518 while True:
523 block_size = struct.unpack('>H', data[idx:idx+2])[0]
519 block_size = struct.unpack('>H', data[idx:idx+2])[0]
524 idx = idx + block_size
520 idx = idx + block_size
525 if data[idx:idx+2] == b'\xFF\xC0':
521 if data[idx:idx+2] == b'\xFF\xC0':
526 # found Start of Frame
522 # found Start of Frame
527 iSOF = idx
523 iSOF = idx
528 break
524 break
529 else:
525 else:
530 # read another block
526 # read another block
531 idx += 2
527 idx += 2
532
528
533 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
529 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
534 return w, h
530 return w, h
535
531
536 class Image(DisplayObject):
532 class Image(DisplayObject):
537
533
538 _read_flags = 'rb'
534 _read_flags = 'rb'
539 _FMT_JPEG = u'jpeg'
535 _FMT_JPEG = u'jpeg'
540 _FMT_PNG = u'png'
536 _FMT_PNG = u'png'
541 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
537 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
542
538
543 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
539 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
544 """Create a PNG/JPEG image object given raw data.
540 """Create a PNG/JPEG image object given raw data.
545
541
546 When this object is returned by an input cell or passed to the
542 When this object is returned by an input cell or passed to the
547 display function, it will result in the image being displayed
543 display function, it will result in the image being displayed
548 in the frontend.
544 in the frontend.
549
545
550 Parameters
546 Parameters
551 ----------
547 ----------
552 data : unicode, str or bytes
548 data : unicode, str or bytes
553 The raw image data or a URL or filename to load the data from.
549 The raw image data or a URL or filename to load the data from.
554 This always results in embedded image data.
550 This always results in embedded image data.
555 url : unicode
551 url : unicode
556 A URL to download the data from. If you specify `url=`,
552 A URL to download the data from. If you specify `url=`,
557 the image data will not be embedded unless you also specify `embed=True`.
553 the image data will not be embedded unless you also specify `embed=True`.
558 filename : unicode
554 filename : unicode
559 Path to a local file to load the data from.
555 Path to a local file to load the data from.
560 Images from a file are always embedded.
556 Images from a file are always embedded.
561 format : unicode
557 format : unicode
562 The format of the image data (png/jpeg/jpg). If a filename or URL is given
558 The format of the image data (png/jpeg/jpg). If a filename or URL is given
563 for format will be inferred from the filename extension.
559 for format will be inferred from the filename extension.
564 embed : bool
560 embed : bool
565 Should the image data be embedded using a data URI (True) or be
561 Should the image data be embedded using a data URI (True) or be
566 loaded using an <img> tag. Set this to True if you want the image
562 loaded using an <img> tag. Set this to True if you want the image
567 to be viewable later with no internet connection in the notebook.
563 to be viewable later with no internet connection in the notebook.
568
564
569 Default is `True`, unless the keyword argument `url` is set, then
565 Default is `True`, unless the keyword argument `url` is set, then
570 default value is `False`.
566 default value is `False`.
571
567
572 Note that QtConsole is not able to display images if `embed` is set to `False`
568 Note that QtConsole is not able to display images if `embed` is set to `False`
573 width : int
569 width : int
574 Width to which to constrain the image in html
570 Width to which to constrain the image in html
575 height : int
571 height : int
576 Height to which to constrain the image in html
572 Height to which to constrain the image in html
577 retina : bool
573 retina : bool
578 Automatically set the width and height to half of the measured
574 Automatically set the width and height to half of the measured
579 width and height.
575 width and height.
580 This only works for embedded images because it reads the width/height
576 This only works for embedded images because it reads the width/height
581 from image data.
577 from image data.
582 For non-embedded images, you can just set the desired display width
578 For non-embedded images, you can just set the desired display width
583 and height directly.
579 and height directly.
584
580
585 Examples
581 Examples
586 --------
582 --------
587 # embedded image data, works in qtconsole and notebook
583 # embedded image data, works in qtconsole and notebook
588 # when passed positionally, the first arg can be any of raw image data,
584 # when passed positionally, the first arg can be any of raw image data,
589 # a URL, or a filename from which to load image data.
585 # a URL, or a filename from which to load image data.
590 # The result is always embedding image data for inline images.
586 # The result is always embedding image data for inline images.
591 Image('http://www.google.fr/images/srpr/logo3w.png')
587 Image('http://www.google.fr/images/srpr/logo3w.png')
592 Image('/path/to/image.jpg')
588 Image('/path/to/image.jpg')
593 Image(b'RAW_PNG_DATA...')
589 Image(b'RAW_PNG_DATA...')
594
590
595 # Specifying Image(url=...) does not embed the image data,
591 # Specifying Image(url=...) does not embed the image data,
596 # it only generates `<img>` tag with a link to the source.
592 # it only generates `<img>` tag with a link to the source.
597 # This will not work in the qtconsole or offline.
593 # This will not work in the qtconsole or offline.
598 Image(url='http://www.google.fr/images/srpr/logo3w.png')
594 Image(url='http://www.google.fr/images/srpr/logo3w.png')
599
595
600 """
596 """
601 if filename is not None:
597 if filename is not None:
602 ext = self._find_ext(filename)
598 ext = self._find_ext(filename)
603 elif url is not None:
599 elif url is not None:
604 ext = self._find_ext(url)
600 ext = self._find_ext(url)
605 elif data is None:
601 elif data is None:
606 raise ValueError("No image data found. Expecting filename, url, or data.")
602 raise ValueError("No image data found. Expecting filename, url, or data.")
607 elif isinstance(data, string_types) and (
603 elif isinstance(data, string_types) and (
608 data.startswith('http') or _safe_exists(data)
604 data.startswith('http') or _safe_exists(data)
609 ):
605 ):
610 ext = self._find_ext(data)
606 ext = self._find_ext(data)
611 else:
607 else:
612 ext = None
608 ext = None
613
609
614 if ext is not None:
610 if ext is not None:
615 format = ext.lower()
611 format = ext.lower()
616 if ext == u'jpg' or ext == u'jpeg':
612 if ext == u'jpg' or ext == u'jpeg':
617 format = self._FMT_JPEG
613 format = self._FMT_JPEG
618 if ext == u'png':
614 if ext == u'png':
619 format = self._FMT_PNG
615 format = self._FMT_PNG
620 elif isinstance(data, bytes) and format == 'png':
616 elif isinstance(data, bytes) and format == 'png':
621 # infer image type from image data header,
617 # infer image type from image data header,
622 # only if format might not have been specified.
618 # only if format might not have been specified.
623 if data[:2] == _JPEG:
619 if data[:2] == _JPEG:
624 format = 'jpeg'
620 format = 'jpeg'
625
621
626 self.format = unicode_type(format).lower()
622 self.format = unicode_type(format).lower()
627 self.embed = embed if embed is not None else (url is None)
623 self.embed = embed if embed is not None else (url is None)
628
624
629 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
625 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
630 raise ValueError("Cannot embed the '%s' image format" % (self.format))
626 raise ValueError("Cannot embed the '%s' image format" % (self.format))
631 self.width = width
627 self.width = width
632 self.height = height
628 self.height = height
633 self.retina = retina
629 self.retina = retina
634 super(Image, self).__init__(data=data, url=url, filename=filename)
630 super(Image, self).__init__(data=data, url=url, filename=filename)
635
631
636 if retina:
632 if retina:
637 self._retina_shape()
633 self._retina_shape()
638
634
639 def _retina_shape(self):
635 def _retina_shape(self):
640 """load pixel-doubled width and height from image data"""
636 """load pixel-doubled width and height from image data"""
641 if not self.embed:
637 if not self.embed:
642 return
638 return
643 if self.format == 'png':
639 if self.format == 'png':
644 w, h = _pngxy(self.data)
640 w, h = _pngxy(self.data)
645 elif self.format == 'jpeg':
641 elif self.format == 'jpeg':
646 w, h = _jpegxy(self.data)
642 w, h = _jpegxy(self.data)
647 else:
643 else:
648 # retina only supports png
644 # retina only supports png
649 return
645 return
650 self.width = w // 2
646 self.width = w // 2
651 self.height = h // 2
647 self.height = h // 2
652
648
653 def reload(self):
649 def reload(self):
654 """Reload the raw data from file or URL."""
650 """Reload the raw data from file or URL."""
655 if self.embed:
651 if self.embed:
656 super(Image,self).reload()
652 super(Image,self).reload()
657 if self.retina:
653 if self.retina:
658 self._retina_shape()
654 self._retina_shape()
659
655
660 def _repr_html_(self):
656 def _repr_html_(self):
661 if not self.embed:
657 if not self.embed:
662 width = height = ''
658 width = height = ''
663 if self.width:
659 if self.width:
664 width = ' width="%d"' % self.width
660 width = ' width="%d"' % self.width
665 if self.height:
661 if self.height:
666 height = ' height="%d"' % self.height
662 height = ' height="%d"' % self.height
667 return u'<img src="%s"%s%s/>' % (self.url, width, height)
663 return u'<img src="%s"%s%s/>' % (self.url, width, height)
668
664
669 def _data_and_metadata(self):
665 def _data_and_metadata(self):
670 """shortcut for returning metadata with shape information, if defined"""
666 """shortcut for returning metadata with shape information, if defined"""
671 md = {}
667 md = {}
672 if self.width:
668 if self.width:
673 md['width'] = self.width
669 md['width'] = self.width
674 if self.height:
670 if self.height:
675 md['height'] = self.height
671 md['height'] = self.height
676 if md:
672 if md:
677 return self.data, md
673 return self.data, md
678 else:
674 else:
679 return self.data
675 return self.data
680
676
681 def _repr_png_(self):
677 def _repr_png_(self):
682 if self.embed and self.format == u'png':
678 if self.embed and self.format == u'png':
683 return self._data_and_metadata()
679 return self._data_and_metadata()
684
680
685 def _repr_jpeg_(self):
681 def _repr_jpeg_(self):
686 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
682 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
687 return self._data_and_metadata()
683 return self._data_and_metadata()
688
684
689 def _find_ext(self, s):
685 def _find_ext(self, s):
690 return unicode_type(s.split('.')[-1].lower())
686 return unicode_type(s.split('.')[-1].lower())
691
687
692
688
693 def clear_output(wait=False):
689 def clear_output(wait=False):
694 """Clear the output of the current cell receiving output.
690 """Clear the output of the current cell receiving output.
695
691
696 Parameters
692 Parameters
697 ----------
693 ----------
698 wait : bool [default: false]
694 wait : bool [default: false]
699 Wait to clear the output until new output is available to replace it."""
695 Wait to clear the output until new output is available to replace it."""
700 from IPython.core.interactiveshell import InteractiveShell
696 from IPython.core.interactiveshell import InteractiveShell
701 if InteractiveShell.initialized():
697 if InteractiveShell.initialized():
702 InteractiveShell.instance().display_pub.clear_output(wait)
698 InteractiveShell.instance().display_pub.clear_output(wait)
703 else:
699 else:
704 from IPython.utils import io
700 from IPython.utils import io
705 print('\033[2K\r', file=io.stdout, end='')
701 print('\033[2K\r', file=io.stdout, end='')
706 io.stdout.flush()
702 io.stdout.flush()
707 print('\033[2K\r', file=io.stderr, end='')
703 print('\033[2K\r', file=io.stderr, end='')
708 io.stderr.flush()
704 io.stderr.flush()
@@ -1,712 +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');
497 var wrapper = $('<div/>').addClass('output_subarea output_svg');
498 wrapper.append(svg);
498 wrapper.append(svg);
499 var svg_element = wrapper.children()[0];
499 var svg_element = wrapper.children()[0];
500
500
501 if (svg_element.classList.contains('ipython-scoped')) {
501 if (md['scoped']) {
502 // To avoid style or use collisions between multiple svg figures,
502 // To avoid style or use collisions between multiple svg figures,
503 // svg figures are wrapped inside an iframe.
503 // svg figures are wrapped inside an iframe.
504 var iframe = $('<iframe/>')
504 var iframe = $('<iframe/>')
505 iframe.attr('frameborder', 0);
505 iframe.attr('frameborder', 0);
506 iframe.attr('scrolling', 'no');
506 iframe.attr('scrolling', 'no');
507
507
508 // Once the iframe is loaded, the svg is dynamically inserted
508 // Once the iframe is loaded, the svg is dynamically inserted
509 iframe.on('load', function() {
509 iframe.on('load', function() {
510 // Set the iframe height and width to fit the svg
510 // Set the iframe height and width to fit the svg
511 // (the +10 pixel offset handles the default body margins
511 // (the +10 pixel offset handles the default body margins
512 // in Chrome)
512 // in Chrome)
513 iframe.width(svg_element.width.baseVal.value + 10);
513 iframe.width(svg_element.width.baseVal.value + 10);
514 iframe.height(svg_element.height.baseVal.value + 10);
514 iframe.height(svg_element.height.baseVal.value + 10);
515
515
516 // Workaround needed by Firefox, to properly render svg inside
516 // Workaround needed by Firefox, to properly render svg inside
517 // iframes, see http://stackoverflow.com/questions/10177190/
517 // iframes, see http://stackoverflow.com/questions/10177190/
518 // svg-dynamically-added-to-iframe-does-not-render-correctly
518 // svg-dynamically-added-to-iframe-does-not-render-correctly
519 iframe.contents()[0].open();
519 iframe.contents()[0].open();
520 iframe.contents()[0].close();
520 iframe.contents()[0].close();
521
521
522 // Insert the svg inside the iframe
522 // Insert the svg inside the iframe
523 var body = iframe.contents().find('body');
523 var body = iframe.contents().find('body');
524 body.html(wrapper.html());
524 body.html(wrapper.html());
525 });
525 });
526
526
527 element.append(iframe);
527 element.append(iframe);
528 } else {
528 } else {
529 element.append(wrapper);
529 element.append(wrapper);
530 }
530 }
531 };
531 };
532
532
533
533
534 OutputArea.prototype._dblclick_to_reset_size = function (img) {
534 OutputArea.prototype._dblclick_to_reset_size = function (img) {
535 // schedule wrapping image in resizable after a delay,
535 // schedule wrapping image in resizable after a delay,
536 // 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
537 var that = this;
537 var that = this;
538 setTimeout(function () {
538 setTimeout(function () {
539 var h0 = img.height();
539 var h0 = img.height();
540 var w0 = img.width();
540 var w0 = img.width();
541 if (!(h0 && w0)) {
541 if (!(h0 && w0)) {
542 // zero size, schedule another timeout
542 // zero size, schedule another timeout
543 that._dblclick_to_reset_size(img);
543 that._dblclick_to_reset_size(img);
544 return;
544 return;
545 }
545 }
546 img.resizable({
546 img.resizable({
547 aspectRatio: true,
547 aspectRatio: true,
548 autoHide: true
548 autoHide: true
549 });
549 });
550 img.dblclick(function () {
550 img.dblclick(function () {
551 // resize wrapper & image together for some reason:
551 // resize wrapper & image together for some reason:
552 img.parent().height(h0);
552 img.parent().height(h0);
553 img.height(h0);
553 img.height(h0);
554 img.parent().width(w0);
554 img.parent().width(w0);
555 img.width(w0);
555 img.width(w0);
556 });
556 });
557 }, 250);
557 }, 250);
558 };
558 };
559
559
560
560
561 OutputArea.prototype.append_png = function (png, md, element) {
561 OutputArea.prototype.append_png = function (png, md, element) {
562 var toinsert = $("<div/>").addClass("output_subarea output_png");
562 var toinsert = $("<div/>").addClass("output_subarea output_png");
563 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
563 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
564 if (md['height']) {
564 if (md['height']) {
565 img.attr('height', md['height']);
565 img.attr('height', md['height']);
566 }
566 }
567 if (md['width']) {
567 if (md['width']) {
568 img.attr('width', md['width']);
568 img.attr('width', md['width']);
569 }
569 }
570 this._dblclick_to_reset_size(img);
570 this._dblclick_to_reset_size(img);
571 toinsert.append(img);
571 toinsert.append(img);
572 element.append(toinsert);
572 element.append(toinsert);
573 };
573 };
574
574
575
575
576 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
576 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
577 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
577 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
578 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
578 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
579 if (md['height']) {
579 if (md['height']) {
580 img.attr('height', md['height']);
580 img.attr('height', md['height']);
581 }
581 }
582 if (md['width']) {
582 if (md['width']) {
583 img.attr('width', md['width']);
583 img.attr('width', md['width']);
584 }
584 }
585 this._dblclick_to_reset_size(img);
585 this._dblclick_to_reset_size(img);
586 toinsert.append(img);
586 toinsert.append(img);
587 element.append(toinsert);
587 element.append(toinsert);
588 };
588 };
589
589
590
590
591 OutputArea.prototype.append_latex = function (latex, md, element) {
591 OutputArea.prototype.append_latex = function (latex, md, element) {
592 // 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
593 // be on the page.
593 // be on the page.
594 var toinsert = $("<div/>").addClass("output_subarea output_latex");
594 var toinsert = $("<div/>").addClass("output_subarea output_latex");
595 toinsert.append(latex);
595 toinsert.append(latex);
596 element.append(toinsert);
596 element.append(toinsert);
597 };
597 };
598
598
599 OutputArea.prototype.append_raw_input = function (msg) {
599 OutputArea.prototype.append_raw_input = function (msg) {
600 var that = this;
600 var that = this;
601 this.expand();
601 this.expand();
602 var content = msg.content;
602 var content = msg.content;
603 var area = this.create_output_area();
603 var area = this.create_output_area();
604
604
605 // disable any other raw_inputs, if they are left around
605 // disable any other raw_inputs, if they are left around
606 $("div.output_subarea.raw_input").remove();
606 $("div.output_subarea.raw_input").remove();
607
607
608 area.append(
608 area.append(
609 $("<div/>")
609 $("<div/>")
610 .addClass("box-flex1 output_subarea raw_input")
610 .addClass("box-flex1 output_subarea raw_input")
611 .append(
611 .append(
612 $("<span/>")
612 $("<span/>")
613 .addClass("input_prompt")
613 .addClass("input_prompt")
614 .text(content.prompt)
614 .text(content.prompt)
615 )
615 )
616 .append(
616 .append(
617 $("<input/>")
617 $("<input/>")
618 .addClass("raw_input")
618 .addClass("raw_input")
619 .attr('type', 'text')
619 .attr('type', 'text')
620 .attr("size", 47)
620 .attr("size", 47)
621 .keydown(function (event, ui) {
621 .keydown(function (event, ui) {
622 // make sure we submit on enter,
622 // make sure we submit on enter,
623 // and don't re-execute the *cell* on shift-enter
623 // and don't re-execute the *cell* on shift-enter
624 if (event.which === utils.keycodes.ENTER) {
624 if (event.which === utils.keycodes.ENTER) {
625 that._submit_raw_input();
625 that._submit_raw_input();
626 return false;
626 return false;
627 }
627 }
628 })
628 })
629 )
629 )
630 );
630 );
631 this.element.append(area);
631 this.element.append(area);
632 // weirdly need double-focus now,
632 // weirdly need double-focus now,
633 // otherwise only the cell will be focused
633 // otherwise only the cell will be focused
634 area.find("input.raw_input").focus().focus();
634 area.find("input.raw_input").focus().focus();
635 }
635 }
636 OutputArea.prototype._submit_raw_input = function (evt) {
636 OutputArea.prototype._submit_raw_input = function (evt) {
637 var container = this.element.find("div.raw_input");
637 var container = this.element.find("div.raw_input");
638 var theprompt = container.find("span.input_prompt");
638 var theprompt = container.find("span.input_prompt");
639 var theinput = container.find("input.raw_input");
639 var theinput = container.find("input.raw_input");
640 var value = theinput.val();
640 var value = theinput.val();
641 var content = {
641 var content = {
642 output_type : 'stream',
642 output_type : 'stream',
643 name : 'stdout',
643 name : 'stdout',
644 text : theprompt.text() + value + '\n'
644 text : theprompt.text() + value + '\n'
645 }
645 }
646 // remove form container
646 // remove form container
647 container.parent().remove();
647 container.parent().remove();
648 // replace with plaintext version in stdout
648 // replace with plaintext version in stdout
649 this.append_output(content, false);
649 this.append_output(content, false);
650 $([IPython.events]).trigger('send_input_reply.Kernel', value);
650 $([IPython.events]).trigger('send_input_reply.Kernel', value);
651 }
651 }
652
652
653
653
654 OutputArea.prototype.handle_clear_output = function (msg) {
654 OutputArea.prototype.handle_clear_output = function (msg) {
655 this.clear_output(msg.content.wait);
655 this.clear_output(msg.content.wait);
656 };
656 };
657
657
658
658
659 OutputArea.prototype.clear_output = function(wait) {
659 OutputArea.prototype.clear_output = function(wait) {
660 if (wait) {
660 if (wait) {
661
661
662 // 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.
663 if (this.clear_queued) {
663 if (this.clear_queued) {
664 this.clear_output(false);
664 this.clear_output(false);
665 };
665 };
666
666
667 this.clear_queued = true;
667 this.clear_queued = true;
668 } else {
668 } else {
669
669
670 // 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
671 // new output (it is being used in an animation).
671 // new output (it is being used in an animation).
672 if (this.clear_queued) {
672 if (this.clear_queued) {
673 var height = this.element.height();
673 var height = this.element.height();
674 this.element.height(height);
674 this.element.height(height);
675 this.clear_queued = false;
675 this.clear_queued = false;
676 }
676 }
677
677
678 // clear all, no need for logic
678 // clear all, no need for logic
679 this.element.html("");
679 this.element.html("");
680 this.outputs = [];
680 this.outputs = [];
681 this.unscroll_area();
681 this.unscroll_area();
682 return;
682 return;
683 };
683 };
684 };
684 };
685
685
686
686
687 // JSON serialization
687 // JSON serialization
688
688
689 OutputArea.prototype.fromJSON = function (outputs) {
689 OutputArea.prototype.fromJSON = function (outputs) {
690 var len = outputs.length;
690 var len = outputs.length;
691 for (var i=0; i<len; i++) {
691 for (var i=0; i<len; i++) {
692 // append with dynamic=false.
692 // append with dynamic=false.
693 this.append_output(outputs[i], false);
693 this.append_output(outputs[i], false);
694 }
694 }
695 };
695 };
696
696
697
697
698 OutputArea.prototype.toJSON = function () {
698 OutputArea.prototype.toJSON = function () {
699 var outputs = [];
699 var outputs = [];
700 var len = this.outputs.length;
700 var len = this.outputs.length;
701 for (var i=0; i<len; i++) {
701 for (var i=0; i<len; i++) {
702 outputs[i] = this.outputs[i];
702 outputs[i] = this.outputs[i];
703 }
703 }
704 return outputs;
704 return outputs;
705 };
705 };
706
706
707
707
708 IPython.OutputArea = OutputArea;
708 IPython.OutputArea = OutputArea;
709
709
710 return IPython;
710 return IPython;
711
711
712 }(IPython));
712 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now