Show More
@@ -507,6 +507,89 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
507 | 507 | return |
|
508 | 508 | self._control.print_(printer) |
|
509 | 509 | |
|
510 | def exportHtmlInline(self, parent = None): | |
|
511 | self.exportHtml(parent, inline = True) | |
|
512 | ||
|
513 | def exportHtml(self, parent = None, inline = False): | |
|
514 | """ Export the contents of the ConsoleWidget as an HTML file. | |
|
515 | ||
|
516 | If inline == True, include images as inline PNGs. Otherwise, | |
|
517 | include them as links to external PNG files, mimicking the | |
|
518 | Firefox's "Web Page, complete" behavior. | |
|
519 | """ | |
|
520 | dialog = QtGui.QFileDialog(parent, 'Save HTML Document') | |
|
521 | dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) | |
|
522 | dialog.setDefaultSuffix('htm') | |
|
523 | dialog.setNameFilter('HTML document (*.htm)') | |
|
524 | if dialog.exec_(): | |
|
525 | filename = str(dialog.selectedFiles()[0]) | |
|
526 | if(inline): | |
|
527 | path = None | |
|
528 | else: | |
|
529 | offset = filename.rfind(".") | |
|
530 | if(offset > 0): | |
|
531 | path = filename[:offset]+"_files" | |
|
532 | else: | |
|
533 | path = filename+"_files" | |
|
534 | import os | |
|
535 | try: | |
|
536 | os.mkdir(path) | |
|
537 | except OSError: | |
|
538 | # TODO: check that this is an "already exists" error | |
|
539 | pass | |
|
540 | ||
|
541 | f = open(filename, 'w') | |
|
542 | try: | |
|
543 | # N.B. this is overly restrictive, but Qt's output is | |
|
544 | # predictable... | |
|
545 | img_re = re.compile(r'<img src="(?P<name>[\d]+)" />') | |
|
546 | f.write(img_re.sub( | |
|
547 | lambda x: self.imagetag(x, path = path, format = "PNG"), | |
|
548 | str(self._control.toHtml().toUtf8()))) | |
|
549 | finally: | |
|
550 | f.close() | |
|
551 | return filename | |
|
552 | return None | |
|
553 | ||
|
554 | def exportXhtml(self, parent = None): | |
|
555 | """ Export the contents of the ConsoleWidget as an XHTML file | |
|
556 | with figures as inline SVG. | |
|
557 | """ | |
|
558 | dialog = QtGui.QFileDialog(parent, 'Save XHTML Document') | |
|
559 | dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) | |
|
560 | dialog.setDefaultSuffix('xml') | |
|
561 | dialog.setNameFilter('XHTML document (*.xml)') | |
|
562 | if dialog.exec_(): | |
|
563 | filename = str(dialog.selectedFiles()[0]) | |
|
564 | f = open(filename, 'w') | |
|
565 | try: | |
|
566 | # N.B. this is overly restrictive, but Qt's output is | |
|
567 | # predictable... | |
|
568 | img_re = re.compile(r'<img src="(?P<name>[\d]+)" />') | |
|
569 | html = str(self._control.toHtml().toUtf8()) | |
|
570 | # Hack to make xhtml header -- note that we are not doing | |
|
571 | # any check for valid xml | |
|
572 | offset = html.find("<html>") | |
|
573 | assert(offset > -1) | |
|
574 | html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+ | |
|
575 | html[offset+6:]) | |
|
576 | f.write(img_re.sub( | |
|
577 | lambda x: self.imagetag(x, path = None, format = "SVG"), | |
|
578 | html)) | |
|
579 | finally: | |
|
580 | f.close() | |
|
581 | return filename | |
|
582 | return None | |
|
583 | ||
|
584 | def imagetag(self, match, path = None): | |
|
585 | """ Given an re.match object matching an image name in an HTML export, | |
|
586 | return an appropriate substitution string for the image tag | |
|
587 | (e.g., link, embedded image, ...). As a side effect, files may | |
|
588 | be generated in the directory given by path.""" | |
|
589 | ||
|
590 | # Default case -- not enough information to generate tag | |
|
591 | return "" | |
|
592 | ||
|
510 | 593 | def prompt_to_top(self): |
|
511 | 594 | """ Moves the prompt to the top of the viewport. |
|
512 | 595 | """ |
@@ -744,7 +827,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||
|
744 | 827 | menu.addSeparator() |
|
745 | 828 | print_action = menu.addAction('Print', self.print_) |
|
746 | 829 | print_action.setEnabled(True) |
|
747 | ||
|
830 | html_action = menu.addAction('Export HTML (external PNGs)', | |
|
831 | self.exportHtml) | |
|
832 | html_action.setEnabled(True) | |
|
833 | html_inline_action = menu.addAction('Export HTML (inline PNGs)', | |
|
834 | self.exportHtmlInline) | |
|
835 | html_inline_action.setEnabled(True) | |
|
836 | xhtml_action = menu.addAction('Export XHTML (inline SVGs)', | |
|
837 | self.exportXhtml) | |
|
838 | xhtml_action.setEnabled(True) | |
|
748 | 839 | return menu |
|
749 | 840 | |
|
750 | 841 | def _control_key_down(self, modifiers, include_command=True): |
@@ -25,6 +25,9 b' class RichIPythonWidget(IPythonWidget):' | |||
|
25 | 25 | """ |
|
26 | 26 | kw['kind'] = 'rich' |
|
27 | 27 | super(RichIPythonWidget, self).__init__(*args, **kw) |
|
28 | # Dictionary for resolving Qt names to images when | |
|
29 | # generating XHTML output | |
|
30 | self._name2svg = {} | |
|
28 | 31 | |
|
29 | 32 | #--------------------------------------------------------------------------- |
|
30 | 33 | # 'ConsoleWidget' protected interface |
@@ -68,6 +71,7 b' class RichIPythonWidget(IPythonWidget):' | |||
|
68 | 71 | self._append_plain_text('Received invalid plot data.') |
|
69 | 72 | else: |
|
70 | 73 | format = self._add_image(image) |
|
74 | self._name2svg[str(format.name())] = svg | |
|
71 | 75 | format.setProperty(self._svg_text_format_property, svg) |
|
72 | 76 | cursor = self._get_end_cursor() |
|
73 | 77 | cursor.insertBlock() |
@@ -121,3 +125,53 b' class RichIPythonWidget(IPythonWidget):' | |||
|
121 | 125 | filename = dialog.selectedFiles()[0] |
|
122 | 126 | image = self._get_image(name) |
|
123 | 127 | image.save(filename, format) |
|
128 | ||
|
129 | def imagetag(self, match, path = None, format = "PNG"): | |
|
130 | """ Given an re.match object matching an image name in an HTML dump, | |
|
131 | return an appropriate substitution string for the image tag | |
|
132 | (e.g., link, embedded image, ...). As a side effect, files may | |
|
133 | be generated in the directory given by path.""" | |
|
134 | ||
|
135 | if(format == "PNG"): | |
|
136 | try: | |
|
137 | image = self._get_image(match.group("name")) | |
|
138 | except KeyError: | |
|
139 | return "<b>Couldn't find image %s</b>" % match.group("name") | |
|
140 | ||
|
141 | if(path is not None): | |
|
142 | relpath = path[path.rfind("/")+1:] | |
|
143 | if(image.save("%s/qt_img%s.png" % (path,match.group("name")), | |
|
144 | "PNG")): | |
|
145 | return '<img src="%s/qt_img%s.png">' % (relpath, | |
|
146 | match.group("name")) | |
|
147 | else: | |
|
148 | return "<b>Couldn't save image!</b>" | |
|
149 | else: | |
|
150 | ba = QtCore.QByteArray() | |
|
151 | buffer_ = QtCore.QBuffer(ba) | |
|
152 | buffer_.open(QtCore.QIODevice.WriteOnly) | |
|
153 | image.save(buffer_, "PNG") | |
|
154 | buffer_.close() | |
|
155 | import re | |
|
156 | return '<img src="data:image/png;base64,\n%s\n" />' % ( | |
|
157 | re.sub(r'(.{60})',r'\1\n',str(ba.toBase64()))) | |
|
158 | ||
|
159 | elif(format == "SVG"): | |
|
160 | try: | |
|
161 | svg = str(self._name2svg[match.group("name")]) | |
|
162 | except KeyError: | |
|
163 | return "<b>Couldn't find image %s</b>" % match.group("name") | |
|
164 | ||
|
165 | # Not currently checking path, because it's tricky to find a | |
|
166 | # cross-browser way to embed external SVG images (e.g., via | |
|
167 | # object or embed tags). | |
|
168 | ||
|
169 | # Chop stand-alone header from matplotlib SVG | |
|
170 | offset = svg.find("<svg") | |
|
171 | assert(offset > -1) | |
|
172 | ||
|
173 | return svg[offset:] | |
|
174 | ||
|
175 | else: | |
|
176 | return '<b>Unrecognized image format</b>' | |
|
177 |
General Comments 0
You need to be logged in to leave comments.
Login now