##// END OF EJS Templates
A lot of bug fixes......
Jonathan Frederic -
Show More
@@ -1,695 +1,695 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 not raw:
113 if not raw:
114 format = InteractiveShell.instance().display_formatter.format
114 format = InteractiveShell.instance().display_formatter.format
115
115
116 for obj in objs:
116 for obj in objs:
117
117
118 # If _ipython_display_ is defined, use that to display this object. If
118 # If _ipython_display_ is defined, use that to display this object. If
119 # it returns NotImplemented, use the _repr_ logic (default).
119 # it returns NotImplemented, use the _repr_ logic (default).
120 if not hasattr(obj, '_ipython_display_') or isinstance(obj._ipython_display_(**kwargs), NotImplemented):
120 if not hasattr(obj, '_ipython_display_') or obj._ipython_display_(**kwargs) == NotImplemented:
121 if raw:
121 if raw:
122 publish_display_data('display', obj, metadata)
122 publish_display_data('display', obj, metadata)
123 else:
123 else:
124 format_dict, md_dict = format(obj, include=include, exclude=exclude)
124 format_dict, md_dict = format(obj, include=include, exclude=exclude)
125 if metadata:
125 if metadata:
126 # kwarg-specified metadata gets precedence
126 # kwarg-specified metadata gets precedence
127 _merge(md_dict, metadata)
127 _merge(md_dict, metadata)
128 publish_display_data('display', format_dict, md_dict)
128 publish_display_data('display', format_dict, md_dict)
129
129
130
130
131 def display_pretty(*objs, **kwargs):
131 def display_pretty(*objs, **kwargs):
132 """Display the pretty (default) representation of an object.
132 """Display the pretty (default) representation of an object.
133
133
134 Parameters
134 Parameters
135 ----------
135 ----------
136 objs : tuple of objects
136 objs : tuple of objects
137 The Python objects to display, or if raw=True raw text data to
137 The Python objects to display, or if raw=True raw text data to
138 display.
138 display.
139 raw : bool
139 raw : bool
140 Are the data objects raw data or Python objects that need to be
140 Are the data objects raw data or Python objects that need to be
141 formatted before display? [default: False]
141 formatted before display? [default: False]
142 metadata : dict (optional)
142 metadata : dict (optional)
143 Metadata to be associated with the specific mimetype output.
143 Metadata to be associated with the specific mimetype output.
144 """
144 """
145 _display_mimetype('text/plain', objs, **kwargs)
145 _display_mimetype('text/plain', objs, **kwargs)
146
146
147
147
148 def display_html(*objs, **kwargs):
148 def display_html(*objs, **kwargs):
149 """Display the HTML representation of an object.
149 """Display the HTML representation of an object.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 objs : tuple of objects
153 objs : tuple of objects
154 The Python objects to display, or if raw=True raw HTML data to
154 The Python objects to display, or if raw=True raw HTML data to
155 display.
155 display.
156 raw : bool
156 raw : bool
157 Are the data objects raw data or Python objects that need to be
157 Are the data objects raw data or Python objects that need to be
158 formatted before display? [default: False]
158 formatted before display? [default: False]
159 metadata : dict (optional)
159 metadata : dict (optional)
160 Metadata to be associated with the specific mimetype output.
160 Metadata to be associated with the specific mimetype output.
161 """
161 """
162 _display_mimetype('text/html', objs, **kwargs)
162 _display_mimetype('text/html', objs, **kwargs)
163
163
164
164
165 def display_svg(*objs, **kwargs):
165 def display_svg(*objs, **kwargs):
166 """Display the SVG representation of an object.
166 """Display the SVG representation of an object.
167
167
168 Parameters
168 Parameters
169 ----------
169 ----------
170 objs : tuple of objects
170 objs : tuple of objects
171 The Python objects to display, or if raw=True raw svg data to
171 The Python objects to display, or if raw=True raw svg data to
172 display.
172 display.
173 raw : bool
173 raw : bool
174 Are the data objects raw data or Python objects that need to be
174 Are the data objects raw data or Python objects that need to be
175 formatted before display? [default: False]
175 formatted before display? [default: False]
176 metadata : dict (optional)
176 metadata : dict (optional)
177 Metadata to be associated with the specific mimetype output.
177 Metadata to be associated with the specific mimetype output.
178 """
178 """
179 _display_mimetype('image/svg+xml', objs, **kwargs)
179 _display_mimetype('image/svg+xml', objs, **kwargs)
180
180
181
181
182 def display_png(*objs, **kwargs):
182 def display_png(*objs, **kwargs):
183 """Display the PNG representation of an object.
183 """Display the PNG representation of an object.
184
184
185 Parameters
185 Parameters
186 ----------
186 ----------
187 objs : tuple of objects
187 objs : tuple of objects
188 The Python objects to display, or if raw=True raw png data to
188 The Python objects to display, or if raw=True raw png data to
189 display.
189 display.
190 raw : bool
190 raw : bool
191 Are the data objects raw data or Python objects that need to be
191 Are the data objects raw data or Python objects that need to be
192 formatted before display? [default: False]
192 formatted before display? [default: False]
193 metadata : dict (optional)
193 metadata : dict (optional)
194 Metadata to be associated with the specific mimetype output.
194 Metadata to be associated with the specific mimetype output.
195 """
195 """
196 _display_mimetype('image/png', objs, **kwargs)
196 _display_mimetype('image/png', objs, **kwargs)
197
197
198
198
199 def display_jpeg(*objs, **kwargs):
199 def display_jpeg(*objs, **kwargs):
200 """Display the JPEG representation of an object.
200 """Display the JPEG representation of an object.
201
201
202 Parameters
202 Parameters
203 ----------
203 ----------
204 objs : tuple of objects
204 objs : tuple of objects
205 The Python objects to display, or if raw=True raw JPEG data to
205 The Python objects to display, or if raw=True raw JPEG data to
206 display.
206 display.
207 raw : bool
207 raw : bool
208 Are the data objects raw data or Python objects that need to be
208 Are the data objects raw data or Python objects that need to be
209 formatted before display? [default: False]
209 formatted before display? [default: False]
210 metadata : dict (optional)
210 metadata : dict (optional)
211 Metadata to be associated with the specific mimetype output.
211 Metadata to be associated with the specific mimetype output.
212 """
212 """
213 _display_mimetype('image/jpeg', objs, **kwargs)
213 _display_mimetype('image/jpeg', objs, **kwargs)
214
214
215
215
216 def display_latex(*objs, **kwargs):
216 def display_latex(*objs, **kwargs):
217 """Display the LaTeX representation of an object.
217 """Display the LaTeX representation of an object.
218
218
219 Parameters
219 Parameters
220 ----------
220 ----------
221 objs : tuple of objects
221 objs : tuple of objects
222 The Python objects to display, or if raw=True raw latex data to
222 The Python objects to display, or if raw=True raw latex data to
223 display.
223 display.
224 raw : bool
224 raw : bool
225 Are the data objects raw data or Python objects that need to be
225 Are the data objects raw data or Python objects that need to be
226 formatted before display? [default: False]
226 formatted before display? [default: False]
227 metadata : dict (optional)
227 metadata : dict (optional)
228 Metadata to be associated with the specific mimetype output.
228 Metadata to be associated with the specific mimetype output.
229 """
229 """
230 _display_mimetype('text/latex', objs, **kwargs)
230 _display_mimetype('text/latex', objs, **kwargs)
231
231
232
232
233 def display_json(*objs, **kwargs):
233 def display_json(*objs, **kwargs):
234 """Display the JSON representation of an object.
234 """Display the JSON representation of an object.
235
235
236 Note that not many frontends support displaying JSON.
236 Note that not many frontends support displaying JSON.
237
237
238 Parameters
238 Parameters
239 ----------
239 ----------
240 objs : tuple of objects
240 objs : tuple of objects
241 The Python objects to display, or if raw=True raw json data to
241 The Python objects to display, or if raw=True raw json data to
242 display.
242 display.
243 raw : bool
243 raw : bool
244 Are the data objects raw data or Python objects that need to be
244 Are the data objects raw data or Python objects that need to be
245 formatted before display? [default: False]
245 formatted before display? [default: False]
246 metadata : dict (optional)
246 metadata : dict (optional)
247 Metadata to be associated with the specific mimetype output.
247 Metadata to be associated with the specific mimetype output.
248 """
248 """
249 _display_mimetype('application/json', objs, **kwargs)
249 _display_mimetype('application/json', objs, **kwargs)
250
250
251
251
252 def display_javascript(*objs, **kwargs):
252 def display_javascript(*objs, **kwargs):
253 """Display the Javascript representation of an object.
253 """Display the Javascript representation of an object.
254
254
255 Parameters
255 Parameters
256 ----------
256 ----------
257 objs : tuple of objects
257 objs : tuple of objects
258 The Python objects to display, or if raw=True raw javascript data to
258 The Python objects to display, or if raw=True raw javascript data to
259 display.
259 display.
260 raw : bool
260 raw : bool
261 Are the data objects raw data or Python objects that need to be
261 Are the data objects raw data or Python objects that need to be
262 formatted before display? [default: False]
262 formatted before display? [default: False]
263 metadata : dict (optional)
263 metadata : dict (optional)
264 Metadata to be associated with the specific mimetype output.
264 Metadata to be associated with the specific mimetype output.
265 """
265 """
266 _display_mimetype('application/javascript', objs, **kwargs)
266 _display_mimetype('application/javascript', objs, **kwargs)
267
267
268 #-----------------------------------------------------------------------------
268 #-----------------------------------------------------------------------------
269 # Smart classes
269 # Smart classes
270 #-----------------------------------------------------------------------------
270 #-----------------------------------------------------------------------------
271
271
272
272
273 class DisplayObject(object):
273 class DisplayObject(object):
274 """An object that wraps data to be displayed."""
274 """An object that wraps data to be displayed."""
275
275
276 _read_flags = 'r'
276 _read_flags = 'r'
277
277
278 def __init__(self, data=None, url=None, filename=None):
278 def __init__(self, data=None, url=None, filename=None):
279 """Create a display object given raw data.
279 """Create a display object given raw data.
280
280
281 When this object is returned by an expression or passed to the
281 When this object is returned by an expression or passed to the
282 display function, it will result in the data being displayed
282 display function, it will result in the data being displayed
283 in the frontend. The MIME type of the data should match the
283 in the frontend. The MIME type of the data should match the
284 subclasses used, so the Png subclass should be used for 'image/png'
284 subclasses used, so the Png subclass should be used for 'image/png'
285 data. If the data is a URL, the data will first be downloaded
285 data. If the data is a URL, the data will first be downloaded
286 and then displayed. If
286 and then displayed. If
287
287
288 Parameters
288 Parameters
289 ----------
289 ----------
290 data : unicode, str or bytes
290 data : unicode, str or bytes
291 The raw data or a URL or file to load the data from
291 The raw data or a URL or file to load the data from
292 url : unicode
292 url : unicode
293 A URL to download the data from.
293 A URL to download the data from.
294 filename : unicode
294 filename : unicode
295 Path to a local file to load the data from.
295 Path to a local file to load the data from.
296 """
296 """
297 if data is not None and isinstance(data, string_types):
297 if data is not None and isinstance(data, string_types):
298 if data.startswith('http') and url is None:
298 if data.startswith('http') and url is None:
299 url = data
299 url = data
300 filename = None
300 filename = None
301 data = None
301 data = None
302 elif _safe_exists(data) and filename is None:
302 elif _safe_exists(data) and filename is None:
303 url = None
303 url = None
304 filename = data
304 filename = data
305 data = None
305 data = None
306
306
307 self.data = data
307 self.data = data
308 self.url = url
308 self.url = url
309 self.filename = None if filename is None else unicode_type(filename)
309 self.filename = None if filename is None else unicode_type(filename)
310
310
311 self.reload()
311 self.reload()
312 self._check_data()
312 self._check_data()
313
313
314 def _check_data(self):
314 def _check_data(self):
315 """Override in subclasses if there's something to check."""
315 """Override in subclasses if there's something to check."""
316 pass
316 pass
317
317
318 def reload(self):
318 def reload(self):
319 """Reload the raw data from file or URL."""
319 """Reload the raw data from file or URL."""
320 if self.filename is not None:
320 if self.filename is not None:
321 with open(self.filename, self._read_flags) as f:
321 with open(self.filename, self._read_flags) as f:
322 self.data = f.read()
322 self.data = f.read()
323 elif self.url is not None:
323 elif self.url is not None:
324 try:
324 try:
325 try:
325 try:
326 from urllib.request import urlopen # Py3
326 from urllib.request import urlopen # Py3
327 except ImportError:
327 except ImportError:
328 from urllib2 import urlopen
328 from urllib2 import urlopen
329 response = urlopen(self.url)
329 response = urlopen(self.url)
330 self.data = response.read()
330 self.data = response.read()
331 # extract encoding from header, if there is one:
331 # extract encoding from header, if there is one:
332 encoding = None
332 encoding = None
333 for sub in response.headers['content-type'].split(';'):
333 for sub in response.headers['content-type'].split(';'):
334 sub = sub.strip()
334 sub = sub.strip()
335 if sub.startswith('charset'):
335 if sub.startswith('charset'):
336 encoding = sub.split('=')[-1].strip()
336 encoding = sub.split('=')[-1].strip()
337 break
337 break
338 # decode data, if an encoding was specified
338 # decode data, if an encoding was specified
339 if encoding:
339 if encoding:
340 self.data = self.data.decode(encoding, 'replace')
340 self.data = self.data.decode(encoding, 'replace')
341 except:
341 except:
342 self.data = None
342 self.data = None
343
343
344 class TextDisplayObject(DisplayObject):
344 class TextDisplayObject(DisplayObject):
345 """Validate that display data is text"""
345 """Validate that display data is text"""
346 def _check_data(self):
346 def _check_data(self):
347 if self.data is not None and not isinstance(self.data, string_types):
347 if self.data is not None and not isinstance(self.data, string_types):
348 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
348 raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
349
349
350 class Pretty(TextDisplayObject):
350 class Pretty(TextDisplayObject):
351
351
352 def _repr_pretty_(self):
352 def _repr_pretty_(self):
353 return self.data
353 return self.data
354
354
355
355
356 class HTML(TextDisplayObject):
356 class HTML(TextDisplayObject):
357
357
358 def _repr_html_(self):
358 def _repr_html_(self):
359 return self.data
359 return self.data
360
360
361 def __html__(self):
361 def __html__(self):
362 """
362 """
363 This method exists to inform other HTML-using modules (e.g. Markupsafe,
363 This method exists to inform other HTML-using modules (e.g. Markupsafe,
364 htmltag, etc) that this object is HTML and does not need things like
364 htmltag, etc) that this object is HTML and does not need things like
365 special characters (<>&) escaped.
365 special characters (<>&) escaped.
366 """
366 """
367 return self._repr_html_()
367 return self._repr_html_()
368
368
369
369
370 class Math(TextDisplayObject):
370 class Math(TextDisplayObject):
371
371
372 def _repr_latex_(self):
372 def _repr_latex_(self):
373 s = self.data.strip('$')
373 s = self.data.strip('$')
374 return "$$%s$$" % s
374 return "$$%s$$" % s
375
375
376
376
377 class Latex(TextDisplayObject):
377 class Latex(TextDisplayObject):
378
378
379 def _repr_latex_(self):
379 def _repr_latex_(self):
380 return self.data
380 return self.data
381
381
382
382
383 class SVG(DisplayObject):
383 class SVG(DisplayObject):
384
384
385 # wrap data in a property, which extracts the <svg> tag, discarding
385 # wrap data in a property, which extracts the <svg> tag, discarding
386 # document headers
386 # document headers
387 _data = None
387 _data = None
388
388
389 @property
389 @property
390 def data(self):
390 def data(self):
391 return self._data
391 return self._data
392
392
393 @data.setter
393 @data.setter
394 def data(self, svg):
394 def data(self, svg):
395 if svg is None:
395 if svg is None:
396 self._data = None
396 self._data = None
397 return
397 return
398 # parse into dom object
398 # parse into dom object
399 from xml.dom import minidom
399 from xml.dom import minidom
400 svg = cast_bytes_py2(svg)
400 svg = cast_bytes_py2(svg)
401 x = minidom.parseString(svg)
401 x = minidom.parseString(svg)
402 # get svg tag (should be 1)
402 # get svg tag (should be 1)
403 found_svg = x.getElementsByTagName('svg')
403 found_svg = x.getElementsByTagName('svg')
404 if found_svg:
404 if found_svg:
405 svg = found_svg[0].toxml()
405 svg = found_svg[0].toxml()
406 else:
406 else:
407 # fallback on the input, trust the user
407 # fallback on the input, trust the user
408 # but this is probably an error.
408 # but this is probably an error.
409 pass
409 pass
410 svg = cast_unicode(svg)
410 svg = cast_unicode(svg)
411 self._data = svg
411 self._data = svg
412
412
413 def _repr_svg_(self):
413 def _repr_svg_(self):
414 return self.data
414 return self.data
415
415
416
416
417 class JSON(TextDisplayObject):
417 class JSON(TextDisplayObject):
418
418
419 def _repr_json_(self):
419 def _repr_json_(self):
420 return self.data
420 return self.data
421
421
422 css_t = """$("head").append($("<link/>").attr({
422 css_t = """$("head").append($("<link/>").attr({
423 rel: "stylesheet",
423 rel: "stylesheet",
424 type: "text/css",
424 type: "text/css",
425 href: "%s"
425 href: "%s"
426 }));
426 }));
427 """
427 """
428
428
429 lib_t1 = """$.getScript("%s", function () {
429 lib_t1 = """$.getScript("%s", function () {
430 """
430 """
431 lib_t2 = """});
431 lib_t2 = """});
432 """
432 """
433
433
434 class Javascript(TextDisplayObject):
434 class Javascript(TextDisplayObject):
435
435
436 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
436 def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
437 """Create a Javascript display object given raw data.
437 """Create a Javascript display object given raw data.
438
438
439 When this object is returned by an expression or passed to the
439 When this object is returned by an expression or passed to the
440 display function, it will result in the data being displayed
440 display function, it will result in the data being displayed
441 in the frontend. If the data is a URL, the data will first be
441 in the frontend. If the data is a URL, the data will first be
442 downloaded and then displayed.
442 downloaded and then displayed.
443
443
444 In the Notebook, the containing element will be available as `element`,
444 In the Notebook, the containing element will be available as `element`,
445 and jQuery will be available. The output area starts hidden, so if
445 and jQuery will be available. The output area starts hidden, so if
446 the js appends content to `element` that should be visible, then
446 the js appends content to `element` that should be visible, then
447 it must call `container.show()` to unhide the area.
447 it must call `container.show()` to unhide the area.
448
448
449 Parameters
449 Parameters
450 ----------
450 ----------
451 data : unicode, str or bytes
451 data : unicode, str or bytes
452 The Javascript source code or a URL to download it from.
452 The Javascript source code or a URL to download it from.
453 url : unicode
453 url : unicode
454 A URL to download the data from.
454 A URL to download the data from.
455 filename : unicode
455 filename : unicode
456 Path to a local file to load the data from.
456 Path to a local file to load the data from.
457 lib : list or str
457 lib : list or str
458 A sequence of Javascript library URLs to load asynchronously before
458 A sequence of Javascript library URLs to load asynchronously before
459 running the source code. The full URLs of the libraries should
459 running the source code. The full URLs of the libraries should
460 be given. A single Javascript library URL can also be given as a
460 be given. A single Javascript library URL can also be given as a
461 string.
461 string.
462 css: : list or str
462 css: : list or str
463 A sequence of css files to load before running the source code.
463 A sequence of css files to load before running the source code.
464 The full URLs of the css files should be given. A single css URL
464 The full URLs of the css files should be given. A single css URL
465 can also be given as a string.
465 can also be given as a string.
466 """
466 """
467 if isinstance(lib, string_types):
467 if isinstance(lib, string_types):
468 lib = [lib]
468 lib = [lib]
469 elif lib is None:
469 elif lib is None:
470 lib = []
470 lib = []
471 if isinstance(css, string_types):
471 if isinstance(css, string_types):
472 css = [css]
472 css = [css]
473 elif css is None:
473 elif css is None:
474 css = []
474 css = []
475 if not isinstance(lib, (list,tuple)):
475 if not isinstance(lib, (list,tuple)):
476 raise TypeError('expected sequence, got: %r' % lib)
476 raise TypeError('expected sequence, got: %r' % lib)
477 if not isinstance(css, (list,tuple)):
477 if not isinstance(css, (list,tuple)):
478 raise TypeError('expected sequence, got: %r' % css)
478 raise TypeError('expected sequence, got: %r' % css)
479 self.lib = lib
479 self.lib = lib
480 self.css = css
480 self.css = css
481 super(Javascript, self).__init__(data=data, url=url, filename=filename)
481 super(Javascript, self).__init__(data=data, url=url, filename=filename)
482
482
483 def _repr_javascript_(self):
483 def _repr_javascript_(self):
484 r = ''
484 r = ''
485 for c in self.css:
485 for c in self.css:
486 r += css_t % c
486 r += css_t % c
487 for l in self.lib:
487 for l in self.lib:
488 r += lib_t1 % l
488 r += lib_t1 % l
489 r += self.data
489 r += self.data
490 r += lib_t2*len(self.lib)
490 r += lib_t2*len(self.lib)
491 return r
491 return r
492
492
493 # constants for identifying png/jpeg data
493 # constants for identifying png/jpeg data
494 _PNG = b'\x89PNG\r\n\x1a\n'
494 _PNG = b'\x89PNG\r\n\x1a\n'
495 _JPEG = b'\xff\xd8'
495 _JPEG = b'\xff\xd8'
496
496
497 def _pngxy(data):
497 def _pngxy(data):
498 """read the (width, height) from a PNG header"""
498 """read the (width, height) from a PNG header"""
499 ihdr = data.index(b'IHDR')
499 ihdr = data.index(b'IHDR')
500 # next 8 bytes are width/height
500 # next 8 bytes are width/height
501 w4h4 = data[ihdr+4:ihdr+12]
501 w4h4 = data[ihdr+4:ihdr+12]
502 return struct.unpack('>ii', w4h4)
502 return struct.unpack('>ii', w4h4)
503
503
504 def _jpegxy(data):
504 def _jpegxy(data):
505 """read the (width, height) from a JPEG header"""
505 """read the (width, height) from a JPEG header"""
506 # adapted from http://www.64lines.com/jpeg-width-height
506 # adapted from http://www.64lines.com/jpeg-width-height
507
507
508 idx = 4
508 idx = 4
509 while True:
509 while True:
510 block_size = struct.unpack('>H', data[idx:idx+2])[0]
510 block_size = struct.unpack('>H', data[idx:idx+2])[0]
511 idx = idx + block_size
511 idx = idx + block_size
512 if data[idx:idx+2] == b'\xFF\xC0':
512 if data[idx:idx+2] == b'\xFF\xC0':
513 # found Start of Frame
513 # found Start of Frame
514 iSOF = idx
514 iSOF = idx
515 break
515 break
516 else:
516 else:
517 # read another block
517 # read another block
518 idx += 2
518 idx += 2
519
519
520 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
520 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
521 return w, h
521 return w, h
522
522
523 class Image(DisplayObject):
523 class Image(DisplayObject):
524
524
525 _read_flags = 'rb'
525 _read_flags = 'rb'
526 _FMT_JPEG = u'jpeg'
526 _FMT_JPEG = u'jpeg'
527 _FMT_PNG = u'png'
527 _FMT_PNG = u'png'
528 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
528 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
529
529
530 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
530 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
531 """Create a PNG/JPEG image object given raw data.
531 """Create a PNG/JPEG image object given raw data.
532
532
533 When this object is returned by an input cell or passed to the
533 When this object is returned by an input cell or passed to the
534 display function, it will result in the image being displayed
534 display function, it will result in the image being displayed
535 in the frontend.
535 in the frontend.
536
536
537 Parameters
537 Parameters
538 ----------
538 ----------
539 data : unicode, str or bytes
539 data : unicode, str or bytes
540 The raw image data or a URL or filename to load the data from.
540 The raw image data or a URL or filename to load the data from.
541 This always results in embedded image data.
541 This always results in embedded image data.
542 url : unicode
542 url : unicode
543 A URL to download the data from. If you specify `url=`,
543 A URL to download the data from. If you specify `url=`,
544 the image data will not be embedded unless you also specify `embed=True`.
544 the image data will not be embedded unless you also specify `embed=True`.
545 filename : unicode
545 filename : unicode
546 Path to a local file to load the data from.
546 Path to a local file to load the data from.
547 Images from a file are always embedded.
547 Images from a file are always embedded.
548 format : unicode
548 format : unicode
549 The format of the image data (png/jpeg/jpg). If a filename or URL is given
549 The format of the image data (png/jpeg/jpg). If a filename or URL is given
550 for format will be inferred from the filename extension.
550 for format will be inferred from the filename extension.
551 embed : bool
551 embed : bool
552 Should the image data be embedded using a data URI (True) or be
552 Should the image data be embedded using a data URI (True) or be
553 loaded using an <img> tag. Set this to True if you want the image
553 loaded using an <img> tag. Set this to True if you want the image
554 to be viewable later with no internet connection in the notebook.
554 to be viewable later with no internet connection in the notebook.
555
555
556 Default is `True`, unless the keyword argument `url` is set, then
556 Default is `True`, unless the keyword argument `url` is set, then
557 default value is `False`.
557 default value is `False`.
558
558
559 Note that QtConsole is not able to display images if `embed` is set to `False`
559 Note that QtConsole is not able to display images if `embed` is set to `False`
560 width : int
560 width : int
561 Width to which to constrain the image in html
561 Width to which to constrain the image in html
562 height : int
562 height : int
563 Height to which to constrain the image in html
563 Height to which to constrain the image in html
564 retina : bool
564 retina : bool
565 Automatically set the width and height to half of the measured
565 Automatically set the width and height to half of the measured
566 width and height.
566 width and height.
567 This only works for embedded images because it reads the width/height
567 This only works for embedded images because it reads the width/height
568 from image data.
568 from image data.
569 For non-embedded images, you can just set the desired display width
569 For non-embedded images, you can just set the desired display width
570 and height directly.
570 and height directly.
571
571
572 Examples
572 Examples
573 --------
573 --------
574 # embedded image data, works in qtconsole and notebook
574 # embedded image data, works in qtconsole and notebook
575 # when passed positionally, the first arg can be any of raw image data,
575 # when passed positionally, the first arg can be any of raw image data,
576 # a URL, or a filename from which to load image data.
576 # a URL, or a filename from which to load image data.
577 # The result is always embedding image data for inline images.
577 # The result is always embedding image data for inline images.
578 Image('http://www.google.fr/images/srpr/logo3w.png')
578 Image('http://www.google.fr/images/srpr/logo3w.png')
579 Image('/path/to/image.jpg')
579 Image('/path/to/image.jpg')
580 Image(b'RAW_PNG_DATA...')
580 Image(b'RAW_PNG_DATA...')
581
581
582 # Specifying Image(url=...) does not embed the image data,
582 # Specifying Image(url=...) does not embed the image data,
583 # it only generates `<img>` tag with a link to the source.
583 # it only generates `<img>` tag with a link to the source.
584 # This will not work in the qtconsole or offline.
584 # This will not work in the qtconsole or offline.
585 Image(url='http://www.google.fr/images/srpr/logo3w.png')
585 Image(url='http://www.google.fr/images/srpr/logo3w.png')
586
586
587 """
587 """
588 if filename is not None:
588 if filename is not None:
589 ext = self._find_ext(filename)
589 ext = self._find_ext(filename)
590 elif url is not None:
590 elif url is not None:
591 ext = self._find_ext(url)
591 ext = self._find_ext(url)
592 elif data is None:
592 elif data is None:
593 raise ValueError("No image data found. Expecting filename, url, or data.")
593 raise ValueError("No image data found. Expecting filename, url, or data.")
594 elif isinstance(data, string_types) and (
594 elif isinstance(data, string_types) and (
595 data.startswith('http') or _safe_exists(data)
595 data.startswith('http') or _safe_exists(data)
596 ):
596 ):
597 ext = self._find_ext(data)
597 ext = self._find_ext(data)
598 else:
598 else:
599 ext = None
599 ext = None
600
600
601 if ext is not None:
601 if ext is not None:
602 format = ext.lower()
602 format = ext.lower()
603 if ext == u'jpg' or ext == u'jpeg':
603 if ext == u'jpg' or ext == u'jpeg':
604 format = self._FMT_JPEG
604 format = self._FMT_JPEG
605 if ext == u'png':
605 if ext == u'png':
606 format = self._FMT_PNG
606 format = self._FMT_PNG
607 elif isinstance(data, bytes) and format == 'png':
607 elif isinstance(data, bytes) and format == 'png':
608 # infer image type from image data header,
608 # infer image type from image data header,
609 # only if format might not have been specified.
609 # only if format might not have been specified.
610 if data[:2] == _JPEG:
610 if data[:2] == _JPEG:
611 format = 'jpeg'
611 format = 'jpeg'
612
612
613 self.format = unicode_type(format).lower()
613 self.format = unicode_type(format).lower()
614 self.embed = embed if embed is not None else (url is None)
614 self.embed = embed if embed is not None else (url is None)
615
615
616 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
616 if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
617 raise ValueError("Cannot embed the '%s' image format" % (self.format))
617 raise ValueError("Cannot embed the '%s' image format" % (self.format))
618 self.width = width
618 self.width = width
619 self.height = height
619 self.height = height
620 self.retina = retina
620 self.retina = retina
621 super(Image, self).__init__(data=data, url=url, filename=filename)
621 super(Image, self).__init__(data=data, url=url, filename=filename)
622
622
623 if retina:
623 if retina:
624 self._retina_shape()
624 self._retina_shape()
625
625
626 def _retina_shape(self):
626 def _retina_shape(self):
627 """load pixel-doubled width and height from image data"""
627 """load pixel-doubled width and height from image data"""
628 if not self.embed:
628 if not self.embed:
629 return
629 return
630 if self.format == 'png':
630 if self.format == 'png':
631 w, h = _pngxy(self.data)
631 w, h = _pngxy(self.data)
632 elif self.format == 'jpeg':
632 elif self.format == 'jpeg':
633 w, h = _jpegxy(self.data)
633 w, h = _jpegxy(self.data)
634 else:
634 else:
635 # retina only supports png
635 # retina only supports png
636 return
636 return
637 self.width = w // 2
637 self.width = w // 2
638 self.height = h // 2
638 self.height = h // 2
639
639
640 def reload(self):
640 def reload(self):
641 """Reload the raw data from file or URL."""
641 """Reload the raw data from file or URL."""
642 if self.embed:
642 if self.embed:
643 super(Image,self).reload()
643 super(Image,self).reload()
644 if self.retina:
644 if self.retina:
645 self._retina_shape()
645 self._retina_shape()
646
646
647 def _repr_html_(self):
647 def _repr_html_(self):
648 if not self.embed:
648 if not self.embed:
649 width = height = ''
649 width = height = ''
650 if self.width:
650 if self.width:
651 width = ' width="%d"' % self.width
651 width = ' width="%d"' % self.width
652 if self.height:
652 if self.height:
653 height = ' height="%d"' % self.height
653 height = ' height="%d"' % self.height
654 return u'<img src="%s"%s%s/>' % (self.url, width, height)
654 return u'<img src="%s"%s%s/>' % (self.url, width, height)
655
655
656 def _data_and_metadata(self):
656 def _data_and_metadata(self):
657 """shortcut for returning metadata with shape information, if defined"""
657 """shortcut for returning metadata with shape information, if defined"""
658 md = {}
658 md = {}
659 if self.width:
659 if self.width:
660 md['width'] = self.width
660 md['width'] = self.width
661 if self.height:
661 if self.height:
662 md['height'] = self.height
662 md['height'] = self.height
663 if md:
663 if md:
664 return self.data, md
664 return self.data, md
665 else:
665 else:
666 return self.data
666 return self.data
667
667
668 def _repr_png_(self):
668 def _repr_png_(self):
669 if self.embed and self.format == u'png':
669 if self.embed and self.format == u'png':
670 return self._data_and_metadata()
670 return self._data_and_metadata()
671
671
672 def _repr_jpeg_(self):
672 def _repr_jpeg_(self):
673 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
673 if self.embed and (self.format == u'jpeg' or self.format == u'jpg'):
674 return self._data_and_metadata()
674 return self._data_and_metadata()
675
675
676 def _find_ext(self, s):
676 def _find_ext(self, s):
677 return unicode_type(s.split('.')[-1].lower())
677 return unicode_type(s.split('.')[-1].lower())
678
678
679
679
680 def clear_output(wait=False):
680 def clear_output(wait=False):
681 """Clear the output of the current cell receiving output.
681 """Clear the output of the current cell receiving output.
682
682
683 Parameters
683 Parameters
684 ----------
684 ----------
685 wait : bool [default: false]
685 wait : bool [default: false]
686 Wait to clear the output until new output is available to replace it."""
686 Wait to clear the output until new output is available to replace it."""
687 from IPython.core.interactiveshell import InteractiveShell
687 from IPython.core.interactiveshell import InteractiveShell
688 if InteractiveShell.initialized():
688 if InteractiveShell.initialized():
689 InteractiveShell.instance().display_pub.clear_output(wait)
689 InteractiveShell.instance().display_pub.clear_output(wait)
690 else:
690 else:
691 from IPython.utils import io
691 from IPython.utils import io
692 print('\033[2K\r', file=io.stdout, end='')
692 print('\033[2K\r', file=io.stdout, end='')
693 io.stdout.flush()
693 io.stdout.flush()
694 print('\033[2K\r', file=io.stderr, end='')
694 print('\033[2K\r', file=io.stderr, end='')
695 io.stderr.flush()
695 io.stderr.flush()
@@ -1,121 +1,121 b''
1 //----------------------------------------------------------------------------
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
2 // Copyright (C) 2013 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 // BoolWidget
9 // BoolWidget
10 //============================================================================
10 //============================================================================
11
11
12 /**
12 /**
13 * @module IPython
13 * @module IPython
14 * @namespace IPython
14 * @namespace IPython
15 **/
15 **/
16
16
17 define(["notebook/js/widgets/widget"], function(widget_manager){
17 define(["notebook/js/widgets/widget"], function(widget_manager){
18 var CheckboxView = IPython.DOMWidgetView.extend({
18 var CheckBoxView = IPython.DOMWidgetView.extend({
19
19
20 // Called when view is rendered.
20 // Called when view is rendered.
21 render : function(){
21 render : function(){
22 this.$el
22 this.$el
23 .addClass('widget-hbox-single');
23 .addClass('widget-hbox-single');
24 this.$label = $('<div />')
24 this.$label = $('<div />')
25 .addClass('widget-hlabel')
25 .addClass('widget-hlabel')
26 .appendTo(this.$el)
26 .appendTo(this.$el)
27 .hide();
27 .hide();
28 var that = this;
28 var that = this;
29 this.$checkbox = $('<input />')
29 this.$checkbox = $('<input />')
30 .attr('type', 'checkbox')
30 .attr('type', 'checkbox')
31 .click(function(el) {
31 .click(function(el) {
32
32
33 // Calling model.set will trigger all of the other views of the
33 // Calling model.set will trigger all of the other views of the
34 // model to update.
34 // model to update.
35 that.model.set('value', that.$checkbox.prop('checked'), {updated_view: this});
35 that.model.set('value', that.$checkbox.prop('checked'), {updated_view: this});
36 that.touch();
36 that.touch();
37 })
37 })
38 .appendTo(this.$el);
38 .appendTo(this.$el);
39
39
40 this.$el_to_style = this.$checkbox; // Set default element to style
40 this.$el_to_style = this.$checkbox; // Set default element to style
41 this.update(); // Set defaults.
41 this.update(); // Set defaults.
42 },
42 },
43
43
44 update : function(options){
44 update : function(options){
45 // Update the contents of this view
45 // Update the contents of this view
46 //
46 //
47 // Called when the model is changed. The model may have been
47 // Called when the model is changed. The model may have been
48 // changed by another view or by a state update from the back-end.
48 // changed by another view or by a state update from the back-end.
49 if (options === undefined || options.updated_view != this) {
49 if (options === undefined || options.updated_view != this) {
50 this.$checkbox.prop('checked', this.model.get('value'));
50 this.$checkbox.prop('checked', this.model.get('value'));
51
51
52 var disabled = this.model.get('disabled');
52 var disabled = this.model.get('disabled');
53 this.$checkbox.prop('disabled', disabled);
53 this.$checkbox.prop('disabled', disabled);
54
54
55 var description = this.model.get('description');
55 var description = this.model.get('description');
56 if (description.length === 0) {
56 if (description.length === 0) {
57 this.$label.hide();
57 this.$label.hide();
58 } else {
58 } else {
59 this.$label.html(description);
59 this.$label.html(description);
60 this.$label.show();
60 this.$label.show();
61 }
61 }
62 }
62 }
63 return CheckboxView.__super__.update.apply(this);
63 return CheckBoxView.__super__.update.apply(this);
64 },
64 },
65
65
66 });
66 });
67
67
68 widget_manager.register_widget_view('CheckboxView', CheckboxView);
68 widget_manager.register_widget_view('CheckBoxView', CheckBoxView);
69
69
70 var ToggleButtonView = IPython.DOMWidgetView.extend({
70 var ToggleButtonView = IPython.DOMWidgetView.extend({
71
71
72 // Called when view is rendered.
72 // Called when view is rendered.
73 render : function(){
73 render : function(){
74 this.setElement($('<button />')
74 this.setElement($('<button />')
75 .addClass('btn')
75 .addClass('btn')
76 .attr('type', 'button')
76 .attr('type', 'button')
77 .attr('data-toggle', 'button'));
77 .attr('data-toggle', 'button'));
78
78
79 this.update(); // Set defaults.
79 this.update(); // Set defaults.
80 },
80 },
81
81
82 update : function(options){
82 update : function(options){
83 // Update the contents of this view
83 // Update the contents of this view
84 //
84 //
85 // Called when the model is changed. The model may have been
85 // Called when the model is changed. The model may have been
86 // changed by another view or by a state update from the back-end.
86 // changed by another view or by a state update from the back-end.
87 if (this.model.get('value')) {
87 if (this.model.get('value')) {
88 this.$el.addClass('active');
88 this.$el.addClass('active');
89 } else {
89 } else {
90 this.$el.removeClass('active');
90 this.$el.removeClass('active');
91 }
91 }
92
92
93 if (options === undefined || options.updated_view != this) {
93 if (options === undefined || options.updated_view != this) {
94 var disabled = this.model.get('disabled');
94 var disabled = this.model.get('disabled');
95 this.$el.prop('disabled', disabled);
95 this.$el.prop('disabled', disabled);
96
96
97 var description = this.model.get('description');
97 var description = this.model.get('description');
98 if (description.length === 0) {
98 if (description.length === 0) {
99 this.$el.html(' '); // Preserve button height
99 this.$el.html(' '); // Preserve button height
100 } else {
100 } else {
101 this.$el.html(description);
101 this.$el.html(description);
102 }
102 }
103 }
103 }
104 return ToggleButtonView.__super__.update.apply(this);
104 return ToggleButtonView.__super__.update.apply(this);
105 },
105 },
106
106
107 events: {"click" : "handleClick"},
107 events: {"click" : "handleClick"},
108
108
109 // Handles and validates user input.
109 // Handles and validates user input.
110 handleClick: function(e) {
110 handleClick: function(e) {
111
111
112 // Calling model.set will trigger all of the other views of the
112 // Calling model.set will trigger all of the other views of the
113 // model to update.
113 // model to update.
114 this.model.set('value', ! $(this.$el).hasClass('active'), {updated_view: this});
114 this.model.set('value', ! $(this.$el).hasClass('active'), {updated_view: this});
115 this.touch();
115 this.touch();
116 },
116 },
117 });
117 });
118
118
119 widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
119 widget_manager.register_widget_view('ToggleButtonView', ToggleButtonView);
120
120
121 });
121 });
@@ -1,447 +1,447 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import inspect
16 import inspect
17 import types
17 import types
18
18
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool
22 from IPython.utils.py3compat import string_types
22 from IPython.utils.py3compat import string_types
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 class Widget(LoggingConfigurable):
27 class Widget(LoggingConfigurable):
28
28
29 # Shared declarations (Class level)
29 # Shared declarations (Class level)
30 widget_construction_callback = None
30 widget_construction_callback = None
31 widgets = {}
31 widgets = {}
32
32
33 def on_widget_constructed(callback):
33 def on_widget_constructed(callback):
34 """Class method, registers a callback to be called when a widget is
34 """Class method, registers a callback to be called when a widget is
35 constructed. The callback must have the following signature:
35 constructed. The callback must have the following signature:
36 callback(widget)"""
36 callback(widget)"""
37 Widget.widget_construction_callback = callback
37 Widget.widget_construction_callback = callback
38
38
39 def _call_widget_constructed(widget):
39 def _call_widget_constructed(widget):
40 """Class method, called when a widget is constructed."""
40 """Class method, called when a widget is constructed."""
41 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
41 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
42 Widget.widget_construction_callback(widget)
42 Widget.widget_construction_callback(widget)
43
43
44
44
45
45
46 # Public declarations (Instance level)
46 # Public declarations (Instance level)
47 model_name = Unicode('WidgetModel', help="""Name of the backbone model
47 model_name = Unicode('WidgetModel', help="""Name of the backbone model
48 registered in the front-end to create and sync this widget with.""")
48 registered in the front-end to create and sync this widget with.""")
49 view_name = Unicode(help="""Default view registered in the front-end
49 view_name = Unicode(help="""Default view registered in the front-end
50 to use to represent the widget.""", sync=True)
50 to use to represent the widget.""", sync=True)
51
51
52 @contextmanager
52 @contextmanager
53 def property_lock(self, key, value):
53 def property_lock(self, key, value):
54 """Lock a property-value pair.
54 """Lock a property-value pair.
55
55
56 NOTE: This, in addition to the single lock for all state changes, is
56 NOTE: This, in addition to the single lock for all state changes, is
57 flawed. In the future we may want to look into buffering state changes
57 flawed. In the future we may want to look into buffering state changes
58 back to the front-end."""
58 back to the front-end."""
59 self._property_lock = (key, value)
59 self._property_lock = (key, value)
60 try:
60 try:
61 yield
61 yield
62 finally:
62 finally:
63 self._property_lock = (None, None)
63 self._property_lock = (None, None)
64
64
65 def should_send_property(self, key, value):
65 def should_send_property(self, key, value):
66 """Check the property lock (property_lock)"""
66 """Check the property lock (property_lock)"""
67 return key != self._property_lock[0] or \
67 return key != self._property_lock[0] or \
68 value != self._property_lock[1]
68 value != self._property_lock[1]
69
69
70 @property
70 @property
71 def keys(self):
71 def keys(self):
72 if self._keys is None:
72 if self._keys is None:
73 self._keys = []
73 self._keys = []
74 for trait_name in self.trait_names():
74 for trait_name in self.trait_names():
75 if self.trait_metadata(trait_name, 'sync'):
75 if self.trait_metadata(trait_name, 'sync'):
76 self._keys.append(trait_name)
76 self._keys.append(trait_name)
77 return self._keys
77 return self._keys
78
78
79 # Private/protected declarations
79 # Private/protected declarations
80 _comm = Instance('IPython.kernel.comm.Comm')
80 _comm = Instance('IPython.kernel.comm.Comm')
81
81
82 def __init__(self, **kwargs):
82 def __init__(self, **kwargs):
83 """Public constructor
83 """Public constructor
84 """
84 """
85 self.closed = False
85 self.closed = False
86 self._property_lock = (None, None)
86 self._property_lock = (None, None)
87 self._display_callbacks = []
87 self._display_callbacks = []
88 self._msg_callbacks = []
88 self._msg_callbacks = []
89 self._keys = None
89 self._keys = None
90 super(Widget, self).__init__(**kwargs)
90 super(Widget, self).__init__(**kwargs)
91
91
92 self.on_trait_change(self._handle_property_changed, self.keys)
92 self.on_trait_change(self._handle_property_changed, self.keys)
93 Widget._call_widget_constructed(self)
93 Widget._call_widget_constructed(self)
94
94
95 def __del__(self):
95 def __del__(self):
96 """Object disposal"""
96 """Object disposal"""
97 self.close()
97 self.close()
98
98
99 def close(self):
99 def close(self):
100 """Close method. Closes the widget which closes the underlying comm.
100 """Close method. Closes the widget which closes the underlying comm.
101 When the comm is closed, all of the widget views are automatically
101 When the comm is closed, all of the widget views are automatically
102 removed from the front-end."""
102 removed from the front-end."""
103 if not self.closed:
103 if not self.closed:
104 self._comm.close()
104 self._comm.close()
105 self._close()
105 self._close()
106
106
107
107
108 def _close(self):
108 def _close(self):
109 """Unsafe close"""
109 """Unsafe close"""
110 del Widget.widgets[self.model_id]
110 del Widget.widgets[self.model_id]
111 self._comm = None
111 self._comm = None
112 self.closed = True
112 self.closed = True
113
113
114
114
115 @property
115 @property
116 def comm(self):
116 def comm(self):
117 if self._comm is None:
117 if self._comm is None:
118 # Create a comm.
118 # Create a comm.
119 self._comm = Comm(target_name=self.model_name)
119 self._comm = Comm(target_name=self.model_name)
120 self._comm.on_msg(self._handle_msg)
120 self._comm.on_msg(self._handle_msg)
121 self._comm.on_close(self._close)
121 self._comm.on_close(self._close)
122 Widget.widgets[self.model_id] = self
122 Widget.widgets[self.model_id] = self
123
123
124 # first update
124 # first update
125 self.send_state()
125 self.send_state()
126 return self._comm
126 return self._comm
127
127
128 @property
128 @property
129 def model_id(self):
129 def model_id(self):
130 return self.comm.comm_id
130 return self.comm.comm_id
131
131
132 # Event handlers
132 # Event handlers
133 def _handle_msg(self, msg):
133 def _handle_msg(self, msg):
134 """Called when a msg is received from the front-end"""
134 """Called when a msg is received from the front-end"""
135 data = msg['content']['data']
135 data = msg['content']['data']
136 method = data['method']
136 method = data['method']
137 if not method in ['backbone', 'custom']:
137 if not method in ['backbone', 'custom']:
138 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
138 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
139
139
140 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
140 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
141 if method == 'backbone' and 'sync_data' in data:
141 if method == 'backbone' and 'sync_data' in data:
142 sync_data = data['sync_data']
142 sync_data = data['sync_data']
143 self._handle_receive_state(sync_data) # handles all methods
143 self._handle_receive_state(sync_data) # handles all methods
144
144
145 # Handle a custom msg from the front-end
145 # Handle a custom msg from the front-end
146 elif method == 'custom':
146 elif method == 'custom':
147 if 'custom_content' in data:
147 if 'custom_content' in data:
148 self._handle_custom_msg(data['custom_content'])
148 self._handle_custom_msg(data['custom_content'])
149
149
150
150
151 def _handle_receive_state(self, sync_data):
151 def _handle_receive_state(self, sync_data):
152 """Called when a state is received from the front-end."""
152 """Called when a state is received from the front-end."""
153 for name in self.keys:
153 for name in self.keys:
154 if name in sync_data:
154 if name in sync_data:
155 value = self._unpack_widgets(sync_data[name])
155 value = self._unpack_widgets(sync_data[name])
156 with self.property_lock(name, value):
156 with self.property_lock(name, value):
157 setattr(self, name, value)
157 setattr(self, name, value)
158
158
159
159
160 def _handle_custom_msg(self, content):
160 def _handle_custom_msg(self, content):
161 """Called when a custom msg is received."""
161 """Called when a custom msg is received."""
162 for handler in self._msg_callbacks:
162 for handler in self._msg_callbacks:
163 handler(self, content)
163 handler(self, content)
164
164
165
165
166 def _handle_property_changed(self, name, old, new):
166 def _handle_property_changed(self, name, old, new):
167 """Called when a property has been changed."""
167 """Called when a property has been changed."""
168 # Make sure this isn't information that the front-end just sent us.
168 # Make sure this isn't information that the front-end just sent us.
169 if should_send_property(self, name, new):
169 if self.should_send_property(name, new):
170 # Send new state to front-end
170 # Send new state to front-end
171 self.send_state(key=name)
171 self.send_state(key=name)
172
172
173 def _handle_displayed(self, **kwargs):
173 def _handle_displayed(self, **kwargs):
174 """Called when a view has been displayed for this widget instance"""
174 """Called when a view has been displayed for this widget instance"""
175 for handler in self._display_callbacks:
175 for handler in self._display_callbacks:
176 handler(self, **kwargs)
176 handler(self, **kwargs)
177
177
178 # Public methods
178 # Public methods
179 def send_state(self, key=None):
179 def send_state(self, key=None):
180 """Sends the widget state, or a piece of it, to the front-end.
180 """Sends the widget state, or a piece of it, to the front-end.
181
181
182 Parameters
182 Parameters
183 ----------
183 ----------
184 key : unicode (optional)
184 key : unicode (optional)
185 A single property's name to sync with the front-end.
185 A single property's name to sync with the front-end.
186 """
186 """
187 self._send({"method": "update",
187 self._send({"method": "update",
188 "state": self.get_state()})
188 "state": self.get_state()})
189
189
190 def get_state(self, key=None):
190 def get_state(self, key=None):
191 """Gets the widget state, or a piece of it.
191 """Gets the widget state, or a piece of it.
192
192
193 Parameters
193 Parameters
194 ----------
194 ----------
195 key : unicode (optional)
195 key : unicode (optional)
196 A single property's name to get.
196 A single property's name to get.
197 """
197 """
198 keys = self.keys if key is None else [key]
198 keys = self.keys if key is None else [key]
199 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
199 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
200
200
201
201
202 def _pack_widgets(self, values):
202 def _pack_widgets(self, values):
203 """This function recursively converts all widget instances to model id
203 """This function recursively converts all widget instances to model id
204 strings.
204 strings.
205
205
206 Children widgets will be stored and transmitted to the front-end by
206 Children widgets will be stored and transmitted to the front-end by
207 their model ids."""
207 their model ids."""
208 if isinstance(values, dict):
208 if isinstance(values, dict):
209 new_dict = {}
209 new_dict = {}
210 for key, value in values.items():
210 for key, value in values.items():
211 new_dict[key] = self._pack_widgets(value)
211 new_dict[key] = self._pack_widgets(value)
212 return new_dict
212 return new_dict
213 elif isinstance(values, list):
213 elif isinstance(values, list):
214 new_list = []
214 new_list = []
215 for value in values:
215 for value in values:
216 new_list.append(self._pack_widgets(value))
216 new_list.append(self._pack_widgets(value))
217 return new_list
217 return new_list
218 elif isinstance(values, Widget):
218 elif isinstance(values, Widget):
219 return values.model_id
219 return values.model_id
220 else:
220 else:
221 return values
221 return values
222
222
223
223
224 def _unpack_widgets(self, values):
224 def _unpack_widgets(self, values):
225 """This function recursively converts all model id strings to widget
225 """This function recursively converts all model id strings to widget
226 instances.
226 instances.
227
227
228 Children widgets will be stored and transmitted to the front-end by
228 Children widgets will be stored and transmitted to the front-end by
229 their model ids."""
229 their model ids."""
230 if isinstance(values, dict):
230 if isinstance(values, dict):
231 new_dict = {}
231 new_dict = {}
232 for key, values in values.items():
232 for key, values in values.items():
233 new_dict[key] = self._unpack_widgets(values[key])
233 new_dict[key] = self._unpack_widgets(values[key])
234 return new_dict
234 return new_dict
235 elif isinstance(values, list):
235 elif isinstance(values, list):
236 new_list = []
236 new_list = []
237 for value in values:
237 for value in values:
238 new_list.append(self._unpack_widgets(value))
238 new_list.append(self._unpack_widgets(value))
239 return new_list
239 return new_list
240 elif isinstance(values, string_types):
240 elif isinstance(values, string_types):
241 if widget.model_id in Widget.widgets:
241 if widget.model_id in Widget.widgets:
242 return Widget.widgets[widget.model_id]
242 return Widget.widgets[widget.model_id]
243 else:
243 else:
244 return values
244 return values
245 else:
245 else:
246 return values
246 return values
247
247
248
248
249 def send(self, content):
249 def send(self, content):
250 """Sends a custom msg to the widget model in the front-end.
250 """Sends a custom msg to the widget model in the front-end.
251
251
252 Parameters
252 Parameters
253 ----------
253 ----------
254 content : dict
254 content : dict
255 Content of the message to send.
255 Content of the message to send.
256 """
256 """
257 self._send({"method": "custom", "custom_content": content})
257 self._send({"method": "custom", "custom_content": content})
258
258
259
259
260 def on_msg(self, callback, remove=False):
260 def on_msg(self, callback, remove=False):
261 """Register or unregister a callback for when a custom msg is recieved
261 """Register or unregister a callback for when a custom msg is recieved
262 from the front-end.
262 from the front-end.
263
263
264 Parameters
264 Parameters
265 ----------
265 ----------
266 callback: method handler
266 callback: method handler
267 Can have a signature of:
267 Can have a signature of:
268 - callback(content)
268 - callback(content)
269 - callback(sender, content)
269 - callback(sender, content)
270 remove: bool
270 remove: bool
271 True if the callback should be unregistered."""
271 True if the callback should be unregistered."""
272 if remove and callback in self._msg_callbacks:
272 if remove and callback in self._msg_callbacks:
273 self._msg_callbacks.remove(callback)
273 self._msg_callbacks.remove(callback)
274 elif not remove and not callback in self._msg_callbacks:
274 elif not remove and not callback in self._msg_callbacks:
275 if callable(callback):
275 if callable(callback):
276 argspec = inspect.getargspec(callback)
276 argspec = inspect.getargspec(callback)
277 nargs = len(argspec[0])
277 nargs = len(argspec[0])
278
278
279 # Bound methods have an additional 'self' argument
279 # Bound methods have an additional 'self' argument
280 if isinstance(callback, types.MethodType):
280 if isinstance(callback, types.MethodType):
281 nargs -= 1
281 nargs -= 1
282
282
283 # Call the callback
283 # Call the callback
284 if nargs == 1:
284 if nargs == 1:
285 self._msg_callbacks.append(lambda sender, content: callback(content))
285 self._msg_callbacks.append(lambda sender, content: callback(content))
286 elif nargs == 2:
286 elif nargs == 2:
287 self._msg_callbacks.append(callback)
287 self._msg_callbacks.append(callback)
288 else:
288 else:
289 raise TypeError('Widget msg callback must ' \
289 raise TypeError('Widget msg callback must ' \
290 'accept 1 or 2 arguments, not %d.' % nargs)
290 'accept 1 or 2 arguments, not %d.' % nargs)
291 else:
291 else:
292 raise Exception('Callback must be callable.')
292 raise Exception('Callback must be callable.')
293
293
294
294
295 def on_displayed(self, callback, remove=False):
295 def on_displayed(self, callback, remove=False):
296 """Register or unregister a callback to be called when the widget has
296 """Register or unregister a callback to be called when the widget has
297 been displayed.
297 been displayed.
298
298
299 Parameters
299 Parameters
300 ----------
300 ----------
301 callback: method handler
301 callback: method handler
302 Can have a signature of:
302 Can have a signature of:
303 - callback(sender, **kwargs)
303 - callback(sender, **kwargs)
304 kwargs from display call passed through without modification.
304 kwargs from display call passed through without modification.
305 remove: bool
305 remove: bool
306 True if the callback should be unregistered."""
306 True if the callback should be unregistered."""
307 if remove and callback in self._display_callbacks:
307 if remove and callback in self._display_callbacks:
308 self._display_callbacks.remove(callback)
308 self._display_callbacks.remove(callback)
309 elif not remove and not callback in self._display_callbacks:
309 elif not remove and not callback in self._display_callbacks:
310 if callable(handler):
310 if callable(handler):
311 self._display_callbacks.append(callback)
311 self._display_callbacks.append(callback)
312 else:
312 else:
313 raise Exception('Callback must be callable.')
313 raise Exception('Callback must be callable.')
314
314
315
315
316 # Support methods
316 # Support methods
317 def _ipython_display_(self, **kwargs):
317 def _ipython_display_(self, **kwargs):
318 """Function that is called when `IPython.display.display` is called on
318 """Function that is called when `IPython.display.display` is called on
319 the widget."""
319 the widget."""
320
320
321 # Show view. By sending a display message, the comm is opened and the
321 # Show view. By sending a display message, the comm is opened and the
322 # initial state is sent.
322 # initial state is sent.
323 self._send({"method": "display"})
323 self._send({"method": "display"})
324 self._handle_displayed(**kwargs)
324 self._handle_displayed(**kwargs)
325
325
326
326
327 def _send(self, msg):
327 def _send(self, msg):
328 """Sends a message to the model in the front-end"""
328 """Sends a message to the model in the front-end"""
329 self.comm.send(msg)
329 self.comm.send(msg)
330
330
331
331
332 class DOMWidget(Widget):
332 class DOMWidget(Widget):
333 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
333 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
334
334
335 # Private/protected declarations
335 # Private/protected declarations
336 _css = Dict(sync=True) # Internal CSS property dict
336 _css = Dict(sync=True) # Internal CSS property dict
337
337
338 def get_css(self, key, selector=""):
338 def get_css(self, key, selector=""):
339 """Get a CSS property of the widget.
339 """Get a CSS property of the widget.
340
340
341 Note: This function does not actually request the CSS from the
341 Note: This function does not actually request the CSS from the
342 front-end; Only properties that have been set with set_css can be read.
342 front-end; Only properties that have been set with set_css can be read.
343
343
344 Parameters
344 Parameters
345 ----------
345 ----------
346 key: unicode
346 key: unicode
347 CSS key
347 CSS key
348 selector: unicode (optional)
348 selector: unicode (optional)
349 JQuery selector used when the CSS key/value was set.
349 JQuery selector used when the CSS key/value was set.
350 """
350 """
351 if selector in self._css and key in self._css[selector]:
351 if selector in self._css and key in self._css[selector]:
352 return self._css[selector][key]
352 return self._css[selector][key]
353 else:
353 else:
354 return None
354 return None
355
355
356 def set_css(self, *args, **kwargs):
356 def set_css(self, *args, **kwargs):
357 """Set one or more CSS properties of the widget.
357 """Set one or more CSS properties of the widget.
358
358
359 This function has two signatures:
359 This function has two signatures:
360 - set_css(css_dict, selector='')
360 - set_css(css_dict, selector='')
361 - set_css(key, value, selector='')
361 - set_css(key, value, selector='')
362
362
363 Parameters
363 Parameters
364 ----------
364 ----------
365 css_dict : dict
365 css_dict : dict
366 CSS key/value pairs to apply
366 CSS key/value pairs to apply
367 key: unicode
367 key: unicode
368 CSS key
368 CSS key
369 value
369 value
370 CSS value
370 CSS value
371 selector: unicode (optional)
371 selector: unicode (optional)
372 JQuery selector to use to apply the CSS key/value. If no selector
372 JQuery selector to use to apply the CSS key/value. If no selector
373 is provided, an empty selector is used. An empty selector makes the
373 is provided, an empty selector is used. An empty selector makes the
374 front-end try to apply the css to a default element. The default
374 front-end try to apply the css to a default element. The default
375 element is an attribute unique to each view, which is a DOM element
375 element is an attribute unique to each view, which is a DOM element
376 of the view that should be styled with common CSS (see
376 of the view that should be styled with common CSS (see
377 `$el_to_style` in the Javascript code).
377 `$el_to_style` in the Javascript code).
378 """
378 """
379 selector = kwargs.get('selector', '')
379 selector = kwargs.get('selector', '')
380
380
381 # Signature 1: set_css(css_dict, selector='')
381 # Signature 1: set_css(css_dict, selector='')
382 if len(args) == 1:
382 if len(args) == 1:
383 if isinstance(args[0], dict):
383 if isinstance(args[0], dict):
384 for (key, value) in args[0].items():
384 for (key, value) in args[0].items():
385 if not (key in self._css[selector] and value == self._css[selector][key]):
385 if not (key in self._css[selector] and value == self._css[selector][key]):
386 self._css[selector][key] = value
386 self._css[selector][key] = value
387 self.send_state('_css')
387 self.send_state('_css')
388 else:
388 else:
389 raise Exception('css_dict must be a dict.')
389 raise Exception('css_dict must be a dict.')
390
390
391 # Signature 2: set_css(key, value, selector='')
391 # Signature 2: set_css(key, value, selector='')
392 elif len(args) == 2 or len(args) == 3:
392 elif len(args) == 2 or len(args) == 3:
393
393
394 # Selector can be a positional arg if it's the 3rd value
394 # Selector can be a positional arg if it's the 3rd value
395 if len(args) == 3:
395 if len(args) == 3:
396 selector = args[2]
396 selector = args[2]
397 if selector not in self._css:
397 if selector not in self._css:
398 self._css[selector] = {}
398 self._css[selector] = {}
399
399
400 # Only update the property if it has changed.
400 # Only update the property if it has changed.
401 key = args[0]
401 key = args[0]
402 value = args[1]
402 value = args[1]
403 if not (key in self._css[selector] and value == self._css[selector][key]):
403 if not (key in self._css[selector] and value == self._css[selector][key]):
404 self._css[selector][key] = value
404 self._css[selector][key] = value
405 self.send_state('_css') # Send new state to client.
405 self.send_state('_css') # Send new state to client.
406 else:
406 else:
407 raise Exception('set_css only accepts 1-3 arguments')
407 raise Exception('set_css only accepts 1-3 arguments')
408
408
409
409
410 def add_class(self, class_names, selector=""):
410 def add_class(self, class_names, selector=""):
411 """Add class[es] to a DOM element
411 """Add class[es] to a DOM element
412
412
413 Parameters
413 Parameters
414 ----------
414 ----------
415 class_names: unicode or list
415 class_names: unicode or list
416 Class name(s) to add to the DOM element(s).
416 Class name(s) to add to the DOM element(s).
417 selector: unicode (optional)
417 selector: unicode (optional)
418 JQuery selector to select the DOM element(s) that the class(es) will
418 JQuery selector to select the DOM element(s) that the class(es) will
419 be added to.
419 be added to.
420 """
420 """
421 class_list = class_names
421 class_list = class_names
422 if isinstance(class_list, list):
422 if isinstance(class_list, list):
423 class_list = ' '.join(class_list)
423 class_list = ' '.join(class_list)
424
424
425 self.send({"msg_type": "add_class",
425 self.send({"msg_type": "add_class",
426 "class_list": class_list,
426 "class_list": class_list,
427 "selector": selector})
427 "selector": selector})
428
428
429
429
430 def remove_class(self, class_names, selector=""):
430 def remove_class(self, class_names, selector=""):
431 """Remove class[es] from a DOM element
431 """Remove class[es] from a DOM element
432
432
433 Parameters
433 Parameters
434 ----------
434 ----------
435 class_names: unicode or list
435 class_names: unicode or list
436 Class name(s) to remove from the DOM element(s).
436 Class name(s) to remove from the DOM element(s).
437 selector: unicode (optional)
437 selector: unicode (optional)
438 JQuery selector to select the DOM element(s) that the class(es) will
438 JQuery selector to select the DOM element(s) that the class(es) will
439 be removed from.
439 be removed from.
440 """
440 """
441 class_list = class_names
441 class_list = class_names
442 if isinstance(class_list, list):
442 if isinstance(class_list, list):
443 class_list = ' '.join(class_list)
443 class_list = ' '.join(class_list)
444
444
445 self.send({"msg_type": "remove_class",
445 self.send({"msg_type": "remove_class",
446 "class_list": class_list,
446 "class_list": class_list,
447 "selector": selector})
447 "selector": selector})
@@ -1,32 +1,32 b''
1 """BoolWidget class.
1 """BoolWidget class.
2
2
3 Represents a boolean using a widget.
3 Represents a boolean using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, Bool, List
17 from IPython.utils.traitlets import Unicode, Bool, List
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class CheckBoxWidget(DOMWidget):
22 class CheckBoxWidget(DOMWidget):
23 view_name = Unicode('CheckBoxView', sync=True)
23 view_name = Unicode('CheckBoxView', sync=True)
24
24
25 # Model Keys
25 # Model Keys
26 value = Bool(False, help="Bool value", sync=True)
26 value = Bool(False, help="Bool value", sync=True)
27 description = Unicode('', help="Description of the boolean (label).", sync=True)
27 description = Unicode('', help="Description of the boolean (label).", sync=True)
28 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
28 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
29
29
30 class ToggleButtonWidget(CheckboxWidget):
30 class ToggleButtonWidget(CheckBoxWidget):
31 view_name = Unicode('ToggleButtonView', sync=True)
31 view_name = Unicode('ToggleButtonView', sync=True)
32 No newline at end of file
32
@@ -1,41 +1,41 b''
1 """SelectionWidget class.
1 """SelectionWidget class.
2
2
3 Represents an enumeration using a widget.
3 Represents an enumeration using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, List, Bool
17 from IPython.utils.traitlets import Unicode, List, Bool
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # SelectionWidget
20 # SelectionWidget
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class ToggleButtonsWidget(DOMWidget):
22 class ToggleButtonsWidget(DOMWidget):
23 view_name = Unicode('ToggleButtonsView', sync=True)
23 view_name = Unicode('ToggleButtonsView', sync=True)
24
24
25 # Keys
25 # Keys
26 value = Unicode(help="Selected value", sync=True) # TODO: Any support
26 value = Unicode(help="Selected value", sync=True) # TODO: Any support
27 values = List(help="List of values the user can select", sync=True)
27 values = List(help="List of values the user can select", sync=True)
28 disabled = Bool(False, help="Enable or disable user changes", sync=True)
28 disabled = Bool(False, help="Enable or disable user changes", sync=True)
29 description = Unicode(help="Description of the value this widget represents", sync=True)
29 description = Unicode(help="Description of the value this widget represents", sync=True)
30
30
31
31
32 class DropdownWidget(SelectionWidget):
32 class DropdownWidget(ToggleButtonsWidget):
33 view_name = Unicode('DropdownView', sync=True)
33 view_name = Unicode('DropdownView', sync=True)
34
34
35
35
36 class RadioButtonsWidget(SelectionWidget):
36 class RadioButtonsWidget(ToggleButtonsWidget):
37 view_name = Unicode('RadioButtonsView', sync=True)
37 view_name = Unicode('RadioButtonsView', sync=True)
38
38
39
39
40 class ListBoxWidget(SelectionWidget):
40 class ListBoxWidget(ToggleButtonsWidget):
41 view_name = Unicode('ListBoxView', sync=True)
41 view_name = Unicode('ListBoxView', sync=True)
General Comments 0
You need to be logged in to leave comments. Login now