Edit book: Allow linking of stylesheets into HTML files automatically. Right click ont he selected HTML files in the File browser and choose "Link stylesheets" to have the <link> tags for the shhets automatically inserted.

This commit is contained in:
Kovid Goyal 2013-12-22 13:58:35 +05:30
parent 6d2029b0f2
commit 4c158cb44b
4 changed files with 89 additions and 8 deletions

View File

@ -171,6 +171,12 @@ book by clicking :guilabel:`File->New file`. This lets you either import a file
by clicking the :guilabel:`Import resource file` button or create a new blank html file by clicking the :guilabel:`Import resource file` button or create a new blank html file
or stylesheet by simply entering the file name into the box for the new file. or stylesheet by simply entering the file name into the box for the new file.
Linking stylesheets to HTML files efficiently
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As a convenience, you can select multiple HTML files in the Files Browser,
right click and choose Link stylesheets to have |app| automatically insert the
<link> tags for those stylesheets into all the selected HTML files.
Search & Replace Search & Replace
------------------- -------------------

View File

@ -37,3 +37,28 @@ class CommentFinder(object):
q = bisect(self.starts, offset) - 1 q = bisect(self.starts, offset) - 1
return q >= 0 and self.starts[q] <= offset <= self.ends[q] return q >= 0 and self.starts[q] <= offset <= self.ends[q]
def link_stylesheets(container, names, sheets, mtype='text/css'):
from calibre.ebooks.oeb.base import XPath, XHTML
changed_names = set()
snames = set(sheets)
lp = XPath('//h:link[@href]')
hp = XPath('//h:head')
for name in names:
root = container.parsed(name)
existing = {container.href_to_name(l.get('href'), name) for l in lp(root) if (l.get('type', mtype) or mtype) == mtype}
extra = snames - existing
if extra:
changed_names.add(name)
try:
parent = hp(root)[0]
except (TypeError, IndexError):
parent = XHTML('head')
container.insert_into_xml(root, parent, index=0)
for sheet in sheets:
if sheet in extra:
container.insert_into_xml(
parent, parent.makeelement(XHTML('link'), rel='stylesheet', type=mtype,
href=container.name_to_href(sheet, name)))
container.dirty(name)
return changed_names

View File

@ -24,6 +24,7 @@ from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file from calibre.ebooks.oeb.polish.replace import rename_files, replace_file
from calibre.ebooks.oeb.polish.split import split, merge, AbortError from calibre.ebooks.oeb.polish.split import split, merge, AbortError
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc
from calibre.ebooks.oeb.polish.utils import link_stylesheets
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.tweak_book import set_current_container, current_container, tprefs, actions, editors from calibre.gui2.tweak_book import set_current_container, current_container, tprefs, actions, editors
@ -96,6 +97,7 @@ class Boss(QObject):
fl.mark_requested.connect(self.mark_requested) fl.mark_requested.connect(self.mark_requested)
fl.export_requested.connect(self.export_requested) fl.export_requested.connect(self.export_requested)
fl.replace_requested.connect(self.replace_requested) fl.replace_requested.connect(self.replace_requested)
fl.link_stylesheets_requested.connect(self.link_stylesheets_requested)
self.gui.central.current_editor_changed.connect(self.apply_current_editor_state) self.gui.central.current_editor_changed.connect(self.apply_current_editor_state)
self.gui.central.close_requested.connect(self.editor_close_requested) self.gui.central.close_requested.connect(self.editor_close_requested)
self.gui.central.search_panel.search_triggered.connect(self.search) self.gui.central.search_panel.search_triggered.connect(self.search)
@ -228,12 +230,13 @@ class Boss(QObject):
self.gui.file_list.request_edit(ef) self.gui.file_list.request_edit(ef)
self.gui.toc_view.update_if_visible() self.gui.toc_view.update_if_visible()
def update_editors_from_container(self, container=None): def update_editors_from_container(self, container=None, names=None):
c = container or current_container() c = container or current_container()
for name, ed in tuple(editors.iteritems()): for name, ed in tuple(editors.iteritems()):
if c.has_name(name): if c.has_name(name):
ed.replace_data(c.raw_data(name)) if names is None or name in names:
ed.is_synced_to_container = True ed.replace_data(c.raw_data(name))
ed.is_synced_to_container = True
else: else:
self.close_editor(name) self.close_editor(name)
@ -830,6 +833,15 @@ class Boss(QObject):
if master in editors: if master in editors:
self.show_editor(master) self.show_editor(master)
@in_thread_job
def link_stylesheets_requested(self, names, sheets):
self.commit_all_editors_to_container()
self.add_savepoint(_('Link stylesheets'))
changed_names = link_stylesheets(current_container(), names, sheets)
if changed_names:
self.update_editors_from_container(names=changed_names)
self.set_modified()
@in_thread_job @in_thread_job
def export_requested(self, name, path): def export_requested(self, name, path):
if name in editors and not editors[name].is_synced_to_container: if name in editors and not editors[name].is_synced_to_container:

View File

