##// 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,73 +533,129 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...')
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
514
575
515 def export_html(self, parent = None, inline = False):
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')
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
534 else:
535 offset = filename.rfind(".")
536 if(offset > 0):
537 path = filename[:offset]+"_files"
538 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
588 # N.B. this is overly restrictive, but Qt's output is
550 # predictable...
589 # predictable...
551 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
590 img_re = re.compile(r'<img src="(?P<name>[\d]+)" />')
552 html = self.fix_html_encoding(
591 html = self.fix_html_encoding(
553 str(self._control.toHtml().toUtf8()))
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')
597 else:
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'
629 else:
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:
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:
649 f.close()
650 raise e
651 else:
558 f.close()
652 f.close()
559 return filename
653 return filename
560 return None
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')
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')
659 f = open(filename, 'w')
572 try:
660 try:
573 # N.B. this is overly restrictive, but Qt's output is
661 # N.B. this is overly restrictive, but Qt's output is
@@ -585,10 +673,12 b' class ConsoleWidget(Configurable, QtGui.QWidget):'
585 f.write(img_re.sub(
673 f.write(img_re.sub(
586 lambda x: self.image_tag(x, path = None, format = "svg"),
674 lambda x: self.image_tag(x, path = None, format = "svg"),
587 html))
675 html))
588 finally:
676 except Exception, e:
677 f.close()
678 raise e
679 else:
589 f.close()
680 f.close()
590 return filename
681 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