From 00a368909a038fa4c108cae9fb084907fbeddc7e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jun 2014 19:26:28 +0530 Subject: [PATCH] UI for new Add Cover tool --- src/calibre/gui2/tweak_book/__init__.py | 1 + src/calibre/gui2/tweak_book/boss.py | 35 ++++-- src/calibre/gui2/tweak_book/file_list.py | 25 ++-- src/calibre/gui2/tweak_book/ui.py | 2 + src/calibre/gui2/tweak_book/widgets.py | 144 +++++++++++++++++++++-- 5 files changed, 178 insertions(+), 29 deletions(-) diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py index 8c78d907ab..c286bed43a 100644 --- a/src/calibre/gui2/tweak_book/__init__.py +++ b/src/calibre/gui2/tweak_book/__init__.py @@ -58,6 +58,7 @@ d['editor_html_toolbar'] = ['fix-html-current', 'pretty-current', 'insert-image' d['editor_format_toolbar'] = [('format-text-' + x) for x in ( 'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'color', 'background-color')] d['spell_check_case_sensitive_search'] = False +d['add_cover_preserve_aspect_ratio'] = False del d ucase_map = {l:string.ascii_uppercase[i] for i, l in enumerate(string.ascii_lowercase)} diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index b5f9b6dbc0..31984eb962 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -42,7 +42,7 @@ from calibre.gui2.tweak_book.search import validate_search_request, run_search from calibre.gui2.tweak_book.spell import find_next as find_next_word, find_next_error from calibre.gui2.tweak_book.widgets import ( RationalizeFolders, MultiSplit, ImportForeign, QuickOpen, InsertLink, - InsertSemantics, BusyCursor, InsertTag, FilterCSS) + InsertSemantics, BusyCursor, InsertTag, FilterCSS, AddCover) _diff_dialogs = [] @@ -353,27 +353,29 @@ class Boss(QObject): d = NewFileDialog(self.gui) if d.exec_() != d.Accepted: return - self.add_savepoint(_('Before: Add file %s') % self.gui.elided_text(d.file_name)) + self.do_add_file(d.file_name, d.file_data, using_template=d.using_template, edit_file=True) + + def do_add_file(self, file_name, data, using_template=False, edit_file=False): + self.add_savepoint(_('Before: Add file %s') % self.gui.elided_text(file_name)) c = current_container() - data = d.file_data - if d.using_template: + if using_template: data = data.replace(b'%CURSOR%', b'') try: - c.add_file(d.file_name, data) + c.add_file(file_name, data) except: self.rewind_savepoint() raise self.gui.file_list.build(c) - self.gui.file_list.select_name(d.file_name) + self.gui.file_list.select_name(file_name) if c.opf_name in editors: editors[c.opf_name].replace_data(c.raw_data(c.opf_name)) - mt = c.mime_map[d.file_name] - syntax = syntax_from_mime(d.file_name, mt) - if syntax: - if d.using_template: - self.edit_file(d.file_name, syntax, use_template=d.file_data.decode('utf-8')) + mt = c.mime_map[file_name] + syntax = syntax_from_mime(file_name, mt) + if syntax and edit_file: + if using_template: + self.edit_file(file_name, syntax, use_template=data.decode('utf-8')) else: - self.edit_file(d.file_name, syntax) + self.edit_file(file_name, syntax) self.set_modified() def add_files(self): @@ -406,6 +408,15 @@ class Boss(QObject): editors[c.opf_name].replace_data(c.raw_data(c.opf_name)) self.set_modified() + def add_cover(self): + d = AddCover(current_container(), self.gui) + d.import_requested.connect(self.do_add_file) + try: + if d.exec_() == d.Accepted: + pass + finally: + d.import_requested.disconnect() + def edit_toc(self): if current_container() is None: return error_dialog(self.gui, _('No book opened'), _( diff --git a/src/calibre/gui2/tweak_book/file_list.py b/src/calibre/gui2/tweak_book/file_list.py index c27af04da1..e7ab9aced0 100644 --- a/src/calibre/gui2/tweak_book/file_list.py +++ b/src/calibre/gui2/tweak_book/file_list.py @@ -685,7 +685,7 @@ class FileList(QTreeWidget): class NewFileDialog(QDialog): # {{{ - def __init__(self, initial_choice='html', parent=None): + def __init__(self, parent=None): QDialog.__init__(self, parent) self.l = l = QVBoxLayout() self.setLayout(l) @@ -715,6 +715,7 @@ class NewFileDialog(QDialog): # {{{ self.file_data = b'' self.using_template = False + self.setMinimumWidth(350) def show_error(self, msg): self.err_label.setText('

' + msg) @@ -723,15 +724,19 @@ class NewFileDialog(QDialog): # {{{ def import_file(self): path = choose_files(self, 'tweak-book-new-resource-file', _('Choose file'), select_only_single_file=True) if path: - path = path[0] - with open(path, 'rb') as f: - self.file_data = f.read() - name = os.path.basename(path) - fmap = get_recommended_folders(current_container(), (name,)) - if fmap[name]: - name = '/'.join((fmap[name], name)) - self.name.setText(name) - self.la.setText(_('Choose a name for the imported file')) + self.do_import_file(path[0]) + + def do_import_file(self, path, hide_button=False): + with open(path, 'rb') as f: + self.file_data = f.read() + name = os.path.basename(path) + fmap = get_recommended_folders(current_container(), (name,)) + if fmap[name]: + name = '/'.join((fmap[name], name)) + self.name.setText(name) + self.la.setText(_('Choose a name for the imported file')) + if hide_button: + self.imp_button.setVisible(False) @property def name_is_ok(self): diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 0bfeda5906..232d28f99c 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -357,6 +357,7 @@ class Main(MainWindow): self.action_filter_css = treg('filter.png', _('&Filter style information'), self.boss.filter_css, 'filter-css', (), _('Filter style information')) self.action_manage_fonts = treg('font.png', _('Manage &fonts'), self.boss.manage_fonts, 'manage-fonts', (), _('Manage fonts in the book')) + self.action_add_cover = treg('default_cover.png', _('Add &cover'), self.boss.add_cover, 'add-cover', (), _('Add a cover to the book')) def ereg(icon, text, target, sid, keys, description): return reg(icon, text, partial(self.boss.editor_action, target), sid, keys, description) @@ -496,6 +497,7 @@ class Main(MainWindow): e.addAction(self.action_fix_html_all) e.addAction(self.action_pretty_all) e.addAction(self.action_rationalize_folders) + e.addAction(self.action_add_cover) e.addAction(self.action_set_semantics) e.addAction(self.action_filter_css) e.addAction(self.action_spell_check_book) diff --git a/src/calibre/gui2/tweak_book/widgets.py b/src/calibre/gui2/tweak_book/widgets.py index 8d2386860c..85ca0c1002 100644 --- a/src/calibre/gui2/tweak_book/widgets.py +++ b/src/calibre/gui2/tweak_book/widgets.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' -import os +import os, textwrap from itertools import izip from collections import OrderedDict @@ -15,12 +15,13 @@ from PyQt4.Qt import ( QFormLayout, QHBoxLayout, QToolButton, QIcon, QApplication, Qt, QWidget, QPoint, QSizePolicy, QPainter, QStaticText, pyqtSignal, QTextOption, QAbstractListModel, QModelIndex, QVariant, QStyledItemDelegate, QStyle, - QListView, QTextDocument, QSize, QComboBox, QFrame, QCursor, QCheckBox) + QListView, QTextDocument, QSize, QComboBox, QFrame, QCursor, QCheckBox, + QSplitter, QPixmap, QRect) -from calibre import prepare_string_for_xml -from calibre.ebooks.oeb.polish.utils import lead_text -from calibre.gui2 import error_dialog, choose_files, choose_save_file, NONE, info_dialog -from calibre.gui2.tweak_book import tprefs +from calibre import prepare_string_for_xml, human_readable +from calibre.ebooks.oeb.polish.utils import lead_text, guess_type +from calibre.gui2 import error_dialog, choose_files, choose_save_file, NONE, info_dialog, choose_images +from calibre.gui2.tweak_book import tprefs, current_container from calibre.utils.icu import primary_sort_key, sort_key, primary_contains from calibre.utils.matcher import get_char, Matcher from calibre.gui2.complete2 import EditWithComplete @@ -569,6 +570,12 @@ class NamesModel(QAbstractListModel): if text == name: return i + def name_for_index(self, index): + try: + return self.items[index.row()][0] + except IndexError: + pass + def create_filterable_names_list(names, filter_text=None, parent=None, model=NamesModel): nl = QListView(parent) nl.m = m = model(names, parent=nl) @@ -992,7 +999,130 @@ class FilterCSS(Dialog): # {{{ # }}} +# Add Cover {{{ + +class CoverView(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.current_pixmap_size = QSize(0, 0) + self.pixmap = QPixmap() + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) + + def set_pixmap(self, data): + self.pixmap.loadFromData(data) + self.current_pixmap_size = self.pixmap.size() + self.update() + + def paintEvent(self, event): + if self.pixmap.isNull(): + return + canvas_size = self.rect() + width = self.current_pixmap_size.width() + extrax = canvas_size.width() - width + if extrax < 0: + extrax = 0 + x = int(extrax/2.) + height = self.current_pixmap_size.height() + extray = canvas_size.height() - height + if extray < 0: + extray = 0 + y = int(extray/2.) + target = QRect(x, y, min(canvas_size.width(), width), min(canvas_size.height(), height)) + p = QPainter(self) + p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) + p.drawPixmap(target, self.pixmap.scaled(target.size(), + Qt.KeepAspectRatio, Qt.SmoothTransformation)) + p.end() + + def sizeHint(self): + return QSize(300, 400) + +class AddCover(Dialog): + + import_requested = pyqtSignal(object, object) + + def __init__(self, container, parent=None): + self.container = container + Dialog.__init__(self, _('Add a cover'), 'add-cover-wizard', parent) + + @property + def image_names(self): + img_types = {guess_type('a.'+x) for x in ('png', 'jpeg', 'gif')} + for name, mt in self.container.mime_map.iteritems(): + if mt.lower() in img_types: + yield name + + def setup_ui(self): + self.l = l = QVBoxLayout(self) + self.setLayout(l) + self.names, self.names_filter = create_filterable_names_list(sorted(self.image_names, key=sort_key), filter_text=_('Filter the list of images')) + self.cover_view = CoverView(self) + l.addWidget(self.names_filter) + + self.splitter = s = QSplitter(self) + l.addWidget(s) + s.addWidget(self.names) + s.addWidget(self.cover_view) + + self.h = h = QHBoxLayout() + self.preserve = p = QCheckBox(_('Preserve aspect ratio')) + p.setToolTip(textwrap.fill(_('If enabled the cover image you select will be embedded' + ' into the book in such a way that when viewed, its aspect' + ' ratio (ratio of width to height) will be preserved.' + ' This will mean blank spaces around the image if the screen' + ' the book is being viewed on has an aspect ratio different' + ' to the image.'))) + p.setChecked(tprefs['add_cover_preserve_aspect_ratio']) + p.setVisible(self.container.book_type != 'azw3') + p.stateChanged.connect(lambda s:tprefs.set('add_cover_preserve_aspect_ratio', s == Qt.Checked)) + self.info_label = il = QLabel('\xa0') + h.addWidget(p), h.addStretch(1), h.addWidget(il) + l.addLayout(h) + + l.addWidget(self.bb) + b = self.bb.addButton(_('Import &image'), self.bb.ActionRole) + b.clicked.connect(self.import_image) + b.setIcon(QIcon(I('document_open.png'))) + self.names.setFocus(Qt.OtherFocusReason) + self.names.selectionModel().currentChanged.connect(self.current_image_changed) + + def current_image_changed(self): + self.info_label.setText('') + name = self.names.model().name_for_index(self.names.currentIndex()) + if name is not None: + data = self.container.raw_data(name, decode=False) + self.cover_view.set_pixmap(data) + self.info_label.setText('{0}x{1}px | {2}'.format( + self.cover_view.pixmap.width(), self.cover_view.pixmap.height(), human_readable(len(data)))) + + def import_image(self): + ans = choose_images(self, 'add-cover-choose-image', _('Choose a cover image'), formats=( + 'jpg', 'jpeg', 'png', 'gif')) + if ans: + from calibre.gui2.tweak_book.file_list import NewFileDialog + d = NewFileDialog(self) + d.do_import_file(ans[0], hide_button=True) + if d.exec_() == d.Accepted: + self.import_requested.emit(d.file_name, d.file_data) + self.container = current_container() + self.names_filter.clear() + self.names.model().set_names(sorted(self.image_names, key=sort_key)) + i = self.names.model().find_name(d.file_name) + self.names.setCurrentIndex(self.names.model().index(i)) + self.current_image_changed() + + @classmethod + def test(cls): + import sys + from calibre.ebooks.oeb.polish.container import get_container + c = get_container(sys.argv[-1], tweak_mode=True) + d = cls(c) + if d.exec_() == d.Accepted: + pass + +# }}} if __name__ == '__main__': app = QApplication([]) - FilterCSS.test() + AddCover.test()