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