mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-31 14:33:54 -04:00
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:
parent
6d2029b0f2
commit
4c158cb44b
@ -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
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user