rich_ipython_widget.py
151 lines
| 6.4 KiB
| text/x-python
|
PythonLexer
Brian Granger
|
r2827 | import os | ||
epatters
|
r2758 | # System library imports | ||
from PyQt4 import QtCore, QtGui | ||||
# Local imports | ||||
epatters
|
r2765 | from IPython.frontend.qt.svg import save_svg, svg_to_clipboard, svg_to_image | ||
epatters
|
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
|
r2765 | # Protected class variables. | ||
_svg_text_format_property = 1 | ||||
epatters
|
r2758 | #--------------------------------------------------------------------------- | ||
epatters
|
r2776 | # 'object' interface | ||
epatters
|
r2758 | #--------------------------------------------------------------------------- | ||
epatters
|
r2776 | def __init__(self, *args, **kw): | ||
epatters
|
r2758 | """ Create a RichIPythonWidget. | ||
""" | ||||
epatters
|
r2776 | kw['kind'] = 'rich' | ||
super(RichIPythonWidget, self).__init__(*args, **kw) | ||||
epatters
|
r2765 | |||
#--------------------------------------------------------------------------- | ||||
# 'ConsoleWidget' protected interface | ||||
#--------------------------------------------------------------------------- | ||||
def _show_context_menu(self, pos): | ||||
""" Reimplemented to show a custom context menu for images. | ||||
""" | ||||
format = self._control.cursorForPosition(pos).charFormat() | ||||
name = format.stringProperty(QtGui.QTextFormat.ImageName) | ||||
if name.isEmpty(): | ||||
super(RichIPythonWidget, self)._show_context_menu(pos) | ||||
else: | ||||
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) | ||||
if not svg.isEmpty(): | ||||
menu.addSeparator() | ||||
menu.addAction('Copy SVG', lambda: svg_to_clipboard(svg)) | ||||
menu.addAction('Save SVG As...', | ||||
lambda: save_svg(svg, self._control)) | ||||
menu.exec_(self._control.mapToGlobal(pos)) | ||||
epatters
|
r2758 | |||
#--------------------------------------------------------------------------- | ||||
epatters
|
r2762 | # 'FrontendWidget' protected interface | ||
epatters
|
r2758 | #--------------------------------------------------------------------------- | ||
epatters
|
r2770 | def _process_execute_ok(self, msg): | ||
""" Reimplemented to handle matplotlib plot payloads. | ||||
epatters
|
r2758 | """ | ||
epatters
|
r2770 | payload = msg['content']['payload'] | ||
epatters
|
r2818 | for item in payload: | ||
Brian Granger
|
r2827 | if item['source'] == 'IPython.zmq.pylab.backend_payload.add_plot_payload': | ||
epatters
|
r2818 | if item['format'] == 'svg': | ||
svg = item['data'] | ||||
try: | ||||
image = svg_to_image(svg) | ||||
except ValueError: | ||||
self._append_plain_text('Received invalid plot data.') | ||||
Brian Granger
|
r2814 | else: | ||
epatters
|
r2818 | format = self._add_image(image) | ||
format.setProperty(self._svg_text_format_property, svg) | ||||
cursor = self._get_end_cursor() | ||||
cursor.insertBlock() | ||||
cursor.insertImage(format) | ||||
cursor.insertBlock() | ||||
Brian Granger
|
r2814 | else: | ||
epatters
|
r2818 | # Add other plot formats here! | ||
Brian Granger
|
r2814 | pass | ||
Brian Granger
|
r2827 | elif item['source'] == 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic': | ||
# TODO: I have implmented the logic for TextMate on the Mac. | ||||
# But, we need to allow payload handlers on the non-rich | ||||
# text IPython widget as well. Furthermore, we should probably | ||||
# move these handlers to separate methods. But, we need to | ||||
# be very careful to process the payload list in order. Thus, | ||||
# we will probably need a _handle_payload method of the | ||||
# base class that dispatches to the separate handler methods | ||||
# for each payload source. If a particular subclass doesn't | ||||
# have a handler for a payload source, it should at least | ||||
# print a nice message. | ||||
filename = item['filename'] | ||||
line_number = item['line_number'] | ||||
if line_number is None: | ||||
cmd = 'mate %s' % filename | ||||
else: | ||||
cmd = 'mate -l %s %s' % (line_number, filename) | ||||
os.system(cmd) | ||||
Brian Granger
|
r2830 | elif item['source'] == 'IPython.zmq.page.page': | ||
# TODO: This is probably a good place to start, but Evan can | ||||
# add better paging capabilities. | ||||
self._append_plain_text(item['data']) | ||||
epatters
|
r2818 | else: | ||
# Add other payload types here! | ||||
pass | ||||
epatters
|
r2762 | else: | ||
epatters
|
r2770 | super(RichIPythonWidget, self)._process_execute_ok(msg) | ||
epatters
|
r2765 | |||
#--------------------------------------------------------------------------- | ||||
# 'RichIPythonWidget' protected interface | ||||
#--------------------------------------------------------------------------- | ||||
def _add_image(self, image): | ||||
""" Adds the specified QImage to the document and returns a | ||||
QTextImageFormat that references it. | ||||
""" | ||||
document = self._control.document() | ||||
name = QtCore.QString.number(image.cacheKey()) | ||||
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() | ||||
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) | ||||