From e11b615ef6f0a91220562343e7ad9ca739d5f7d6 2010-10-18 23:34:07 From: MinRK Date: 2010-10-18 23:34:07 Subject: [PATCH] HTML/XML export now single menu entry * added keyboard shortcuts for print/save * display dialog when export encounters error --- diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 00d5bf3..e7cbbd5 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -161,10 +161,34 @@ class ConsoleWidget(Configurable, QtGui.QWidget): self._reading_callback = None self._tab_width = 8 self._text_completing_pos = 0 + self._filename = os.path.join(os.curdir, 'ipython.html') # Set a monospaced font. self.reset_font() + # Configure actions. + action = QtGui.QAction('Print', None) + action.setEnabled(True) + action.setShortcut(QtGui.QKeySequence.Print) + action.triggered.connect(self.print_) + self.addAction(action) + self._print_action = action + + action = QtGui.QAction('Save as HTML/XML', None) + action.setEnabled(self.can_export()) + action.setShortcut(QtGui.QKeySequence.Save) + action.triggered.connect(self.export) + self.addAction(action) + self._export_action = action + + action = QtGui.QAction('Select All', None) + action.setEnabled(True) + action.setShortcut(QtGui.QKeySequence.SelectAll) + action.triggered.connect(self.select_all) + self.addAction(action) + self._select_all_action = action + + def eventFilter(self, obj, event): """ Reimplemented to ensure a console-like behavior in the underlying text widgets. @@ -301,6 +325,12 @@ class ConsoleWidget(Configurable, QtGui.QWidget): return not QtGui.QApplication.clipboard().text().isEmpty() return False + def can_export(self): + """Returns whether we can export. Currently only rich widgets + can export html. + """ + return self.kind == "rich" + def clear(self, keep_input=True): """ Clear the console. @@ -502,96 +532,111 @@ class ConsoleWidget(Configurable, QtGui.QWidget): def print_(self, printer = None): """ Print the contents of the ConsoleWidget to the specified QPrinter. """ - if(printer is None): + if (not printer): printer = QtGui.QPrinter() if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted): return self._control.print_(printer) - def export_html_inline(self, parent = None): - """ Export the contents of the ConsoleWidget as HTML with inline PNGs. - """ - self.export_html(parent, inline = True) - - def export_html(self, parent = None, inline = False): + def export(self, parent = None): + """Export HTML/XML in various modes from one Dialog.""" + parent = parent or None # sometimes parent is False + dialog = QtGui.QFileDialog(parent, 'Save Console as...') + dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + filters = [ + 'HTML with inline PNGs (*.html *.htm)', + 'HTML with external PNGs (*.html *.htm)', + 'XHTML with inline SVGs (*.xhtml *.xml)' + ] + dialog.setNameFilters(filters) + if self._filename: + dialog.selectFile(self._filename) + root,ext = os.path.splitext(self._filename) + if ext.lower() in ('.xml', '.xhtml'): + dialog.selectNameFilter(filters[-1]) + if dialog.exec_(): + filename = str(dialog.selectedFiles()[0]) + self._filename = filename + choice = str(dialog.selectedNameFilter()) + + if choice.startswith('XHTML'): + exporter = self.export_xhtml + else: + exporter = lambda filename: self.export_html(filename, 'inline' in choice) + + try: + return exporter(filename) + except Exception, e: + title = self.window().windowTitle() + msg = "Error while saving to: %s\n"%filename+str(e) + reply = QtGui.QMessageBox.warning(self, title, msg, + QtGui.QMessageBox.Ok, QtGui.QMessageBox.Ok) + return None + + def export_html(self, filename, inline=False): """ Export the contents of the ConsoleWidget as HTML. Parameters: ----------- + filename : str + The file to be saved. inline : bool, optional [default True] - If True, include images as inline PNGs. Otherwise, include them as links to external PNG files, mimicking - Firefox's "Web Page, complete" behavior. + web browsers' "Web Page, Complete" behavior. """ - dialog = QtGui.QFileDialog(parent, 'Save HTML Document') - dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - dialog.setDefaultSuffix('html') - dialog.setNameFilter('HTML Document (*.htm *.html *)') - if dialog.exec_(): - filename = str(dialog.selectedFiles()[0]) - if(inline): - path = None - else: - offset = filename.rfind(".") - if(offset > 0): - path = filename[:offset]+"_files" - else: - path = filename+"_files" - if os.path.isfile(path): - raise OSError("%s exists, but is not a dir"%path) - # don't mkdir unless there are images - # try: - # os.mkdir(path) - # except OSError: - # # TODO: check that this is an "already exists" error - # pass - - f = open(filename, 'w') - try: - # N.B. this is overly restrictive, but Qt's output is - # predictable... - img_re = re.compile(r'') - html = self.fix_html_encoding( - str(self._control.toHtml().toUtf8())) - f.write(img_re.sub( - lambda x: self.image_tag(x, path = path, format = "png"), - html)) - finally: - f.close() - return filename - return None + if(inline): + path = None + else: + root,ext = os.path.splitext(filename) + path = root+"_files" + if os.path.isfile(path): + raise OSError("%s exists, but is not a directory."%path) + + f = open(filename, 'w') + try: + # N.B. this is overly restrictive, but Qt's output is + # predictable... + img_re = re.compile(r'') + html = self.fix_html_encoding( + str(self._control.toHtml().toUtf8())) + f.write(img_re.sub( + lambda x: self.image_tag(x, path = path, format = "png"), + html)) + except Exception, e: + f.close() + raise e + else: + f.close() + return filename + - def export_xhtml(self, parent = None): + def export_xhtml(self, filename): """ Export the contents of the ConsoleWidget as XHTML with inline SVGs. """ - dialog = QtGui.QFileDialog(parent, 'Save XHTML Document') - dialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - dialog.setDefaultSuffix('xml') - dialog.setNameFilter('XHTML document (*.xml *.xhtml *)') - if dialog.exec_(): - filename = str(dialog.selectedFiles()[0]) - f = open(filename, 'w') - try: - # N.B. this is overly restrictive, but Qt's output is - # predictable... - img_re = re.compile(r'') - html = str(self._control.toHtml().toUtf8()) - # Hack to make xhtml header -- note that we are not doing - # any check for valid xml - offset = html.find("") - assert(offset > -1) - html = ('\n'+ - html[offset+6:]) - # And now declare UTF-8 encoding - html = self.fix_html_encoding(html) - f.write(img_re.sub( - lambda x: self.image_tag(x, path = None, format = "svg"), - html)) - finally: - f.close() - return filename - return None + f = open(filename, 'w') + try: + # N.B. this is overly restrictive, but Qt's output is + # predictable... + img_re = re.compile(r'') + html = str(self._control.toHtml().toUtf8()) + # Hack to make xhtml header -- note that we are not doing + # any check for valid xml + offset = html.find("") + assert(offset > -1) + html = ('\n'+ + html[offset+6:]) + # And now declare UTF-8 encoding + html = self.fix_html_encoding(html) + f.write(img_re.sub( + lambda x: self.image_tag(x, path = None, format = "svg"), + html)) + except Exception, e: + f.close() + raise e + else: + f.close() + return filename def fix_html_encoding(self, html): """ Return html string, with a UTF-8 declaration added to . @@ -857,7 +902,7 @@ class ConsoleWidget(Configurable, QtGui.QWidget): def _context_menu_make(self, pos): """ Creates a context menu for the given QPoint (in widget coordinates). """ - menu = QtGui.QMenu() + menu = QtGui.QMenu(self) cut_action = menu.addAction('Cut', self.cut) cut_action.setEnabled(self.can_cut()) @@ -872,22 +917,12 @@ class ConsoleWidget(Configurable, QtGui.QWidget): paste_action.setShortcut(QtGui.QKeySequence.Paste) menu.addSeparator() - menu.addAction('Select All', self.select_all) - - if self.kind == 'rich': - # only the rich frontend can export html/xml - menu.addSeparator() - print_action = menu.addAction('Print', self.print_) - print_action.setEnabled(True) - html_action = menu.addAction('Export HTML (external PNGs)', - self.export_html) - html_action.setEnabled(True) - html_inline_action = menu.addAction('Export HTML (inline PNGs)', - self.export_html_inline) - html_inline_action.setEnabled(True) - xhtml_action = menu.addAction('Export XHTML (inline SVGs)', - self.export_xhtml) - xhtml_action.setEnabled(True) + menu.addAction(self._select_all_action) + + menu.addSeparator() + menu.addAction(self._export_action) + menu.addAction(self._print_action) + return menu def _control_key_down(self, modifiers, include_command=True):