mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-12-27 07:10:19 -05:00
UI for new Add Cover tool
This commit is contained in:
parent
0841399d0b
commit
00a368909a
@ -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)}
|
||||
|
||||
@ -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'), _(
|
||||
|
||||
@ -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('<p style="color:red">' + 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):
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user