##// END OF EJS Templates
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.

File last commit:

r3361:ee1265ca
r3361:ee1265ca
Show More
rich_ipython_widget.py
272 lines | 10.8 KiB | text/x-python | PythonLexer
/ IPython / frontend / qt / console / rich_ipython_widget.py
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
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image
epatters
* The SVG payload matplotlib backend now works....
r2758 from ipython_widget import IPythonWidget
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.
_payload_source_plot = 'IPython.zmq.pylab.backend_payload.add_plot_payload'
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765 _svg_text_format_property = 1
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
# Dictionary for resolving Qt names to images when generating XHTML
# output
Mark Voorhies
Applied lowercase_with_underscores naming convention
r3130 self._name_to_svg = {}
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()
svg = format.stringProperty(self._svg_text_format_property)
epatters
Yet more PySide compatibility fixes.
r3307 if svg:
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))
menu.addAction('Save SVG As...',
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
#---------------------------------------------------------------------------
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']
prompt_number = content['execution_count']
data = content['data']
if data.has_key('image/svg+xml'):
self._append_plain_text(self.output_sep)
self._append_html(self._make_out_prompt(prompt_number))
# TODO: try/except this call.
self._append_svg(data['image/svg+xml'])
self._append_html(self.output_sep2)
Brian Granger
More improvements to the display system....
r3279 elif data.has_key('image/png'):
self._append_plain_text(self.output_sep)
self._append_html(self._make_out_prompt(prompt_number))
Brian Granger
Small fixes for latex/png printing....
r3284 # This helps the output to look nice.
self._append_plain_text('\n')
Brian Granger
More improvements to the display system....
r3279 # TODO: try/except these calls
png = decodestring(data['image/png'])
self._append_png(png)
self._append_html(self.output_sep2)
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?
if data.has_key('image/svg+xml'):
svg = data['image/svg+xml']
# TODO: try/except this call.
self._append_svg(svg)
Brian Granger
More improvements to the display system....
r3279 elif data.has_key('image/png'):
# TODO: try/except these calls
# PNG data is base64 encoded as it passes over the network
# in a JSON structure so we decode it.
png = decodestring(data['image/png'])
self._append_png(png)
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
* Fixed bug where ConsoleWidget accepted non-ASCII characters on paste (and also rich text in 'rich' mode)...
r2762 # 'FrontendWidget' protected interface
epatters
* The SVG payload matplotlib backend now works....
r2758 #---------------------------------------------------------------------------
epatters
* Refactored payload handling mechanism....
r2835 def _process_execute_payload(self, item):
epatters
* Moved KernelManager attribute management code in FrontendWidget into a mixin class usable in any Qt frontend. Registering handlers for message types is now trivial....
r2770 """ Reimplemented to handle matplotlib plot payloads.
epatters
* The SVG payload matplotlib backend now works....
r2758 """
Brian Granger
Display system is fully working now....
r3278 # TODO: remove this as all plot data is coming back through the
# display_data message type.
epatters
* Refactored payload handling mechanism....
r2835 if item['source'] == self._payload_source_plot:
if item['format'] == 'svg':
svg = item['data']
Brian Granger
Mostly final version of display data....
r3277 self._append_svg(svg)
epatters
* Refactored payload handling mechanism....
r2835 return True
epatters
Minor cleanup to RichIPythonWidget.
r2818 else:
epatters
* Refactored payload handling mechanism....
r2835 # Add other plot formats here!
return False
epatters
* Fixed bug where ConsoleWidget accepted non-ASCII characters on paste (and also rich text in 'rich' mode)...
r2762 else:
epatters
Fixed bug introduced by the recent payload processing refactor.
r2846 return super(RichIPythonWidget, self)._process_execute_payload(item)
epatters
* Added a custom context menu to the RichIPythonWidget which allows saving plot as an images or SVG documents....
r2765
#---------------------------------------------------------------------------
# 'RichIPythonWidget' protected interface
#---------------------------------------------------------------------------
Brian Granger
Mostly final version of display data....
r3277 def _append_svg(self, svg):
""" Append raw svg data to the widget.
"""
try:
image = svg_to_image(svg)
except ValueError:
self._append_plain_text('Received invalid plot data.')
else:
format = self._add_image(image)
self._name_to_svg[str(format.name())] = svg
format.setProperty(self._svg_text_format_property, svg)
cursor = self._get_end_cursor()
cursor.insertBlock()
cursor.insertImage(format)
Brian Granger
More improvements to the display system....
r3279 cursor.insertBlock()
def _append_png(self, png):
""" Append raw svg data to the widget.
"""
try:
image = QtGui.QImage()
image.loadFromData(png, 'PNG')
except ValueError:
self._append_plain_text('Received invalid plot data.')
else:
format = self._add_image(image)
cursor = self._get_end_cursor()
cursor.insertBlock()
cursor.insertImage(format)
Brian Granger
Mostly final version of display data....
r3277 cursor.insertBlock()
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()
variant = document.resource(QtGui.QTextDocument.ImageResource,
QtCore.QUrl(name))
return variant.toPyObject()
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
format : "png"|"svg", optional [default "png"]
Format for returned or referenced images.
"""
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 if format == "png":
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)
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 if image.save("%s/qt_img%s.png" % (path,match.group("name")),
"PNG"):
Mark Voorhies
Add HTML export options...
r3125 return '<img src="%s/qt_img%s.png">' % (relpath,
match.group("name"))
else:
return "<b>Couldn't save image!</b>"
else:
ba = QtCore.QByteArray()
buffer_ = QtCore.QBuffer(ba)
buffer_.open(QtCore.QIODevice.WriteOnly)
image.save(buffer_, "PNG")
buffer_.close()
return '<img src="data:image/png;base64,\n%s\n" />' % (
re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
epatters
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
r3361 elif format == "svg":
Mark Voorhies
Add HTML export options...
r3125 try:
Mark Voorhies
Applied lowercase_with_underscores naming convention
r3130 svg = str(self._name_to_svg[match.group("name")])
Mark Voorhies
Add HTML export options...
r3125 except KeyError:
return "<b>Couldn't find image %s</b>" % match.group("name")
# 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)
return svg[offset:]
else:
return '<b>Unrecognized image format</b>'
Brian Granger
Mostly final version of display data....
r3277
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)