##// END OF EJS Templates
Merge remote branch 'minrk/htmlfix' into trunk....
MinRK -
r3177:35e407aa merge
parent child Browse files
Show More
@@ -7,6 +7,7 b''
7 # Standard library imports
7 # Standard library imports
8 from os.path import commonprefix
8 from os.path import commonprefix
9 import re
9 import re
10 import os
10 import sys
11 import sys
11 from textwrap import dedent
12 from textwrap import dedent
12
13
@@ -160,10 +161,35 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
160 self._reading_callback = None
161 self._reading_callback = None
161 self._tab_width = 8
162 self._tab_width = 8
162 self._text_completing_pos = 0
163 self._text_completing_pos = 0
164 self._filename = 'ipython.html'
165 self._png_mode=None
163
166
164 # Set a monospaced font.
167 # Set a monospaced font.
165 self.reset_font()
168 self.reset_font()
166
169
170 # Configure actions.
171 action = QtGui.QAction('Print', None)
172 action.setEnabled(True)
173 action.setShortcut(QtGui.QKeySequence.Print)
174 action.triggered.connect(self.print_)
175 self.addAction(action)
176 self._print_action = action
177
178 action = QtGui.QAction('Save as HTML/XML', None)
179 action.setEnabled(self.can_export())
180 action.setShortcut(QtGui.QKeySequence.Save)
181 action.triggered.connect(self.export)
182 self.addAction(action)
183 self._export_action = action
184
185 action = QtGui.QAction('Select All', None)
186 action.setEnabled(True)
187 action.setShortcut(QtGui.QKeySequence.SelectAll)
188 action.triggered.connect(self.select_all)
189 self.addAction(action)
190 self._select_all_action = action
191
192
167 def eventFilter(self, obj, event):
193 def eventFilter(self, obj, event):
168 """ Reimplemented to ensure a console-like behavior in the underlying
194 """ Reimplemented to ensure a console-like behavior in the underlying
169 text widgets.
195 text widgets.
@@ -300,6 +326,12 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
300 return not QtGui.QApplication.clipboard().text().isEmpty()
326 return not QtGui.QApplication.clipboard().text().isEmpty()
301 return False
327 return False
302
328
329 def can_export(self):
330 """Returns whether we can export. Currently only rich widgets
331 can export html.
332 """
333 return self.kind == "rich"
334
303 def clear(self, keep_input=True):
335 def clear(self, keep_input=True):
304 """ Clear the console.
336 """ Clear the console.
305
337
@@ -501,94 +533,152 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
501 def print_(self, printer = None):
533 def print_(self, printer = None):
502 """ Print the contents of the ConsoleWidget to the specified QPrinter.
534 """ Print the contents of the ConsoleWidget to the specified QPrinter.
503 """
535 """
504 if(printer is None):
536 if (not printer):
505 printer = QtGui.QPrinter()
537 printer = QtGui.QPrinter()
506 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
538 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
507 return
539 return
508 self._control.print_(printer)
540 self._control.print_(printer)
509
541
510 def export_html_inline(self, parent = None):
542 def export(self, parent = None):
511 """ Export the contents of the ConsoleWidget as HTML with inline PNGs.
543 """Export HTML/XML in various modes from one Dialog."""
512 """
544 parent = parent or None # sometimes parent is False
513 self.export_html(parent, inline = True)
545 dialog = QtGui.QFileDialog(parent, 'Save Console as...')
514
546 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
515 def export_html(self, parent = None, inline = False):
547 filters = [
548 'HTML with PNG figures (*.html *.htm)',
549 'XHTML with inline SVG figures (*.xhtml *.xml)'
550 ]
551 dialog.setNameFilters(filters)
552 if self._filename:
553 dialog.selectFile(self._filename)
554 root,ext = os.path.splitext(self._filename)
555 if ext.lower() in ('.xml', '.xhtml'):
556 dialog.selectNameFilter(filters[-1])
557 if dialog.exec_():
558 filename = str(dialog.selectedFiles()[0])
559 self._filename = filename
560 choice = str(dialog.selectedNameFilter())
561
562 if choice.startswith('XHTML'):
563 exporter = self.export_xhtml
564 else:
565 exporter = self.export_html
566
567 try:
568 return exporter(filename)
569 except Exception, e:
570 title = self.window().windowTitle()
571 msg = "Error while saving to: %s\n"%filename+str(e)
572 reply = QtGui.QMessageBox.warning(self, title, msg,
573 QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok)
574 return None
575
576 def export_html(self, filename):
516 """ Export the contents of the ConsoleWidget as HTML.
577 """ Export the contents of the ConsoleWidget as HTML.
517
578
518 Parameters:
579 Parameters:
519 -----------
580 -----------
581 filename : str
582 The file to be saved.
520 inline : bool, optional [default True]
583 inline : bool, optional [default True]
521
522 If True, include images as inline PNGs. Otherwise,
584 If True, include images as inline PNGs. Otherwise,
523 include them as links to external PNG files, mimicking
585 include them as links to external PNG files, mimicking
524 Firefox's "Web Page, complete" behavior.
586 web browsers' "Web Page, Complete" behavior.
525 """
587 """
526 dialog = QtGui.QFileDialog(parent, 'Save HTML Document')
588 # N.B. this is overly restrictive, but Qt's output is
527 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
589 # predictable...
528 dialog.setDefaultSuffix('htm')
590 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
529 dialog.setNameFilter('HTML document (*.htm)')
591 html = self.fix_html_encoding(
530 if dialog.exec_():
592 str(self._control.toHtml().toUtf8()))
531 filename = str(dialog.selectedFiles()[0])
593 if self._png_mode:
532 if(inline):
594 # preference saved, don't ask again
533 path = None
595 if img_re.search(html):
596 inline = (self._png_mode == 'inline')
534 else:
597 else:
535 offset = filename.rfind(".")
598 inline = True
536 if(offset > 0):
599 elif img_re.search(html):
537 path = filename[:offset]+"_files"
600 # there are images
601 widget = QtGui.QWidget()
602 layout = QtGui.QVBoxLayout(widget)
603 title = self.window().windowTitle()
604 msg = "Exporting HTML with PNGs"
605 info = "Would you like inline PNGs (single large html file) or "+\
606 "external image files?"
607 checkbox = QtGui.QCheckBox("&Don't ask again")
608 checkbox.setShortcut('D')
609 ib = QtGui.QPushButton("&Inline", self)
610 ib.setShortcut('I')
611 eb = QtGui.QPushButton("&External", self)
612 eb.setShortcut('E')
613 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
614 box.setInformativeText(info)
615 box.addButton(ib,QtGui.QMessageBox.NoRole)
616 box.addButton(eb,QtGui.QMessageBox.YesRole)
617 box.setDefaultButton(ib)
618 layout.setSpacing(0)
619 layout.addWidget(box)
620 layout.addWidget(checkbox)
621 widget.setLayout(layout)
622 widget.show()
623 reply = box.exec_()
624 inline = (reply == 0)
625 if checkbox.checkState():
626 # don't ask anymore, always use this choice
627 if inline:
628 self._png_mode='inline'
538 else:
629 else:
539 path = filename+"_files"
630 self._png_mode='external'
540 import os
631 else:
541 try:
632 # no images
542 os.mkdir(path)
633 inline = True
543 except OSError:
634
544 # TODO: check that this is an "already exists" error
635 if inline:
545 pass
636 path = None
546
637 else:
547 f = open(filename, 'w')
638 root,ext = os.path.splitext(filename)
548 try:
639 path = root+"_files"
549 # N.B. this is overly restrictive, but Qt's output is
640 if os.path.isfile(path):
550 # predictable...
641 raise OSError("%s exists, but is not a directory."%path)
551 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
642
552 html = self.fix_html_encoding(
643 f = open(filename, 'w')
553 str(self._control.toHtml().toUtf8()))
644 try:
554 f.write(img_re.sub(
645 f.write(img_re.sub(
555 lambda x: self.image_tag(x, path = path, format = "png"),
646 lambda x: self.image_tag(x, path = path, format = "png"),
556 html))
647 html))
557 finally:
648 except Exception, e:
558 f.close()
649 f.close()
559 return filename
650 raise e
560 return None
651 else:
652 f.close()
653 return filename
561
654
562 def export_xhtml(self, parent = None):
655
656 def export_xhtml(self, filename):
563 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
657 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
564 """
658 """
565 dialog = QtGui.QFileDialog(parent, 'Save XHTML Document')
659 f = open(filename, 'w')
566 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
660 try:
567 dialog.setDefaultSuffix('xml')
661 # N.B. this is overly restrictive, but Qt's output is
568 dialog.setNameFilter('XHTML document (*.xml)')
662 # predictable...
569 if dialog.exec_():
663 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
570 filename = str(dialog.selectedFiles()[0])
664 html = str(self._control.toHtml().toUtf8())
571 f = open(filename, 'w')
665 # Hack to make xhtml header -- note that we are not doing
572 try:
666 # any check for valid xml
573 # N.B. this is overly restrictive, but Qt's output is
667 offset = html.find("<html>")
574 # predictable...
668 assert(offset > -1)
575 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
669 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
576 html = str(self._control.toHtml().toUtf8())
670 html[offset+6:])
577 # Hack to make xhtml header -- note that we are not doing
671 # And now declare UTF-8 encoding
578 # any check for valid xml
672 html = self.fix_html_encoding(html)
579 offset = html.find("<html>")
673 f.write(img_re.sub(
580 assert(offset > -1)
674 lambda x: self.image_tag(x, path = None, format = "svg"),
581 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
675 html))
582 html[offset+6:])
676 except Exception, e:
583 # And now declare UTF-8 encoding
677 f.close()
584 html = self.fix_html_encoding(html)
678 raise e
585 f.write(img_re.sub(
679 else:
586 lambda x: self.image_tag(x, path = None, format = "svg"),
680 f.close()
587 html))
681 return filename
588 finally:
589 f.close()
590 return filename
591 return None
592
682
593 def fix_html_encoding(self, html):
683 def fix_html_encoding(self, html):
594 """ Return html string, with a UTF-8 declaration added to <HEAD>.
684 """ Return html string, with a UTF-8 declaration added to <HEAD>.
@@ -854,7 +944,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
854 def _context_menu_make(self, pos):
944 def _context_menu_make(self, pos):
855 """ Creates a context menu for the given QPoint (in widget coordinates).
945 """ Creates a context menu for the given QPoint (in widget coordinates).
856 """
946 """
857 menu = QtGui.QMenu()
947 menu = QtGui.QMenu(self)
858
948
859 cut_action = menu.addAction('Cut', self.cut)
949 cut_action = menu.addAction('Cut', self.cut)
860 cut_action.setEnabled(self.can_cut())
950 cut_action.setEnabled(self.can_cut())
@@ -869,23 +959,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
869 paste_action.setShortcut(QtGui.QKeySequence.Paste)
959 paste_action.setShortcut(QtGui.QKeySequence.Paste)
870
960
871 menu.addSeparator()
961 menu.addSeparator()
872 menu.addAction('Select All', self.select_all)
962 menu.addAction(self._select_all_action)
873
963
874 menu.addSeparator()
964 menu.addSeparator()
875 print_action = menu.addAction('Print', self.print_)
965 menu.addAction(self._export_action)
876 print_action.setEnabled(True)
966 menu.addAction(self._print_action)
877 html_action = menu.addAction('Export HTML (external PNGs)',
967
878 self.export_html)
879 html_action.setEnabled(True)
880 html_inline_action = menu.addAction('Export HTML (inline PNGs)',
881 self.export_html_inline)
882 html_inline_action.setEnabled(True)
883 xhtml_action = menu.addAction('Export XHTML (inline SVGs)',
884 self.export_xhtml)
885 xhtml_action.setEnabled(True)
886 return menu
968 return menu
887
969
888 def _control_key_down(self, modifiers, include_command=True):
970 def _control_key_down(self, modifiers, include_command=False):
889 """ Given a KeyboardModifiers flags object, return whether the Control
971 """ Given a KeyboardModifiers flags object, return whether the Control
890 key is down.
972 key is down.
891
973
@@ -1,4 +1,6 b''
1 # System library imports
1 # System library imports
2 import os
3 import re
2 from PyQt4 import QtCore, QtGui
4 from PyQt4 import QtCore, QtGui
3
5
4 # Local imports
6 # Local imports
@@ -154,7 +156,9 b' class RichIPythonWidget(IPythonWidget):'
154 return "<b>Couldn't find image %s</b>" % match.group("name")
156 return "<b>Couldn't find image %s</b>" % match.group("name")
155
157
156 if(path is not None):
158 if(path is not None):
157 relpath = path[path.rfind("/")+1:]
159 if not os.path.exists(path):
160 os.mkdir(path)
161 relpath = os.path.basename(path)
158 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
162 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
159 "PNG")):
163 "PNG")):
160 return '<img src="%s/qt_img%s.png">' % (relpath,
164 return '<img src="%s/qt_img%s.png">' % (relpath,
@@ -167,7 +171,6 b' class RichIPythonWidget(IPythonWidget):'
167 buffer_.open(QtCore.QIODevice.WriteOnly)
171 buffer_.open(QtCore.QIODevice.WriteOnly)
168 image.save(buffer_, "PNG")
172 image.save(buffer_, "PNG")
169 buffer_.close()
173 buffer_.close()
170 import re
171 return '<img src="data:image/png;base64,\n%s\n" />' % (
174 return '<img src="data:image/png;base64,\n%s\n" />' % (
172 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
175 re.sub(r'(.{60})',r'\1\n',str(ba.toBase64())))
173
176
General Comments 0
You need to be logged in to leave comments. Login now