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
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
-------------------

View File

@ -37,3 +37,28 @@ class CommentFinder(object):
q = bisect(self.starts, offset) - 1
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.split import split, merge, AbortError
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.dialogs.confirm_delete import confirm
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.export_requested.connect(self.export_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.close_requested.connect(self.editor_close_requested)
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.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()
for name, ed in tuple(editors.iteritems()):
if c.has_name(name):
ed.replace_data(c.raw_data(name))
ed.is_synced_to_container = True
if names is None or name in names:
ed.replace_data(c.raw_data(name))
ed.is_synced_to_container = True
else:
self.close_editor(name)
@ -830,6 +833,15 @@ class Boss(QObject):
if master in editors:
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
def export_requested(self, name, path):
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,
QStyledItemDelegate, QStyle, QPixmap, QPainter, pyqtSignal, QMenu, QTimer,
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.ebooks.oeb.base import OEB_STYLES, OEB_DOCS
@ -120,6 +120,7 @@ class FileList(QTreeWidget):
mark_requested = pyqtSignal(object, object)
export_requested = pyqtSignal(object, object)
replace_requested = pyqtSignal(object, object, object, object)
link_stylesheets_requested = pyqtSignal(object, object)
def __init__(self, parent=None):
QTreeWidget.__init__(self, parent)
@ -412,6 +413,9 @@ class FileList(QTreeWidget):
for items in selected_map.itervalues():
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:
m.addAction(QIcon(I('merge.png')), _('&Merge selected text files'), partial(self.start_merge, 'text', selected_map['text']))
if len(selected_map['styles']) > 1:
@ -611,6 +615,40 @@ class FileList(QTreeWidget):
nname = nname + '.' + ext.lower()
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): # {{{
@ -729,6 +767,7 @@ class FileListWidget(QWidget):
mark_requested = pyqtSignal(object, object)
export_requested = pyqtSignal(object, object)
replace_requested = pyqtSignal(object, object, object, object)
link_stylesheets_requested = pyqtSignal(object, object)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
@ -736,10 +775,9 @@ class FileListWidget(QWidget):
self.file_list = FileList(self)
self.layout().addWidget(self.file_list)
self.layout().setContentsMargins(0, 0, 0, 0)
for x in ('delete_requested', 'reorder_spine', 'rename_requested',
'edit_file', 'merge_requested', 'mark_requested',
'export_requested', 'replace_requested', 'bulk_rename_requested'):
getattr(self.file_list, x).connect(getattr(self, x))
for k, o in vars(self.__class__).iteritems():
if isinstance(o, pyqtSignal) and hasattr(self.file_list, k):
getattr(self.file_list, k).connect(getattr(self, k))
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))