##// END OF EJS Templates
Refactored ConsoleWidget's HTML exportaton code + other minor code cleanup.
epatters -
Show More
@@ -0,0 +1,233 b''
1 """ Defines classes and functions for working with Qt's rich text system.
2 """
3 #-----------------------------------------------------------------------------
4 # Imports
5 #-----------------------------------------------------------------------------
6
7 from __future__ import with_statement
8
9 # Standard library imports.
10 import os
11 import re
12
13 # System library imports.
14 from IPython.external.qt import QtGui
15
16 #-----------------------------------------------------------------------------
17 # Constants
18 #-----------------------------------------------------------------------------
19
20 # A regular expression for matching images in rich text HTML.
21 # Note that this is overly restrictive, but Qt's output is predictable...
22 IMG_RE = re.compile(r'<img src="(?P<name>[\d]+)" />')
23
24 #-----------------------------------------------------------------------------
25 # Classes
26 #-----------------------------------------------------------------------------
27
28 class HtmlExporter(object):
29 """ A stateful HTML exporter for a Q(Plain)TextEdit.
30
31 This class is designed for convenient user interaction.
32 """
33
34 def __init__(self, control):
35 """ Creates an HtmlExporter for the given Q(Plain)TextEdit.
36 """
37 assert isinstance(control, (QtGui.QPlainTextEdit, QtGui.QTextEdit))
38 self.control = control
39 self.filename = 'ipython.html'
40 self.image_tag = None
41 self.inline_png = None
42
43 def export(self):
44 """ Displays a dialog for exporting HTML generated by Qt's rich text
45 system.
46
47 Returns
48 -------
49 The name of the file that was saved, or None if no file was saved.
50 """
51 parent = self.control.window()
52 dialog = QtGui.QFileDialog(parent, 'Save as...')
53 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
54 filters = [
55 'HTML with PNG figures (*.html *.htm)',
56 'XHTML with inline SVG figures (*.xhtml *.xml)'
57 ]
58 dialog.setNameFilters(filters)
59 if self.filename:
60 dialog.selectFile(self.filename)
61 root,ext = os.path.splitext(self.filename)
62 if ext.lower() in ('.xml', '.xhtml'):
63 dialog.selectNameFilter(filters[-1])
64
65 if dialog.exec_():
66 self.filename = dialog.selectedFiles()[0]
67 choice = dialog.selectedNameFilter()
68 html = self.control.document().toHtml().encode('utf-8')
69
70 # Configure the exporter.
71 if choice.startswith('XHTML'):
72 exporter = export_xhtml
73 else:
74 # If there are PNGs, decide how to export them.
75 inline = self.inline_png
76 if inline is None and IMG_RE.search(html):
77 dialog = QtGui.QDialog(parent)
78 dialog.setWindowTitle('Save as...')
79 layout = QtGui.QVBoxLayout(dialog)
80 msg = "Exporting HTML with PNGs"
81 info = "Would you like inline PNGs (single large html " \
82 "file) or external image files?"
83 checkbox = QtGui.QCheckBox("&Don't ask again")
84 checkbox.setShortcut('D')
85 ib = QtGui.QPushButton("&Inline")
86 ib.setShortcut('I')
87 eb = QtGui.QPushButton("&External")
88 eb.setShortcut('E')
89 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
90 dialog.windowTitle(), msg)
91 box.setInformativeText(info)
92 box.addButton(ib, QtGui.QMessageBox.NoRole)
93 box.addButton(eb, QtGui.QMessageBox.YesRole)
94 box.setDefaultButton(ib)
95 layout.setSpacing(0)
96 layout.addWidget(box)
97 layout.addWidget(checkbox)
98 dialog.setLayout(layout)
99 dialog.show()
100 reply = box.exec_()
101 dialog.hide()
102 inline = (reply == 0)
103 if checkbox.checkState():
104 # Don't ask anymore; always use this choice.
105 self.inline_png = inline
106 exporter = lambda h, f, i: export_html(h, f, i, inline)
107
108 # Perform the export!
109 try:
110 return exporter(html, self.filename, self.image_tag)
111 except Exception, e:
112 title = self.window().windowTitle()
113 msg = "Error while saving to: %s\n" % filename + str(e)
114 reply = QtGui.QMessageBox.warning(parent, title, msg,
115 QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
116
117 return None
118
119 #-----------------------------------------------------------------------------
120 # Functions
121 #-----------------------------------------------------------------------------
122
123 def export_html(html, filename, image_tag = None, inline = True):
124 """ Export the contents of the ConsoleWidget as HTML.
125
126 Parameters:
127 -----------
128 html : str,
129 A utf-8 encoded Python string containing the Qt HTML to export.
130
131 filename : str
132 The file to be saved.
133
134 image_tag : callable, optional (default None)
135 Used to convert images. See ``default_image_tag()`` for information.
136
137 inline : bool, optional [default True]
138 If True, include images as inline PNGs. Otherwise, include them as
139 links to external PNG files, mimicking web browsers' "Web Page,
140 Complete" behavior.
141 """
142 if image_tag is None:
143 image_tag = default_image_tag
144
145 if inline:
146 path = None
147 else:
148 root,ext = os.path.splitext(filename)
149 path = root + "_files"
150 if os.path.isfile(path):
151 raise OSError("%s exists, but is not a directory." % path)
152
153 with open(filename, 'w') as f:
154 html = fix_html_encoding(html)
155 f.write(IMG_RE.sub(lambda x: image_tag(x, path = path, format = "png"),
156 html))
157
158
159 def export_xhtml(html, filename, image_tag=None):
160 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
161
162 Parameters:
163 -----------
164 html : str,
165 A utf-8 encoded Python string containing the Qt HTML to export.
166
167 filename : str
168 The file to be saved.
169
170 image_tag : callable, optional (default None)
171 Used to convert images. See ``default_image_tag()`` for information.
172 """
173 if image_tag is None:
174 image_tag = default_image_tag
175
176 with open(filename, 'w') as f:
177 # Hack to make xhtml header -- note that we are not doing any check for
178 # valid XML.
179 offset = html.find("<html>")
180 assert(offset > -1)
181 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
182 html[offset+6:])
183
184 html = fix_html_encoding(html)
185 f.write(IMG_RE.sub(lambda x: image_tag(x, path = None, format = "svg"),
186 html))
187
188
189 def default_image_tag(match, path = None, format = "png"):
190 """ Return (X)HTML mark-up for the image-tag given by match.
191
192 This default implementation merely removes the image, and exists mostly
193 for documentation purposes. More information than is present in the Qt
194 HTML is required to supply the images.
195
196 Parameters
197 ----------
198 match : re.SRE_Match
199 A match to an HTML image tag as exported by Qt, with match.group("Name")
200 containing the matched image ID.
201
202 path : string|None, optional [default None]
203 If not None, specifies a path to which supporting files may be written
204 (e.g., for linked images). If None, all images are to be included
205 inline.
206
207 format : "png"|"svg", optional [default "png"]
208 Format for returned or referenced images.
209 """
210 return ''
211
212
213 def fix_html_encoding(html):
214 """ Return html string, with a UTF-8 declaration added to <HEAD>.
215
216 Assumes that html is Qt generated and has already been UTF-8 encoded
217 and coerced to a python string. If the expected head element is
218 not found, the given object is returned unmodified.
219
220 This patching is needed for proper rendering of some characters
221 (e.g., indented commands) when viewing exported HTML on a local
222 system (i.e., without seeing an encoding declaration in an HTTP
223 header).
224
225 C.f. http://www.w3.org/International/O-charset for details.
226 """
227 offset = html.find('<head>')
228 if offset > -1:
229 html = (html[:offset+6]+
230 '\n<meta http-equiv="Content-Type" '+
231 'content="text/html; charset=utf-8" />\n'+
232 html[offset+6:])
233 return html
@@ -17,6 +17,7 b' from IPython.external.qt import QtCore, QtGui'
17
17
18 # Local imports
18 # Local imports
19 from IPython.config.configurable import Configurable
19 from IPython.config.configurable import Configurable
20 from IPython.frontend.qt.rich_text import HtmlExporter
20 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
21 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
21 from IPython.utils.traitlets import Bool, Enum, Int
22 from IPython.utils.traitlets import Bool, Enum, Int
22 from ansi_code_processor import QtAnsiCodeProcessor
23 from ansi_code_processor import QtAnsiCodeProcessor
@@ -170,6 +171,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
170 self._executing = False
171 self._executing = False
171 self._filter_drag = False
172 self._filter_drag = False
172 self._filter_resize = False
173 self._filter_resize = False
174 self._html_exporter = HtmlExporter(self._control)
173 self._prompt = ''
175 self._prompt = ''
174 self._prompt_html = None
176 self._prompt_html = None
175 self._prompt_pos = 0
177 self._prompt_pos = 0
@@ -178,8 +180,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
178 self._reading_callback = None
180 self._reading_callback = None
179 self._tab_width = 8
181 self._tab_width = 8
180 self._text_completing_pos = 0
182 self._text_completing_pos = 0
181 self._filename = 'ipython.html'
182 self._png_mode=None
183
183
184 # Set a monospaced font.
184 # Set a monospaced font.
185 self.reset_font()
185 self.reset_font()
@@ -190,7 +190,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
190 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
190 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
191 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
191 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
192 # Only override the default if there is a collision.
192 # Only override the default if there is a collision.
193 # Qt ctrl = cmd on OSX, so the match gets a false positive on darwin.
193 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
194 printkey = "Ctrl+Shift+P"
194 printkey = "Ctrl+Shift+P"
195 action.setShortcut(printkey)
195 action.setShortcut(printkey)
196 action.triggered.connect(self.print_)
196 action.triggered.connect(self.print_)
@@ -198,9 +198,8 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
198 self._print_action = action
198 self._print_action = action
199
199
200 action = QtGui.QAction('Save as HTML/XML', None)
200 action = QtGui.QAction('Save as HTML/XML', None)
201 action.setEnabled(self.can_export())
202 action.setShortcut(QtGui.QKeySequence.Save)
201 action.setShortcut(QtGui.QKeySequence.Save)
203 action.triggered.connect(self.export)
202 action.triggered.connect(self._html_exporter.export)
204 self.addAction(action)
203 self.addAction(action)
205 self._export_action = action
204 self._export_action = action
206
205
@@ -210,7 +209,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
210 action.triggered.connect(self.select_all)
209 action.triggered.connect(self.select_all)
211 self.addAction(action)
210 self.addAction(action)
212 self._select_all_action = action
211 self._select_all_action = action
213
214
212
215 def eventFilter(self, obj, event):
213 def eventFilter(self, obj, event):
216 """ Reimplemented to ensure a console-like behavior in the underlying
214 """ Reimplemented to ensure a console-like behavior in the underlying
@@ -348,12 +346,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
348 return bool(QtGui.QApplication.clipboard().text())
346 return bool(QtGui.QApplication.clipboard().text())
349 return False
347 return False
350
348
351 def can_export(self):
352 """Returns whether we can export. Currently only rich widgets
353 can export html.
354 """
355 return self.kind == "rich"
356
357 def clear(self, keep_input=True):
349 def clear(self, keep_input=True):
358 """ Clear the console.
350 """ Clear the console.
359
351
@@ -561,194 +553,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
561 return
553 return
562 self._control.print_(printer)
554 self._control.print_(printer)
563
555
564 def export(self, parent = None):
565 """Export HTML/XML in various modes from one Dialog."""
566 parent = parent or None # sometimes parent is False
567 dialog = QtGui.QFileDialog(parent, 'Save Console as...')
568 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
569 filters = [
570 'HTML with PNG figures (*.html *.htm)',
571 'XHTML with inline SVG figures (*.xhtml *.xml)'
572 ]
573 dialog.setNameFilters(filters)
574 if self._filename:
575 dialog.selectFile(self._filename)
576 root,ext = os.path.splitext(self._filename)
577 if ext.lower() in ('.xml', '.xhtml'):
578 dialog.selectNameFilter(filters[-1])
579 if dialog.exec_():
580 filename = str(dialog.selectedFiles()[0])
581 self._filename = filename
582 choice = str(dialog.selectedNameFilter())
583
584 if choice.startswith('XHTML'):
585 exporter = self.export_xhtml
586 else:
587 exporter = self.export_html
588
589 try:
590 return exporter(filename)
591 except Exception, e:
592 title = self.window().windowTitle()
593 msg = "Error while saving to: %s\n"%filename+str(e)
594 reply = QtGui.QMessageBox.warning(self, title, msg,
595 QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
596 return None
597
598 def export_html(self, filename):
599 """ Export the contents of the ConsoleWidget as HTML.
600
601 Parameters:
602 -----------
603 filename : str
604 The file to be saved.
605 inline : bool, optional [default True]
606 If True, include images as inline PNGs. Otherwise,
607 include them as links to external PNG files, mimicking
608 web browsers' "Web Page, Complete" behavior.
609 """
610 # N.B. this is overly restrictive, but Qt's output is
611 # predictable...
612 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
613 html = self.fix_html_encoding(
614 str(self._control.toHtml().toUtf8()))
615 if self._png_mode:
616 # preference saved, don't ask again
617 if img_re.search(html):
618 inline = (self._png_mode == 'inline')
619 else:
620 inline = True
621 elif img_re.search(html):
622 # there are images
623 widget = QtGui.QWidget()
624 layout = QtGui.QVBoxLayout(widget)
625 title = self.window().windowTitle()
626 msg = "Exporting HTML with PNGs"
627 info = "Would you like inline PNGs (single large html file) or "+\
628 "external image files?"
629 checkbox = QtGui.QCheckBox("&Don't ask again")
630 checkbox.setShortcut('D')
631 ib = QtGui.QPushButton("&Inline", self)
632 ib.setShortcut('I')
633 eb = QtGui.QPushButton("&External", self)
634 eb.setShortcut('E')
635 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
636 box.setInformativeText(info)
637 box.addButton(ib,QtGui.QMessageBox.NoRole)
638 box.addButton(eb,QtGui.QMessageBox.YesRole)
639 box.setDefaultButton(ib)
640 layout.setSpacing(0)
641 layout.addWidget(box)
642 layout.addWidget(checkbox)
643 widget.setLayout(layout)
644 widget.show()
645 reply = box.exec_()
646 inline = (reply == 0)
647 if checkbox.checkState():
648 # don't ask anymore, always use this choice
649 if inline:
650 self._png_mode='inline'
651 else:
652 self._png_mode='external'
653 else:
654 # no images
655 inline = True
656
657 if inline:
658 path = None
659 else:
660 root,ext = os.path.splitext(filename)
661 path = root+"_files"
662 if os.path.isfile(path):
663 raise OSError("%s exists, but is not a directory."%path)
664
665 f = open(filename, 'w')
666 try:
667 f.write(img_re.sub(
668 lambda x: self.image_tag(x, path = path, format = "png"),
669 html))
670 except Exception, e:
671 f.close()
672 raise e
673 else:
674 f.close()
675 return filename
676
677
678 def export_xhtml(self, filename):
679 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
680 """
681 f = open(filename, 'w')
682 try:
683 # N.B. this is overly restrictive, but Qt's output is
684 # predictable...
685 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
686 html = str(self._control.toHtml().toUtf8())
687 # Hack to make xhtml header -- note that we are not doing
688 # any check for valid xml
689 offset = html.find("<html>")
690 assert(offset > -1)
691 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
692 html[offset+6:])
693 # And now declare UTF-8 encoding
694 html = self.fix_html_encoding(html)
695 f.write(img_re.sub(
696 lambda x: self.image_tag(x, path = None, format = "svg"),
697 html))
698 except Exception, e:
699 f.close()
700 raise e
701 else:
702 f.close()
703 return filename
704
705 def fix_html_encoding(self, html):
706 """ Return html string, with a UTF-8 declaration added to <HEAD>.
707
708 Assumes that html is Qt generated and has already been UTF-8 encoded
709 and coerced to a python string. If the expected head element is
710 not found, the given object is returned unmodified.
711
712 This patching is needed for proper rendering of some characters
713 (e.g., indented commands) when viewing exported HTML on a local
714 system (i.e., without seeing an encoding declaration in an HTTP
715 header).
716
717 C.f. http://www.w3.org/International/O-charset for details.
718 """
719 offset = html.find("<head>")
720 if(offset > -1):
721 html = (html[:offset+6]+
722 '\n<meta http-equiv="Content-Type" '+
723 'content="text/html; charset=utf-8" />\n'+
724 html[offset+6:])
725
726 return html
727
728 def image_tag(self, match, path = None, format = "png"):
729 """ Return (X)HTML mark-up for the image-tag given by match.
730
731 Parameters
732 ----------
733 match : re.SRE_Match
734 A match to an HTML image tag as exported by Qt, with
735 match.group("Name") containing the matched image ID.
736
737 path : string|None, optional [default None]
738 If not None, specifies a path to which supporting files
739 may be written (e.g., for linked images).
740 If None, all images are to be included inline.
741
742 format : "png"|"svg", optional [default "png"]
743 Format for returned or referenced images.
744
745 Subclasses supporting image display should override this
746 method.
747 """
748
749 # Default case -- not enough information to generate tag
750 return ""
751
752 def prompt_to_top(self):
556 def prompt_to_top(self):
753 """ Moves the prompt to the top of the viewport.
557 """ Moves the prompt to the top of the viewport.
754 """
558 """
@@ -1722,10 +1526,6 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1722 """ Called immediately after a prompt is finished, i.e. when some input
1526 """ Called immediately after a prompt is finished, i.e. when some input
1723 will be processed and a new prompt displayed.
1527 will be processed and a new prompt displayed.
1724 """
1528 """
1725 # Flush all state from the input splitter so the next round of
1726 # reading input starts with a clean buffer.
1727 self._input_splitter.reset()
1728
1729 self._control.setReadOnly(True)
1529 self._control.setReadOnly(True)
1730 self._prompt_finished_hook()
1530 self._prompt_finished_hook()
1731
1531
@@ -1882,6 +1682,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
1882 diff = maximum - scrollbar.maximum()
1682 diff = maximum - scrollbar.maximum()
1883 scrollbar.setRange(0, maximum)
1683 scrollbar.setRange(0, maximum)
1884 scrollbar.setPageStep(step)
1684 scrollbar.setPageStep(step)
1685
1885 # Compensate for undesirable scrolling that occurs automatically due to
1686 # Compensate for undesirable scrolling that occurs automatically due to
1886 # maximumBlockCount() text truncation.
1687 # maximumBlockCount() text truncation.
1887 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1688 if diff < 0 and document.blockCount() == document.maximumBlockCount():
@@ -143,8 +143,9 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
143 document = self._control.document()
143 document = self._control.document()
144 document.contentsChange.connect(self._document_contents_change)
144 document.contentsChange.connect(self._document_contents_change)
145
145
146 # set flag for whether we are connected via localhost
146 # Set flag for whether we are connected via localhost.
147 self._local_kernel = kw.get('local_kernel', FrontendWidget._local_kernel)
147 self._local_kernel = kw.get('local_kernel',
148 FrontendWidget._local_kernel)
148
149
149 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
150 # 'ConsoleWidget' public interface
151 # 'ConsoleWidget' public interface
@@ -192,6 +193,10 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
192 """ Called immediately after a prompt is finished, i.e. when some input
193 """ Called immediately after a prompt is finished, i.e. when some input
193 will be processed and a new prompt displayed.
194 will be processed and a new prompt displayed.
194 """
195 """
196 # Flush all state from the input splitter so the next round of
197 # reading input starts with a clean buffer.
198 self._input_splitter.reset()
199
195 if not self._reading:
200 if not self._reading:
196 self._highlighter.highlighting_on = False
201 self._highlighter.highlighting_on = False
197
202
@@ -383,7 +388,8 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
383 title = self.window().windowTitle()
388 title = self.window().windowTitle()
384 if not msg['content']['restart']:
389 if not msg['content']['restart']:
385 reply = QtGui.QMessageBox.question(self, title,
390 reply = QtGui.QMessageBox.question(self, title,
386 "Kernel has been shutdown permanently. Close the Console?",
391 "Kernel has been shutdown permanently. "
392 "Close the Console?",
387 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
393 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
388 if reply == QtGui.QMessageBox.Yes:
394 if reply == QtGui.QMessageBox.Yes:
389 sys.exit(0)
395 sys.exit(0)
@@ -30,8 +30,12 b' class RichIPythonWidget(IPythonWidget):'
30 """
30 """
31 kw['kind'] = 'rich'
31 kw['kind'] = 'rich'
32 super(RichIPythonWidget, self).__init__(*args, **kw)
32 super(RichIPythonWidget, self).__init__(*args, **kw)
33 # Dictionary for resolving Qt names to images when
33
34 # generating XHTML output
34 # Configure the ConsoleWidget HTML exporter for our formats.
35 self._html_exporter.image_tag = self._get_image_tag
36
37 # Dictionary for resolving Qt names to images when generating XHTML
38 # output
35 self._name_to_svg = {}
39 self._name_to_svg = {}
36
40
37 #---------------------------------------------------------------------------
41 #---------------------------------------------------------------------------
@@ -194,51 +198,35 b' class RichIPythonWidget(IPythonWidget):'
194 QtCore.QUrl(name))
198 QtCore.QUrl(name))
195 return variant.toPyObject()
199 return variant.toPyObject()
196
200
197 def _save_image(self, name, format='PNG'):
201 def _get_image_tag(self, match, path = None, format = "png"):
198 """ Shows a save dialog for the ImageResource with 'name'.
199 """
200 dialog = QtGui.QFileDialog(self._control, 'Save Image')
201 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
202 dialog.setDefaultSuffix(format.lower())
203 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
204 if dialog.exec_():
205 filename = dialog.selectedFiles()[0]
206 image = self._get_image(name)
207 image.save(filename, format)
208
209 def image_tag(self, match, path = None, format = "png"):
210 """ Return (X)HTML mark-up for the image-tag given by match.
202 """ Return (X)HTML mark-up for the image-tag given by match.
211
203
212 Parameters
204 Parameters
213 ----------
205 ----------
214 match : re.SRE_Match
206 match : re.SRE_Match
215 A match to an HTML image tag as exported by Qt, with
207 A match to an HTML image tag as exported by Qt, with
216 match.group("Name") containing the matched image ID.
208 match.group("Name") containing the matched image ID.
217
209
218 path : string|None, optional [default None]
210 path : string|None, optional [default None]
219 If not None, specifies a path to which supporting files
211 If not None, specifies a path to which supporting files may be
220 may be written (e.g., for linked images).
212 written (e.g., for linked images). If None, all images are to be
221 If None, all images are to be included inline.
213 included inline.
222
214
223 format : "png"|"svg", optional [default "png"]
215 format : "png"|"svg", optional [default "png"]
224 Format for returned or referenced images.
216 Format for returned or referenced images.
225
226 Subclasses supporting image display should override this
227 method.
228 """
217 """
229
218 if format == "png":
230 if(format == "png"):
231 try:
219 try:
232 image = self._get_image(match.group("name"))
220 image = self._get_image(match.group("name"))
233 except KeyError:
221 except KeyError:
234 return "<b>Couldn't find image %s</b>" % match.group("name")
222 return "<b>Couldn't find image %s</b>" % match.group("name")
235
223
236 if(path is not None):
224 if path is not None:
237 if not os.path.exists(path):
225 if not os.path.exists(path):
238 os.mkdir(path)
226 os.mkdir(path)
239 relpath = os.path.basename(path)
227 relpath = os.path.basename(path)
240 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
228 if image.save("%s/qt_img%s.png" % (path,match.group("name")),
241 "PNG")):
229 "PNG"):
242 return '<img src="%s/qt_img%s.png">' % (relpath,
230 return '<img src="%s/qt_img%s.png">' % (relpath,
243 match.group("name"))
231 match.group("name"))
244 else:
232 else:
@@ -252,7 +240,7 b' class RichIPythonWidget(IPythonWidget):'
252 return '<img src="data:image/png;base64,\n%s\n" />' % (
240 return '<img src="data:image/png;base64,\n%s\n" />' % (
253 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
241 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
254
242
255 elif(format == "svg"):
243 elif format == "svg":
256 try:
244 try:
257 svg = str(self._name_to_svg[match.group("name")])
245 svg = str(self._name_to_svg[match.group("name")])
258 except KeyError:
246 except KeyError:
@@ -271,3 +259,14 b' class RichIPythonWidget(IPythonWidget):'
271 else:
259 else:
272 return '<b>Unrecognized image format</b>'
260 return '<b>Unrecognized image format</b>'
273
261
262 def _save_image(self, name, format='PNG'):
263 """ Shows a save dialog for the ImageResource with 'name'.
264 """
265 dialog = QtGui.QFileDialog(self._control, 'Save Image')
266 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
267 dialog.setDefaultSuffix(format.lower())
268 dialog.setNameFilter('%s file (*.%s)' % (format, format.lower()))
269 if dialog.exec_():
270 filename = dialog.selectedFiles()[0]
271 image = self._get_image(name)
272 image.save(filename, format)
General Comments 0
You need to be logged in to leave comments. Login now