From 9ff64bd715de0294f2d10d8f39ef282452a212e5 Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 11 Feb 2009 20:19:11 -0500 Subject: [PATCH 1/7] Beginning of PDF conversion --- src/calibre/ebooks/pdf/from_any.py | 69 +++++++++++++ src/calibre/ebooks/pdf/writer.py | 153 +++++++++++++++++++++++++++++ src/calibre/linux.py | 1 + src/calibre/parallel.py | 3 + 4 files changed, 226 insertions(+) create mode 100644 src/calibre/ebooks/pdf/from_any.py create mode 100644 src/calibre/ebooks/pdf/writer.py diff --git a/src/calibre/ebooks/pdf/from_any.py b/src/calibre/ebooks/pdf/from_any.py new file mode 100644 index 0000000000..e4fb937cdb --- /dev/null +++ b/src/calibre/ebooks/pdf/from_any.py @@ -0,0 +1,69 @@ +''' +Convert any ebook format to PDF. +''' + +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net ' \ + 'and Marshall T. Vandegrift ' \ + 'and John Schember ' +__docformat__ = 'restructuredtext en' + +import sys, os, glob, logging + +from calibre.ebooks.epub.from_any import any2epub, formats, USAGE +from calibre.ebooks.epub import config as common_config +from calibre.ptempfile import TemporaryDirectory +from calibre.ebooks.pdf.writer import oeb2pdf, config as pdf_config + +def config(defaults=None): + c = common_config(defaults=defaults, name='pdf') + c.remove_opt('profile') + pdfc = pdf_config(defaults=defaults) + c.update(pdfc) + return c + +def option_parser(usage=USAGE): + usage = usage % ('PDF', formats()) + parser = config().option_parser(usage=usage) + return parser + +def any2pdf(opts, path, notification=None): + ext = os.path.splitext(path)[1] + if not ext: + raise ValueError('Unknown file type: '+path) + ext = ext.lower()[1:] + + if opts.output is None: + opts.output = os.path.splitext(os.path.basename(path))[0]+'.pdf' + + opts.output = os.path.abspath(opts.output) + orig_output = opts.output + + with TemporaryDirectory('_any2pdf') as tdir: + oebdir = os.path.join(tdir, 'oeb') + os.mkdir(oebdir) + opts.output = os.path.join(tdir, 'dummy.epub') + opts.profile = 'None' + opts.dont_split_on_page_breaks = True + orig_bfs = opts.base_font_size2 + opts.base_font_size2 = 0 + any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir) + opts.base_font_size2 = orig_bfs + opf = glob.glob(os.path.join(oebdir, '*.opf'))[0] + opts.output = orig_output + logging.getLogger('html2epub').info(_('Creating PDF file from EPUB...')) + oeb2pdf(opts, opf) + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + if len(args) < 2: + parser.print_help() + print 'No input file specified.' + return 1 + any2pdf(opts, args[1]) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py new file mode 100644 index 0000000000..d46d1fc0ed --- /dev/null +++ b/src/calibre/ebooks/pdf/writer.py @@ -0,0 +1,153 @@ +''' +Write content to PDF. +''' +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' + +import os, logging, shutil, sys +from calibre.ebooks.oeb.base import Logger, OEBBook +from calibre.ebooks.oeb.profile import Context +from calibre.ebooks.epub.iterator import SpineItem +from calibre.ebooks.metadata.opf2 import OPF +from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.customize.ui import run_plugins_on_postprocess +from calibre.utils.config import Config, StringConfig + +from PyQt4 import QtCore +from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, QPrinter, \ + QMetaObject +from PyQt4.Qt import * +from PyQt4.QtWebKit import QWebView + +from pyPdf import PdfFileWriter, PdfFileReader + +class PDFWriter(QObject): + def __init__(self): + if QApplication.instance() is None: + QApplication([]) + QObject.__init__(self) + + self.loop = QEventLoop() + self.view = QWebView() + self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html) + self.render_queue = [] + self.combine_queue = [] + self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') + + def dump(self, oeb, oebpath, path): + self._reset() + + opf = OPF(oebpath, os.path.dirname(oebpath)) + self.render_queue = [SpineItem(i.path) for i in opf.spine] + + self.path = path + + QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection) + self.loop.exec_() + + @QtCore.pyqtSignature('_render_book()') + def _render_book(self): + if len(self.render_queue) == 0: + self._write() + else: + self._render_next() + + def _render_next(self): + item = str(self.render_queue.pop(0)) + self.combine_queue.append(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(item))) + + self.view.load(QUrl(item)) + + def _render_html(self, ok): + if ok: + printer = QPrinter(QPrinter.HighResolution) + printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) + printer.setOutputFormat(QPrinter.PdfFormat) + printer.setOutputFileName(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(str(self.view.url().toLocalFile())))) + self.view.print_(printer) + self._render_book() + + def _reset(self): + self.render_queue = [] + self.combine_queue = [] + self.path = '' + if os.path.exists(self.tmp_path): + shutil.rmtree(self.tmp_path, True) + self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') + + def _write(self): + print self.path + try: + outPDF = PdfFileWriter() + for item in self.combine_queue: + inputPDF = PdfFileReader(file(item, 'rb')) + for page in inputPDF.pages: + outPDF.addPage(page) + outputStream = file(self.path, 'wb') + outPDF.write(outputStream) + outputStream.close() + finally: + self._reset() + self.loop.exit(0) + + +def config(defaults=None): + desc = _('Options to control the conversion to PDF') + if defaults is None: + c = Config('pdf', desc) + else: + c = StringConfig(defaults, desc) + + pdf = c.add_group('PDF', _('PDF options.')) + + return c + + +def option_parser(): + c = config() + parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf') + parser.add_option( + '-o', '--output', default=None, + help=_('Output file. Default is derived from input filename.')) + parser.add_option( + '-v', '--verbose', default=0, action='count', + help=_('Useful for debugging.')) + return parser + +def oeb2pdf(opts, inpath): + logger = Logger(logging.getLogger('oeb2pdf')) + logger.setup_cli_handler(opts.verbose) + outpath = opts.output + if outpath is None: + outpath = os.path.basename(inpath) + outpath = os.path.splitext(outpath)[0] + '.pdf' +# source = opts.source_profile +# if source not in Context.PROFILES: +# logger.error(_('Unknown source profile %r') % source) +# return 1 +# dest = opts.dest_profile +# if dest not in Context.PROFILES: +# logger.error(_('Unknown destination profile %r') % dest) +# return 1 + + oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding) + writer = PDFWriter() + writer.dump(oeb, inpath, outpath) + run_plugins_on_postprocess(outpath, 'pdf') + logger.info(_('Output written to ') + outpath) + +def main(argv=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(argv[1:]) + if len(args) != 1: + parser.print_help() + return 1 + inpath = args[0] + retval = oeb2pdf(opts, inpath) + return retval + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/src/calibre/linux.py b/src/calibre/linux.py index edcfa99342..81b8424199 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -51,6 +51,7 @@ entry_points = { 'any2epub = calibre.ebooks.epub.from_any:main', 'any2lit = calibre.ebooks.lit.from_any:main', 'any2mobi = calibre.ebooks.mobi.from_any:main', + 'any2pdf = calibre.ebooks.pdf.from_any:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', diff --git a/src/calibre/parallel.py b/src/calibre/parallel.py index f9b4513c78..7bdb606642 100644 --- a/src/calibre/parallel.py +++ b/src/calibre/parallel.py @@ -71,6 +71,9 @@ PARALLEL_FUNCS = { 'any2mobi' : ('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None), + 'any2pdf' : + ('calibre.ebooks.pdf.from_any', 'any2pdf', {}, None), + 'feeds2mobi' : ('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'), From 2f3562ca20095b01dd74e9dd6cc139327382b995 Mon Sep 17 00:00:00 2001 From: John Schember Date: Thu, 12 Feb 2009 17:19:38 -0500 Subject: [PATCH 2/7] PDF writer cleanups --- src/calibre/ebooks/pdf/writer.py | 49 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index d46d1fc0ed..079b972990 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -7,8 +7,8 @@ __license__ = 'GPL v3' __copyright__ = '2009, John Schember ' import os, logging, shutil, sys -from calibre.ebooks.oeb.base import Logger, OEBBook -from calibre.ebooks.oeb.profile import Context + +from calibre import LoggingInterface from calibre.ebooks.epub.iterator import SpineItem from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import PersistentTemporaryDirectory @@ -17,8 +17,7 @@ from calibre.utils.config import Config, StringConfig from PyQt4 import QtCore from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, QPrinter, \ - QMetaObject -from PyQt4.Qt import * + QMetaObject, Qt from PyQt4.QtWebKit import QWebView from pyPdf import PdfFileWriter, PdfFileReader @@ -29,6 +28,8 @@ class PDFWriter(QObject): QApplication([]) QObject.__init__(self) + self.logger = logging.getLogger('oeb2pdf') + self.loop = QEventLoop() self.view = QWebView() self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html) @@ -36,12 +37,12 @@ class PDFWriter(QObject): self.combine_queue = [] self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') - def dump(self, oeb, oebpath, path): - self._reset() + def dump(self, oebpath, path): + self._delete_tmpdir() opf = OPF(oebpath, os.path.dirname(oebpath)) self.render_queue = [SpineItem(i.path) for i in opf.spine] - + self.combine_queue = [] self.path = path QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection) @@ -57,28 +58,32 @@ class PDFWriter(QObject): def _render_next(self): item = str(self.render_queue.pop(0)) self.combine_queue.append(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(item))) + + self.logger.info('Processing %s...' % item) self.view.load(QUrl(item)) def _render_html(self, ok): if ok: + item_path = os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(str(self.view.url().toLocalFile()))) + + self.logger.debug('\tRendering item as %s' % item_path) + printer = QPrinter(QPrinter.HighResolution) printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) printer.setOutputFormat(QPrinter.PdfFormat) - printer.setOutputFileName(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(str(self.view.url().toLocalFile())))) + printer.setOutputFileName(item_path) self.view.print_(printer) self._render_book() - def _reset(self): - self.render_queue = [] - self.combine_queue = [] - self.path = '' + def _delete_tmpdir(self): if os.path.exists(self.tmp_path): shutil.rmtree(self.tmp_path, True) self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') def _write(self): - print self.path + self.logger.info('Combining individual PDF parts...') + try: outPDF = PdfFileWriter() for item in self.combine_queue: @@ -89,7 +94,7 @@ class PDFWriter(QObject): outPDF.write(outputStream) outputStream.close() finally: - self._reset() + self._delete_tmpdir() self.loop.exit(0) @@ -117,26 +122,18 @@ def option_parser(): return parser def oeb2pdf(opts, inpath): - logger = Logger(logging.getLogger('oeb2pdf')) + logger = LoggingInterface(logging.getLogger('oeb2pdf')) logger.setup_cli_handler(opts.verbose) + outpath = opts.output if outpath is None: outpath = os.path.basename(inpath) outpath = os.path.splitext(outpath)[0] + '.pdf' -# source = opts.source_profile -# if source not in Context.PROFILES: -# logger.error(_('Unknown source profile %r') % source) -# return 1 -# dest = opts.dest_profile -# if dest not in Context.PROFILES: -# logger.error(_('Unknown destination profile %r') % dest) -# return 1 - oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding) writer = PDFWriter() - writer.dump(oeb, inpath, outpath) + writer.dump(inpath, outpath) run_plugins_on_postprocess(outpath, 'pdf') - logger.info(_('Output written to ') + outpath) + logger.log_info(_('Output written to ') + outpath) def main(argv=sys.argv): parser = option_parser() From 86ad16de680d524dc966ee6b781055a33238b5c6 Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 13 Feb 2009 07:38:32 -0500 Subject: [PATCH 3/7] Fix bug 1059: First page of pdf used as cover image --- src/calibre/ebooks/metadata/pdf.py | 100 +++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 19 deletions(-) diff --git a/src/calibre/ebooks/metadata/pdf.py b/src/calibre/ebooks/metadata/pdf.py index ad59351248..8ff652c01b 100644 --- a/src/calibre/ebooks/metadata/pdf.py +++ b/src/calibre/ebooks/metadata/pdf.py @@ -1,16 +1,37 @@ -__license__ = 'GPL v3' -__copyright__ = '2008, Kovid Goyal ' '''Read meta information from PDF files''' -import sys, os, re +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' + +import sys, os, re, StringIO from calibre.ebooks.metadata import MetaInformation, authors_to_string, get_parser -from pyPdf import PdfFileReader +from calibre.ptempfile import TemporaryDirectory +from pyPdf import PdfFileReader, PdfFileWriter +import Image +try: + from calibre.utils.PythonMagickWand import \ + NewMagickWand, MagickReadImage, MagickSetImageFormat, MagickWriteImage + _imagemagick_loaded = True +except: + _imagemagick_loaded = False -def get_metadata(stream): +def get_metadata(stream, extract_cover=True): """ Return metadata as a L{MetaInfo} object """ mi = MetaInformation(_('Unknown'), [_('Unknown')]) stream.seek(0) + + if extract_cover and _imagemagick_loaded: + try: + cdata = get_cover(stream) + if cdata is not None: + mi.cover_data = ('jpg', cdata) + except: + import traceback + traceback.print_exc() + try: info = PdfFileReader(stream).getDocumentInfo() if info.title: @@ -45,27 +66,68 @@ def set_metadata(stream, mi): stream.write(raw) stream.seek(0) +def get_cover(stream): + try: + pdf = PdfFileReader(stream) + output = PdfFileWriter() + + if len(pdf.pages) >= 1: + output.addPage(pdf.getPage(0)) + + with TemporaryDirectory('_pdfmeta') as tdir: + cover_path = os.path.join(tdir, 'cover.pdf') + + outputStream = file(cover_path, "wb") + output.write(outputStream) + outputStream.close() + + wand = NewMagickWand() + MagickReadImage(wand, cover_path) + MagickSetImageFormat(wand, 'JPEG') + MagickWriteImage(wand, '%s.jpg' % cover_path) + + img = Image.open('%s.jpg' % cover_path) + + data = StringIO.StringIO() + img.save(data, 'JPEG') + return data.getvalue() + except: + import traceback + traceback.print_exc() + def option_parser(): p = get_parser('pdf') p.remove_option('--category') p.remove_option('--comment') + p.add_option('--get-cover', default=False, action='store_true', + help=_('Extract the cover')) return p def main(args=sys.argv): - #p = option_parser() - #opts, args = p.parse_args(args) - if len(args) != 2: - print >>sys.stderr, _('Usage: pdf-meta file.pdf') - print >>sys.stderr, _('No filename specified.') - return 1 - - stream = open(os.path.abspath(os.path.expanduser(args[1])), 'r+b') - #mi = MetaInformation(opts.title, opts.authors) - #if mi.title or mi.authors: - # set_metadata(stream, mi) - print unicode(get_metadata(stream)).encode('utf-8') - + p = option_parser() + opts, args = p.parse_args(args) + + with open(os.path.abspath(os.path.expanduser(args[1])), 'r+b') as stream: + mi = get_metadata(stream, extract_cover=opts.get_cover) + changed = False + if opts.title: + mi.title = opts.title + changed = True + if opts.authors: + mi.authors = opts.authors.split(',') + changed = True + + if changed: + set_metadata(stream, mi) + print unicode(get_metadata(stream, extract_cover=False)).encode('utf-8') + + if mi.cover_data[1] is not None: + cpath = os.path.splitext(os.path.basename(args[1]))[0] + '_cover.jpg' + with open(cpath, 'wb') as f: + f.write(mi.cover_data[1]) + print 'Cover saved to', f.name + return 0 if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) From cef40cd73ce243a47a0f2f4611e0e415e4ff00de Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 Feb 2009 07:44:07 -0500 Subject: [PATCH 4/7] Make PDF conversion work with epubs which have content in multiple directories. --- src/calibre/ebooks/pdf/writer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 079b972990..cfdd3bb336 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -56,8 +56,8 @@ class PDFWriter(QObject): self._render_next() def _render_next(self): - item = str(self.render_queue.pop(0)) - self.combine_queue.append(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(item))) + item = str(self.render_queue.pop(0)) + self.combine_queue.append(os.path.join(self.tmp_path, '%s_%i.pdf' % (os.path.basename(item), len(self.combine_queue)))) self.logger.info('Processing %s...' % item) @@ -65,7 +65,7 @@ class PDFWriter(QObject): def _render_html(self, ok): if ok: - item_path = os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(str(self.view.url().toLocalFile()))) + item_path = os.path.join(self.tmp_path, '%s_%i.pdf' % (os.path.basename(str(self.view.url().toLocalFile())), len(self.combine_queue) - 1)) self.logger.debug('\tRendering item as %s' % item_path) From 21619e1b4fd187f19f171f5523d2cbd92fc7a3b4 Mon Sep 17 00:00:00 2001 From: John Schember Date: Thu, 19 Feb 2009 21:06:52 -0500 Subject: [PATCH 5/7] implement bug #739 --- src/calibre/gui2/images/print-preview.svg | 14298 ++++++++++++++++++++ src/calibre/gui2/images/print.svg | 14229 +++++++++++++++++++ src/calibre/gui2/viewer/main.py | 11 + src/calibre/gui2/viewer/main.ui | 15 +- src/calibre/gui2/viewer/printing.py | 125 + 5 files changed, 28676 insertions(+), 2 deletions(-) create mode 100644 src/calibre/gui2/images/print-preview.svg create mode 100644 src/calibre/gui2/images/print.svg create mode 100644 src/calibre/gui2/viewer/printing.py diff --git a/src/calibre/gui2/images/print-preview.svg b/src/calibre/gui2/images/print-preview.svg new file mode 100644 index 0000000000..6ffe4fafa8 --- /dev/null +++ b/src/calibre/gui2/images/print-preview.svg @@ -0,0 +1,14298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/calibre/gui2/images/print.svg b/src/calibre/gui2/images/print.svg new file mode 100644 index 0000000000..dffa8b94ba --- /dev/null +++ b/src/calibre/gui2/images/print.svg @@ -0,0 +1,14229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index fedebc66d7..6be7d9a9ae 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -12,6 +12,7 @@ from PyQt4.Qt import QMovie, QApplication, Qt, QIcon, QTimer, QWidget, SIGNAL, \ QToolButton, QMenu, QInputDialog from calibre.gui2.viewer.main_ui import Ui_EbookViewer +from calibre.gui2.viewer.printing import Printing from calibre.gui2.main_window import MainWindow from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \ info_dialog, error_dialog @@ -267,6 +268,16 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.tool_bar2.setContextMenuPolicy(Qt.PreventContextMenu) self.tool_bar.widgetForAction(self.action_bookmark).setPopupMode(QToolButton.MenuButtonPopup) self.action_full_screen.setCheckable(True) + + self.print_menu = QMenu() + self.print_menu.addAction(QIcon(':/images/print-preview.svg'), _('Print Preview')) + self.action_print.setMenu(self.print_menu) + self.tool_bar.widgetForAction(self.action_print).setPopupMode(QToolButton.MenuButtonPopup) + self.connect(self.action_print, SIGNAL("triggered(bool)"), partial(self.print_book, preview=False)) + self.connect(self.print_menu.actions()[0], SIGNAL("triggered(bool)"), partial(self.print_book, preview=True)) + + def print_book(self, preview): + Printing(self.iterator.spine, preview) def toggle_fullscreen(self, x): if self.isFullScreen(): diff --git a/src/calibre/gui2/viewer/main.ui b/src/calibre/gui2/viewer/main.ui index 59f813b2bd..c4a571be00 100644 --- a/src/calibre/gui2/viewer/main.ui +++ b/src/calibre/gui2/viewer/main.ui @@ -27,8 +27,8 @@ - - + + about:blank @@ -89,6 +89,8 @@ + + @@ -234,6 +236,15 @@ Toggle full screen + + + + :/images/print.svg:/images/print.svg + + + Print + + diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py new file mode 100644 index 0000000000..8644cd311b --- /dev/null +++ b/src/calibre/gui2/viewer/printing.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +import os, sys, traceback, urlparse + +from BeautifulSoup import BeautifulSoup, Tag + +from calibre.ebooks.epub.iterator import EbookIterator +from calibre.ptempfile import TemporaryDirectory + +from PyQt4 import QtCore +from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, Qt, \ + QPrinter, QPrintPreviewDialog, QPrintDialog, QDialog, QMetaObject, Q_ARG +from PyQt4 import QtCore +from PyQt4.QtWebKit import QWebView + +PRINTCSS = 'body{width:100%;margin:0;padding:0;font-family:Arial;color:#000;background:none;font-size:12pt;text-align:left;}h1,h2,h3,h4,h5,h6{font-family:Helvetica;}h1{font-size:19pt;}h2{font-size:17pt;}h3{font-size:15pt;}h4,h5,h6{font-size:12pt;}pre,code,samp{font:10ptCourier,monospace;white-space:pre-wrap;page-break-inside:avoid;}blockquote{margin:1.3em;padding:1em;font-size:10pt;}hr{background-color:#ccc;}aimg{border:none;}a:link,a:visited{background:transparent;font-weight:700;text-decoration:underline;color:#333;}a:link:after,a{color:#000;}table{margin:1px;text-align:left;}th{border-bottom:1pxsolid#333;font-weight:bold;}td{border-bottom:1pxsolid#333;}th,td{padding:4px10px4px0;}tfoot{font-style:italic;}caption{background:#fff;margin-bottom:2em;text-align:left;}thead{display:table-header-group;}tr{page-break-inside:avoid;}#header,.header,#footer,.footer,#navbar,.navbar,#navigation,.navigation,#rightSideBar,.rightSideBar,#leftSideBar,.leftSideBar{display:none;}' + +class Printing(QObject): + def __init__(self, spine, preview): + if QApplication.instance() is None: + QApplication([]) + QObject.__init__(self) + self.loop = QEventLoop() + + self.view = QWebView() + if preview: + self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_preview) + else: + self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_book) + + self.process_content(spine) + + def process_content(self, spine): + content = '' + + for path in spine: + raw = self.raw_content(path) + content += self.parsed_content(raw, path) + + refined_content = self.refine_content(content) + + base = os.path.splitdrive(spine[0])[0] + base = base if base != '' else '/' + + QMetaObject.invokeMethod(self, "load_content", Qt.QueuedConnection, Q_ARG('QString', refined_content), Q_ARG('QString', base)) + self.loop.exec_() + + @QtCore.pyqtSignature('load_content(QString, QString)') + def load_content(self, content, base): + self.view.setHtml(content, QUrl(base)) + + def raw_content(self, path): + return open(path, 'rb').read().decode(path.encoding) + + def parsed_content(self, raw_content, path): + dom_tree = BeautifulSoup(raw_content).body + + # Remove sytle information that is applied to the entire document. + # This does not remove styles applied within a tag. + styles = dom_tree.findAll('style') + for s in styles: + s.extract() + + scripts = dom_tree.findAll('script') + for s in scripts: + s.extract() + + # Convert all relative links to absolute paths. + links = dom_tree.findAll(src=True) + for s in links: + if QUrl(s['src']).isRelative(): + s['src'] = urlparse.urljoin(path, s['src']) + links = dom_tree.findAll(href=True) + for s in links: + if QUrl(s['href']).isRelative(): + s['href'] = urlparse.urljoin(path, s['href']) + + return unicode(dom_tree) + + # Adds the begenning and endings tags to the document. + # Adds the print css. + def refine_content(self, content): + dom_tree = BeautifulSoup('%s' % content) + + css = dom_tree.findAll('link') + for c in css: + c.extract() + + print_css = Tag(BeautifulSoup(), 'style', [('type', 'text/css'), ('title', 'override_css')]) + print_css.insert(0, PRINTCSS) + dom_tree.findAll('head')[0].insert(0, print_css) + + return unicode(dom_tree) + + def print_preview(self, ok): + printer = QPrinter(QPrinter.HighResolution) + printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) + + previewDialog = QPrintPreviewDialog(printer) + + self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_) + previewDialog.exec_() + self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_) + + self.loop.quit() + + def print_book(self, ok): + printer = QPrinter(QPrinter.HighResolution) + printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) + + printDialog = QPrintDialog(printer) + printDialog.setWindowTitle(_("Print eBook")) + + printDialog.exec_() + if printDialog.result() == QDialog.Accepted: + self.view.print_(printer) + + self.loop.quit() + +def main(): + return 0 + +if __name__ == '__main__': + sys.exit(main()) + From 480a3cd3bbfb68f55d2b47d0d36d3e67d8e6834a Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 21 Feb 2009 13:41:22 -0500 Subject: [PATCH 6/7] Fix Cybook cover size being a postage stamp withing the thumbnail generated. --- src/calibre/devices/cybookg3/driver.py | 2 ++ src/calibre/devices/cybookg3/t2b.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index f092473675..16f27e03e8 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -35,6 +35,8 @@ class CYBOOKG3(USBMS): EBOOK_DIR_CARD = "eBooks" SUPPORTS_SUB_DIRS = True + THUMBNAIL_HEIGHT = 144 + def upload_books(self, files, names, on_card=False, end_session=True, metadata=None): if on_card and not self._card_prefix: diff --git a/src/calibre/devices/cybookg3/t2b.py b/src/calibre/devices/cybookg3/t2b.py index 5bf512f22d..7aaeeb63d7 100644 --- a/src/calibre/devices/cybookg3/t2b.py +++ b/src/calibre/devices/cybookg3/t2b.py @@ -30,7 +30,7 @@ def write_t2b(t2bfile, coverdata=None): if coverdata != None: coverdata = StringIO.StringIO(coverdata) cover = Image.open(coverdata).convert("L") - cover.thumbnail((96, 144)) + cover.thumbnail((96, 144), Image.ANTIALIAS) t2bcover = Image.new('L', (96, 144), 'white') x, y = cover.size From e496f88ed0c4d007425e0a6ae52631359d0aca5b Mon Sep 17 00:00:00 2001 From: John Schember Date: Wed, 25 Feb 2009 09:50:42 -0500 Subject: [PATCH 7/7] eBook-viewer: Basic bookmark manager --- src/calibre/ebooks/epub/iterator.py | 5 +- src/calibre/gui2/viewer/bookmarkmanager.py | 93 ++++++++++++++++++++++ src/calibre/gui2/viewer/bookmarkmanager.ui | 57 +++++++++++++ src/calibre/gui2/viewer/main.py | 24 +++++- src/calibre/gui2/viewer/printing.py | 4 + 5 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 src/calibre/gui2/viewer/bookmarkmanager.py create mode 100644 src/calibre/gui2/viewer/bookmarkmanager.ui diff --git a/src/calibre/ebooks/epub/iterator.py b/src/calibre/ebooks/epub/iterator.py index e953cbda51..82b9aa09ef 100644 --- a/src/calibre/ebooks/epub/iterator.py +++ b/src/calibre/ebooks/epub/iterator.py @@ -195,5 +195,8 @@ class EbookIterator(object): self.bookmarks.append(bm) self.save_bookmarks() + def set_bookmarks(self, bookmarks): + self.bookmarks = bookmarks + def __exit__(self, *args): - self._tdir.__exit__(*args) \ No newline at end of file + self._tdir.__exit__(*args) diff --git a/src/calibre/gui2/viewer/bookmarkmanager.py b/src/calibre/gui2/viewer/bookmarkmanager.py new file mode 100644 index 0000000000..6dcd662754 --- /dev/null +++ b/src/calibre/gui2/viewer/bookmarkmanager.py @@ -0,0 +1,93 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' + + +from PyQt4.Qt import Qt, QDialog, QAbstractTableModel, QVariant, SIGNAL, \ + QModelIndex, QInputDialog, QLineEdit + +from calibre.gui2.viewer.bookmarkmanager_ui import Ui_BookmarkManager +from calibre.gui2 import NONE, qstring_to_unicode + +class BookmarkManager(QDialog, Ui_BookmarkManager): + def __init__(self, parent, bookmarks): + QDialog.__init__(self, parent) + + self.setupUi(self) + + self.bookmarks = bookmarks[:] + self.set_bookmarks() + + self.connect(self.button_revert, SIGNAL('clicked()'), self.set_bookmarks) + self.connect(self.button_delete, SIGNAL('clicked()'), self.delete_bookmark) + self.connect(self.button_edit, SIGNAL('clicked()'), self.edit_bookmark) + + def set_bookmarks(self): + self._model = BookmarkTableModel(self, self.bookmarks) + self.bookmarks_table.setModel(self._model) + + def delete_bookmark(self): + indexes = self.bookmarks_table.selectionModel().selectedIndexes() + if indexes != []: + self._model.remove_row(indexes[0].row()) + + def edit_bookmark(self): + indexes = self.bookmarks_table.selectionModel().selectedIndexes() + if indexes != []: + title, ok = QInputDialog.getText(self, _('Edit bookmark'), _('New title for bookmark:'), QLineEdit.Normal, self._model.data(indexes[0], Qt.DisplayRole).toString()) + title = QVariant(unicode(title).strip()) + if ok and title: + self._model.setData(indexes[0], title, Qt.EditRole) + + def get_bookmarks(self): + return self._model.bookmarks + + +class BookmarkTableModel(QAbstractTableModel): + headers = [_("Name")] + + def __init__(self, parent, bookmarks): + QAbstractTableModel.__init__(self, parent) + + self.bookmarks = bookmarks[:] + + def rowCount(self, parent): + if parent and parent.isValid(): + return 0 + return len(self.bookmarks) + + def columnCount(self, parent): + if parent and parent.isValid(): + return 0 + return len(self.headers) + + def data(self, index, role): + if role in (Qt.DisplayRole, Qt.EditRole): + ans = self.bookmarks[index.row()][0] + return NONE if ans is None else QVariant(ans) + return NONE + + def setData(self, index, value, role): + if role == Qt.EditRole: + self.bookmarks[index.row()] = (qstring_to_unicode(value.toString()).strip(), self.bookmarks[index.row()][1]) + self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index) + return True + return False + + def flags(self, index): + flags = QAbstractTableModel.flags(self, index) + flags |= Qt.ItemIsEditable + return flags + + def headerData(self, section, orientation, role): + if role != Qt.DisplayRole: + return NONE + if orientation == Qt.Horizontal: + return QVariant(self.headers[section]) + else: + return QVariant(section+1) + + def remove_row(self, row): + self.beginRemoveRows(QModelIndex(), row, row) + del self.bookmarks[row] + self.endRemoveRows() + diff --git a/src/calibre/gui2/viewer/bookmarkmanager.ui b/src/calibre/gui2/viewer/bookmarkmanager.ui new file mode 100644 index 0000000000..44e044b52a --- /dev/null +++ b/src/calibre/gui2/viewer/bookmarkmanager.ui @@ -0,0 +1,57 @@ + + BookmarkManager + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + false + + + true + + + QAbstractItemView::SingleSelection + + + false + + + + + + + Revert + + + + + + + Delete + + + + + + + Edit + + + + + + + + diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index ec0d878ae8..990281a471 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -13,6 +13,7 @@ from PyQt4.Qt import QMovie, QApplication, Qt, QIcon, QTimer, QWidget, SIGNAL, \ from calibre.gui2.viewer.main_ui import Ui_EbookViewer from calibre.gui2.viewer.printing import Printing +from calibre.gui2.viewer.bookmarkmanager import BookmarkManager from calibre.gui2.main_window import MainWindow from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \ info_dialog, error_dialog @@ -263,7 +264,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.connect(self.toc, SIGNAL('clicked(QModelIndex)'), self.toc_clicked) self.connect(self.reference, SIGNAL('goto(PyQt_PyObject)'), self.goto) + + self.bookmarks_menu = QMenu() + self.action_bookmark.setMenu(self.bookmarks_menu) self.set_bookmarks([]) + if pathtoebook is not None: f = functools.partial(self.load_ebook, pathtoebook) QTimer.singleShot(50, f) @@ -488,17 +493,28 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.setCursor(Qt.BusyCursor) def set_bookmarks(self, bookmarks): - menu = QMenu() + self.bookmarks_menu.clear() + self.bookmarks_menu.addAction(_("Manage Bookmarks"), self.manage_bookmarks) + self.bookmarks_menu.addSeparator() current_page = None for bm in bookmarks: if bm[0] == 'calibre_current_page_bookmark': current_page = bm else: - menu.addAction(bm[0], partial(self.goto_bookmark, bm)) - self.action_bookmark.setMenu(menu) - self._menu = menu + self.bookmarks_menu.addAction(bm[0], partial(self.goto_bookmark, bm)) return current_page + def manage_bookmarks(self): + bmm = BookmarkManager(self, self.iterator.bookmarks) + bmm.exec_() + + bookmarks = bmm.get_bookmarks() + + if bookmarks != self.iterator.bookmarks: + self.iterator.set_bookmarks(bookmarks) + self.iterator.save_bookmarks() + self.set_bookmarks(bookmarks) + def save_current_position(self): try: pos = self.view.bookmark() diff --git a/src/calibre/gui2/viewer/printing.py b/src/calibre/gui2/viewer/printing.py index 8644cd311b..e948360338 100644 --- a/src/calibre/gui2/viewer/printing.py +++ b/src/calibre/gui2/viewer/printing.py @@ -1,5 +1,9 @@ #!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' + + import os, sys, traceback, urlparse from BeautifulSoup import BeautifulSoup, Tag