##// END OF EJS Templates
More improvements to the display system....
Brian Granger -
Show More
@@ -0,0 +1,62 b''
1 # -*- coding: utf-8 -*-
2 """Tools for handling LaTeX.
3
4 Authors:
5
6 * Brian Granger
7 """
8 #-----------------------------------------------------------------------------
9 # Copyright (c) 2010, IPython Development Team.
10 #
11 # Distributed under the terms of the Modified BSD License.
12 #
13 # The full license is in the file COPYING.txt, distributed with this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
19
20 from StringIO import StringIO
21 from base64 import encodestring
22
23 #-----------------------------------------------------------------------------
24 # Tools
25 #-----------------------------------------------------------------------------
26
27
28 def latex_to_png(s, encode=True):
29 """Render a LaTeX string to PNG using matplotlib.mathtext.
30
31 Parameters
32 ----------
33 s : str
34 The raw string containing valid inline LaTeX.
35 encode : bool, optional
36 Should the PNG data bebase64 encoded to make it JSON'able.
37 """
38 from matplotlib import mathtext
39
40 mt = mathtext.MathTextParser('bitmap')
41 f = StringIO()
42 mt.to_png(f, s, fontsize=12)
43 bin_data = f.getvalue()
44 if encode:
45 bin_data = encodestring(bin_data)
46 return bin_data
47
48 _data_uri_template_png = """<img src="data:image/png;base64,%s" alt=%s />"""
49
50 def latex_to_html(s, alt='image'):
51 """Render LaTeX to HTML with embedded PNG data using data URIs.
52
53 Parameters
54 ----------
55 s : str
56 The raw string containing valid inline LateX.
57 alt : str
58 The alt text to use for the HTML.
59 """
60 base64_data = latex_to_png(s, encode=True)
61 return _data_uri_template_png % (base64_data, alt)
62
@@ -1,106 +1,117 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) 2008-2010 The IPython Development Team
10 # Copyright (C) 2008-2010 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 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Main functions
21 # Main functions
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 def display(obj, include=None, exclude=None):
24 def display(obj, include=None, exclude=None):
25 """Display a Python object in all frontends.
25 """Display a Python object in all frontends.
26
26
27 By default all representations will be computed and sent to the frontends.
27 By default all representations will be computed and sent to the frontends.
28 Frontends can decide which representation is used and how.
28 Frontends can decide which representation is used and how.
29
29
30 Parameters
30 Parameters
31 ----------
31 ----------
32 obj : object
32 obj : object
33 The Python object to display.
33 The Python object to display.
34 include : list or tuple, optional
34 include : list or tuple, optional
35 A list of format type strings (MIME types) to include in the
35 A list of format type strings (MIME types) to include in the
36 format data dict. If this is set *only* the format types included
36 format data dict. If this is set *only* the format types included
37 in this list will be computed.
37 in this list will be computed.
38 exclude : list or tuple, optional
38 exclude : list or tuple, optional
39 A list of format type string (MIME types) to exclue in the format
39 A list of format type string (MIME types) to exclue in the format
40 data dict. If this is set all format types will be computed,
40 data dict. If this is set all format types will be computed,
41 except for those included in this argument.
41 except for those included in this argument.
42 """
42 """
43 from IPython.core.interactiveshell import InteractiveShell
43 from IPython.core.interactiveshell import InteractiveShell
44 format = InteractiveShell.instance().display_formatter.format
44 format = InteractiveShell.instance().display_formatter.format
45 publish = InteractiveShell.instance().display_pub.publish
45 publish = InteractiveShell.instance().display_pub.publish
46
46
47 format_dict = format(obj, include=include, exclude=exclude)
47 format_dict = format(obj, include=include, exclude=exclude)
48 publish('IPython.core.display.display', format_dict)
48 publish('IPython.core.display.display', format_dict)
49
49
50
50
51 def display_pretty(obj):
52 """Display the pretty (default) representation of an object.
53
54 Parameters
55 ----------
56 obj : object
57 The Python object to display.
58 """
59 display(obj, include=['text/plain'])
60
61
51 def display_html(obj):
62 def display_html(obj):
52 """Display the HTML representation of an object.
63 """Display the HTML representation of an object.
53
64
54 Parameters
65 Parameters
55 ----------
66 ----------
56 obj : object
67 obj : object
57 The Python object to display.
68 The Python object to display.
58 """
69 """
59 display(obj, include=['text/plain','text/html'])
70 display(obj, include=['text/plain','text/html'])
60
71
61
72
62 def display_svg(obj):
73 def display_svg(obj):
63 """Display the SVG representation of an object.
74 """Display the SVG representation of an object.
64
75
65 Parameters
76 Parameters
66 ----------
77 ----------
67 obj : object
78 obj : object
68 The Python object to display.
79 The Python object to display.
69 """
80 """
70 display(obj, include=['text/plain','image/svg+xml'])
81 display(obj, include=['text/plain','image/svg+xml'])
71
82
72
83
73 def display_png(obj):
84 def display_png(obj):
74 """Display the PNG representation of an object.
85 """Display the PNG representation of an object.
75
86
76 Parameters
87 Parameters
77 ----------
88 ----------
78 obj : object
89 obj : object
79 The Python object to display.
90 The Python object to display.
80 """
91 """
81 display(obj, include=['text/plain','image/png'])
92 display(obj, include=['text/plain','image/png'])
82
93
83
94
84 def display_latex(obj):
95 def display_latex(obj):
85 """Display the LaTeX representation of an object.
96 """Display the LaTeX representation of an object.
86
97
87 Parameters
98 Parameters
88 ----------
99 ----------
89 obj : object
100 obj : object
90 The Python object to display.
101 The Python object to display.
91 """
102 """
92 display(obj, include=['text/plain','text/latex'])
103 display(obj, include=['text/plain','text/latex'])
93
104
94
105
95 def display_json(obj):
106 def display_json(obj):
96 """Display the JSON representation of an object.
107 """Display the JSON representation of an object.
97
108
98 Parameters
109 Parameters
99 ----------
110 ----------
100 obj : object
111 obj : object
101 The Python object to display.
112 The Python object to display.
102 """
113 """
103 display(obj, include=['text/plain','application/json'])
114 display(obj, include=['text/plain','application/json'])
104
115
105
116
106
117
@@ -1,453 +1,474 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Display formatters.
2 """Display formatters.
3
3
4
4
5 Authors:
5 Authors:
6
6
7 * Robert Kern
7 * Robert Kern
8 * Brian Granger
8 * Brian Granger
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (c) 2010, IPython Development Team.
11 # Copyright (c) 2010, IPython Development Team.
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
19 # Imports
20 #-----------------------------------------------------------------------------
21
18 # Stdlib imports
22 # Stdlib imports
19 import abc
23 import abc
20 # We must use StringIO, as cStringIO doesn't handle unicode properly.
24 # We must use StringIO, as cStringIO doesn't handle unicode properly.
21 from StringIO import StringIO
25 from StringIO import StringIO
22
26
23 # Our own imports
27 # Our own imports
24 from IPython.config.configurable import Configurable
28 from IPython.config.configurable import Configurable
25 from IPython.external import pretty
29 from IPython.external import pretty
26 from IPython.utils.traitlets import Bool, Dict, Int, Str
30 from IPython.utils.traitlets import Bool, Dict, Int, Str
27
31
28
32
29 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
30 # The main DisplayFormatter class
34 # The main DisplayFormatter class
31 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
32
36
33
37
34 class DisplayFormatter(Configurable):
38 class DisplayFormatter(Configurable):
35
39
36 # A dict of formatter whose keys are format types (MIME types) and whose
40 # A dict of formatter whose keys are format types (MIME types) and whose
37 # values are subclasses of BaseFormatter.
41 # values are subclasses of BaseFormatter.
38 formatters = Dict(config=True)
42 formatters = Dict(config=True)
39 def _formatters_default(self):
43 def _formatters_default(self):
40 """Activate the default formatters."""
44 """Activate the default formatters."""
41 formatter_classes = [
45 formatter_classes = [
42 PlainTextFormatter,
46 PlainTextFormatter,
43 HTMLFormatter,
47 HTMLFormatter,
44 SVGFormatter,
48 SVGFormatter,
45 PNGFormatter,
49 PNGFormatter,
46 LatexFormatter,
50 LatexFormatter,
47 JSONFormatter
51 JSONFormatter
48 ]
52 ]
49 d = {}
53 d = {}
50 for cls in formatter_classes:
54 for cls in formatter_classes:
51 f = cls(config=self.config)
55 f = cls(config=self.config)
52 d[f.format_type] = f
56 d[f.format_type] = f
53 return d
57 return d
54
58
55 def format(self, obj, include=None, exclude=None):
59 def format(self, obj, include=None, exclude=None):
56 """Return a format data dict for an object.
60 """Return a format data dict for an object.
57
61
58 By default all format types will be computed.
62 By default all format types will be computed.
59
63
60 The following MIME types are currently implemented:
64 The following MIME types are currently implemented:
61
65
62 * text/plain
66 * text/plain
63 * text/html
67 * text/html
64 * text/latex
68 * text/latex
65 * application/json
69 * application/json
66 * image/png
70 * image/png
67 * immage/svg+xml
71 * immage/svg+xml
68
72
69 Parameters
73 Parameters
70 ----------
74 ----------
71 obj : object
75 obj : object
72 The Python object whose format data will be computed.
76 The Python object whose format data will be computed.
73
77
74 Returns
78 Returns
75 -------
79 -------
76 format_dict : dict
80 format_dict : dict
77 A dictionary of key/value pairs, one or each format that was
81 A dictionary of key/value pairs, one or each format that was
78 generated for the object. The keys are the format types, which
82 generated for the object. The keys are the format types, which
79 will usually be MIME type strings and the values and JSON'able
83 will usually be MIME type strings and the values and JSON'able
80 data structure containing the raw data for the representation in
84 data structure containing the raw data for the representation in
81 that format.
85 that format.
82 include : list or tuple, optional
86 include : list or tuple, optional
83 A list of format type strings (MIME types) to include in the
87 A list of format type strings (MIME types) to include in the
84 format data dict. If this is set *only* the format types included
88 format data dict. If this is set *only* the format types included
85 in this list will be computed.
89 in this list will be computed.
86 exclude : list or tuple, optional
90 exclude : list or tuple, optional
87 A list of format type string (MIME types) to exclue in the format
91 A list of format type string (MIME types) to exclue in the format
88 data dict. If this is set all format types will be computed,
92 data dict. If this is set all format types will be computed,
89 except for those included in this argument.
93 except for those included in this argument.
90 """
94 """
91 format_dict = {}
95 format_dict = {}
92 for format_type, formatter in self.formatters.items():
96 for format_type, formatter in self.formatters.items():
93 if include is not None:
97 if include is not None:
94 if format_type not in include:
98 if format_type not in include:
95 continue
99 continue
96 if exclude is not None:
100 if exclude is not None:
97 if format_type in exclude:
101 if format_type in exclude:
98 continue
102 continue
99 try:
103 try:
100 data = formatter(obj)
104 data = formatter(obj)
101 except:
105 except:
102 # FIXME: log the exception
106 # FIXME: log the exception
103 raise
107 raise
104 if data is not None:
108 if data is not None:
105 format_dict[format_type] = data
109 format_dict[format_type] = data
106 return format_dict
110 return format_dict
107
111
108 @property
112 @property
109 def format_types(self):
113 def format_types(self):
110 """Return the format types (MIME types) of the active formatters."""
114 """Return the format types (MIME types) of the active formatters."""
111 return self.formatters.keys()
115 return self.formatters.keys()
112
116
113
117
114 #-----------------------------------------------------------------------------
118 #-----------------------------------------------------------------------------
115 # Formatters for specific format types (text, html, svg, etc.)
119 # Formatters for specific format types (text, html, svg, etc.)
116 #-----------------------------------------------------------------------------
120 #-----------------------------------------------------------------------------
117
121
118
122
119 class FormatterABC(object):
123 class FormatterABC(object):
120 """ Abstract base class for Formatters.
124 """ Abstract base class for Formatters.
121
125
122 A formatter is a callable class that is responsible for computing the
126 A formatter is a callable class that is responsible for computing the
123 raw format data for a particular format type (MIME type). For example,
127 raw format data for a particular format type (MIME type). For example,
124 an HTML formatter would have a format type of `text/html` and would return
128 an HTML formatter would have a format type of `text/html` and would return
125 the HTML representation of the object when called.
129 the HTML representation of the object when called.
126 """
130 """
127 __metaclass__ = abc.ABCMeta
131 __metaclass__ = abc.ABCMeta
128
132
129 # The format type of the data returned, usually a MIME type.
133 # The format type of the data returned, usually a MIME type.
130 format_type = 'text/plain'
134 format_type = 'text/plain'
131
135
132 @abc.abstractmethod
136 @abc.abstractmethod
133 def __call__(self, obj):
137 def __call__(self, obj):
134 """Return a JSON'able representation of the object.
138 """Return a JSON'able representation of the object.
135
139
136 If the object cannot be formatted by this formatter, then return None
140 If the object cannot be formatted by this formatter, then return None
137 """
141 """
138 try:
142 try:
139 return repr(obj)
143 return repr(obj)
140 except TypeError:
144 except TypeError:
141 return None
145 return None
142
146
143
147
144 class BaseFormatter(Configurable):
148 class BaseFormatter(Configurable):
145 """A base formatter class that is configurable.
149 """A base formatter class that is configurable.
146
150
147 This formatter should usually be used as the base class of all formatters.
151 This formatter should usually be used as the base class of all formatters.
148 It is a traited :class:`Configurable` class and includes an extensible
152 It is a traited :class:`Configurable` class and includes an extensible
149 API for users to determine how their objects are formatted. The following
153 API for users to determine how their objects are formatted. The following
150 logic is used to find a function to format an given object.
154 logic is used to find a function to format an given object.
151
155
152 1. The object is introspected to see if it has a method with the name
156 1. The object is introspected to see if it has a method with the name
153 :attr:`print_method`. If is does, that object is passed to that method
157 :attr:`print_method`. If is does, that object is passed to that method
154 for formatting.
158 for formatting.
155 2. If no print method is found, three internal dictionaries are consulted
159 2. If no print method is found, three internal dictionaries are consulted
156 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
160 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
157 and :attr:`deferred_printers`.
161 and :attr:`deferred_printers`.
158
162
159 Users should use these dictionarie to register functions that will be used
163 Users should use these dictionarie to register functions that will be used
160 to compute the format data for their objects (if those objects don't have
164 to compute the format data for their objects (if those objects don't have
161 the special print methods). The easiest way of using these dictionaries
165 the special print methods). The easiest way of using these dictionaries
162 is through the :meth:`for_type` and :meth:`for_type_by_name` methods.
166 is through the :meth:`for_type` and :meth:`for_type_by_name` methods.
163
167
164 If no function/callable is found to compute the format data, ``None`` is
168 If no function/callable is found to compute the format data, ``None`` is
165 returned and this format type is not used.
169 returned and this format type is not used.
166 """
170 """
167
171
168 format_type = Str('text/plain')
172 format_type = Str('text/plain')
169
173
170 print_method = Str('__repr__')
174 print_method = Str('__repr__')
171
175
172 # The singleton printers.
176 # The singleton printers.
173 # Maps the IDs of the builtin singleton objects to the format functions.
177 # Maps the IDs of the builtin singleton objects to the format functions.
174 singleton_printers = Dict(config=True)
178 singleton_printers = Dict(config=True)
175 def _singleton_printers_default(self):
179 def _singleton_printers_default(self):
176 return {}
180 return {}
177
181
178 # The type-specific printers.
182 # The type-specific printers.
179 # Map type objects to the format functions.
183 # Map type objects to the format functions.
180 type_printers = Dict(config=True)
184 type_printers = Dict(config=True)
181 def _type_printers_default(self):
185 def _type_printers_default(self):
182 return {}
186 return {}
183
187
184 # The deferred-import type-specific printers.
188 # The deferred-import type-specific printers.
185 # Map (modulename, classname) pairs to the format functions.
189 # Map (modulename, classname) pairs to the format functions.
186 deferred_printers = Dict(config=True)
190 deferred_printers = Dict(config=True)
187 def _deferred_printers_default(self):
191 def _deferred_printers_default(self):
188 return {}
192 return {}
189
193
190 def __call__(self, obj):
194 def __call__(self, obj):
191 """Compute the format for an object."""
195 """Compute the format for an object."""
192 obj_id = id(obj)
196 obj_id = id(obj)
193 try:
197 try:
194 obj_class = getattr(obj, '__class__', None) or type(obj)
198 obj_class = getattr(obj, '__class__', None) or type(obj)
195 if hasattr(obj_class, self.print_method):
199 if hasattr(obj_class, self.print_method):
196 printer = getattr(obj_class, self.print_method)
200 printer = getattr(obj_class, self.print_method)
197 return printer(obj)
201 return printer(obj)
198 try:
202 try:
199 printer = self.singleton_printers[obj_id]
203 printer = self.singleton_printers[obj_id]
200 except (TypeError, KeyError):
204 except (TypeError, KeyError):
201 pass
205 pass
202 else:
206 else:
203 return printer(obj)
207 return printer(obj)
204 for cls in pretty._get_mro(obj_class):
208 for cls in pretty._get_mro(obj_class):
205 if cls in self.type_printers:
209 if cls in self.type_printers:
206 return self.type_printers[cls](obj)
210 return self.type_printers[cls](obj)
207 else:
211 else:
208 printer = self._in_deferred_types(cls)
212 printer = self._in_deferred_types(cls)
209 if printer is not None:
213 if printer is not None:
210 return printer(obj)
214 return printer(obj)
211 return None
215 return None
212 except Exception:
216 except Exception:
213 pass
217 pass
214
218
215 def for_type(self, typ, func):
219 def for_type(self, typ, func):
216 """Add a format function for a given type.
220 """Add a format function for a given type.
217
221
218 Parameteres
222 Parameteres
219 -----------
223 -----------
220 typ : class
224 typ : class
221 The class of the object that will be formatted using `func`.
225 The class of the object that will be formatted using `func`.
222 func : callable
226 func : callable
223 The callable that will be called to compute the format data. The
227 The callable that will be called to compute the format data. The
224 call signature of this function is simple, it must take the
228 call signature of this function is simple, it must take the
225 object to be formatted and return the raw data for the given
229 object to be formatted and return the raw data for the given
226 format. Subclasses may use a different call signature for the
230 format. Subclasses may use a different call signature for the
227 `func` argument.
231 `func` argument.
228 """
232 """
229 oldfunc = self.type_printers.get(typ, None)
233 oldfunc = self.type_printers.get(typ, None)
230 if func is not None:
234 if func is not None:
231 # To support easy restoration of old printers, we need to ignore
235 # To support easy restoration of old printers, we need to ignore
232 # Nones.
236 # Nones.
233 self.type_printers[typ] = func
237 self.type_printers[typ] = func
234 return oldfunc
238 return oldfunc
235
239
236 def for_type_by_name(self, type_module, type_name, func):
240 def for_type_by_name(self, type_module, type_name, func):
237 """Add a format function for a type specified by the full dotted
241 """Add a format function for a type specified by the full dotted
238 module and name of the type, rather than the type of the object.
242 module and name of the type, rather than the type of the object.
239
243
240 Parameters
244 Parameters
241 ----------
245 ----------
242 type_module : str
246 type_module : str
243 The full dotted name of the module the type is defined in, like
247 The full dotted name of the module the type is defined in, like
244 ``numpy``.
248 ``numpy``.
245 type_name : str
249 type_name : str
246 The name of the type (the class name), like ``dtype``
250 The name of the type (the class name), like ``dtype``
247 func : callable
251 func : callable
248 The callable that will be called to compute the format data. The
252 The callable that will be called to compute the format data. The
249 call signature of this function is simple, it must take the
253 call signature of this function is simple, it must take the
250 object to be formatted and return the raw data for the given
254 object to be formatted and return the raw data for the given
251 format. Subclasses may use a different call signature for the
255 format. Subclasses may use a different call signature for the
252 `func` argument.
256 `func` argument.
253 """
257 """
254 key = (type_module, type_name)
258 key = (type_module, type_name)
255 oldfunc = self.deferred_printers.get(key, None)
259 oldfunc = self.deferred_printers.get(key, None)
256 if func is not None:
260 if func is not None:
257 # To support easy restoration of old printers, we need to ignore
261 # To support easy restoration of old printers, we need to ignore
258 # Nones.
262 # Nones.
259 self.deferred_printers[key] = func
263 self.deferred_printers[key] = func
260 return oldfunc
264 return oldfunc
261
265
266 def _in_deferred_types(self, cls):
267 """
268 Check if the given class is specified in the deferred type registry.
269
270 Returns the printer from the registry if it exists, and None if the
271 class is not in the registry. Successful matches will be moved to the
272 regular type registry for future use.
273 """
274 mod = getattr(cls, '__module__', None)
275 name = getattr(cls, '__name__', None)
276 key = (mod, name)
277 printer = None
278 if key in self.deferred_printers:
279 # Move the printer over to the regular registry.
280 printer = self.deferred_printers.pop(key)
281 self.type_printers[cls] = printer
282 return printer
262
283
263 class PlainTextFormatter(BaseFormatter):
284 class PlainTextFormatter(BaseFormatter):
264 """The default pretty-printer.
285 """The default pretty-printer.
265
286
266 This uses :mod:`IPython.external.pretty` to compute the format data of
287 This uses :mod:`IPython.external.pretty` to compute the format data of
267 the object. If the object cannot be pretty printed, :func:`repr` is used.
288 the object. If the object cannot be pretty printed, :func:`repr` is used.
268 See the documentation of :mod:`IPython.external.pretty` for details on
289 See the documentation of :mod:`IPython.external.pretty` for details on
269 how to write pretty printers. Here is a simple example::
290 how to write pretty printers. Here is a simple example::
270
291
271 def dtype_pprinter(obj, p, cycle):
292 def dtype_pprinter(obj, p, cycle):
272 if cycle:
293 if cycle:
273 return p.text('dtype(...)')
294 return p.text('dtype(...)')
274 if hasattr(obj, 'fields'):
295 if hasattr(obj, 'fields'):
275 if obj.fields is None:
296 if obj.fields is None:
276 p.text(repr(obj))
297 p.text(repr(obj))
277 else:
298 else:
278 p.begin_group(7, 'dtype([')
299 p.begin_group(7, 'dtype([')
279 for i, field in enumerate(obj.descr):
300 for i, field in enumerate(obj.descr):
280 if i > 0:
301 if i > 0:
281 p.text(',')
302 p.text(',')
282 p.breakable()
303 p.breakable()
283 p.pretty(field)
304 p.pretty(field)
284 p.end_group(7, '])')
305 p.end_group(7, '])')
285 """
306 """
286
307
287 # The format type of data returned.
308 # The format type of data returned.
288 format_type = Str('text/plain')
309 format_type = Str('text/plain')
289
310
290 # Look for a __pretty__ methods to use for pretty printing.
311 # Look for a __pretty__ methods to use for pretty printing.
291 print_method = Str('__pretty__')
312 print_method = Str('__pretty__')
292
313
293 # Whether to pretty-print or not.
314 # Whether to pretty-print or not.
294 pprint = Bool(True, config=True)
315 pprint = Bool(True, config=True)
295
316
296 # Whether to be verbose or not.
317 # Whether to be verbose or not.
297 verbose = Bool(False, config=True)
318 verbose = Bool(False, config=True)
298
319
299 # The maximum width.
320 # The maximum width.
300 max_width = Int(79, config=True)
321 max_width = Int(79, config=True)
301
322
302 # The newline character.
323 # The newline character.
303 newline = Str('\n', config=True)
324 newline = Str('\n', config=True)
304
325
305 # Use the default pretty printers from IPython.external.pretty.
326 # Use the default pretty printers from IPython.external.pretty.
306 def _singleton_printers_default(self):
327 def _singleton_printers_default(self):
307 return pretty._singleton_pprinters.copy()
328 return pretty._singleton_pprinters.copy()
308
329
309 def _type_printers_default(self):
330 def _type_printers_default(self):
310 return pretty._type_pprinters.copy()
331 return pretty._type_pprinters.copy()
311
332
312 def _deferred_printers_default(self):
333 def _deferred_printers_default(self):
313 return pretty._deferred_type_pprinters.copy()
334 return pretty._deferred_type_pprinters.copy()
314
335
315 #### FormatterABC interface ####
336 #### FormatterABC interface ####
316
337
317 def __call__(self, obj):
338 def __call__(self, obj):
318 """Compute the pretty representation of the object."""
339 """Compute the pretty representation of the object."""
319 if not self.pprint:
340 if not self.pprint:
320 try:
341 try:
321 return repr(obj)
342 return repr(obj)
322 except TypeError:
343 except TypeError:
323 return ''
344 return ''
324 else:
345 else:
325 # This uses use StringIO, as cStringIO doesn't handle unicode.
346 # This uses use StringIO, as cStringIO doesn't handle unicode.
326 stream = StringIO()
347 stream = StringIO()
327 printer = pretty.RepresentationPrinter(stream, self.verbose,
348 printer = pretty.RepresentationPrinter(stream, self.verbose,
328 self.max_width, self.newline,
349 self.max_width, self.newline,
329 singleton_pprinters=self.singleton_printers,
350 singleton_pprinters=self.singleton_printers,
330 type_pprinters=self.type_printers,
351 type_pprinters=self.type_printers,
331 deferred_pprinters=self.deferred_printers)
352 deferred_pprinters=self.deferred_printers)
332 printer.pretty(obj)
353 printer.pretty(obj)
333 printer.flush()
354 printer.flush()
334 return stream.getvalue()
355 return stream.getvalue()
335
356
336
357
337 class HTMLFormatter(BaseFormatter):
358 class HTMLFormatter(BaseFormatter):
338 """An HTML formatter.
359 """An HTML formatter.
339
360
340 To define the callables that compute the HTML representation of your
361 To define the callables that compute the HTML representation of your
341 objects, define a :meth:`__html__` method or use the :meth:`for_type`
362 objects, define a :meth:`__html__` method or use the :meth:`for_type`
342 or :meth:`for_type_by_name` methods to register functions that handle
363 or :meth:`for_type_by_name` methods to register functions that handle
343 this.
364 this.
344 """
365 """
345 format_type = Str('text/html')
366 format_type = Str('text/html')
346
367
347 print_method = Str('__html__')
368 print_method = Str('__html__')
348
369
349
370
350 class SVGFormatter(BaseFormatter):
371 class SVGFormatter(BaseFormatter):
351 """An SVG formatter.
372 """An SVG formatter.
352
373
353 To define the callables that compute the SVG representation of your
374 To define the callables that compute the SVG representation of your
354 objects, define a :meth:`__svg__` method or use the :meth:`for_type`
375 objects, define a :meth:`__svg__` method or use the :meth:`for_type`
355 or :meth:`for_type_by_name` methods to register functions that handle
376 or :meth:`for_type_by_name` methods to register functions that handle
356 this.
377 this.
357 """
378 """
358 format_type = Str('image/svg+xml')
379 format_type = Str('image/svg+xml')
359
380
360 print_method = Str('__svg__')
381 print_method = Str('__svg__')
361
382
362
383
363 class PNGFormatter(BaseFormatter):
384 class PNGFormatter(BaseFormatter):
364 """A PNG formatter.
385 """A PNG formatter.
365
386
366 To define the callables that compute the PNG representation of your
387 To define the callables that compute the PNG representation of your
367 objects, define a :meth:`__svg__` method or use the :meth:`for_type`
388 objects, define a :meth:`__svg__` method or use the :meth:`for_type`
368 or :meth:`for_type_by_name` methods to register functions that handle
389 or :meth:`for_type_by_name` methods to register functions that handle
369 this. The raw data should be the base64 encoded raw png data.
390 this. The raw data should be the base64 encoded raw png data.
370 """
391 """
371 format_type = Str('image/png')
392 format_type = Str('image/png')
372
393
373 print_method = Str('__png__')
394 print_method = Str('__png__')
374
395
375
396
376 class LatexFormatter(BaseFormatter):
397 class LatexFormatter(BaseFormatter):
377 """A LaTeX formatter.
398 """A LaTeX formatter.
378
399
379 To define the callables that compute the LaTeX representation of your
400 To define the callables that compute the LaTeX representation of your
380 objects, define a :meth:`__latex__` method or use the :meth:`for_type`
401 objects, define a :meth:`__latex__` method or use the :meth:`for_type`
381 or :meth:`for_type_by_name` methods to register functions that handle
402 or :meth:`for_type_by_name` methods to register functions that handle
382 this.
403 this.
383 """
404 """
384 format_type = Str('text/latex')
405 format_type = Str('text/latex')
385
406
386 print_method = Str('__latex__')
407 print_method = Str('__latex__')
387
408
388
409
389 class JSONFormatter(BaseFormatter):
410 class JSONFormatter(BaseFormatter):
390 """A JSON string formatter.
411 """A JSON string formatter.
391
412
392 To define the callables that compute the JSON string representation of
413 To define the callables that compute the JSON string representation of
393 your objects, define a :meth:`__json__` method or use the :meth:`for_type`
414 your objects, define a :meth:`__json__` method or use the :meth:`for_type`
394 or :meth:`for_type_by_name` methods to register functions that handle
415 or :meth:`for_type_by_name` methods to register functions that handle
395 this.
416 this.
396 """
417 """
397 format_type = Str('application/json')
418 format_type = Str('application/json')
398
419
399 print_method = Str('__json__')
420 print_method = Str('__json__')
400
421
401
422
402 FormatterABC.register(BaseFormatter)
423 FormatterABC.register(BaseFormatter)
403 FormatterABC.register(PlainTextFormatter)
424 FormatterABC.register(PlainTextFormatter)
404 FormatterABC.register(HTMLFormatter)
425 FormatterABC.register(HTMLFormatter)
405 FormatterABC.register(SVGFormatter)
426 FormatterABC.register(SVGFormatter)
406 FormatterABC.register(PNGFormatter)
427 FormatterABC.register(PNGFormatter)
407 FormatterABC.register(LatexFormatter)
428 FormatterABC.register(LatexFormatter)
408 FormatterABC.register(JSONFormatter)
429 FormatterABC.register(JSONFormatter)
409
430
410
431
411 def format_display_data(obj, include=None, exclude=None):
432 def format_display_data(obj, include=None, exclude=None):
412 """Return a format data dict for an object.
433 """Return a format data dict for an object.
413
434
414 By default all format types will be computed.
435 By default all format types will be computed.
415
436
416 The following MIME types are currently implemented:
437 The following MIME types are currently implemented:
417
438
418 * text/plain
439 * text/plain
419 * text/html
440 * text/html
420 * text/latex
441 * text/latex
421 * application/json
442 * application/json
422 * image/png
443 * image/png
423 * immage/svg+xml
444 * immage/svg+xml
424
445
425 Parameters
446 Parameters
426 ----------
447 ----------
427 obj : object
448 obj : object
428 The Python object whose format data will be computed.
449 The Python object whose format data will be computed.
429
450
430 Returns
451 Returns
431 -------
452 -------
432 format_dict : dict
453 format_dict : dict
433 A dictionary of key/value pairs, one or each format that was
454 A dictionary of key/value pairs, one or each format that was
434 generated for the object. The keys are the format types, which
455 generated for the object. The keys are the format types, which
435 will usually be MIME type strings and the values and JSON'able
456 will usually be MIME type strings and the values and JSON'able
436 data structure containing the raw data for the representation in
457 data structure containing the raw data for the representation in
437 that format.
458 that format.
438 include : list or tuple, optional
459 include : list or tuple, optional
439 A list of format type strings (MIME types) to include in the
460 A list of format type strings (MIME types) to include in the
440 format data dict. If this is set *only* the format types included
461 format data dict. If this is set *only* the format types included
441 in this list will be computed.
462 in this list will be computed.
442 exclude : list or tuple, optional
463 exclude : list or tuple, optional
443 A list of format type string (MIME types) to exclue in the format
464 A list of format type string (MIME types) to exclue in the format
444 data dict. If this is set all format types will be computed,
465 data dict. If this is set all format types will be computed,
445 except for those included in this argument.
466 except for those included in this argument.
446 """
467 """
447 from IPython.core.interactiveshell import InteractiveShell
468 from IPython.core.interactiveshell import InteractiveShell
448
469
449 InteractiveShell.instance().display_formatter.format(
470 InteractiveShell.instance().display_formatter.format(
450 obj,
471 obj,
451 include,
472 include,
452 exclude
473 exclude
453 )
474 )
@@ -1,45 +1,57 b''
1 """A print function that pretty prints sympy Basic objects.
1 """A print function that pretty prints sympy Basic objects.
2
2
3 Authors:
3 Authors:
4 * Brian Granger
4 * Brian Granger
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Copyright (C) 2008-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 from sympy import pretty
17 from IPython.lib.latextools import latex_to_png
18
19 from sympy import pretty, latex
18
20
19 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
20 # Definitions of magic functions for use with IPython
22 # Definitions of magic functions for use with IPython
21 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
22
24
23 def print_basic_unicode(o, p, cycle):
25 def print_basic_unicode(o, p, cycle):
24 """A function to pretty print sympy Basic objects."""
26 """A function to pretty print sympy Basic objects."""
25 if cycle:
27 if cycle:
26 return p.text('Basic(...)')
28 return p.text('Basic(...)')
27 out = pretty(o, use_unicode=True)
29 out = pretty(o, use_unicode=True)
28 if '\n' in out:
30 if '\n' in out:
29 p.text(u'\n')
31 p.text(u'\n')
30 p.text(out)
32 p.text(out)
31
33
32
34
35 def print_png(o):
36 """A funciton to display sympy expression using LaTex -> PNG."""
37 s = latex(o)
38 png = latex_to_png(s, encode=True)
39 return png
40
33 _loaded = False
41 _loaded = False
34
42
35
43
36 def load_ipython_extension(ip):
44 def load_ipython_extension(ip):
37 """Load the extension in IPython."""
45 """Load the extension in IPython."""
38 global _loaded
46 global _loaded
39 if not _loaded:
47 if not _loaded:
40 plaintext_formatter = ip.display_formatter.formatters['text/plain']
48 plaintext_formatter = ip.display_formatter.formatters['text/plain']
41 plaintext_formatter.for_type_by_name(
49 plaintext_formatter.for_type_by_name(
42 'sympy.core.basic', 'Basic', print_basic_unicode
50 'sympy.core.basic', 'Basic', print_basic_unicode
43 )
51 )
52 png_formatter = ip.display_formatter.formatters['image/png']
53 png_formatter.for_type_by_name(
54 'sympy.core.basic', 'Basic', print_png
55 )
44 _loaded = True
56 _loaded = True
45
57
@@ -1,495 +1,497 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 # Standard library imports
13 # Standard library imports
14 from collections import namedtuple
14 from collections import namedtuple
15 import re
15 import re
16 from subprocess import Popen
16 from subprocess import Popen
17 from textwrap import dedent
17 from textwrap import dedent
18
18
19 # System library imports
19 # System library imports
20 from PyQt4 import QtCore, QtGui
20 from PyQt4 import QtCore, QtGui
21
21
22 # Local imports
22 # Local imports
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
24 transform_ipy_prompt
24 transform_ipy_prompt
25 from IPython.core.usage import default_gui_banner
25 from IPython.core.usage import default_gui_banner
26 from IPython.utils.traitlets import Bool, Str
26 from IPython.utils.traitlets import Bool, Str
27 from frontend_widget import FrontendWidget
27 from frontend_widget import FrontendWidget
28 from styles import (default_light_style_sheet, default_light_syntax_style,
28 from styles import (default_light_style_sheet, default_light_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
30 default_bw_style_sheet, default_bw_syntax_style)
30 default_bw_style_sheet, default_bw_syntax_style)
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Constants
33 # Constants
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 # Default strings to build and display input and output prompts (and separators
36 # Default strings to build and display input and output prompts (and separators
37 # in between)
37 # in between)
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
40 default_input_sep = '\n'
40 default_input_sep = '\n'
41 default_output_sep = ''
41 default_output_sep = ''
42 default_output_sep2 = ''
42 default_output_sep2 = ''
43
43
44 # Base path for most payload sources.
44 # Base path for most payload sources.
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # IPythonWidget class
48 # IPythonWidget class
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 class IPythonWidget(FrontendWidget):
51 class IPythonWidget(FrontendWidget):
52 """ A FrontendWidget for an IPython kernel.
52 """ A FrontendWidget for an IPython kernel.
53 """
53 """
54
54
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # settings.
57 # settings.
58 custom_edit = Bool(False)
58 custom_edit = Bool(False)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
60
60
61 # A command for invoking a system text editor. If the string contains a
61 # A command for invoking a system text editor. If the string contains a
62 # {filename} format specifier, it will be used. Otherwise, the filename will
62 # {filename} format specifier, it will be used. Otherwise, the filename will
63 # be appended to the end the command.
63 # be appended to the end the command.
64 editor = Str('default', config=True)
64 editor = Str('default', config=True)
65
65
66 # The editor command to use when a specific line number is requested. The
66 # The editor command to use when a specific line number is requested. The
67 # string should contain two format specifiers: {line} and {filename}. If
67 # string should contain two format specifiers: {line} and {filename}. If
68 # this parameter is not specified, the line number option to the %edit magic
68 # this parameter is not specified, the line number option to the %edit magic
69 # will be ignored.
69 # will be ignored.
70 editor_line = Str(config=True)
70 editor_line = Str(config=True)
71
71
72 # A CSS stylesheet. The stylesheet can contain classes for:
72 # A CSS stylesheet. The stylesheet can contain classes for:
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
76 style_sheet = Str(config=True)
76 style_sheet = Str(config=True)
77
77
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
79 # the style sheet is queried for Pygments style information.
79 # the style sheet is queried for Pygments style information.
80 syntax_style = Str(config=True)
80 syntax_style = Str(config=True)
81
81
82 # Prompts.
82 # Prompts.
83 in_prompt = Str(default_in_prompt, config=True)
83 in_prompt = Str(default_in_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
85 input_sep = Str(default_input_sep, config=True)
85 input_sep = Str(default_input_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
88
88
89 # FrontendWidget protected class variables.
89 # FrontendWidget protected class variables.
90 _input_splitter_class = IPythonInputSplitter
90 _input_splitter_class = IPythonInputSplitter
91
91
92 # IPythonWidget protected class variables.
92 # IPythonWidget protected class variables.
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
97 _payload_source_page = 'IPython.zmq.page.page'
97 _payload_source_page = 'IPython.zmq.page.page'
98
98
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100 # 'object' interface
100 # 'object' interface
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102
102
103 def __init__(self, *args, **kw):
103 def __init__(self, *args, **kw):
104 super(IPythonWidget, self).__init__(*args, **kw)
104 super(IPythonWidget, self).__init__(*args, **kw)
105
105
106 # IPythonWidget protected variables.
106 # IPythonWidget protected variables.
107 self._code_to_load = None
107 self._code_to_load = None
108 self._payload_handlers = {
108 self._payload_handlers = {
109 self._payload_source_edit : self._handle_payload_edit,
109 self._payload_source_edit : self._handle_payload_edit,
110 self._payload_source_exit : self._handle_payload_exit,
110 self._payload_source_exit : self._handle_payload_exit,
111 self._payload_source_page : self._handle_payload_page,
111 self._payload_source_page : self._handle_payload_page,
112 self._payload_source_loadpy : self._handle_payload_loadpy }
112 self._payload_source_loadpy : self._handle_payload_loadpy }
113 self._previous_prompt_obj = None
113 self._previous_prompt_obj = None
114 self._keep_kernel_on_exit = None
114 self._keep_kernel_on_exit = None
115
115
116 # Initialize widget styling.
116 # Initialize widget styling.
117 if self.style_sheet:
117 if self.style_sheet:
118 self._style_sheet_changed()
118 self._style_sheet_changed()
119 self._syntax_style_changed()
119 self._syntax_style_changed()
120 else:
120 else:
121 self.set_default_style()
121 self.set_default_style()
122
122
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124 # 'BaseFrontendMixin' abstract interface
124 # 'BaseFrontendMixin' abstract interface
125 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
126
126
127 def _handle_complete_reply(self, rep):
127 def _handle_complete_reply(self, rep):
128 """ Reimplemented to support IPython's improved completion machinery.
128 """ Reimplemented to support IPython's improved completion machinery.
129 """
129 """
130 cursor = self._get_cursor()
130 cursor = self._get_cursor()
131 info = self._request_info.get('complete')
131 info = self._request_info.get('complete')
132 if info and info.id == rep['parent_header']['msg_id'] and \
132 if info and info.id == rep['parent_header']['msg_id'] and \
133 info.pos == cursor.position():
133 info.pos == cursor.position():
134 matches = rep['content']['matches']
134 matches = rep['content']['matches']
135 text = rep['content']['matched_text']
135 text = rep['content']['matched_text']
136 offset = len(text)
136 offset = len(text)
137
137
138 # Clean up matches with period and path separators if the matched
138 # Clean up matches with period and path separators if the matched
139 # text has not been transformed. This is done by truncating all
139 # text has not been transformed. This is done by truncating all
140 # but the last component and then suitably decreasing the offset
140 # but the last component and then suitably decreasing the offset
141 # between the current cursor position and the start of completion.
141 # between the current cursor position and the start of completion.
142 if len(matches) > 1 and matches[0][:offset] == text:
142 if len(matches) > 1 and matches[0][:offset] == text:
143 parts = re.split(r'[./\\]', text)
143 parts = re.split(r'[./\\]', text)
144 sep_count = len(parts) - 1
144 sep_count = len(parts) - 1
145 if sep_count:
145 if sep_count:
146 chop_length = sum(map(len, parts[:sep_count])) + sep_count
146 chop_length = sum(map(len, parts[:sep_count])) + sep_count
147 matches = [ match[chop_length:] for match in matches ]
147 matches = [ match[chop_length:] for match in matches ]
148 offset -= chop_length
148 offset -= chop_length
149
149
150 # Move the cursor to the start of the match and complete.
150 # Move the cursor to the start of the match and complete.
151 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
151 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
152 self._complete_with_items(cursor, matches)
152 self._complete_with_items(cursor, matches)
153
153
154 def _handle_execute_reply(self, msg):
154 def _handle_execute_reply(self, msg):
155 """ Reimplemented to support prompt requests.
155 """ Reimplemented to support prompt requests.
156 """
156 """
157 info = self._request_info.get('execute')
157 info = self._request_info.get('execute')
158 if info and info.id == msg['parent_header']['msg_id']:
158 if info and info.id == msg['parent_header']['msg_id']:
159 if info.kind == 'prompt':
159 if info.kind == 'prompt':
160 number = msg['content']['execution_count'] + 1
160 number = msg['content']['execution_count'] + 1
161 self._show_interpreter_prompt(number)
161 self._show_interpreter_prompt(number)
162 else:
162 else:
163 super(IPythonWidget, self)._handle_execute_reply(msg)
163 super(IPythonWidget, self)._handle_execute_reply(msg)
164
164
165 def _handle_history_reply(self, msg):
165 def _handle_history_reply(self, msg):
166 """ Implemented to handle history replies, which are only supported by
166 """ Implemented to handle history replies, which are only supported by
167 the IPython kernel.
167 the IPython kernel.
168 """
168 """
169 history_dict = msg['content']['history']
169 history_dict = msg['content']['history']
170 input_history_dict = {}
170 input_history_dict = {}
171 for key,val in history_dict.items():
171 for key,val in history_dict.items():
172 input_history_dict[int(key)] = val
172 input_history_dict[int(key)] = val
173 items = [ val.rstrip() for _, val in sorted(input_history_dict.items()) ]
173 items = [ val.rstrip() for _, val in sorted(input_history_dict.items()) ]
174 self._set_history(items)
174 self._set_history(items)
175
175
176 def _handle_pyout(self, msg):
176 def _handle_pyout(self, msg):
177 """ Reimplemented for IPython-style "display hook".
177 """ Reimplemented for IPython-style "display hook".
178 """
178 """
179 if not self._hidden and self._is_from_this_session(msg):
179 if not self._hidden and self._is_from_this_session(msg):
180 content = msg['content']
180 content = msg['content']
181 prompt_number = content['execution_count']
181 prompt_number = content['execution_count']
182 data = content['data']
182 data = content['data']
183 if data.has_key('text/html'):
183 if data.has_key('text/html'):
184 self._append_plain_text(self.output_sep)
184 self._append_plain_text(self.output_sep)
185 self._append_html(self._make_out_prompt(prompt_number))
185 self._append_html(self._make_out_prompt(prompt_number))
186 html = data['text/html']
186 html = data['text/html']
187 self._append_plain_text('\n')
187 self._append_plain_text('\n')
188 self._append_html(html + self.output_sep2)
188 self._append_html(html + self.output_sep2)
189 elif data.has_key('text/plain'):
189 elif data.has_key('text/plain'):
190 self._append_plain_text(self.output_sep)
190 self._append_plain_text(self.output_sep)
191 self._append_html(self._make_out_prompt(prompt_number))
191 self._append_html(self._make_out_prompt(prompt_number))
192 text = data['text/plain']
192 text = data['text/plain']
193 self._append_plain_text(text + self.output_sep2)
193 self._append_plain_text(text + self.output_sep2)
194
194
195 def _handle_display_data(self, msg):
195 def _handle_display_data(self, msg):
196 """ The base handler for the ``display_data`` message.
196 """ The base handler for the ``display_data`` message.
197 """
197 """
198 # For now, we don't display data from other frontends, but we
198 # For now, we don't display data from other frontends, but we
199 # eventually will as this allows all frontends to monitor the display
199 # eventually will as this allows all frontends to monitor the display
200 # data. But we need to figure out how to handle this in the GUI.
200 # data. But we need to figure out how to handle this in the GUI.
201 if not self._hidden and self._is_from_this_session(msg):
201 if not self._hidden and self._is_from_this_session(msg):
202 source = msg['content']['source']
202 source = msg['content']['source']
203 data = msg['content']['data']
203 data = msg['content']['data']
204 metadata = msg['content']['metadata']
204 metadata = msg['content']['metadata']
205 # In the regular IPythonWidget, we simply print the plain text
205 # In the regular IPythonWidget, we simply print the plain text
206 # representation.
206 # representation.
207 if data.has_key('text/html'):
207 if data.has_key('text/html'):
208 html = data['text/html']
208 html = data['text/html']
209 self._append_html(html)
209 self._append_html(html)
210 elif data.has_key('text/plain'):
210 elif data.has_key('text/plain'):
211 text = data['text/plain']
211 text = data['text/plain']
212 self._append_plain_text(text)
212 self._append_plain_text(text)
213 # This newline seems to be needed for text and html output.
214 self._append_plain_text(u'\n')
213
215
214 def _started_channels(self):
216 def _started_channels(self):
215 """ Reimplemented to make a history request.
217 """ Reimplemented to make a history request.
216 """
218 """
217 super(IPythonWidget, self)._started_channels()
219 super(IPythonWidget, self)._started_channels()
218 self.kernel_manager.xreq_channel.history(raw=True, output=False)
220 self.kernel_manager.xreq_channel.history(raw=True, output=False)
219
221
220 #---------------------------------------------------------------------------
222 #---------------------------------------------------------------------------
221 # 'ConsoleWidget' public interface
223 # 'ConsoleWidget' public interface
222 #---------------------------------------------------------------------------
224 #---------------------------------------------------------------------------
223
225
224 def copy(self):
226 def copy(self):
225 """ Copy the currently selected text to the clipboard, removing prompts
227 """ Copy the currently selected text to the clipboard, removing prompts
226 if possible.
228 if possible.
227 """
229 """
228 text = unicode(self._control.textCursor().selection().toPlainText())
230 text = unicode(self._control.textCursor().selection().toPlainText())
229 if text:
231 if text:
230 lines = map(transform_ipy_prompt, text.splitlines())
232 lines = map(transform_ipy_prompt, text.splitlines())
231 text = '\n'.join(lines)
233 text = '\n'.join(lines)
232 QtGui.QApplication.clipboard().setText(text)
234 QtGui.QApplication.clipboard().setText(text)
233
235
234 #---------------------------------------------------------------------------
236 #---------------------------------------------------------------------------
235 # 'FrontendWidget' public interface
237 # 'FrontendWidget' public interface
236 #---------------------------------------------------------------------------
238 #---------------------------------------------------------------------------
237
239
238 def execute_file(self, path, hidden=False):
240 def execute_file(self, path, hidden=False):
239 """ Reimplemented to use the 'run' magic.
241 """ Reimplemented to use the 'run' magic.
240 """
242 """
241 self.execute('%%run %s' % path, hidden=hidden)
243 self.execute('%%run %s' % path, hidden=hidden)
242
244
243 #---------------------------------------------------------------------------
245 #---------------------------------------------------------------------------
244 # 'FrontendWidget' protected interface
246 # 'FrontendWidget' protected interface
245 #---------------------------------------------------------------------------
247 #---------------------------------------------------------------------------
246
248
247 def _complete(self):
249 def _complete(self):
248 """ Reimplemented to support IPython's improved completion machinery.
250 """ Reimplemented to support IPython's improved completion machinery.
249 """
251 """
250 # We let the kernel split the input line, so we *always* send an empty
252 # We let the kernel split the input line, so we *always* send an empty
251 # text field. Readline-based frontends do get a real text field which
253 # text field. Readline-based frontends do get a real text field which
252 # they can use.
254 # they can use.
253 text = ''
255 text = ''
254
256
255 # Send the completion request to the kernel
257 # Send the completion request to the kernel
256 msg_id = self.kernel_manager.xreq_channel.complete(
258 msg_id = self.kernel_manager.xreq_channel.complete(
257 text, # text
259 text, # text
258 self._get_input_buffer_cursor_line(), # line
260 self._get_input_buffer_cursor_line(), # line
259 self._get_input_buffer_cursor_column(), # cursor_pos
261 self._get_input_buffer_cursor_column(), # cursor_pos
260 self.input_buffer) # block
262 self.input_buffer) # block
261 pos = self._get_cursor().position()
263 pos = self._get_cursor().position()
262 info = self._CompletionRequest(msg_id, pos)
264 info = self._CompletionRequest(msg_id, pos)
263 self._request_info['complete'] = info
265 self._request_info['complete'] = info
264
266
265 def _get_banner(self):
267 def _get_banner(self):
266 """ Reimplemented to return IPython's default banner.
268 """ Reimplemented to return IPython's default banner.
267 """
269 """
268 return default_gui_banner
270 return default_gui_banner
269
271
270 def _process_execute_error(self, msg):
272 def _process_execute_error(self, msg):
271 """ Reimplemented for IPython-style traceback formatting.
273 """ Reimplemented for IPython-style traceback formatting.
272 """
274 """
273 content = msg['content']
275 content = msg['content']
274 traceback = '\n'.join(content['traceback']) + '\n'
276 traceback = '\n'.join(content['traceback']) + '\n'
275 if False:
277 if False:
276 # FIXME: For now, tracebacks come as plain text, so we can't use
278 # FIXME: For now, tracebacks come as plain text, so we can't use
277 # the html renderer yet. Once we refactor ultratb to produce
279 # the html renderer yet. Once we refactor ultratb to produce
278 # properly styled tracebacks, this branch should be the default
280 # properly styled tracebacks, this branch should be the default
279 traceback = traceback.replace(' ', '&nbsp;')
281 traceback = traceback.replace(' ', '&nbsp;')
280 traceback = traceback.replace('\n', '<br/>')
282 traceback = traceback.replace('\n', '<br/>')
281
283
282 ename = content['ename']
284 ename = content['ename']
283 ename_styled = '<span class="error">%s</span>' % ename
285 ename_styled = '<span class="error">%s</span>' % ename
284 traceback = traceback.replace(ename, ename_styled)
286 traceback = traceback.replace(ename, ename_styled)
285
287
286 self._append_html(traceback)
288 self._append_html(traceback)
287 else:
289 else:
288 # This is the fallback for now, using plain text with ansi escapes
290 # This is the fallback for now, using plain text with ansi escapes
289 self._append_plain_text(traceback)
291 self._append_plain_text(traceback)
290
292
291 def _process_execute_payload(self, item):
293 def _process_execute_payload(self, item):
292 """ Reimplemented to dispatch payloads to handler methods.
294 """ Reimplemented to dispatch payloads to handler methods.
293 """
295 """
294 handler = self._payload_handlers.get(item['source'])
296 handler = self._payload_handlers.get(item['source'])
295 if handler is None:
297 if handler is None:
296 # We have no handler for this type of payload, simply ignore it
298 # We have no handler for this type of payload, simply ignore it
297 return False
299 return False
298 else:
300 else:
299 handler(item)
301 handler(item)
300 return True
302 return True
301
303
302 def _show_interpreter_prompt(self, number=None):
304 def _show_interpreter_prompt(self, number=None):
303 """ Reimplemented for IPython-style prompts.
305 """ Reimplemented for IPython-style prompts.
304 """
306 """
305 # If a number was not specified, make a prompt number request.
307 # If a number was not specified, make a prompt number request.
306 if number is None:
308 if number is None:
307 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
309 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
308 info = self._ExecutionRequest(msg_id, 'prompt')
310 info = self._ExecutionRequest(msg_id, 'prompt')
309 self._request_info['execute'] = info
311 self._request_info['execute'] = info
310 return
312 return
311
313
312 # Show a new prompt and save information about it so that it can be
314 # Show a new prompt and save information about it so that it can be
313 # updated later if the prompt number turns out to be wrong.
315 # updated later if the prompt number turns out to be wrong.
314 self._prompt_sep = self.input_sep
316 self._prompt_sep = self.input_sep
315 self._show_prompt(self._make_in_prompt(number), html=True)
317 self._show_prompt(self._make_in_prompt(number), html=True)
316 block = self._control.document().lastBlock()
318 block = self._control.document().lastBlock()
317 length = len(self._prompt)
319 length = len(self._prompt)
318 self._previous_prompt_obj = self._PromptBlock(block, length, number)
320 self._previous_prompt_obj = self._PromptBlock(block, length, number)
319
321
320 # Update continuation prompt to reflect (possibly) new prompt length.
322 # Update continuation prompt to reflect (possibly) new prompt length.
321 self._set_continuation_prompt(
323 self._set_continuation_prompt(
322 self._make_continuation_prompt(self._prompt), html=True)
324 self._make_continuation_prompt(self._prompt), html=True)
323
325
324 # Load code from the %loadpy magic, if necessary.
326 # Load code from the %loadpy magic, if necessary.
325 if self._code_to_load is not None:
327 if self._code_to_load is not None:
326 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
328 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
327 self._code_to_load = None
329 self._code_to_load = None
328
330
329 def _show_interpreter_prompt_for_reply(self, msg):
331 def _show_interpreter_prompt_for_reply(self, msg):
330 """ Reimplemented for IPython-style prompts.
332 """ Reimplemented for IPython-style prompts.
331 """
333 """
332 # Update the old prompt number if necessary.
334 # Update the old prompt number if necessary.
333 content = msg['content']
335 content = msg['content']
334 previous_prompt_number = content['execution_count']
336 previous_prompt_number = content['execution_count']
335 if self._previous_prompt_obj and \
337 if self._previous_prompt_obj and \
336 self._previous_prompt_obj.number != previous_prompt_number:
338 self._previous_prompt_obj.number != previous_prompt_number:
337 block = self._previous_prompt_obj.block
339 block = self._previous_prompt_obj.block
338
340
339 # Make sure the prompt block has not been erased.
341 # Make sure the prompt block has not been erased.
340 if block.isValid() and not block.text().isEmpty():
342 if block.isValid() and not block.text().isEmpty():
341
343
342 # Remove the old prompt and insert a new prompt.
344 # Remove the old prompt and insert a new prompt.
343 cursor = QtGui.QTextCursor(block)
345 cursor = QtGui.QTextCursor(block)
344 cursor.movePosition(QtGui.QTextCursor.Right,
346 cursor.movePosition(QtGui.QTextCursor.Right,
345 QtGui.QTextCursor.KeepAnchor,
347 QtGui.QTextCursor.KeepAnchor,
346 self._previous_prompt_obj.length)
348 self._previous_prompt_obj.length)
347 prompt = self._make_in_prompt(previous_prompt_number)
349 prompt = self._make_in_prompt(previous_prompt_number)
348 self._prompt = self._insert_html_fetching_plain_text(
350 self._prompt = self._insert_html_fetching_plain_text(
349 cursor, prompt)
351 cursor, prompt)
350
352
351 # When the HTML is inserted, Qt blows away the syntax
353 # When the HTML is inserted, Qt blows away the syntax
352 # highlighting for the line, so we need to rehighlight it.
354 # highlighting for the line, so we need to rehighlight it.
353 self._highlighter.rehighlightBlock(cursor.block())
355 self._highlighter.rehighlightBlock(cursor.block())
354
356
355 self._previous_prompt_obj = None
357 self._previous_prompt_obj = None
356
358
357 # Show a new prompt with the kernel's estimated prompt number.
359 # Show a new prompt with the kernel's estimated prompt number.
358 self._show_interpreter_prompt(previous_prompt_number + 1)
360 self._show_interpreter_prompt(previous_prompt_number + 1)
359
361
360 #---------------------------------------------------------------------------
362 #---------------------------------------------------------------------------
361 # 'IPythonWidget' interface
363 # 'IPythonWidget' interface
362 #---------------------------------------------------------------------------
364 #---------------------------------------------------------------------------
363
365
364 def set_default_style(self, colors='lightbg'):
366 def set_default_style(self, colors='lightbg'):
365 """ Sets the widget style to the class defaults.
367 """ Sets the widget style to the class defaults.
366
368
367 Parameters:
369 Parameters:
368 -----------
370 -----------
369 colors : str, optional (default lightbg)
371 colors : str, optional (default lightbg)
370 Whether to use the default IPython light background or dark
372 Whether to use the default IPython light background or dark
371 background or B&W style.
373 background or B&W style.
372 """
374 """
373 colors = colors.lower()
375 colors = colors.lower()
374 if colors=='lightbg':
376 if colors=='lightbg':
375 self.style_sheet = default_light_style_sheet
377 self.style_sheet = default_light_style_sheet
376 self.syntax_style = default_light_syntax_style
378 self.syntax_style = default_light_syntax_style
377 elif colors=='linux':
379 elif colors=='linux':
378 self.style_sheet = default_dark_style_sheet
380 self.style_sheet = default_dark_style_sheet
379 self.syntax_style = default_dark_syntax_style
381 self.syntax_style = default_dark_syntax_style
380 elif colors=='nocolor':
382 elif colors=='nocolor':
381 self.style_sheet = default_bw_style_sheet
383 self.style_sheet = default_bw_style_sheet
382 self.syntax_style = default_bw_syntax_style
384 self.syntax_style = default_bw_syntax_style
383 else:
385 else:
384 raise KeyError("No such color scheme: %s"%colors)
386 raise KeyError("No such color scheme: %s"%colors)
385
387
386 #---------------------------------------------------------------------------
388 #---------------------------------------------------------------------------
387 # 'IPythonWidget' protected interface
389 # 'IPythonWidget' protected interface
388 #---------------------------------------------------------------------------
390 #---------------------------------------------------------------------------
389
391
390 def _edit(self, filename, line=None):
392 def _edit(self, filename, line=None):
391 """ Opens a Python script for editing.
393 """ Opens a Python script for editing.
392
394
393 Parameters:
395 Parameters:
394 -----------
396 -----------
395 filename : str
397 filename : str
396 A path to a local system file.
398 A path to a local system file.
397
399
398 line : int, optional
400 line : int, optional
399 A line of interest in the file.
401 A line of interest in the file.
400 """
402 """
401 if self.custom_edit:
403 if self.custom_edit:
402 self.custom_edit_requested.emit(filename, line)
404 self.custom_edit_requested.emit(filename, line)
403 elif self.editor == 'default':
405 elif self.editor == 'default':
404 self._append_plain_text('No default editor available.\n')
406 self._append_plain_text('No default editor available.\n')
405 else:
407 else:
406 try:
408 try:
407 filename = '"%s"' % filename
409 filename = '"%s"' % filename
408 if line and self.editor_line:
410 if line and self.editor_line:
409 command = self.editor_line.format(filename=filename,
411 command = self.editor_line.format(filename=filename,
410 line=line)
412 line=line)
411 else:
413 else:
412 try:
414 try:
413 command = self.editor.format()
415 command = self.editor.format()
414 except KeyError:
416 except KeyError:
415 command = self.editor.format(filename=filename)
417 command = self.editor.format(filename=filename)
416 else:
418 else:
417 command += ' ' + filename
419 command += ' ' + filename
418 except KeyError:
420 except KeyError:
419 self._append_plain_text('Invalid editor command.\n')
421 self._append_plain_text('Invalid editor command.\n')
420 else:
422 else:
421 try:
423 try:
422 Popen(command, shell=True)
424 Popen(command, shell=True)
423 except OSError:
425 except OSError:
424 msg = 'Opening editor with command "%s" failed.\n'
426 msg = 'Opening editor with command "%s" failed.\n'
425 self._append_plain_text(msg % command)
427 self._append_plain_text(msg % command)
426
428
427 def _make_in_prompt(self, number):
429 def _make_in_prompt(self, number):
428 """ Given a prompt number, returns an HTML In prompt.
430 """ Given a prompt number, returns an HTML In prompt.
429 """
431 """
430 body = self.in_prompt % number
432 body = self.in_prompt % number
431 return '<span class="in-prompt">%s</span>' % body
433 return '<span class="in-prompt">%s</span>' % body
432
434
433 def _make_continuation_prompt(self, prompt):
435 def _make_continuation_prompt(self, prompt):
434 """ Given a plain text version of an In prompt, returns an HTML
436 """ Given a plain text version of an In prompt, returns an HTML
435 continuation prompt.
437 continuation prompt.
436 """
438 """
437 end_chars = '...: '
439 end_chars = '...: '
438 space_count = len(prompt.lstrip('\n')) - len(end_chars)
440 space_count = len(prompt.lstrip('\n')) - len(end_chars)
439 body = '&nbsp;' * space_count + end_chars
441 body = '&nbsp;' * space_count + end_chars
440 return '<span class="in-prompt">%s</span>' % body
442 return '<span class="in-prompt">%s</span>' % body
441
443
442 def _make_out_prompt(self, number):
444 def _make_out_prompt(self, number):
443 """ Given a prompt number, returns an HTML Out prompt.
445 """ Given a prompt number, returns an HTML Out prompt.
444 """
446 """
445 body = self.out_prompt % number
447 body = self.out_prompt % number
446 return '<span class="out-prompt">%s</span>' % body
448 return '<span class="out-prompt">%s</span>' % body
447
449
448 #------ Payload handlers --------------------------------------------------
450 #------ Payload handlers --------------------------------------------------
449
451
450 # Payload handlers with a generic interface: each takes the opaque payload
452 # Payload handlers with a generic interface: each takes the opaque payload
451 # dict, unpacks it and calls the underlying functions with the necessary
453 # dict, unpacks it and calls the underlying functions with the necessary
452 # arguments.
454 # arguments.
453
455
454 def _handle_payload_edit(self, item):
456 def _handle_payload_edit(self, item):
455 self._edit(item['filename'], item['line_number'])
457 self._edit(item['filename'], item['line_number'])
456
458
457 def _handle_payload_exit(self, item):
459 def _handle_payload_exit(self, item):
458 self._keep_kernel_on_exit = item['keepkernel']
460 self._keep_kernel_on_exit = item['keepkernel']
459 self.exit_requested.emit()
461 self.exit_requested.emit()
460
462
461 def _handle_payload_loadpy(self, item):
463 def _handle_payload_loadpy(self, item):
462 # Simple save the text of the .py file for later. The text is written
464 # Simple save the text of the .py file for later. The text is written
463 # to the buffer when _prompt_started_hook is called.
465 # to the buffer when _prompt_started_hook is called.
464 self._code_to_load = item['text']
466 self._code_to_load = item['text']
465
467
466 def _handle_payload_page(self, item):
468 def _handle_payload_page(self, item):
467 # Since the plain text widget supports only a very small subset of HTML
469 # Since the plain text widget supports only a very small subset of HTML
468 # and we have no control over the HTML source, we only page HTML
470 # and we have no control over the HTML source, we only page HTML
469 # payloads in the rich text widget.
471 # payloads in the rich text widget.
470 if item['html'] and self.kind == 'rich':
472 if item['html'] and self.kind == 'rich':
471 self._page(item['html'], html=True)
473 self._page(item['html'], html=True)
472 else:
474 else:
473 self._page(item['text'], html=False)
475 self._page(item['text'], html=False)
474
476
475 #------ Trait change handlers --------------------------------------------
477 #------ Trait change handlers --------------------------------------------
476
478
477 def _style_sheet_changed(self):
479 def _style_sheet_changed(self):
478 """ Set the style sheets of the underlying widgets.
480 """ Set the style sheets of the underlying widgets.
479 """
481 """
480 self.setStyleSheet(self.style_sheet)
482 self.setStyleSheet(self.style_sheet)
481 self._control.document().setDefaultStyleSheet(self.style_sheet)
483 self._control.document().setDefaultStyleSheet(self.style_sheet)
482 if self._page_control:
484 if self._page_control:
483 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
485 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
484
486
485 bg_color = self._control.palette().background().color()
487 bg_color = self._control.palette().background().color()
486 self._ansi_processor.set_background_color(bg_color)
488 self._ansi_processor.set_background_color(bg_color)
487
489
488 def _syntax_style_changed(self):
490 def _syntax_style_changed(self):
489 """ Set the style for the syntax highlighter.
491 """ Set the style for the syntax highlighter.
490 """
492 """
491 if self.syntax_style:
493 if self.syntax_style:
492 self._highlighter.set_style(self.syntax_style)
494 self._highlighter.set_style(self.syntax_style)
493 else:
495 else:
494 self._highlighter.set_style_sheet(self.style_sheet)
496 self._highlighter.set_style_sheet(self.style_sheet)
495
497
@@ -1,240 +1,269 b''
1 # System library imports
1 # System library imports
2 import os
2 import os
3 import re
3 import re
4 from base64 import decodestring
4 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
5
6
6 # Local imports
7 # Local imports
7 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
8 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
8 from ipython_widget import IPythonWidget
9 from ipython_widget import IPythonWidget
9
10
10
11
11 class RichIPythonWidget(IPythonWidget):
12 class RichIPythonWidget(IPythonWidget):
12 """ An IPythonWidget that supports rich text, including lists, images, and
13 """ An IPythonWidget that supports rich text, including lists, images, and
13 tables. Note that raw performance will be reduced compared to the plain
14 tables. Note that raw performance will be reduced compared to the plain
14 text version.
15 text version.
15 """
16 """
16
17
17 # RichIPythonWidget protected class variables.
18 # RichIPythonWidget protected class variables.
18 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
19 _payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
19 _svg_text_format_property = 1
20 _svg_text_format_property = 1
20
21
21 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
22 # 'object' interface
23 # 'object' interface
23 #---------------------------------------------------------------------------
24 #---------------------------------------------------------------------------
24
25
25 def __init__(self, *args, **kw):
26 def __init__(self, *args, **kw):
26 """ Create a RichIPythonWidget.
27 """ Create a RichIPythonWidget.
27 """
28 """
28 kw['kind'] = 'rich'
29 kw['kind'] = 'rich'
29 super(RichIPythonWidget, self).__init__(*args, **kw)
30 super(RichIPythonWidget, self).__init__(*args, **kw)
30 # Dictionary for resolving Qt names to images when
31 # Dictionary for resolving Qt names to images when
31 # generating XHTML output
32 # generating XHTML output
32 self._name_to_svg = {}
33 self._name_to_svg = {}
33
34
34 #---------------------------------------------------------------------------
35 #---------------------------------------------------------------------------
35 # 'ConsoleWidget' protected interface
36 # 'ConsoleWidget' protected interface
36 #---------------------------------------------------------------------------
37 #---------------------------------------------------------------------------
37
38
38 def _context_menu_make(self, pos):
39 def _context_menu_make(self, pos):
39 """ Reimplemented to return a custom context menu for images.
40 """ Reimplemented to return a custom context menu for images.
40 """
41 """
41 format = self._control.cursorForPosition(pos).charFormat()
42 format = self._control.cursorForPosition(pos).charFormat()
42 name = format.stringProperty(QtGui.QTextFormat.ImageName)
43 name = format.stringProperty(QtGui.QTextFormat.ImageName)
43 if name.isEmpty():
44 if name.isEmpty():
44 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
45 menu = super(RichIPythonWidget, self)._context_menu_make(pos)
45 else:
46 else:
46 menu = QtGui.QMenu()
47 menu = QtGui.QMenu()
47
48
48 menu.addAction('Copy Image', lambda: self._copy_image(name))
49 menu.addAction('Copy Image', lambda: self._copy_image(name))
49 menu.addAction('Save Image As...', lambda: self._save_image(name))
50 menu.addAction('Save Image As...', lambda: self._save_image(name))
50 menu.addSeparator()
51 menu.addSeparator()
51
52
52 svg = format.stringProperty(self._svg_text_format_property)
53 svg = format.stringProperty(self._svg_text_format_property)
53 if not svg.isEmpty():
54 if not svg.isEmpty():
54 menu.addSeparator()
55 menu.addSeparator()
55 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
56 menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
56 menu.addAction('Save SVG As...',
57 menu.addAction('Save SVG As...',
57 lambda: save_svg(svg, self._control))
58 lambda: save_svg(svg, self._control))
58 return menu
59 return menu
59
60
60 #---------------------------------------------------------------------------
61 #---------------------------------------------------------------------------
61 # 'BaseFrontendMixin' abstract interface
62 # 'BaseFrontendMixin' abstract interface
62 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
63
64
64 def _handle_pyout(self, msg):
65 def _handle_pyout(self, msg):
65 """ Overridden to handle rich data types, like SVG.
66 """ Overridden to handle rich data types, like SVG.
66 """
67 """
67 if not self._hidden and self._is_from_this_session(msg):
68 if not self._hidden and self._is_from_this_session(msg):
68 content = msg['content']
69 content = msg['content']
69 prompt_number = content['execution_count']
70 prompt_number = content['execution_count']
70 data = content['data']
71 data = content['data']
71 if data.has_key('image/svg+xml'):
72 if data.has_key('image/svg+xml'):
72 self._append_plain_text(self.output_sep)
73 self._append_plain_text(self.output_sep)
73 self._append_html(self._make_out_prompt(prompt_number))
74 self._append_html(self._make_out_prompt(prompt_number))
74 # TODO: try/except this call.
75 # TODO: try/except this call.
75 self._append_svg(data['image/svg+xml'])
76 self._append_svg(data['image/svg+xml'])
76 self._append_html(self.output_sep2)
77 self._append_html(self.output_sep2)
78 elif data.has_key('image/png'):
79 self._append_plain_text(self.output_sep)
80 self._append_html(self._make_out_prompt(prompt_number))
81 # TODO: try/except these calls
82 png = decodestring(data['image/png'])
83 self._append_png(png)
84 self._append_html(self.output_sep2)
77 else:
85 else:
78 # Default back to the plain text representation.
86 # Default back to the plain text representation.
79 return super(RichIPythonWidget, self)._handle_pyout(msg)
87 return super(RichIPythonWidget, self)._handle_pyout(msg)
80
88
81 def _handle_display_data(self, msg):
89 def _handle_display_data(self, msg):
82 """ Overridden to handle rich data types, like SVG.
90 """ Overridden to handle rich data types, like SVG.
83 """
91 """
84 if not self._hidden and self._is_from_this_session(msg):
92 if not self._hidden and self._is_from_this_session(msg):
85 source = msg['content']['source']
93 source = msg['content']['source']
86 data = msg['content']['data']
94 data = msg['content']['data']
87 metadata = msg['content']['metadata']
95 metadata = msg['content']['metadata']
88 # Try to use the svg or html representations.
96 # Try to use the svg or html representations.
89 # FIXME: Is this the right ordering of things to try?
97 # FIXME: Is this the right ordering of things to try?
90 if data.has_key('image/svg+xml'):
98 if data.has_key('image/svg+xml'):
91 svg = data['image/svg+xml']
99 svg = data['image/svg+xml']
92 # TODO: try/except this call.
100 # TODO: try/except this call.
93 self._append_svg(svg)
101 self._append_svg(svg)
102 elif data.has_key('image/png'):
103 # TODO: try/except these calls
104 # PNG data is base64 encoded as it passes over the network
105 # in a JSON structure so we decode it.
106 png = decodestring(data['image/png'])
107 self._append_png(png)
94 else:
108 else:
95 # Default back to the plain text representation.
109 # Default back to the plain text representation.
96 return super(RichIPythonWidget, self)._handle_display_data(msg)
110 return super(RichIPythonWidget, self)._handle_display_data(msg)
97
111
98 #---------------------------------------------------------------------------
112 #---------------------------------------------------------------------------
99 # 'FrontendWidget' protected interface
113 # 'FrontendWidget' protected interface
100 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
101
115
102 def _process_execute_payload(self, item):
116 def _process_execute_payload(self, item):
103 """ Reimplemented to handle matplotlib plot payloads.
117 """ Reimplemented to handle matplotlib plot payloads.
104 """
118 """
105 # TODO: remove this as all plot data is coming back through the
119 # TODO: remove this as all plot data is coming back through the
106 # display_data message type.
120 # display_data message type.
107 if item['source'] == self._payload_source_plot:
121 if item['source'] == self._payload_source_plot:
108 if item['format'] == 'svg':
122 if item['format'] == 'svg':
109 svg = item['data']
123 svg = item['data']
110 self._append_svg(svg)
124 self._append_svg(svg)
111 return True
125 return True
112 else:
126 else:
113 # Add other plot formats here!
127 # Add other plot formats here!
114 return False
128 return False
115 else:
129 else:
116 return super(RichIPythonWidget, self)._process_execute_payload(item)
130 return super(RichIPythonWidget, self)._process_execute_payload(item)
117
131
118 #---------------------------------------------------------------------------
132 #---------------------------------------------------------------------------
119 # 'RichIPythonWidget' protected interface
133 # 'RichIPythonWidget' protected interface
120 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
121
135
122 def _append_svg(self, svg):
136 def _append_svg(self, svg):
123 """ Append raw svg data to the widget.
137 """ Append raw svg data to the widget.
124 """
138 """
125 try:
139 try:
126 image = svg_to_image(svg)
140 image = svg_to_image(svg)
127 except ValueError:
141 except ValueError:
128 self._append_plain_text('Received invalid plot data.')
142 self._append_plain_text('Received invalid plot data.')
129 else:
143 else:
130 format = self._add_image(image)
144 format = self._add_image(image)
131 self._name_to_svg[str(format.name())] = svg
145 self._name_to_svg[str(format.name())] = svg
132 format.setProperty(self._svg_text_format_property, svg)
146 format.setProperty(self._svg_text_format_property, svg)
133 cursor = self._get_end_cursor()
147 cursor = self._get_end_cursor()
134 cursor.insertBlock()
148 cursor.insertBlock()
135 cursor.insertImage(format)
149 cursor.insertImage(format)
136 cursor.insertBlock()
150 cursor.insertBlock()
137
151
152 def _append_png(self, png):
153 """ Append raw svg data to the widget.
154 """
155 try:
156 image = QtGui.QImage()
157 image.loadFromData(png, 'PNG')
158 except ValueError:
159 self._append_plain_text('Received invalid plot data.')
160 else:
161 format = self._add_image(image)
162 cursor = self._get_end_cursor()
163 cursor.insertBlock()
164 cursor.insertImage(format)
165 cursor.insertBlock()
166
138 def _add_image(self, image):
167 def _add_image(self, image):
139 """ Adds the specified QImage to the document and returns a
168 """ Adds the specified QImage to the document and returns a
140 QTextImageFormat that references it.
169 QTextImageFormat that references it.
141 """
170 """
142 document = self._control.document()
171 document = self._control.document()
143 name = QtCore.QString.number(image.cacheKey())
172 name = QtCore.QString.number(image.cacheKey())
144 document.addResource(QtGui.QTextDocument.ImageResource,
173 document.addResource(QtGui.QTextDocument.ImageResource,
145 QtCore.QUrl(name), image)
174 QtCore.QUrl(name), image)
146 format = QtGui.QTextImageFormat()
175 format = QtGui.QTextImageFormat()
147 format.setName(name)
176 format.setName(name)
148 return format
177 return format
149
178
150 def _copy_image(self, name):
179 def _copy_image(self, name):
151 """ Copies the ImageResource with 'name' to the clipboard.
180 """ Copies the ImageResource with 'name' to the clipboard.
152 """
181 """
153 image = self._get_image(name)
182 image = self._get_image(name)
154 QtGui.QApplication.clipboard().setImage(image)
183 QtGui.QApplication.clipboard().setImage(image)
155
184
156 def _get_image(self, name):
185 def _get_image(self, name):
157 """ Returns the QImage stored as the ImageResource with 'name'.
186 """ Returns the QImage stored as the ImageResource with 'name'.
158 """
187 """
159 document = self._control.document()
188 document = self._control.document()
160 variant = document.resource(QtGui.QTextDocument.ImageResource,
189 variant = document.resource(QtGui.QTextDocument.ImageResource,
161 QtCore.QUrl(name))
190 QtCore.QUrl(name))
162 return variant.toPyObject()
191 return variant.toPyObject()
163
192
164 def _save_image(self, name, format='PNG'):
193 def _save_image(self, name, format='PNG'):
165 """ Shows a save dialog for the ImageResource with 'name'.
194 """ Shows a save dialog for the ImageResource with 'name'.
166 """
195 """
167 dialog = QtGui.QFileDialog(self._control, 'Save Image')
196 dialog = QtGui.QFileDialog(self._control, 'Save Image')
168 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
197 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
169 dialog.setDefaultSuffix(format.lower())
198 dialog.setDefaultSuffix(format.lower())
170 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
199 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
171 if dialog.exec_():
200 if dialog.exec_():
172 filename = dialog.selectedFiles()[0]
201 filename = dialog.selectedFiles()[0]
173 image = self._get_image(name)
202 image = self._get_image(name)
174 image.save(filename, format)
203 image.save(filename, format)
175
204
176 def image_tag(self, match, path = None, format = "png"):
205 def image_tag(self, match, path = None, format = "png"):
177 """ Return (X)HTML mark-up for the image-tag given by match.
206 """ Return (X)HTML mark-up for the image-tag given by match.
178
207
179 Parameters
208 Parameters
180 ----------
209 ----------
181 match : re.SRE_Match
210 match : re.SRE_Match
182 A match to an HTML image tag as exported by Qt, with
211 A match to an HTML image tag as exported by Qt, with
183 match.group("Name") containing the matched image ID.
212 match.group("Name") containing the matched image ID.
184
213
185 path : string|None, optional [default None]
214 path : string|None, optional [default None]
186 If not None, specifies a path to which supporting files
215 If not None, specifies a path to which supporting files
187 may be written (e.g., for linked images).
216 may be written (e.g., for linked images).
188 If None, all images are to be included inline.
217 If None, all images are to be included inline.
189
218
190 format : "png"|"svg", optional [default "png"]
219 format : "png"|"svg", optional [default "png"]
191 Format for returned or referenced images.
220 Format for returned or referenced images.
192
221
193 Subclasses supporting image display should override this
222 Subclasses supporting image display should override this
194 method.
223 method.
195 """
224 """
196
225
197 if(format == "png"):
226 if(format == "png"):
198 try:
227 try:
199 image = self._get_image(match.group("name"))
228 image = self._get_image(match.group("name"))
200 except KeyError:
229 except KeyError:
201 return "<b>Couldn't find image %s</b>" % match.group("name")
230 return "<b>Couldn't find image %s</b>" % match.group("name")
202
231
203 if(path is not None):
232 if(path is not None):
204 if not os.path.exists(path):
233 if not os.path.exists(path):
205 os.mkdir(path)
234 os.mkdir(path)
206 relpath = os.path.basename(path)
235 relpath = os.path.basename(path)
207 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
236 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
208 "PNG")):
237 "PNG")):
209 return '<img src="%s/qt_img%s.png">' % (relpath,
238 return '<img src="%s/qt_img%s.png">' % (relpath,
210 match.group("name"))
239 match.group("name"))
211 else:
240 else:
212 return "<b>Couldn't save image!</b>"
241 return "<b>Couldn't save image!</b>"
213 else:
242 else:
214 ba = QtCore.QByteArray()
243 ba = QtCore.QByteArray()
215 buffer_ = QtCore.QBuffer(ba)
244 buffer_ = QtCore.QBuffer(ba)
216 buffer_.open(QtCore.QIODevice.WriteOnly)
245 buffer_.open(QtCore.QIODevice.WriteOnly)
217 image.save(buffer_, "PNG")
246 image.save(buffer_, "PNG")
218 buffer_.close()
247 buffer_.close()
219 return '<img src="data:image/png;base64,\n%s\n" />' % (
248 return '<img src="data:image/png;base64,\n%s\n" />' % (
220 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
249 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
221
250
222 elif(format == "svg"):
251 elif(format == "svg"):
223 try:
252 try:
224 svg = str(self._name_to_svg[match.group("name")])
253 svg = str(self._name_to_svg[match.group("name")])
225 except KeyError:
254 except KeyError:
226 return "<b>Couldn't find image %s</b>" % match.group("name")
255 return "<b>Couldn't find image %s</b>" % match.group("name")
227
256
228 # Not currently checking path, because it's tricky to find a
257 # Not currently checking path, because it's tricky to find a
229 # cross-browser way to embed external SVG images (e.g., via
258 # cross-browser way to embed external SVG images (e.g., via
230 # object or embed tags).
259 # object or embed tags).
231
260
232 # Chop stand-alone header from matplotlib SVG
261 # Chop stand-alone header from matplotlib SVG
233 offset = svg.find("<svg")
262 offset = svg.find("<svg")
234 assert(offset > -1)
263 assert(offset > -1)
235
264
236 return svg[offset:]
265 return svg[offset:]
237
266
238 else:
267 else:
239 return '<b>Unrecognized image format</b>'
268 return '<b>Unrecognized image format</b>'
240
269
General Comments 0
You need to be logged in to leave comments. Login now