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 |
|
|
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 |
|
|
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 |
|
|
|
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 |
|
|
|
542 | os.mkdir(path) | |
|
543 | except OSError: | |
|
544 | # TODO: check that this is an "already exists" error | |
|
545 | pass | |
|
546 | ||
|
547 |
|
|
|
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 |
|
|
|
555 |
|
|
|
556 |
|
|
|
557 | finally: | |
|
558 |
|
|
|
559 |
re |
|
|
560 |
|
|
|
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( |
|
|
962 | menu.addAction(self._select_all_action) | |
|
873 | 963 | |
|
874 | 964 | menu.addSeparator() |
|
875 |
|
|
|
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= |
|
|
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