mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Edit book: Set semantics tool: Add support for EPUB 3 landmarks
This commit is contained in:
parent
47143b0506
commit
e416b1c30b
@ -994,7 +994,7 @@ class Boss(QObject):
|
|||||||
return error_dialog(self.gui, _('Not supported'), _(
|
return error_dialog(self.gui, _('Not supported'), _(
|
||||||
'Semantics are not supported for the AZW3 format.'), show=True)
|
'Semantics are not supported for the AZW3 format.'), show=True)
|
||||||
d = InsertSemantics(c, parent=self.gui)
|
d = InsertSemantics(c, parent=self.gui)
|
||||||
if d.exec_() == QDialog.DialogCode.Accepted and d.changed_type_map:
|
if d.exec_() == QDialog.DialogCode.Accepted and d.changes:
|
||||||
self.add_savepoint(_('Before: Set Semantics'))
|
self.add_savepoint(_('Before: Set Semantics'))
|
||||||
d.apply_changes(current_container())
|
d.apply_changes(current_container())
|
||||||
self.apply_container_update_to_gui()
|
self.apply_container_update_to_gui()
|
||||||
|
@ -5,27 +5,41 @@
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
import os, textwrap, unicodedata
|
import os
|
||||||
|
import textwrap
|
||||||
|
import unicodedata
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from qt.core import (
|
from qt.core import (
|
||||||
QGridLayout, QLabel, QLineEdit, QVBoxLayout, QFormLayout, QHBoxLayout,
|
QAbstractListModel, QApplication, QCheckBox, QComboBox, QCursor, QDialog,
|
||||||
QToolButton, QIcon, QApplication, Qt, QWidget, QPoint, QSizePolicy,
|
QDialogButtonBox, QEvent, QFormLayout, QFrame, QGridLayout, QGroupBox,
|
||||||
QPainter, QStaticText, pyqtSignal, QTextOption, QAbstractListModel, QItemSelectionModel,
|
QHBoxLayout, QIcon, QItemSelectionModel, QLabel, QLineEdit, QListView, QMimeData,
|
||||||
QModelIndex, QStyledItemDelegate, QStyle, QCheckBox, QListView, QPalette,
|
QModelIndex, QPainter, QPalette, QPixmap, QPlainTextEdit, QPoint, QRect, QSize,
|
||||||
QTextDocument, QSize, QComboBox, QFrame, QCursor, QGroupBox, QSplitter,
|
QSizePolicy, QSplitter, QStaticText, QStyle, QStyledItemDelegate, Qt,
|
||||||
QPixmap, QRect, QPlainTextEdit, QMimeData, QDialog, QEvent, QDialogButtonBox)
|
QTextDocument, QTextOption, QToolButton, QVBoxLayout, QWidget, pyqtSignal
|
||||||
|
)
|
||||||
|
|
||||||
from calibre import prepare_string_for_xml, human_readable
|
from calibre import human_readable, prepare_string_for_xml
|
||||||
from calibre.constants import iswindows
|
from calibre.constants import iswindows
|
||||||
from calibre.ebooks.oeb.polish.cover import get_raster_cover_name
|
from calibre.ebooks.oeb.polish.cover import get_raster_cover_name
|
||||||
from calibre.ebooks.oeb.polish.utils import lead_text, guess_type
|
from calibre.ebooks.oeb.polish.toc import (
|
||||||
from calibre.gui2 import error_dialog, choose_files, choose_save_file, info_dialog, choose_images
|
ensure_container_has_nav, get_guide_landmarks, get_nav_landmarks, set_landmarks
|
||||||
from calibre.gui2.tweak_book import tprefs, current_container
|
)
|
||||||
from calibre.gui2.widgets2 import Dialog as BaseDialog, HistoryComboBox, to_plain_text, PARAGRAPH_SEPARATOR
|
from calibre.ebooks.oeb.polish.upgrade import guide_epubtype_map
|
||||||
from calibre.utils.icu import primary_sort_key, sort_key, primary_contains, numeric_sort_key
|
from calibre.ebooks.oeb.polish.utils import guess_type, lead_text
|
||||||
from calibre.utils.matcher import get_char, Matcher, DEFAULT_LEVEL1, DEFAULT_LEVEL2, DEFAULT_LEVEL3
|
from calibre.gui2 import (
|
||||||
|
choose_files, choose_images, choose_save_file, error_dialog, info_dialog
|
||||||
|
)
|
||||||
from calibre.gui2.complete2 import EditWithComplete
|
from calibre.gui2.complete2 import EditWithComplete
|
||||||
|
from calibre.gui2.tweak_book import current_container, tprefs
|
||||||
|
from calibre.gui2.widgets2 import (
|
||||||
|
PARAGRAPH_SEPARATOR, Dialog as BaseDialog, HistoryComboBox, to_plain_text
|
||||||
|
)
|
||||||
|
from calibre.utils.icu import (
|
||||||
|
numeric_sort_key, primary_contains, primary_sort_key, sort_key
|
||||||
|
)
|
||||||
|
from calibre.utils.matcher import (
|
||||||
|
DEFAULT_LEVEL1, DEFAULT_LEVEL2, DEFAULT_LEVEL3, Matcher, get_char
|
||||||
|
)
|
||||||
from polyglot.builtins import iteritems
|
from polyglot.builtins import iteritems
|
||||||
|
|
||||||
ROOT = QModelIndex()
|
ROOT = QModelIndex()
|
||||||
@ -774,6 +788,7 @@ class InsertLink(Dialog):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def test(cls):
|
def test(cls):
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from calibre.ebooks.oeb.polish.container import get_container
|
from calibre.ebooks.oeb.polish.container import get_container
|
||||||
c = get_container(sys.argv[-1], tweak_mode=True)
|
c = get_container(sys.argv[-1], tweak_mode=True)
|
||||||
d = cls(c, next(c.spine_names)[0])
|
d = cls(c, next(c.spine_names)[0])
|
||||||
@ -789,11 +804,11 @@ class InsertSemantics(Dialog):
|
|||||||
|
|
||||||
def __init__(self, container, parent=None):
|
def __init__(self, container, parent=None):
|
||||||
self.container = container
|
self.container = container
|
||||||
self.anchor_cache = {}
|
|
||||||
self.original_type_map = {item.get('type', ''):(container.href_to_name(item.get('href'), container.opf_name), item.get('href', '').partition('#')[-1])
|
|
||||||
for item in container.opf_xpath('//opf:guide/opf:reference[@href and @type]')}
|
|
||||||
self.final_type_map = self.original_type_map.copy()
|
|
||||||
self.create_known_type_map()
|
self.create_known_type_map()
|
||||||
|
self.anchor_cache = {}
|
||||||
|
self.original_guide_map = {item['type']: item for item in get_guide_landmarks(container)}
|
||||||
|
self.original_nav_map = {item['type']: item for item in get_nav_landmarks(container)}
|
||||||
|
self.changes = {}
|
||||||
Dialog.__init__(self, _('Set semantics'), 'insert-semantics', parent=parent)
|
Dialog.__init__(self, _('Set semantics'), 'insert-semantics', parent=parent)
|
||||||
|
|
||||||
def sizeHint(self):
|
def sizeHint(self):
|
||||||
@ -801,14 +816,16 @@ class InsertSemantics(Dialog):
|
|||||||
|
|
||||||
def create_known_type_map(self):
|
def create_known_type_map(self):
|
||||||
_ = lambda x: x
|
_ = lambda x: x
|
||||||
|
self.epubtype_guide_map = {v: k for k, v in guide_epubtype_map.items()}
|
||||||
self.known_type_map = {
|
self.known_type_map = {
|
||||||
'title-page': _('Title page'),
|
'titlepage': _('Title page'),
|
||||||
'toc': _('Table of Contents'),
|
'toc': _('Table of Contents'),
|
||||||
'index': _('Index'),
|
'index': _('Index'),
|
||||||
'glossary': _('Glossary'),
|
'glossary': _('Glossary'),
|
||||||
'acknowledgements': _('Acknowledgements'),
|
'acknowledgments': _('Acknowledgements'),
|
||||||
'bibliography': _('Bibliography'),
|
'bibliography': _('Bibliography'),
|
||||||
'colophon': _('Colophon'),
|
'colophon': _('Colophon'),
|
||||||
|
'cover': _('Cover'),
|
||||||
'copyright-page': _('Copyright page'),
|
'copyright-page': _('Copyright page'),
|
||||||
'dedication': _('Dedication'),
|
'dedication': _('Dedication'),
|
||||||
'epigraph': _('Epigraph'),
|
'epigraph': _('Epigraph'),
|
||||||
@ -817,13 +834,14 @@ class InsertSemantics(Dialog):
|
|||||||
'lot': _('List of tables'),
|
'lot': _('List of tables'),
|
||||||
'notes': _('Notes'),
|
'notes': _('Notes'),
|
||||||
'preface': _('Preface'),
|
'preface': _('Preface'),
|
||||||
'text': _('Text'),
|
'bodymatter': _('Text'),
|
||||||
}
|
}
|
||||||
_ = __builtins__['_']
|
_ = __builtins__['_']
|
||||||
type_map_help = {
|
type_map_help = {
|
||||||
'title-page': _('Page with title, author, publisher, etc.'),
|
'titlepage': _('Page with title, author, publisher, etc.'),
|
||||||
|
'cover': _('The book cover, typically a single HTML file with a cover image inside'),
|
||||||
'index': _('Back-of-book style index'),
|
'index': _('Back-of-book style index'),
|
||||||
'text': _('First "real" page of content'),
|
'bodymatter': _('First "real" page of content'),
|
||||||
}
|
}
|
||||||
t = _
|
t = _
|
||||||
all_types = [(k, (('%s (%s)' % (t(v), type_map_help[k])) if k in type_map_help else t(v))) for k, v in iteritems(self.known_type_map)]
|
all_types = [(k, (('%s (%s)' % (t(v), type_map_help[k])) if k in type_map_help else t(v))) for k, v in iteritems(self.known_type_map)]
|
||||||
@ -835,6 +853,7 @@ class InsertSemantics(Dialog):
|
|||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
|
|
||||||
self.tl = tl = QFormLayout()
|
self.tl = tl = QFormLayout()
|
||||||
|
tl.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||||
self.semantic_type = QComboBox(self)
|
self.semantic_type = QComboBox(self)
|
||||||
for key, val in iteritems(self.all_types):
|
for key, val in iteritems(self.all_types):
|
||||||
self.semantic_type.addItem(val, key)
|
self.semantic_type.addItem(val, key)
|
||||||
@ -888,9 +907,21 @@ class InsertSemantics(Dialog):
|
|||||||
d.resize(d.sizeHint())
|
d.resize(d.sizeHint())
|
||||||
d.exec_()
|
d.exec_()
|
||||||
|
|
||||||
|
def dest_for_type(self, item_type):
|
||||||
|
if item_type in self.changes:
|
||||||
|
return self.changes[item_type]
|
||||||
|
if item_type in self.original_nav_map:
|
||||||
|
item = self.original_nav_map[item_type]
|
||||||
|
return item['dest'], item['frag']
|
||||||
|
item_type = self.epubtype_guide_map.get(item_type, item_type)
|
||||||
|
if item_type in self.original_guide_map:
|
||||||
|
item = self.original_guide_map[item_type]
|
||||||
|
return item['dest'], item['frag']
|
||||||
|
return None, None
|
||||||
|
|
||||||
def semantic_type_changed(self):
|
def semantic_type_changed(self):
|
||||||
item_type = str(self.semantic_type.itemData(self.semantic_type.currentIndex()) or '')
|
item_type = str(self.semantic_type.itemData(self.semantic_type.currentIndex()) or '')
|
||||||
name, frag = self.final_type_map.get(item_type, (None, None))
|
name, frag = self.dest_for_type(item_type)
|
||||||
self.show_type(name, frag)
|
self.show_type(name, frag)
|
||||||
|
|
||||||
def show_type(self, name, frag):
|
def show_type(self, name, frag):
|
||||||
@ -916,7 +947,8 @@ class InsertSemantics(Dialog):
|
|||||||
def target_text_changed(self):
|
def target_text_changed(self):
|
||||||
name, frag = str(self.target.text()).partition('#')[::2]
|
name, frag = str(self.target.text()).partition('#')[::2]
|
||||||
item_type = str(self.semantic_type.itemData(self.semantic_type.currentIndex()) or '')
|
item_type = str(self.semantic_type.itemData(self.semantic_type.currentIndex()) or '')
|
||||||
self.final_type_map[item_type] = (name, frag or None)
|
if item_type:
|
||||||
|
self.changes[item_type] = (name, frag or None)
|
||||||
|
|
||||||
def selected_file_changed(self, *args):
|
def selected_file_changed(self, *args):
|
||||||
rows = list(self.file_names.selectionModel().selectedRows())
|
rows = list(self.file_names.selectionModel().selectedRows())
|
||||||
@ -950,23 +982,32 @@ class InsertSemantics(Dialog):
|
|||||||
href += frag
|
href += frag
|
||||||
self.target.setText(href or '#')
|
self.target.setText(href or '#')
|
||||||
|
|
||||||
@property
|
|
||||||
def changed_type_map(self):
|
|
||||||
return {k:v for k, v in iteritems(self.final_type_map) if v != self.original_type_map.get(k, None)}
|
|
||||||
|
|
||||||
def apply_changes(self, container):
|
def apply_changes(self, container):
|
||||||
from calibre.ebooks.oeb.polish.opf import set_guide_item, get_book_language
|
from calibre.ebooks.oeb.polish.opf import get_book_language, set_guide_item
|
||||||
from calibre.translations.dynamic import translate
|
from calibre.translations.dynamic import translate
|
||||||
lang = get_book_language(container)
|
lang = get_book_language(container)
|
||||||
for item_type, (name, frag) in iteritems(self.changed_type_map):
|
|
||||||
title = self.known_type_map[item_type]
|
def title_for_type(item_type):
|
||||||
|
title = self.known_type_map.get(item_type, item_type)
|
||||||
if lang:
|
if lang:
|
||||||
title = translate(lang, title)
|
title = translate(lang, title)
|
||||||
set_guide_item(container, item_type, title, name, frag=frag)
|
return title
|
||||||
|
|
||||||
|
for item_type, (name, frag) in self.changes.items():
|
||||||
|
set_guide_item(container, self.epubtype_guide_map[item_type], title_for_type(item_type), name, frag=frag)
|
||||||
|
|
||||||
|
if container.opf_version_parsed.major > 2:
|
||||||
|
final = self.original_nav_map.copy()
|
||||||
|
for item_type, (name, frag) in self.changes.items():
|
||||||
|
final[item_type] = {'dest': name, 'frag': frag or '', 'title': title_for_type(item_type), 'type': item_type}
|
||||||
|
tocname, root = ensure_container_has_nav(container, lang=lang)
|
||||||
|
set_landmarks(container, root, tocname, final.values())
|
||||||
|
container.dirty(tocname)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def test(cls):
|
def test(cls):
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from calibre.ebooks.oeb.polish.container import get_container
|
from calibre.ebooks.oeb.polish.container import get_container
|
||||||
c = get_container(sys.argv[-1], tweak_mode=True)
|
c = get_container(sys.argv[-1], tweak_mode=True)
|
||||||
d = cls(c)
|
d = cls(c)
|
||||||
@ -1187,6 +1228,7 @@ class AddCover(Dialog):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def test(cls):
|
def test(cls):
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from calibre.ebooks.oeb.polish.container import get_container
|
from calibre.ebooks.oeb.polish.container import get_container
|
||||||
c = get_container(sys.argv[-1], tweak_mode=True)
|
c = get_container(sys.argv[-1], tweak_mode=True)
|
||||||
d = cls(c)
|
d = cls(c)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user