mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
E-book viewer: When displaying metadata for the book, also display custom column metadata
This commit is contained in:
parent
5da59094c9
commit
225a7e0723
197
src/calibre/ebooks/metadata/book/render.py
Normal file
197
src/calibre/ebooks/metadata/book/render.py
Normal file
@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os
|
||||
from functools import partial
|
||||
|
||||
from calibre import prepare_string_for_xml, force_unicode
|
||||
from calibre.ebooks.metadata import fmt_sidx
|
||||
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.formatter import EvalFormatter
|
||||
from calibre.utils.date import is_date_undefined
|
||||
from calibre.utils.localization import calibre_langcode_to_name
|
||||
|
||||
default_sort = ('title', 'title_sort', 'authors', 'author_sort', 'series', 'rating', 'pubdate', 'tags', 'publisher', 'identifiers')
|
||||
|
||||
def field_sort(mi, name):
|
||||
try:
|
||||
title = mi.metadata_for_field(name)['name']
|
||||
except:
|
||||
title = 'zzz'
|
||||
return {x:(i, None) for i, x in enumerate(default_sort)}.get(name, (10000, sort_key(title)))
|
||||
|
||||
def displayable_field_keys(mi):
|
||||
for k in mi.all_field_keys():
|
||||
try:
|
||||
m = mi.metadata_for_field(k)
|
||||
except:
|
||||
continue
|
||||
if (
|
||||
m is not None and m['kind'] == 'field' and m['datatype'] is not None and
|
||||
k not in ('au_map', 'marked', 'ondevice', 'cover', 'series_sort') and
|
||||
not k.endswith('_index')
|
||||
):
|
||||
yield k
|
||||
|
||||
def get_field_list(mi):
|
||||
for field in sorted(displayable_field_keys(mi), key=partial(field_sort, mi)):
|
||||
yield field, True
|
||||
|
||||
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'):
|
||||
if field_list is None:
|
||||
field_list = get_field_list(mi)
|
||||
ans = []
|
||||
comment_fields = []
|
||||
isdevice = not hasattr(mi, 'id')
|
||||
row = u'<td class="title">%s</td><td class="value">%s</td>'
|
||||
p = prepare_string_for_xml
|
||||
a = partial(prepare_string_for_xml, attribute=True)
|
||||
|
||||
for field in (field for field, display in field_list if display):
|
||||
try:
|
||||
metadata = mi.metadata_for_field(field)
|
||||
except:
|
||||
continue
|
||||
if not metadata:
|
||||
continue
|
||||
if field == 'sort':
|
||||
field = 'title_sort'
|
||||
if metadata['datatype'] == 'bool':
|
||||
isnull = mi.get(field) is None
|
||||
else:
|
||||
isnull = mi.is_null(field)
|
||||
if isnull:
|
||||
continue
|
||||
name = metadata['name']
|
||||
if not name:
|
||||
name = field
|
||||
name += ':'
|
||||
if metadata['datatype'] == 'comments' or field == 'comments':
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = force_unicode(val)
|
||||
comment_fields.append(comments_to_html(val))
|
||||
elif metadata['datatype'] == 'rating':
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = val/2.0
|
||||
ans.append((field,
|
||||
u'<td class="title">%s</td><td class="rating value" '
|
||||
'style=\'font-family:"%s"\'>%s</td>'%(
|
||||
name, rating_font, u'\u2605'*int(val))))
|
||||
elif metadata['datatype'] == 'composite' and \
|
||||
metadata['display'].get('contains_html', False):
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = force_unicode(val)
|
||||
ans.append((field,
|
||||
row % (name, comments_to_html(val))))
|
||||
elif field == 'path':
|
||||
if mi.path:
|
||||
path = force_unicode(mi.path, filesystem_encoding)
|
||||
scheme = u'devpath' if isdevice else u'path'
|
||||
url = prepare_string_for_xml(path if isdevice else
|
||||
unicode(mi.id), True)
|
||||
pathstr = _('Click to open')
|
||||
extra = ''
|
||||
if isdevice:
|
||||
durl = url
|
||||
if durl.startswith('mtp:::'):
|
||||
durl = ':::'.join((durl.split(':::'))[2:])
|
||||
extra = '<br><span style="font-size:smaller">%s</span>'%(
|
||||
prepare_string_for_xml(durl))
|
||||
link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url,
|
||||
prepare_string_for_xml(path, True), pathstr, extra)
|
||||
ans.append((field, row % (name, link)))
|
||||
elif field == 'formats':
|
||||
if isdevice:
|
||||
continue
|
||||
path = ''
|
||||
if mi.path:
|
||||
h, t = os.path.split(mi.path)
|
||||
path = '/'.join((os.path.basename(h), t))
|
||||
data = ({
|
||||
'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')),
|
||||
'ext':x.lower(), 'id':mi.id
|
||||
} for x in mi.formats)
|
||||
fmts = [u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data]
|
||||
ans.append((field, row % (name, u', '.join(fmts))))
|
||||
elif field == 'identifiers':
|
||||
urls = urls_from_identifiers(mi.identifiers)
|
||||
links = [u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(name))
|
||||
for name, id_typ, id_val, url in urls]
|
||||
links = u', '.join(links)
|
||||
if links:
|
||||
ans.append((field, row % (_('Ids')+':', links)))
|
||||
elif field == 'authors' and not isdevice:
|
||||
authors = []
|
||||
formatter = EvalFormatter()
|
||||
for aut in mi.authors:
|
||||
link = ''
|
||||
if mi.author_link_map[aut]:
|
||||
link = mi.author_link_map[aut]
|
||||
elif default_author_link:
|
||||
vals = {'author': aut.replace(' ', '+')}
|
||||
try:
|
||||
vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+')
|
||||
except:
|
||||
vals['author_sort'] = aut.replace(' ', '+')
|
||||
link = formatter.safe_format(
|
||||
default_author_link, vals, '', vals)
|
||||
aut = p(aut)
|
||||
if link:
|
||||
authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(a(link), aut))
|
||||
else:
|
||||
authors.append(aut)
|
||||
ans.append((field, row % (name, u' & '.join(authors))))
|
||||
elif field == 'languages':
|
||||
if not mi.languages:
|
||||
continue
|
||||
names = filter(None, map(calibre_langcode_to_name, mi.languages))
|
||||
ans.append((field, row % (name, u', '.join(names))))
|
||||
else:
|
||||
val = mi.format_field(field)[-1]
|
||||
if val is None:
|
||||
continue
|
||||
val = p(val)
|
||||
if metadata['datatype'] == 'series':
|
||||
sidx = mi.get(field+'_index')
|
||||
if sidx is None:
|
||||
sidx = 1.0
|
||||
val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict(
|
||||
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers),
|
||||
series=p(getattr(mi, field)))
|
||||
elif metadata['datatype'] == 'datetime':
|
||||
aval = getattr(mi, field)
|
||||
if is_date_undefined(aval):
|
||||
continue
|
||||
|
||||
ans.append((field, row % (name, val)))
|
||||
|
||||
dc = getattr(mi, 'device_collections', [])
|
||||
if dc:
|
||||
dc = u', '.join(sorted(dc, key=sort_key))
|
||||
ans.append(('device_collections',
|
||||
row % (_('Collections')+':', dc)))
|
||||
|
||||
def classname(field):
|
||||
try:
|
||||
dt = mi.metadata_for_field(field)['datatype']
|
||||
except:
|
||||
dt = 'text'
|
||||
return 'datatype_%s'%dt
|
||||
|
||||
ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'),
|
||||
classname(field), html) for field, html in ans]
|
||||
# print '\n'.join(ans)
|
||||
return u'<table class="fields">%s</table>'%(u'\n'.join(ans)), comment_fields
|
||||
|
||||
|
@ -5,34 +5,24 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, QIcon,
|
||||
QPropertyAnimation, QEasingCurve, QApplication, QFontInfo, QAction,
|
||||
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu,
|
||||
QPen, QColor)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre import fit_image, force_unicode, prepare_string_for_xml
|
||||
from calibre import fit_image
|
||||
from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
|
||||
IMAGE_EXTENSIONS, dnd_has_extension)
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.book.base import (field_metadata, Metadata)
|
||||
from calibre.ebooks.metadata import fmt_sidx
|
||||
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.library.comments import comments_to_html
|
||||
from calibre.ebooks.metadata.book.render import mi_to_html
|
||||
from calibre.gui2 import (config, open_url, pixmap_to_data, gprefs,
|
||||
rating_font)
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.formatter import EvalFormatter
|
||||
from calibre.utils.date import is_date_undefined
|
||||
from calibre.utils.localization import calibre_langcode_to_name
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
def render_html(mi, css, vertical, widget, all_fields=False): # {{{
|
||||
table, comment_fields = render_data(mi, all_fields=all_fields,
|
||||
def render_html(mi, css, vertical, widget, all_fields=False, render_data_func=None): # {{{
|
||||
table, comment_fields = (render_data_func or render_data)(mi, all_fields=all_fields,
|
||||
use_roman_numbers=config['use_roman_numerals_for_series_number'])
|
||||
|
||||
def color_to_string(col):
|
||||
@ -105,149 +95,10 @@ def get_field_list(fm, use_defaults=False):
|
||||
return [(f, d) for f, d in fieldlist if f in available]
|
||||
|
||||
def render_data(mi, use_roman_numbers=True, all_fields=False):
|
||||
ans = []
|
||||
comment_fields = []
|
||||
isdevice = not hasattr(mi, 'id')
|
||||
fm = getattr(mi, 'field_metadata', field_metadata)
|
||||
row = u'<td class="title">%s</td><td class="value">%s</td>'
|
||||
p = prepare_string_for_xml
|
||||
a = partial(prepare_string_for_xml, attribute=True)
|
||||
|
||||
for field, display in get_field_list(fm):
|
||||
metadata = fm.get(field, None)
|
||||
if field == 'sort':
|
||||
field = 'title_sort'
|
||||
if all_fields:
|
||||
display = True
|
||||
if metadata['datatype'] == 'bool':
|
||||
isnull = mi.get(field) is None
|
||||
else:
|
||||
isnull = mi.is_null(field)
|
||||
if (not display or not metadata or isnull):
|
||||
continue
|
||||
name = metadata['name']
|
||||
if not name:
|
||||
name = field
|
||||
name += ':'
|
||||
if metadata['datatype'] == 'comments' or field == 'comments':
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = force_unicode(val)
|
||||
comment_fields.append(comments_to_html(val))
|
||||
elif metadata['datatype'] == 'rating':
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = val/2.0
|
||||
ans.append((field,
|
||||
u'<td class="title">%s</td><td class="rating value" '
|
||||
'style=\'font-family:"%s"\'>%s</td>'%(
|
||||
name, rating_font(), u'\u2605'*int(val))))
|
||||
elif metadata['datatype'] == 'composite' and \
|
||||
metadata['display'].get('contains_html', False):
|
||||
val = getattr(mi, field)
|
||||
if val:
|
||||
val = force_unicode(val)
|
||||
ans.append((field,
|
||||
row % (name, comments_to_html(val))))
|
||||
elif field == 'path':
|
||||
if mi.path:
|
||||
path = force_unicode(mi.path, filesystem_encoding)
|
||||
scheme = u'devpath' if isdevice else u'path'
|
||||
url = prepare_string_for_xml(path if isdevice else
|
||||
unicode(mi.id), True)
|
||||
pathstr = _('Click to open')
|
||||
extra = ''
|
||||
if isdevice:
|
||||
durl = url
|
||||
if durl.startswith('mtp:::'):
|
||||
durl = ':::'.join((durl.split(':::'))[2:])
|
||||
extra = '<br><span style="font-size:smaller">%s</span>'%(
|
||||
prepare_string_for_xml(durl))
|
||||
link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url,
|
||||
prepare_string_for_xml(path, True), pathstr, extra)
|
||||
ans.append((field, row % (name, link)))
|
||||
elif field == 'formats':
|
||||
if isdevice:
|
||||
continue
|
||||
path = ''
|
||||
if mi.path:
|
||||
h, t = os.path.split(mi.path)
|
||||
path = '/'.join((os.path.basename(h), t))
|
||||
data = ({
|
||||
'fmt':x, 'path':a(path or ''), 'fname':a(mi.format_files.get(x, '')),
|
||||
'ext':x.lower(), 'id':mi.id
|
||||
} for x in mi.formats)
|
||||
fmts = [u'<a title="{path}/{fname}.{ext}" href="format:{id}:{fmt}">{fmt}</a>'.format(**x) for x in data]
|
||||
ans.append((field, row % (name, u', '.join(fmts))))
|
||||
elif field == 'identifiers':
|
||||
urls = urls_from_identifiers(mi.identifiers)
|
||||
links = [u'<a href="%s" title="%s:%s">%s</a>' % (a(url), a(id_typ), a(id_val), p(name))
|
||||
for name, id_typ, id_val, url in urls]
|
||||
links = u', '.join(links)
|
||||
if links:
|
||||
ans.append((field, row % (_('Ids')+':', links)))
|
||||
elif field == 'authors' and not isdevice:
|
||||
authors = []
|
||||
formatter = EvalFormatter()
|
||||
for aut in mi.authors:
|
||||
link = ''
|
||||
if mi.author_link_map[aut]:
|
||||
link = mi.author_link_map[aut]
|
||||
elif gprefs.get('default_author_link'):
|
||||
vals = {'author': aut.replace(' ', '+')}
|
||||
try:
|
||||
vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+')
|
||||
except:
|
||||
vals['author_sort'] = aut.replace(' ', '+')
|
||||
link = formatter.safe_format(
|
||||
gprefs.get('default_author_link'), vals, '', vals)
|
||||
aut = p(aut)
|
||||
if link:
|
||||
authors.append(u'<a calibre-data="authors" href="%s">%s</a>'%(a(link), aut))
|
||||
else:
|
||||
authors.append(aut)
|
||||
ans.append((field, row % (name, u' & '.join(authors))))
|
||||
elif field == 'languages':
|
||||
if not mi.languages:
|
||||
continue
|
||||
names = filter(None, map(calibre_langcode_to_name, mi.languages))
|
||||
ans.append((field, row % (name, u', '.join(names))))
|
||||
else:
|
||||
val = mi.format_field(field)[-1]
|
||||
if val is None:
|
||||
continue
|
||||
val = p(val)
|
||||
if metadata['datatype'] == 'series':
|
||||
sidx = mi.get(field+'_index')
|
||||
if sidx is None:
|
||||
sidx = 1.0
|
||||
val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict(
|
||||
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers),
|
||||
series=p(getattr(mi, field)))
|
||||
elif metadata['datatype'] == 'datetime':
|
||||
aval = getattr(mi, field)
|
||||
if is_date_undefined(aval):
|
||||
continue
|
||||
|
||||
ans.append((field, row % (name, val)))
|
||||
|
||||
dc = getattr(mi, 'device_collections', [])
|
||||
if dc:
|
||||
dc = u', '.join(sorted(dc, key=sort_key))
|
||||
ans.append(('device_collections',
|
||||
row % (_('Collections')+':', dc)))
|
||||
|
||||
def classname(field):
|
||||
try:
|
||||
dt = fm[field]['datatype']
|
||||
except:
|
||||
dt = 'text'
|
||||
return 'datatype_%s'%dt
|
||||
|
||||
ans = [u'<tr id="%s" class="%s">%s</tr>'%(field.replace('#', '_'),
|
||||
classname(field), html) for field, html in ans]
|
||||
# print '\n'.join(ans)
|
||||
return u'<table class="fields">%s</table>'%(u'\n'.join(ans)), comment_fields
|
||||
field_list = get_field_list(getattr(mi, 'field_metadata', field_metadata))
|
||||
field_list = [(x, all_fields or display) for x, display in field_list]
|
||||
return mi_to_html(mi, field_list=field_list, use_roman_numbers=use_roman_numbers,
|
||||
rating_font=rating_font(), default_author_link=gprefs.get('default_author_link'))
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -5,11 +5,12 @@ import traceback, os, sys, functools, textwrap
|
||||
from functools import partial
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import (QApplication, Qt, QIcon, QTimer, QByteArray, QSize,
|
||||
QTime, QDoubleSpinBox, QLabel, QTextBrowser, QPropertyAnimation,
|
||||
QPainter, QBrush, QColor, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
|
||||
QLineEdit, QToolButton, QMenu, QInputDialog, QAction,
|
||||
QModelIndex)
|
||||
from PyQt4.Qt import (
|
||||
QApplication, Qt, QIcon, QTimer, QByteArray, QSize, QTime, QDoubleSpinBox,
|
||||
QLabel, QPropertyAnimation, pyqtSignal, QUrl, QRegExpValidator, QRegExp,
|
||||
QLineEdit, QToolButton, QMenu, QInputDialog, QAction, QModelIndex, QPalette,
|
||||
QPainter, QBrush, QColor)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre.gui2.viewer.main_ui import Ui_EbookViewer
|
||||
from calibre.gui2.viewer.printing import Printing
|
||||
@ -17,14 +18,13 @@ from calibre.gui2.viewer.bookmarkmanager import BookmarkManager
|
||||
from calibre.gui2.viewer.toc import TOC
|
||||
from calibre.gui2.widgets import ProgressIndicator
|
||||
from calibre.gui2.main_window import MainWindow
|
||||
from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files,
|
||||
from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files, rating_font,
|
||||
info_dialog, error_dialog, open_url, available_height, setup_gui_option_parser, detach_gui)
|
||||
from calibre.ebooks.oeb.iterator.book import EbookIterator
|
||||
from calibre.ebooks import DRMError
|
||||
from calibre.constants import islinux, filesystem_encoding
|
||||
from calibre.utils.config import Config, StringConfig, JSONConfig
|
||||
from calibre.gui2.search_box import SearchBox2
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.customize.ui import available_input_formats
|
||||
from calibre.gui2.viewer.dictionary import Lookup
|
||||
from calibre import as_unicode, force_unicode, isbytestring
|
||||
@ -102,31 +102,44 @@ class History(list):
|
||||
self.forward_pos = None
|
||||
self.set_actions()
|
||||
|
||||
class Metadata(QLabel):
|
||||
class Metadata(QWebView):
|
||||
|
||||
def __init__(self, parent):
|
||||
QTextBrowser.__init__(self, parent.centralWidget())
|
||||
QWebView.__init__(self, parent.centralWidget())
|
||||
s = self.settings()
|
||||
s.setAttribute(s.JavascriptEnabled, False)
|
||||
self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks)
|
||||
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
||||
palette = self.palette()
|
||||
palette.setBrush(QPalette.Base, Qt.transparent)
|
||||
self.page().setPalette(palette)
|
||||
self.css = P('templates/book_details.css', data=True).decode('utf-8')
|
||||
|
||||
self.view = parent.splitter
|
||||
self.setGeometry(self.view.geometry())
|
||||
self.setWordWrap(True)
|
||||
self.setVisible(False)
|
||||
|
||||
def show_opf(self, opf, ext=''):
|
||||
mi = MetaInformation(opf)
|
||||
html = '<h2 align="center">%s</h2>%s\n<b>%s:</b> %s'\
|
||||
%(_('Metadata'), u''.join(mi.to_html()),
|
||||
_('Book format'), ext.upper())
|
||||
self.setText(html)
|
||||
from calibre.gui2.book_details import render_html
|
||||
from calibre.ebooks.metadata.book.render import mi_to_html
|
||||
|
||||
def render_data(mi, use_roman_numbers=True, all_fields=False):
|
||||
return mi_to_html(mi, use_roman_numbers=use_roman_numbers, rating_font=rating_font())
|
||||
|
||||
mi = opf.to_book_metadata()
|
||||
html = render_html(mi, self.css, True, self, render_data_func=render_data)
|
||||
self.setHtml(html)
|
||||
|
||||
def setVisible(self, x):
|
||||
if x:
|
||||
self.setGeometry(self.view.geometry())
|
||||
QLabel.setVisible(self, x)
|
||||
QWebView.setVisible(self, x)
|
||||
|
||||
def paintEvent(self, ev):
|
||||
p = QPainter(self)
|
||||
p.fillRect(ev.region().boundingRect(), QBrush(QColor(200, 200, 200, 220), Qt.SolidPattern))
|
||||
p.end()
|
||||
QLabel.paintEvent(self, ev)
|
||||
QWebView.paintEvent(self, ev)
|
||||
|
||||
|
||||
class DoubleSpinBox(QDoubleSpinBox):
|
||||
|
Loading…
x
Reference in New Issue
Block a user