##// END OF EJS Templates
Note to self
Note to self

File last commit:

r13347:54891794
r15944:bfa67c7c
Show More
rich_ipython_widget.py
341 lines | 14.5 KiB | text/x-python | PythonLexer
/ IPython / qt / console / rich_ipython_widget.py
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 #-----------------------------------------------------------------------------
Matthias BUSSONNIER
strip copyright
r6640 # Copyright (c) 2010, IPython Development Team.
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 #
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file COPYING.txt, distributed with this software.
#-----------------------------------------------------------------------------
Evan Patterson
Paved the way for PySide support....
r3304 # Standard libary imports.
from base64 import decodestring
MinRK
html export fixes
r3154 import os
import re
Evan Patterson
Paved the way for PySide support....
r3304
# System libary imports.
from IPython.external.qt import QtCore, QtGui
epatters
* The SVG payload matplotlib backend now works....
r2758
# Local imports
Matthias BUSSONNIER
more robust code...
r6638 from IPython.utils.traitlets import Bool
Fernando Perez
Fix all imports for Qt console.
r11022 from IPython.qt.svg import save_svg, svg_to_clipboard, svg_to_image
Thomas Kluyver
Use explicit relative imports...
r13347 from .ipython_widget import IPythonWidget
epatters
* The SVG payload matplotlib backend now works....
r2758
class RichIPythonWidget(IPythonWidget):
""" An IPythonWidget that supports rich text, including lists, images, and
tables. Note that raw performance will be reduced compared to the plain
text version.
"""
epatters
* Refactored payload handling mechanism....
r2835 # RichIPythonWidget protected class variables.
MinRK
mv IPython.zmq to IPython.kernel.zmq
r9372 _payload_source_plot = 'IPython.kernel.zmq.pylab.backend_payload.add_plot_payload'
Matthias BUSSONNIER
more robust code...
r6638 _jpg_supported = Bool(False)
Ian Murray
Tidying of warning dialog strings...
r6866
# Used to determine whether a given html export attempt has already
# displayed a warning about being unable to convert a png to svg.
Ian Murray
Simplified the reset of the _svg_warning_displayed flag in RichIPythonWidget...
r6865 _svg_warning_displayed = False
epatters
* The SVG payload matplotlib backend now works....
r2758 #---------------------------------------------------------------------------
epatters
* Added a pager with several different options to ConsoleWidget....
r2776 # 'object' interface
epatters
* The SVG payload matplotlib backend now works....
r2758 #---------------------------------------------------------------------------
epatters
* Added a pager with several different options to ConsoleWidget....
r2776 def __init__(self, *args, **kw):
epatters
* The SVG payload matplotlib backend now works....
r2758 """ Create a RichIPythonWidget.
"""
epatters
* Added a pager with several different options to ConsoleWidget....
r2776 kw['kind'] = 'rich'
super(RichIPythonWidget, self).__init__(*args, **kw)
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361
# Configure the ConsoleWidget HTML exporter for our formats.
self._html_exporter.image_tag = self._get_image_tag
epatters
Fixed PySide incompatibility with QVariant....
r3364 # Dictionary for resolving document resource names to SVG data.
self._name_to_svg_map = {}
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 # Do we support jpg ?
# it seems that sometime jpg support is a plugin of QT, so try to assume
# it is not always supported.
Matthias BUSSONNIER
more robust code...
r6638 _supported_format = map(str, QtGui.QImageReader.supportedImageFormats())
self._jpg_supported = 'jpeg' in _supported_format
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 #---------------------------------------------------------------------------
Ian Murray
Simplified the reset of the _svg_warning_displayed flag in RichIPythonWidget...
r6865 # 'ConsoleWidget' public interface overides
#---------------------------------------------------------------------------
def export_html(self):
""" Shows a dialog to export HTML/XML in various formats.
Ian Murray
Tidying of warning dialog strings...
r6866 Overridden in order to reset the _svg_warning_displayed flag prior
to the export running.
Ian Murray
Simplified the reset of the _svg_warning_displayed flag in RichIPythonWidget...
r6865 """
self._svg_warning_displayed = False
super(RichIPythonWidget, self).export_html()
#---------------------------------------------------------------------------
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 # 'ConsoleWidget' protected interface
#---------------------------------------------------------------------------
epatters
* Added Cut support to ConsoleWidget....
r2990 def _context_menu_make(self, pos):
""" Reimplemented to return a custom context menu for images.
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 """
format = self._control.cursorForPosition(pos).charFormat()
name = format.stringProperty(QtGui.QTextFormat.ImageName)
epatters
Yet more PySide compatibility fixes.
r3307 if name:
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 menu = QtGui.QMenu()
menu.addAction('Copy Image', lambda: self._copy_image(name))
menu.addAction('Save Image As...', lambda: self._save_image(name))
menu.addSeparator()
epatters
Fixed PySide incompatibility with QVariant....
r3364 svg = self._name_to_svg_map.get(name, None)
if svg is not None:
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 menu.addSeparator()
menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg))
Bernardo B. Marques
remove all trailling spaces
r4872 menu.addAction('Save SVG As...',
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 lambda: save_svg(svg, self._control))
epatters
Yet more PySide compatibility fixes.
r3307 else:
menu = super(RichIPythonWidget, self)._context_menu_make(pos)
epatters
* Added Cut support to ConsoleWidget....
r2990 return menu
Brian Granger
Mostly final version of display data....
r3277
#---------------------------------------------------------------------------
# 'BaseFrontendMixin' abstract interface
#---------------------------------------------------------------------------
Matthias BUSSONNIER
more robust code...
r6638 def _pre_image_append(self, msg, prompt_number):
""" Append the Out[] prompt and make the output nicer
Shared code for some the following if statement
"""
self.log.debug("pyout: %s", msg.get('content', ''))
self._append_plain_text(self.output_sep, True)
self._append_html(self._make_out_prompt(prompt_number), True)
self._append_plain_text('\n', True)
Brian Granger
Mostly final version of display data....
r3277
Brian Granger
Display system is fully working now....
r3278 def _handle_pyout(self, msg):
""" Overridden to handle rich data types, like SVG.
"""
if not self._hidden and self._is_from_this_session(msg):
content = msg['content']
Toby Gilham
making it PEP8 compliant
r6153 prompt_number = content.get('execution_count', 0)
Brian Granger
Display system is fully working now....
r3278 data = content['data']
MinRK
respect image size metadata in qtconsole...
r11169 metadata = msg['content']['metadata']
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 if 'image/svg+xml' in data:
Matthias BUSSONNIER
more robust code...
r6638 self._pre_image_append(msg, prompt_number)
epatters
Use safe appending in RichIPythonWidget.
r4057 self._append_svg(data['image/svg+xml'], True)
self._append_html(self.output_sep2, True)
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 elif 'image/png' in data:
Matthias BUSSONNIER
more robust code...
r6638 self._pre_image_append(msg, prompt_number)
MinRK
respect image size metadata in qtconsole...
r11169 png = decodestring(data['image/png'].encode('ascii'))
self._append_png(png, True, metadata=metadata.get('image/png', None))
epatters
Use safe appending in RichIPythonWidget.
r4057 self._append_html(self.output_sep2, True)
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 elif 'image/jpeg' in data and self._jpg_supported:
Matthias BUSSONNIER
more robust code...
r6638 self._pre_image_append(msg, prompt_number)
MinRK
respect image size metadata in qtconsole...
r11169 jpg = decodestring(data['image/jpeg'].encode('ascii'))
self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 self._append_html(self.output_sep2, True)
Brian Granger
Display system is fully working now....
r3278 else:
# Default back to the plain text representation.
return super(RichIPythonWidget, self)._handle_pyout(msg)
Brian Granger
Mostly final version of display data....
r3277 def _handle_display_data(self, msg):
Brian Granger
Display system is fully working now....
r3278 """ Overridden to handle rich data types, like SVG.
Brian Granger
Mostly final version of display data....
r3277 """
if not self._hidden and self._is_from_this_session(msg):
source = msg['content']['source']
data = msg['content']['data']
metadata = msg['content']['metadata']
# Try to use the svg or html representations.
# FIXME: Is this the right ordering of things to try?
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 if 'image/svg+xml' in data:
MinRK
add debug messages to qt handlers
r4793 self.log.debug("display: %s", msg.get('content', ''))
Brian Granger
Mostly final version of display data....
r3277 svg = data['image/svg+xml']
epatters
Use safe appending in RichIPythonWidget.
r4057 self._append_svg(svg, True)
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 elif 'image/png' in data:
MinRK
add debug messages to qt handlers
r4793 self.log.debug("display: %s", msg.get('content', ''))
Brian Granger
More improvements to the display system....
r3279 # PNG data is base64 encoded as it passes over the network
# in a JSON structure so we decode it.
Grahame Bowland
Make PNG images in the Qt console work in Python 3.
r4773 png = decodestring(data['image/png'].encode('ascii'))
MinRK
respect image size metadata in qtconsole...
r11169 self._append_png(png, True, metadata=metadata.get('image/png', None))
Bradley M. Froehle
2to3: Apply has_key fixer.
r7859 elif 'image/jpeg' in data and self._jpg_supported:
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 self.log.debug("display: %s", msg.get('content', ''))
jpg = decodestring(data['image/jpeg'].encode('ascii'))
MinRK
respect image size metadata in qtconsole...
r11169 self._append_jpg(jpg, True, metadata=metadata.get('image/jpeg', None))
Brian Granger
Mostly final version of display data....
r3277 else:
# Default back to the plain text representation.
return super(RichIPythonWidget, self)._handle_display_data(msg)
epatters
* The SVG payload matplotlib backend now works....
r2758 #---------------------------------------------------------------------------
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 # 'RichIPythonWidget' protected interface
#---------------------------------------------------------------------------
MinRK
respect image size metadata in qtconsole...
r11169 def _append_jpg(self, jpg, before_prompt=False, metadata=None):
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 """ Append raw JPG data to the widget."""
MinRK
respect image size metadata in qtconsole...
r11169 self._append_custom(self._insert_jpg, jpg, before_prompt, metadata=metadata)
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636
MinRK
respect image size metadata in qtconsole...
r11169 def _append_png(self, png, before_prompt=False, metadata=None):
epatters
Use safe appending in RichIPythonWidget.
r4057 """ Append raw PNG data to the widget.
Brian Granger
Mostly final version of display data....
r3277 """
MinRK
respect image size metadata in qtconsole...
r11169 self._append_custom(self._insert_png, png, before_prompt, metadata=metadata)
Brian Granger
More improvements to the display system....
r3279
epatters
Use safe appending in RichIPythonWidget.
r4057 def _append_svg(self, svg, before_prompt=False):
""" Append raw SVG data to the widget.
Brian Granger
More improvements to the display system....
r3279 """
epatters
Use safe appending in RichIPythonWidget.
r4057 self._append_custom(self._insert_svg, svg, before_prompt)
Brian Granger
Mostly final version of display data....
r3277
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 def _add_image(self, image):
""" Adds the specified QImage to the document and returns a
QTextImageFormat that references it.
"""
document = self._control.document()
Evan Patterson
Paved the way for PySide support....
r3304 name = str(image.cacheKey())
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 document.addResource(QtGui.QTextDocument.ImageResource,
QtCore.QUrl(name), image)
format = QtGui.QTextImageFormat()
format.setName(name)
return format
def _copy_image(self, name):
""" Copies the ImageResource with 'name' to the clipboard.
"""
image = self._get_image(name)
QtGui.QApplication.clipboard().setImage(image)
def _get_image(self, name):
""" Returns the QImage stored as the ImageResource with 'name'.
"""
document = self._control.document()
epatters
Fixed PySide incompatibility with QVariant....
r3364 image = document.resource(QtGui.QTextDocument.ImageResource,
QtCore.QUrl(name))
return image
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 def _get_image_tag(self, match, path = None, format = "png"):
Mark Voorhies
Updated docstrings, following the convention in kernelmanager.py.
r3132 """ Return (X)HTML mark-up for the image-tag given by match.
Parameters
----------
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 match : re.SRE_Match
Mark Voorhies
Updated docstrings, following the convention in kernelmanager.py.
r3132 A match to an HTML image tag as exported by Qt, with
match.group("Name") containing the matched image ID.
path : string|None, optional [default None]
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 If not None, specifies a path to which supporting files may be
written (e.g., for linked images). If None, all images are to be
included inline.
Mark Voorhies
Updated docstrings, following the convention in kernelmanager.py.
r3132
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 format : "png"|"svg"|"jpg", optional [default "png"]
Mark Voorhies
Updated docstrings, following the convention in kernelmanager.py.
r3132 Format for returned or referenced images.
"""
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 if format in ("png","jpg"):
Mark Voorhies
Add HTML export options...
r3125 try:
image = self._get_image(match.group("name"))
except KeyError:
return "<b>Couldn't find image %s</b>" % match.group("name")
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 if path is not None:
MinRK
html export fixes
r3154 if not os.path.exists(path):
os.mkdir(path)
relpath = os.path.basename(path)
Matthias BUSSONNIER
pylinting
r6637 if image.save("%s/qt_img%s.%s" % (path, match.group("name"), format),
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 "PNG"):
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 return '<img src="%s/qt_img%s.%s">' % (relpath,
match.group("name"),format)
Mark Voorhies
Add HTML export options...
r3125 else:
return "<b>Couldn't save image!</b>"
else:
ba = QtCore.QByteArray()
buffer_ = QtCore.QBuffer(ba)
buffer_.open(QtCore.QIODevice.WriteOnly)
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 image.save(buffer_, format.upper())
Mark Voorhies
Add HTML export options...
r3125 buffer_.close()
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 return '<img src="data:image/%s;base64,\n%s\n" />' % (
format,re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
Mark Voorhies
Add HTML export options...
r3125
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 elif format == "svg":
Mark Voorhies
Add HTML export options...
r3125 try:
epatters
Fixed PySide incompatibility with QVariant....
r3364 svg = str(self._name_to_svg_map[match.group("name")])
Mark Voorhies
Add HTML export options...
r3125 except KeyError:
Ian Murray
Simplified the reset of the _svg_warning_displayed flag in RichIPythonWidget...
r6865 if not self._svg_warning_displayed:
Ian Murray
Improve warning about exporting SVG...
r6864 QtGui.QMessageBox.warning(self, 'Error converting PNG to SVG.',
MinRK
fix InlineBackend config message...
r12236 'Cannot convert PNG images to SVG, export with PNG figures instead. '
MinRK
clarify that InlineBackend.figure_format only applies to matplotlib
r12293 'If you want to export matplotlib figures as SVG, add '
Ian Murray
Tidying of warning dialog strings...
r6866 'to your ipython config:\n\n'
MinRK
fix InlineBackend config message...
r12236 '\tc.InlineBackend.figure_format = \'svg\'\n\n'
Ian Murray
Improve warning about exporting SVG...
r6864 'And regenerate the figures.',
QtGui.QMessageBox.Ok)
self._svg_warning_displayed = True
MinRK
fix InlineBackend config message...
r12236 return ("<b>Cannot convert PNG images to SVG.</b> "
"You must export this session with PNG images. "
MinRK
clarify that InlineBackend.figure_format only applies to matplotlib
r12293 "If you want to export matplotlib figures as SVG, add to your config "
MinRK
fix InlineBackend config message...
r12236 "<span>c.InlineBackend.figure_format = 'svg'</span> "
Ian Murray
Tidying of warning dialog strings...
r6866 "and regenerate the figures.")
Mark Voorhies
Add HTML export options...
r3125
# Not currently checking path, because it's tricky to find a
# cross-browser way to embed external SVG images (e.g., via
# object or embed tags).
# Chop stand-alone header from matplotlib SVG
offset = svg.find("<svg")
assert(offset > -1)
Bernardo B. Marques
remove all trailling spaces
r4872
Mark Voorhies
Add HTML export options...
r3125 return svg[offset:]
else:
return '<b>Unrecognized image format</b>'
Brian Granger
Mostly final version of display data....
r3277
MinRK
respect image size metadata in qtconsole...
r11169 def _insert_jpg(self, cursor, jpg, metadata=None):
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 """ Insert raw PNG data into the widget."""
MinRK
respect image size metadata in qtconsole...
r11169 self._insert_img(cursor, jpg, 'jpg', metadata=metadata)
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636
MinRK
respect image size metadata in qtconsole...
r11169 def _insert_png(self, cursor, png, metadata=None):
epatters
Use safe appending in RichIPythonWidget.
r4057 """ Insert raw PNG data into the widget.
"""
MinRK
respect image size metadata in qtconsole...
r11169 self._insert_img(cursor, png, 'png', metadata=metadata)
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636
MinRK
respect image size metadata in qtconsole...
r11169 def _insert_img(self, cursor, img, fmt, metadata=None):
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 """ insert a raw image, jpg or png """
MinRK
respect image size metadata in qtconsole...
r11169 if metadata:
width = metadata.get('width', None)
height = metadata.get('height', None)
else:
width = height = None
epatters
Use safe appending in RichIPythonWidget.
r4057 try:
image = QtGui.QImage()
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 image.loadFromData(img, fmt.upper())
MinRK
respect image size metadata in qtconsole...
r11169 if width and height:
MinRK
use smooth transform in QtConsole
r11170 image = image.scaled(width, height, transformMode=QtCore.Qt.SmoothTransformation)
MinRK
respect image size metadata in qtconsole...
r11169 elif width and not height:
MinRK
use smooth transform in QtConsole
r11170 image = image.scaledToWidth(width, transformMode=QtCore.Qt.SmoothTransformation)
MinRK
respect image size metadata in qtconsole...
r11169 elif height and not width:
MinRK
use smooth transform in QtConsole
r11170 image = image.scaledToHeight(height, transformMode=QtCore.Qt.SmoothTransformation)
epatters
Use safe appending in RichIPythonWidget.
r4057 except ValueError:
Matthias BUSSONNIER
handle jpg/jpeg in the qtconsole....
r6636 self._insert_plain_text(cursor, 'Received invalid %s data.'%fmt)
epatters
Use safe appending in RichIPythonWidget.
r4057 else:
format = self._add_image(image)
cursor.insertBlock()
cursor.insertImage(format)
cursor.insertBlock()
def _insert_svg(self, cursor, svg):
""" Insert raw SVG data into the widet.
"""
try:
image = svg_to_image(svg)
except ValueError:
self._insert_plain_text(cursor, 'Received invalid SVG data.')
else:
format = self._add_image(image)
self._name_to_svg_map[format.name()] = svg
cursor.insertBlock()
cursor.insertImage(format)
cursor.insertBlock()
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 def _save_image(self, name, format='PNG'):
""" Shows a save dialog for the ImageResource with 'name'.
"""
dialog = QtGui.QFileDialog(self._control, 'Save Image')
dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
dialog.setDefaultSuffix(format.lower())
dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
if dialog.exec_():
filename = dialog.selectedFiles()[0]
image = self._get_image(name)
image.save(filename, format)