@ -16,7 +16,7 @@ from PyQt4.Qt import (
QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont, QWidget, QTreeWidget, QGridLayout, QSize, Qt, QTreeWidgetItem, QIcon, QFont,
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer, QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer,
QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea, QDialogButtonBox, QDialog, QLabel, QLineEdit, QVBoxLayout, QScrollArea,
QRadioButton, QFormLayout, QSpinBox) QRadioButton, QFormLayout, QSpinBox, QListWidget, QListWidgetItem)
from calibre import human_readable, sanitize_file_name_unicode from calibre import human_readable, sanitize_file_name_unicode
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
@ -120,6 +120,7 @@ class FileList(QTreeWidget):
mark_requested = pyqtSignal(object, object) mark_requested = pyqtSignal(object, object)
export_requested = pyqtSignal(object, object) export_requested = pyqtSignal(object, object)
replace_requested = pyqtSignal(object, object, object, object) replace_requested = pyqtSignal(object, object, object, object)
link_stylesheets_requested = pyqtSignal(object, object)
def __init__(self, parent=None): def __init__(self, parent=None):
QTreeWidget.__init__(self, parent) QTreeWidget.__init__(self, parent)
@ -412,6 +413,9 @@ class FileList(QTreeWidget):
for items in selected_map.itervalues(): for items in selected_map.itervalues():
items.sort(key=self.index_of_name) items.sort(key=self.index_of_name)
if selected_map['text']:
m.addAction(QIcon(I('format-text-color.png')), _('Link &stylesheets...'), partial(self.link_stylesheets, selected_map['text']))
if len(selected_map['text']) > 1: if len(selected_map['text']) > 1:
m.addAction(QIcon(I('merge.png')), _('&Merge selected text files'), partial(self.start_merge, 'text', selected_map['text'])) m.addAction(QIcon(I('merge.png')), _('&Merge selected text files'), partial(self.start_merge, 'text', selected_map['text']))
if len(selected_map['styles']) > 1: if len(selected_map['styles']) > 1:
@ -611,6 +615,40 @@ class FileList(QTreeWidget):
nname = nname + '.' + ext.lower() nname = nname + '.' + ext.lower()
self.replace_requested.emit(name, path, nname, force_mt) self.replace_requested.emit(name, path, nname, force_mt)
def link_stylesheets(self, names):
s = self.categories['styles']
sheets = [unicode(s.child(i).data(0, NAME_ROLE).toString()) for i in xrange(s.childCount())]
if not sheets:
return error_dialog(self, _('No stylesheets'), _(
'This book currently has no stylesheets. You must first create a stylesheet'
' before linking it.'), show=True)
d = QDialog(self)
d.l = l = QVBoxLayout(d)
d.setLayout(l)
d.setWindowTitle(_('Choose stylesheets'))
d.la = la = QLabel(_('Choose the stylesheets to link. Drag and drop to re-arrange'))
la.setWordWrap(True)
l.addWidget(la)
d.s = s = QListWidget(d)
l.addWidget(s)
s.setDragEnabled(True)
s.setDropIndicatorShown(True)
s.setDragDropMode(self.InternalMove)
s.setAutoScroll(True)
s.setDefaultDropAction(Qt.MoveAction)
for name in sheets:
i = QListWidgetItem(name, s)
flags = Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled | Qt.ItemIsSelectable
i.setFlags(flags)
i.setCheckState(Qt.Checked)
d.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
bb.accepted.connect(d.accept), bb.rejected.connect(d.reject)
l.addWidget(bb)
if d.exec_() == d.Accepted:
sheets = [unicode(s.item(i).text()) for i in xrange(s.count()) if s.item(i).checkState() == Qt.Checked]
if sheets:
self.link_stylesheets_requested.emit(names, sheets)
class NewFileDialog(QDialog): # {{{ class NewFileDialog(QDialog): # {{{
@ -729,6 +767,7 @@ class FileListWidget(QWidget):
mark_requested = pyqtSignal(object, object) mark_requested = pyqtSignal(object, object)
export_requested = pyqtSignal(object, object) export_requested = pyqtSignal(object, object)
replace_requested = pyqtSignal(object, object, object, object) replace_requested = pyqtSignal(object, object, object, object)
link_stylesheets_requested = pyqtSignal(object, object)
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -736,10 +775,9 @@ class FileListWidget(QWidget):
self.file_list = FileList(self) self.file_list = FileList(self)
self.layout().addWidget(self.file_list) self.layout().addWidget(self.file_list)
self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setContentsMargins(0, 0, 0, 0)
for x in ('delete_requested', 'reorder_spine', 'rename_requested', for k, o in vars(self.__class__).iteritems():
'edit_file', 'merge_requested', 'mark_requested', if isinstance(o, pyqtSignal) and hasattr(self.file_list, k):
'export_requested', 'replace_requested', 'bulk_rename_requested'): getattr(self.file_list, k).connect(getattr(self, k))
getattr(self.file_list, x).connect(getattr(self, x))
for x in ('delete_done', 'select_name', 'request_edit', 'mark_name_as_current', 'clear_currently_edited_name'): for x in ('delete_done', 'select_name', 'request_edit', 'mark_name_as_current', 'clear_currently_edited_name'):
setattr(self, x, getattr(self.file_list, x)) setattr(self, x, getattr(self.file_list, x))