mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 02:34:06 -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'), _(
|
||||
'Semantics are not supported for the AZW3 format.'), show=True)
|
||||
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'))
|
||||
d.apply_changes(current_container())
|
||||
self.apply_container_update_to_gui()
|
||||
|
@ -5,27 +5,41 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os, textwrap, unicodedata
|
||||
import os
|
||||
import textwrap
|
||||
import unicodedata
|
||||
from collections import OrderedDict
|
||||
|
||||
from qt.core import (
|
||||
QGridLayout, QLabel, QLineEdit, QVBoxLayout, QFormLayout, QHBoxLayout,
|
||||
QToolButton, QIcon, QApplication, Qt, QWidget, QPoint, QSizePolicy,
|
||||
QPainter, QStaticText, pyqtSignal, QTextOption, QAbstractListModel, QItemSelectionModel,
|
||||
QModelIndex, QStyledItemDelegate, QStyle, QCheckBox, QListView, QPalette,
|
||||
QTextDocument, QSize, QComboBox, QFrame, QCursor, QGroupBox, QSplitter,
|
||||
QPixmap, QRect, QPlainTextEdit, QMimeData, QDialog, QEvent, QDialogButtonBox)
|
||||
QAbstractListModel, QApplication, QCheckBox, QComboBox, QCursor, QDialog,
|
||||
QDialogButtonBox, QEvent, QFormLayout, QFrame, QGridLayout, QGroupBox,
|
||||
QHBoxLayout, QIcon, QItemSelectionModel, QLabel, QLineEdit, QListView, QMimeData,
|
||||
QModelIndex, QPainter, QPalette, QPixmap, QPlainTextEdit, QPoint, QRect, QSize,
|
||||
QSizePolicy, QSplitter, QStaticText, QStyle, QStyledItemDelegate, Qt,
|
||||
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.ebooks.oeb.polish.cover import get_raster_cover_name
|
||||
from calibre.ebooks.oeb.polish.utils import lead_text, guess_type
|
||||
from calibre.gui2 import error_dialog, choose_files, choose_save_file, info_dialog, choose_images
|
||||
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.utils.icu import primary_sort_key, sort_key, primary_contains, numeric_sort_key
|
||||
from calibre.utils.matcher import get_char, Matcher, DEFAULT_LEVEL1, DEFAULT_LEVEL2, DEFAULT_LEVEL3
|
||||
from calibre.ebooks.oeb.polish.toc import (
|
||||
ensure_container_has_nav, get_guide_landmarks, get_nav_landmarks, set_landmarks
|
||||
)
|
||||
from calibre.ebooks.oeb.polish.upgrade import guide_epubtype_map
|
||||
from calibre.ebooks.oeb.polish.utils import guess_type, lead_text
|
||||
from calibre.gui2 import (
|
||||
choose_files, choose_images, choose_save_file, error_dialog, info_dialog
|
||||
)
|
||||
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
|
||||
|
||||
ROOT = QModelIndex()
|
||||
@ -774,6 +788,7 @@ class InsertLink(Dialog):
|
||||
@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, next(c.spine_names)[0])
|
||||
@ -789,11 +804,11 @@ class InsertSemantics(Dialog):
|
||||
|
||||
def __init__(self, container, parent=None):
|
||||
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.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)
|
||||
|
||||
def sizeHint(self):
|
||||
@ -801,14 +816,16 @@ class InsertSemantics(Dialog):
|
||||
|
||||
def create_known_type_map(self):
|
||||
_ = lambda x: x
|
||||
self.epubtype_guide_map = {v: k for k, v in guide_epubtype_map.items()}
|
||||
self.known_type_map = {
|
||||
'title-page': _('Title page'),
|
||||
'titlepage': _('Title page'),
|
||||
'toc': _('Table of Contents'),
|
||||
'index': _('Index'),
|
||||
'glossary': _('Glossary'),
|
||||
'acknowledgements': _('Acknowledgements'),
|
||||
'acknowledgments': _('Acknowledgements'),
|
||||
'bibliography': _('Bibliography'),
|
||||
'colophon': _('Colophon'),
|
||||
'cover': _('Cover'),
|
||||
'copyright-page': _('Copyright page'),
|
||||
'dedication': _('Dedication'),
|
||||
'epigraph': _('Epigraph'),
|
||||
@ -817,13 +834,14 @@ class InsertSemantics(Dialog):
|
||||
'lot': _('List of tables'),
|
||||
'notes': _('Notes'),
|
||||
'preface': _('Preface'),
|
||||
'text': _('Text'),
|
||||
'bodymatter': _('Text'),
|
||||
}
|
||||
_ = __builtins__['_']
|
||||
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'),
|
||||
'text': _('First "real" page of content'),
|
||||
'bodymatter': _('First "real" page of content'),
|
||||
}
|
||||
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)]
|
||||
@ -835,6 +853,7 @@ class InsertSemantics(Dialog):
|
||||
self.setLayout(l)
|
||||
|
||||
self.tl = tl = QFormLayout()
|
||||
tl.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
|
||||
self.semantic_type = QComboBox(self)
|
||||
for key, val in iteritems(self.all_types):
|
||||
self.semantic_type.addItem(val, key)
|
||||
@ -888,9 +907,21 @@ class InsertSemantics(Dialog):
|
||||
d.resize(d.sizeHint())
|
||||
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):
|
||||
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)
|
||||
|
||||
def show_type(self, name, frag):
|
||||
@ -916,7 +947,8 @@ class InsertSemantics(Dialog):
|
||||
def target_text_changed(self):
|
||||
name, frag = str(self.target.text()).partition('#')[::2]
|
||||
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):
|
||||
rows = list(self.file_names.selectionModel().selectedRows())
|
||||
@ -950,23 +982,32 @@ class InsertSemantics(Dialog):
|
||||
href += frag
|
||||
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):
|
||||
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
|
||||
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:
|
||||
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
|
||||
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)
|
||||
@ -1187,6 +1228,7 @@ class AddCover(Dialog):
|
||||
@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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user