Show More
@@ -507,6 +507,89 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
507 | return |
|
507 | return | |
508 | self._control.print_(printer) |
|
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 | def prompt_to_top(self): |
|
593 | def prompt_to_top(self): | |
511 | """ Moves the prompt to the top of the viewport. |
|
594 | """ Moves the prompt to the top of the viewport. | |
512 | """ |
|
595 | """ | |
@@ -744,7 +827,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):' | |||||
744 | menu.addSeparator() |
|
827 | menu.addSeparator() | |
745 | print_action = menu.addAction('Print', self.print_) |
|
828 | print_action = menu.addAction('Print', self.print_) | |
746 | print_action.setEnabled(True) |
|
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 | return menu |
|
839 | return menu | |
749 |
|
840 | |||
750 | def _control_key_down(self, modifiers, include_command=True): |
|
841 | def _control_key_down(self, modifiers, include_command=True): |
@@ -25,6 +25,9 b' class RichIPythonWidget(IPythonWidget):' | |||||
25 | """ |
|
25 | """ | |
26 | kw['kind'] = 'rich' |
|
26 | kw['kind'] = 'rich' | |
27 | super(RichIPythonWidget, self).__init__(*args, **kw) |
|
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 | # 'ConsoleWidget' protected interface |
|
33 | # 'ConsoleWidget' protected interface | |
@@ -68,6 +71,7 b' class RichIPythonWidget(IPythonWidget):' | |||||
68 | self._append_plain_text('Received invalid plot data.') |
|
71 | self._append_plain_text('Received invalid plot data.') | |
69 | else: |
|
72 | else: | |
70 | format = self._add_image(image) |
|
73 | format = self._add_image(image) | |
|
74 | self._name2svg[str(format.name())] = svg | |||
71 | format.setProperty(self._svg_text_format_property, svg) |
|
75 | format.setProperty(self._svg_text_format_property, svg) | |
72 | cursor = self._get_end_cursor() |
|
76 | cursor = self._get_end_cursor() | |
73 | cursor.insertBlock() |
|
77 | cursor.insertBlock() | |
@@ -121,3 +125,53 b' class RichIPythonWidget(IPythonWidget):' | |||||
121 | filename = dialog.selectedFiles()[0] |
|
125 | filename = dialog.selectedFiles()[0] | |
122 | image = self._get_image(name) |
|
126 | image = self._get_image(name) | |
123 | image.save(filename, format) |
|
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