diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py
index d4f939e..354d080 100644
--- a/IPython/frontend/qt/console/console_widget.py
+++ b/IPython/frontend/qt/console/console_widget.py
@@ -7,6 +7,7 @@
# Standard library imports
from os.path import commonprefix
import re
+import os
import sys
from textwrap import dedent
@@ -160,10 +161,35 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
self._reading_callback = None
self._tab_width = 8
self._text_completing_pos = 0
+ self._filename = 'ipython.html'
+ self._png_mode=None
# Set a monospaced font.
self.reset_font()
+ # Configure actions.
+ action = QtGui.QAction('Print', None)
+ action.setEnabled(True)
+ action.setShortcut(QtGui.QKeySequence.Print)
+ action.triggered.connect(self.print_)
+ self.addAction(action)
+ self._print_action = action
+
+ action = QtGui.QAction('Save as HTML/XML', None)
+ action.setEnabled(self.can_export())
+ action.setShortcut(QtGui.QKeySequence.Save)
+ action.triggered.connect(self.export)
+ self.addAction(action)
+ self._export_action = action
+
+ action = QtGui.QAction('Select All', None)
+ action.setEnabled(True)
+ action.setShortcut(QtGui.QKeySequence.SelectAll)
+ action.triggered.connect(self.select_all)
+ self.addAction(action)
+ self._select_all_action = action
+
+
def eventFilter(self, obj, event):
""" Reimplemented to ensure a console-like behavior in the underlying
text widgets.
@@ -300,6 +326,12 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
return not QtGui.QApplication.clipboard().text().isEmpty()
return False
+ def can_export(self):
+ """Returns whether we can export. Currently only rich widgets
+ can export html.
+ """
+ return self.kind == "rich"
+
def clear(self, keep_input=True):
""" Clear the console.
@@ -501,94 +533,152 @@ class ConsoleWidget(Configurable, QtGui.QWidget):
def print_(self, printer = None):
""" Print the contents of the ConsoleWidget to the specified QPrinter.
"""
- if(printer is None):
+ if (not printer):
printer = QtGui.QPrinter()
if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
return
self._control.print_(printer)
- def export_html_inline(self, parent = None):
- """ Export the contents of the ConsoleWidget as HTML with inline PNGs.
- """
- self.export_html(parent, inline = True)
-
- def export_html(self, parent = None, inline = False):
+ def export(self, parent = None):
+ """Export HTML/XML in various modes from one Dialog."""
+ parent = parent or None # sometimes parent is False
+ dialog = QtGui.QFileDialog(parent, 'Save Console as...')
+ dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
+ filters = [
+ 'HTML with PNG figures (*.html *.htm)',
+ 'XHTML with inline SVG figures (*.xhtml *.xml)'
+ ]
+ dialog.setNameFilters(filters)
+ if self._filename:
+ dialog.selectFile(self._filename)
+ root,ext = os.path.splitext(self._filename)
+ if ext.lower() in ('.xml', '.xhtml'):
+ dialog.selectNameFilter(filters[-1])
+ if dialog.exec_():
+ filename = str(dialog.selectedFiles()[0])
+ self._filename = filename
+ choice = str(dialog.selectedNameFilter())
+
+ if choice.startswith('XHTML'):
+ exporter = self.export_xhtml
+ else:
+ exporter = self.export_html
+
+ try:
+ return exporter(filename)
+ except Exception, e:
+ title = self.window().windowTitle()
+ msg = "Error while saving to: %s\n"%filename+str(e)
+ reply = QtGui.QMessageBox.warning(self, title, msg,
+ QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
+ return None
+
+ def export_html(self, filename):
""" Export the contents of the ConsoleWidget as HTML.
Parameters:
-----------
+ filename : str
+ The file to be saved.
inline : bool, optional [default True]
-
If True, include images as inline PNGs. Otherwise,
include them as links to external PNG files, mimicking
- Firefox's "Web Page, complete" behavior.
- """
- dialog = QtGui.QFileDialog(parent, 'Save HTML Document')
- dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
- dialog.setDefaultSuffix('htm')
- dialog.setNameFilter('HTML document (*.htm)')
- if dialog.exec_():
- filename = str(dialog.selectedFiles()[0])
- if(inline):
- path = None
+ web browsers' "Web Page, Complete" behavior.
+ """
+ # N.B. this is overly restrictive, but Qt's output is
+ # predictable...
+ img_re = re.compile(r'')
+ html = self.fix_html_encoding(
+ str(self._control.toHtml().toUtf8()))
+ if self._png_mode:
+ # preference saved, don't ask again
+ if img_re.search(html):
+ inline = (self._png_mode == 'inline')
else:
- offset = filename.rfind(".")
- if(offset > 0):
- path = filename[:offset]+"_files"
+ inline = True
+ elif img_re.search(html):
+ # there are images
+ widget = QtGui.QWidget()
+ layout = QtGui.QVBoxLayout(widget)
+ title = self.window().windowTitle()
+ msg = "Exporting HTML with PNGs"
+ info = "Would you like inline PNGs (single large html file) or "+\
+ "external image files?"
+ checkbox = QtGui.QCheckBox("&Don't ask again")
+ checkbox.setShortcut('D')
+ ib = QtGui.QPushButton("&Inline", self)
+ ib.setShortcut('I')
+ eb = QtGui.QPushButton("&External", self)
+ eb.setShortcut('E')
+ box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
+ box.setInformativeText(info)
+ box.addButton(ib,QtGui.QMessageBox.NoRole)
+ box.addButton(eb,QtGui.QMessageBox.YesRole)
+ box.setDefaultButton(ib)
+ layout.setSpacing(0)
+ layout.addWidget(box)
+ layout.addWidget(checkbox)
+ widget.setLayout(layout)
+ widget.show()
+ reply = box.exec_()
+ inline = (reply == 0)
+ if checkbox.checkState():
+ # don't ask anymore, always use this choice
+ if inline:
+ self._png_mode='inline'
else:
- path = filename+"_files"
- import os
- try:
- os.mkdir(path)
- except OSError:
- # TODO: check that this is an "already exists" error
- pass
-
- f = open(filename, 'w')
- try:
- # N.B. this is overly restrictive, but Qt's output is
- # predictable...
- img_re = re.compile(r'
')
- html = self.fix_html_encoding(
- str(self._control.toHtml().toUtf8()))
- f.write(img_re.sub(
- lambda x: self.image_tag(x, path = path, format = "png"),
- html))
- finally:
- f.close()
- return filename
- return None
+ self._png_mode='external'
+ else:
+ # no images
+ inline = True
+
+ if inline:
+ path = None
+ else:
+ root,ext = os.path.splitext(filename)
+ path = root+"_files"
+ if os.path.isfile(path):
+ raise OSError("%s exists, but is not a directory."%path)
+
+ f = open(filename, 'w')
+ try:
+ f.write(img_re.sub(
+ lambda x: self.image_tag(x, path = path, format = "png"),
+ html))
+ except Exception, e:
+ f.close()
+ raise e
+ else:
+ f.close()
+ return filename
- def export_xhtml(self, parent = None):
+
+ def export_xhtml(self, filename):
""" Export the contents of the ConsoleWidget as XHTML with inline SVGs.
"""
- dialog = QtGui.QFileDialog(parent, 'Save XHTML Document')
- dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
- dialog.setDefaultSuffix('xml')
- dialog.setNameFilter('XHTML document (*.xml)')
- if dialog.exec_():
- filename = str(dialog.selectedFiles()[0])
- f = open(filename, 'w')
- try:
- # N.B. this is overly restrictive, but Qt's output is
- # predictable...
- img_re = re.compile(r'
')
- html = str(self._control.toHtml().toUtf8())
- # Hack to make xhtml header -- note that we are not doing
- # any check for valid xml
- offset = html.find("")
- assert(offset > -1)
- html = ('\n'+
- html[offset+6:])
- # And now declare UTF-8 encoding
- html = self.fix_html_encoding(html)
- f.write(img_re.sub(
- lambda x: self.image_tag(x, path = None, format = "svg"),
- html))
- finally:
- f.close()
- return filename
- return None
+ f = open(filename, 'w')
+ try:
+ # N.B. this is overly restrictive, but Qt's output is
+ # predictable...
+ img_re = re.compile(r'
')
+ html = str(self._control.toHtml().toUtf8())
+ # Hack to make xhtml header -- note that we are not doing
+ # any check for valid xml
+ offset = html.find("")
+ assert(offset > -1)
+ html = ('\n'+
+ html[offset+6:])
+ # And now declare UTF-8 encoding
+ html = self.fix_html_encoding(html)
+ f.write(img_re.sub(
+ lambda x: self.image_tag(x, path = None, format = "svg"),
+ html))
+ except Exception, e:
+ f.close()
+ raise e
+ else:
+ f.close()
+ return filename
def fix_html_encoding(self, html):
""" Return html string, with a UTF-8 declaration added to