diff --git a/imgsrc/print-preview.svg b/imgsrc/print-preview.svg deleted file mode 100644 index 6ffe4fafa8..0000000000 --- a/imgsrc/print-preview.svg +++ /dev/null @@ -1,14298 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/resources/images/print-preview.png b/resources/images/print-preview.png deleted file mode 100644 index 88e7cc0c97..0000000000 Binary files a/resources/images/print-preview.png and /dev/null differ diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 5804fda2f9..bde277553a 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -11,7 +11,6 @@ from PyQt5.Qt import ( QPropertyAnimation, QUrl, QInputDialog, QAction, QModelIndex) from calibre.gui2.viewer.ui import Main as MainWindow -from calibre.gui2.viewer.printing import Printing from calibre.gui2.viewer.toc import TOC from calibre.gui2.widgets import ProgressIndicator from calibre.gui2 import ( @@ -156,7 +155,6 @@ class EbookViewer(MainWindow): self.clock_timer.timeout.connect(self.update_clock) self.action_print.triggered.connect(self.print_book) - self.print_menu.actions()[0].triggered.connect(self.print_preview) self.clear_recent_history_action = QAction( _('Clear list of recently opened books'), self) self.clear_recent_history_action.triggered.connect(self.clear_recent_history) @@ -310,12 +308,8 @@ class EbookViewer(MainWindow): open_url(url) def print_book(self): - p = Printing(self.iterator, self) - p.start_print() - - def print_preview(self): - p = Printing(self.iterator, self) - p.start_preview() + from calibre.gui2.viewer.printing import print_book + print_book(self.iterator.pathtoebook, self, self.current_title) def toggle_fullscreen(self): if self.isFullScreen(): diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py index 23c0626a99..74b3b67771 100644 --- a/src/calibre/gui2/viewer/printing.py +++ b/src/calibre/gui2/viewer/printing.py @@ -1,113 +1,193 @@ #!/usr/bin/env python2 -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +# vim:fileencoding=utf-8 from __future__ import (unicode_literals, division, absolute_import, print_function) -__license__ = 'GPL v3' -__copyright__ = '2012, Kovid Goyal ' -__docformat__ = 'restructuredtext en' +__license__ = 'GPL v3' +__copyright__ = '2015, Kovid Goyal ' -from PyQt5.Qt import (QObject, QEventLoop, Qt, QPrintDialog, QPainter, QSize, - QPrintPreviewDialog) -from PyQt5.QtWebKitWidgets import QWebView +import os, subprocess, cPickle, sys +from threading import Thread -from calibre.gui2 import error_dialog -from calibre.ebooks.oeb.display.webview import load_html -from calibre.utils.resources import compiled_coffeescript +from PyQt5.Qt import ( + QFormLayout, QLineEdit, QToolButton, QHBoxLayout, QLabel, QIcon, QPrinter, + QPageSize, QComboBox, QDoubleSpinBox, QCheckBox, QProgressDialog, QTimer) -class Printing(QObject): +from calibre.ptempfile import PersistentTemporaryFile +from calibre.ebooks.conversion.plugins.pdf_output import PAPER_SIZES +from calibre.gui2 import elided_text, error_dialog, choose_save_file, Application, open_local_file +from calibre.gui2.widgets2 import Dialog +from calibre.gui2.viewer.main import vprefs +from calibre.utils.icu import numeric_sort_key +from calibre.utils.ipc.simple_worker import start_pipe_worker - def __init__(self, iterator, parent): - QObject.__init__(self, parent) - self.current_index = 0 - self.iterator = iterator - self.view = QWebView(self.parent()) - self.mf = mf = self.view.page().mainFrame() - for x in (Qt.Horizontal, Qt.Vertical): - mf.setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff) - self.view.loadFinished.connect(self.load_finished) - self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils') - self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged') +class PrintDialog(Dialog): - def load_finished(self, ok): - self.loaded_ok = ok + def __init__(self, book_title, parent=None, prefs=vprefs): + self.book_title = book_title + self.default_file_name = book_title[:75] + '.pdf' + self.paper_size_map = {a:getattr(QPageSize, a.capitalize()) for a in PAPER_SIZES} + Dialog.__init__(self, _('Print to PDF'), 'print-to-pdf', prefs=prefs, parent=parent) - def start_print(self): - self.pd = QPrintDialog(self.parent()) - self.pd.open(self._start_print) + def setup_ui(self): + self.l = l = QFormLayout(self) + l.addRow(QLabel(_('Print %s to a PDF file') % elided_text(self.book_title))) + self.h = h = QHBoxLayout() + self.file_name = f = QLineEdit(self) + f.setText(os.path.abspath(os.path.join(os.path.expanduser('~'), self.default_file_name))) + self.browse_button = b = QToolButton(self) + b.setIcon(QIcon(I('document_open.png'))), b.setToolTip(_('Choose location for PDF file')) + b.clicked.connect(self.choose_file) + h.addWidget(f), h.addWidget(b) + f.setMinimumWidth(350) + w = QLabel(_('&File:')) + l.addRow(w, h), w.setBuddy(f) - def _start_print(self): - self.do_print(self.pd.printer()) + self.paper_size = ps = QComboBox(self) + ps.addItems([a.upper() for a in sorted(self.paper_size_map, key=numeric_sort_key)]) + previous_size = vprefs.get('print-to-pdf-page-size', None) + if previous_size not in self.paper_size_map: + previous_size = (QPrinter().pageLayout().pageSize().name() or '').lower() + if previous_size not in self.paper_size_map: + previous_size = 'a4' + ps.setCurrentIndex(ps.findText(previous_size.upper())) + l.addRow(_('Paper &size:'), ps) + tmap = { + 'left':_('&Left margin:'), + 'top':_('&Top margin:'), + 'right':_('&Right margin:'), + 'bottom':_('&Bottom margin:'), + } + for edge in 'left top right bottom'.split(): + m = QDoubleSpinBox(self) + m.setSuffix(' ' + _('inches')) + m.setMinimum(0), m.setMaximum(3), m.setSingleStep(0.1) + val = vprefs.get('print-to-pdf-%s-margin' % edge, 1) + m.setValue(val) + setattr(self, '%s_margin' % edge, m) + l.addRow(tmap[edge], m) + self.pnum = pnum = QCheckBox(_('Add page &number to printed pages'), self) + pnum.setChecked(vprefs.get('print-to-pdf-page-numbers', True)) + l.addRow(pnum) - def start_preview(self): - self.pd = QPrintPreviewDialog(self.parent()) - self.pd.paintRequested.connect(self.do_print) - self.pd.exec_() + l.addRow(self.bb) - def do_print(self, printer): - painter = QPainter(printer) - zoomx = printer.logicalDpiX()/self.view.logicalDpiX() - zoomy = printer.logicalDpiY()/self.view.logicalDpiY() - painter.scale(zoomx, zoomy) - pr = printer.pageRect() - self.view.page().setViewportSize(QSize(pr.width()/zoomx, - pr.height()/zoomy)) - evaljs = self.mf.evaluateJavaScript - loop = QEventLoop(self) - pagenum = 0 - from_, to = printer.fromPage(), printer.toPage() - first = True + @property + def data(self): + ans = { + 'output': self.file_name.text().strip(), + 'paper_size': self.paper_size.currentText().lower(), + 'page_numbers':self.pnum.isChecked(), + } + for edge in 'left top right bottom'.split(): + ans['margin_' + edge] = getattr(self, '%s_margin' % edge).value() + return ans - for path in self.iterator.spine: - self.loaded_ok = None - load_html(path, self.view, codec=getattr(path, 'encoding', 'utf-8'), - mime_type=getattr(path, 'mime_type', None)) - while self.loaded_ok is None: - loop.processEvents(loop.ExcludeUserInputEvents) - if not self.loaded_ok: - return error_dialog(self.parent(), _('Failed to render'), - _('Failed to render document %s')%path, show=True) - evaljs(self.paged_js) - evaljs(''' - document.body.style.backgroundColor = "white"; - paged_display.set_geometry(1, 0, 0, 0); - paged_display.layout(); - paged_display.fit_images(); - ''') + def choose_file(self): + ans = choose_save_file(self, 'print-to-pdf-choose-file', _('PDF file'), filters=[(_('PDF file'), 'pdf')], + all_files=False, initial_filename=self.default_file_name) + if ans: + self.file_name.setText(ans) - while True: - pagenum += 1 - if (pagenum >= from_ and (to == 0 or pagenum <= to)): - if not first: - printer.newPage() - first = False - self.mf.render(painter) - try: - nsl = int(evaljs('paged_display.next_screen_location()')) - except (TypeError, ValueError): - break - if nsl <= 0: - break - evaljs('window.scrollTo(%d, 0)'%nsl) + def save_used_values(self): + data = self.data + vprefs['print-to-pdf-page-size'] = data['paper_size'] + vprefs['print-to-pdf-page-numbers'] = data['page_numbers'] + for edge in 'left top right bottom'.split(): + vprefs['print-to-pdf-%s-margin' % edge] = data['margin_' + edge] - painter.end() + def accept(self): + fname = self.file_name.text().strip() + if not fname: + return error_dialog(self, _('No filename specified'), _( + 'You must specify a filename for the PDF file to generate'), show=True) + if not fname.lower().endswith('.pdf'): + return error_dialog(self, _('Incorrect filename specified'), _( + 'The filename for the PDF file must end with .pdf'), show=True) + self.save_used_values() + return Dialog.accept(self) + +class DoPrint(Thread): + + daemon = True + + def __init__(self, data): + Thread.__init__(self, name='DoPrint') + self.data = data + self.tb = self.log = None + + def run(self): + try: + with PersistentTemporaryFile('print-to-pdf-log.txt') as f: + p = self.worker = start_pipe_worker('from calibre.gui2.viewer.printing import do_print; do_print()', stdout=f, stderr=subprocess.STDOUT) + p.stdin.write(cPickle.dumps(self.data, -1)), p.stdin.flush(), p.stdin.close() + rc = p.wait() + if rc != 0: + f.seek(0) + self.log = f.read().decode('utf-8', 'replace') + try: + os.remove(f.name) + except EnvironmentError: + pass + except Exception: + import traceback + self.tb = traceback.format_exc() + +def do_print(): + data = cPickle.loads(sys.stdin.read()) + args = ['ebook-convert', data['input'], data['output'], '--override-profile-size', '--paper-size', data['paper_size'], '--pdf-add-toc', + '--disable-remove-fake-margins', '--disable-font-rescaling', '--page-breaks-before', '/', '--chapter-mark', 'none', '-vv'] + if data['page_numbers']: + args.append('--pdf-page-numbers') + for edge in 'left top right bottom'.split(): + args.append('--margin-' + edge), args.append('%.1f' % (data['margin_' + edge] * 72)) + from calibre.ebooks.conversion.cli import main + main(args) + +class Printing(QProgressDialog): + + def __init__(self, thread, parent=None): + QProgressDialog.__init__(self, _('Printing, this will take a while, please wait...'), _('&Cancel'), 0, 0, parent) + self.setWindowTitle(_('Printing...')) + self.setWindowIcon(QIcon(I('print.png'))) + self.thread = thread + self.timer = t = QTimer(self) + t.timeout.connect(self.check) + self.canceled.connect(self.do_cancel) + t.start(100) + + def check(self): + if self.thread.is_alive(): + return + if self.thread.tb or self.thread.log: + error_dialog(self, _('Failed to convert to PDF'), _( + 'Failed to generate PDF file, click "Show details" for more information.'), det_msg=self.thread.tb or self.thread.log, show=True) + else: + open_local_file(self.thread.data['output']) + self.accept() + + def do_cancel(self): + if hasattr(self.thread, 'worker'): + try: + if self.thread.worker.poll() is None: + self.thread.worker.kill() + except EnvironmentError: + import traceback + traceback.print_exc() + self.timer.stop() + self.reject() + +def print_book(path_to_book, parent=None, book_title=None): + book_title = book_title or os.path.splitext(os.path.basename(path_to_book))[0] + d = PrintDialog(book_title, parent) + if d.exec_() == d.Accepted: + data = d.data + data['input'] = path_to_book + t = DoPrint(data) + t.start() + Printing(t, parent).exec_() if __name__ == '__main__': - from calibre.gui2 import Application - from calibre.ebooks.oeb.iterator.book import EbookIterator - from PyQt5.Qt import QPrinter, QTimer - import sys app = Application([]) - - def doit(): - with EbookIterator(sys.argv[-1]) as it: - p = Printing(it, None) - printer = QPrinter() - of = sys.argv[-1]+'.pdf' - printer.setOutputFileName(of) - p.do_print(printer) - print ('Printed to:', of) - app.exit() - QTimer.singleShot(0, doit) - app.exec_() - + print_book(sys.argv[-1]) + del app diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index f38ae6c3be..19092aebf1 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -394,8 +394,7 @@ class Main(MainWindow): a('load_theme', _('Load a theme'), 'wizard.png', menu_name='themes', popup_mode=QToolButton.InstantPopup) self.tool_bar.addSeparator() - a('print', _('Print'), 'print.png', menu_name='print') - self.print_menu.addAction(QIcon(I('print-preview.png')), _('Print Preview')) + a('print', _('Print to PDF file'), 'print.png') a('find_next', _('Find next occurrence'), 'arrow-down.png', tb=self.tool_bar2) a('find_previous', _('Find previous occurrence'), 'arrow-up.png', tb=self.tool_bar2)