##// 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 7 # Standard library imports
8 8 from os.path import commonprefix
9 9 import re
10 import os
10 11 import sys
11 12 from textwrap import dedent
12 13
@@ -160,10 +161,35 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
160 161 self._reading_callback = None
161 162 self._tab_width = 8
162 163 self._text_completing_pos = 0
164 self._filename = 'ipython.html'
165 self._png_mode=None
163 166
164 167 # Set a monospaced font.
165 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 193 def eventFilter(self, obj, event):
168 194 """ Reimplemented to ensure a console-like behavior in the underlying
169 195 text widgets.
@@ -300,6 +326,12 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
300 326 return not QtGui.QApplication.clipboard().text().isEmpty()
301 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 335 def clear(self, keep_input=True):
304 336 """ Clear the console.
305 337
@@ -501,94 +533,152 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
501 533 def print_(self, printer = None):
502 534 """ Print the contents of the ConsoleWidget to the specified QPrinter.
503 535 """
504 if(printer is None):
536 if (not printer):
505 537 printer = QtGui.QPrinter()
506 538 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
507 539 return
508 540 self._control.print_(printer)
509 541
510 def export_html_inline(self, parent = None):
511 """ Export the contents of the ConsoleWidget as HTML with inline PNGs.
512 """
513 self.export_html(parent, inline = True)
514
515 def export_html(self, parent = None, inline = False):
542 def export(self, parent = None):
543 """Export HTML/XML in various modes from one Dialog."""
544 parent = parent or None # sometimes parent is False
545 dialog = QtGui.QFileDialog(parent, 'Save Console as...')
546 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
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 577 """ Export the contents of the ConsoleWidget as HTML.
517 578
518 579 Parameters:
519 580 -----------
581 filename : str
582 The file to be saved.
520 583 inline : bool, optional [default True]
521
522 584 If True, include images as inline PNGs. Otherwise,
523 585 include them as links to external PNG files, mimicking
524 Firefox's "Web Page, complete" behavior.
525 """
526 dialog = QtGui.QFileDialog(parent, 'Save HTML Document')
527 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
528 dialog.setDefaultSuffix('htm')
529 dialog.setNameFilter('HTML document (*.htm)')
530 if dialog.exec_():
531 filename = str(dialog.selectedFiles()[0])
532 if(inline):
533 path = None
586 web browsers' "Web Page, Complete" behavior.
587 """
588 # N.B. this is overly restrictive, but Qt's output is
589 # predictable...
590 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
591 html = self.fix_html_encoding(
592 str(self._control.toHtml().toUtf8()))
593 if self._png_mode:
594 # preference saved, don't ask again
595 if img_re.search(html):
596 inline = (self._png_mode == 'inline')
534 597 else:
535 offset = filename.rfind(".")
536 if(offset > 0):
537 path = filename[:offset]+"_files"
598 inline = True
599 elif img_re.search(html):
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 629 else:
539 path = filename+"_files"
540 import os
541 try:
542 os.mkdir(path)
543 except OSError:
544 # TODO: check that this is an "already exists" error
545 pass
546
547 f = open(filename, 'w')
548 try:
549 # N.B. this is overly restrictive, but Qt's output is
550 # predictable...
551 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
552 html = self.fix_html_encoding(
553 str(self._control.toHtml().toUtf8()))
554 f.write(img_re.sub(
555 lambda x: self.image_tag(x, path = path, format = "png"),
556 html))
557 finally:
558 f.close()
559 return filename
560 return None
630 self._png_mode='external'
631 else:
632 # no images
633 inline = True
634
635 if inline:
636 path = None
637 else:
638 root,ext = os.path.splitext(filename)
639 path = root+"_files"
640 if os.path.isfile(path):
641 raise OSError("%s exists, but is not a directory."%path)
642
643 f = open(filename, 'w')
644 try:
645 f.write(img_re.sub(
646 lambda x: self.image_tag(x, path = path, format = "png"),
647 html))
648 except Exception, e:
649 f.close()
650 raise e
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 657 """ Export the contents of the ConsoleWidget as XHTML with inline SVGs.
564 658 """
565 dialog = QtGui.QFileDialog(parent, 'Save XHTML Document')
566 dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
567 dialog.setDefaultSuffix('xml')
568 dialog.setNameFilter('XHTML document (*.xml)')
569 if dialog.exec_():
570 filename = str(dialog.selectedFiles()[0])
571 f = open(filename, 'w')
572 try:
573 # N.B. this is overly restrictive, but Qt's output is
574 # predictable...
575 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
576 html = str(self._control.toHtml().toUtf8())
577 # Hack to make xhtml header -- note that we are not doing
578 # any check for valid xml
579 offset = html.find("<html>")
580 assert(offset > -1)
581 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
582 html[offset+6:])
583 # And now declare UTF-8 encoding
584 html = self.fix_html_encoding(html)
585 f.write(img_re.sub(
586 lambda x: self.image_tag(x, path = None, format = "svg"),
587 html))
588 finally:
589 f.close()
590 return filename
591 return None
659 f = open(filename, 'w')
660 try:
661 # N.B. this is overly restrictive, but Qt's output is
662 # predictable...
663 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
664 html = str(self._control.toHtml().toUtf8())
665 # Hack to make xhtml header -- note that we are not doing
666 # any check for valid xml
667 offset = html.find("<html>")
668 assert(offset > -1)
669 html = ('<html xmlns="http://www.w3.org/1999/xhtml">\n'+
670 html[offset+6:])
671 # And now declare UTF-8 encoding
672 html = self.fix_html_encoding(html)
673 f.write(img_re.sub(
674 lambda x: self.image_tag(x, path = None, format = "svg"),
675 html))
676 except Exception, e:
677 f.close()
678 raise e
679 else:
680 f.close()
681 return filename
592 682
593 683 def fix_html_encoding(self, html):
594 684 """ Return html string, with a UTF-8 declaration added to <HEAD>.
@@ -854,7 +944,7 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
854 944 def _context_menu_make(self, pos):
855 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 949 cut_action = menu.addAction('Cut', self.cut)
860 950 cut_action.setEnabled(self.can_cut())
@@ -869,23 +959,15 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
869 959 paste_action.setShortcut(QtGui.QKeySequence.Paste)
870 960
871 961 menu.addSeparator()
872 menu.addAction('Select All', self.select_all)
962 menu.addAction(self._select_all_action)
873 963
874 964 menu.addSeparator()
875 print_action = menu.addAction('Print', self.print_)
876 print_action.setEnabled(True)
877 html_action = menu.addAction('Export HTML (external PNGs)',
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)
965 menu.addAction(self._export_action)
966 menu.addAction(self._print_action)
967
886 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 971 """ Given a KeyboardModifiers flags object, return whether the Control
890 972 key is down.
891 973
@@ -1,4 +1,6 b''
1 1 # System library imports
2 import os
3 import re
2 4 from PyQt4 import QtCore, QtGui
3 5
4 6 # Local imports
@@ -154,7 +156,9 b' class RichIPythonWidget(IPythonWidget):'
154 156 return "<b>Couldn't find image %s</b>" % match.group("name")
155 157
156 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 162 if(image.save("%s/qt_img%s.png" % (path,match.group("name")),
159 163 "PNG")):
160 164 return '<img src="%s/qt_img%s.png">' % (relpath,
@@ -167,7 +171,6 b' class RichIPythonWidget(IPythonWidget):'
167 171 buffer_.open(QtCore.QIODevice.WriteOnly)
168 172 image.save(buffer_, "PNG")
169 173 buffer_.close()
170 import re
171 174 return '<img src="data:image/png;base64,\n%s\n" />' % (
172 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