mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
78be0f69a4
@ -753,6 +753,9 @@ class Metadata:
|
||||
res = human_readable(res)
|
||||
return (name, str(res), orig_res, fmeta)
|
||||
|
||||
if self.get(key, None):
|
||||
return (key, str(self.get(key)), self.get(key), None)
|
||||
|
||||
return (None, None, None, None)
|
||||
|
||||
def __unicode__representation__(self):
|
||||
|
@ -12,6 +12,7 @@ from calibre import force_unicode, prepare_string_for_xml
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.db.constants import DATA_DIR_NAME
|
||||
from calibre.ebooks.metadata import fmt_sidx, rating_to_stars
|
||||
from calibre.ebooks.metadata.book.formatter import SafeFormat
|
||||
from calibre.ebooks.metadata.search_internet import DEFAULT_AUTHOR_SOURCE, name_for, qquote, url_for_author_search, url_for_book_search
|
||||
from calibre.ebooks.metadata.sources.identify import urls_from_identifiers
|
||||
from calibre.library.comments import comments_to_html, markdown
|
||||
@ -66,6 +67,19 @@ def search_action_with_data(search_term, value, book_id, field=None, **k):
|
||||
return search_action(search_term, value, field=field, book_id=book_id, **k)
|
||||
|
||||
|
||||
def cc_search_action_with_data(search_term, value, book_id, fm, mi, field=None, **k):
|
||||
if mi is not None and fm is not None:
|
||||
template = fm.get('display', {}).get('web_search_template')
|
||||
if template:
|
||||
formatter = SafeFormat()
|
||||
mi.set('item_value', value)
|
||||
u = formatter.safe_format(template, mi, "BOOK DETAILS WEB LINK", mi)
|
||||
if u:
|
||||
v = prepare_string_for_xml(_('Click to browse to {0}').format(u), attribute=True)
|
||||
return action('cc_url', url=u),v
|
||||
t = _('Click to see books with {0}: {1}').format(mi.get('name', search_term), prepare_string_for_xml(value))
|
||||
return search_action_with_data(search_term, value, book_id, **k), t
|
||||
|
||||
def notes_action(**keys):
|
||||
return 'notes:' + as_hex_unicode(json_dumps(keys))
|
||||
|
||||
@ -211,16 +225,16 @@ def mi_to_html(
|
||||
ans.append((field, row % (name, comments_to_html(val))))
|
||||
else:
|
||||
if not metadata['is_multiple']:
|
||||
val = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action(field, val, book_id=book_id),
|
||||
_('Click to see books with {0}: {1}').format(metadata['name'], a(val)), p(val))
|
||||
u,v = cc_search_action_with_data(field, val, book_id, metadata, mi, field)
|
||||
val = '<a href="{}" title="{}">{}</a>'.format(u, v, p(val))
|
||||
else:
|
||||
all_vals = [v.strip()
|
||||
for v in val.split(metadata['is_multiple']['cache_to_list']) if v.strip()]
|
||||
if show_links:
|
||||
links = ['<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action(field, x, book_id=book_id), _('Click to see books with {0}: {1}').format(
|
||||
metadata['name'], a(x)), p(x)) for x in all_vals]
|
||||
links = []
|
||||
for x in all_vals:
|
||||
u,v = cc_search_action_with_data(field, x, book_id, metadata, mi, field)
|
||||
links.append('<a href="{}" title="{}">{}</a>'.format(u, v, p(x)))
|
||||
else:
|
||||
links = all_vals
|
||||
val = value_list(metadata['is_multiple']['list_to_ui'], links)
|
||||
@ -349,12 +363,19 @@ def mi_to_html(
|
||||
except Exception:
|
||||
st = field
|
||||
series = getattr(mi, field)
|
||||
val = _(
|
||||
'%(sidx)s of <a href="%(href)s" title="%(tt)s">'
|
||||
'<span class="%(cls)s">%(series)s</span></a>') % dict(
|
||||
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls='series_name',
|
||||
series=p(series), href=search_action_with_data(st, series, book_id, field),
|
||||
tt=p(_('Click to see books in this series')))
|
||||
if metadata.get('display', {}).get('web_search_template'):
|
||||
u,v = cc_search_action_with_data(st, series, book_id, metadata, mi, field)
|
||||
val = _('%(sidx)s of <a href="%(href)s" title="%(tt)s">'
|
||||
'<span class="%(cls)s">%(series)s</span></a>') % dict(
|
||||
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls='series_name',
|
||||
series=p(series), href=u, tt=v)
|
||||
else:
|
||||
val = _(
|
||||
'%(sidx)s of <a href="%(href)s" title="%(tt)s">'
|
||||
'<span class="%(cls)s">%(series)s</span></a>') % dict(
|
||||
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls='series_name',
|
||||
series=p(series), href=search_action_with_data(st, series, book_id, field),
|
||||
tt=p(_('Click to see books in this series')))
|
||||
val += add_other_links(field, series)
|
||||
elif metadata['datatype'] == 'datetime':
|
||||
aval = getattr(mi, field)
|
||||
@ -371,32 +392,51 @@ def mi_to_html(
|
||||
search_action_with_data(key, str(aval), book_id, None, original_value=val), a(
|
||||
_('Click to see books with {0}: {1} (derived from {2})').format(
|
||||
metadata['name'] or field, aval, val)), val)
|
||||
elif metadata['datatype'] == 'text' and metadata['is_multiple']:
|
||||
try:
|
||||
st = metadata['search_terms'][0]
|
||||
except Exception:
|
||||
st = field
|
||||
all_vals = mi.get(field)
|
||||
if not metadata.get('display', {}).get('is_names', False):
|
||||
all_vals = sorted(all_vals, key=sort_key)
|
||||
links = []
|
||||
for x in all_vals:
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action_with_data(st, x, book_id, field), _('Click to see books with {0}: {1}').format(
|
||||
metadata['name'] or field, a(x)), p(x))
|
||||
v += add_other_links(field, x)
|
||||
links.append(v)
|
||||
val = value_list(metadata['is_multiple']['list_to_ui'], links)
|
||||
elif metadata['datatype'] == 'text' or metadata['datatype'] == 'enumeration':
|
||||
# text/is_multiple handled above so no need to add the test to the if
|
||||
try:
|
||||
st = metadata['search_terms'][0]
|
||||
except Exception:
|
||||
st = field
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action_with_data(st, unescaped_val, book_id, field), a(
|
||||
_('Click to see books with {0}: {1}').format(metadata['name'] or field, val)), val)
|
||||
val = v + add_other_links(field, val)
|
||||
elif metadata['datatype'] == 'text':
|
||||
if metadata['is_multiple']:
|
||||
try:
|
||||
st = metadata['search_terms'][0]
|
||||
except Exception:
|
||||
st = field
|
||||
all_vals = mi.get(field)
|
||||
if not metadata.get('display', {}).get('is_names', False):
|
||||
all_vals = sorted(all_vals, key=sort_key)
|
||||
links = []
|
||||
if show_links:
|
||||
for x in all_vals:
|
||||
if metadata['is_custom']:
|
||||
u,v = cc_search_action_with_data(field, x, book_id, metadata, mi, field)
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(u, v, p(x))
|
||||
else:
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action_with_data(st, x, book_id, field),
|
||||
_('Click to see books with {0}: {1}').format(
|
||||
metadata['name'] or field, a(x)), p(x))
|
||||
v += add_other_links(field, x)
|
||||
links.append(v)
|
||||
else:
|
||||
links = all_vals
|
||||
val = value_list(metadata['is_multiple']['list_to_ui'], links)
|
||||
else:
|
||||
try:
|
||||
st = metadata['search_terms'][0]
|
||||
except Exception:
|
||||
st = field
|
||||
if show_links:
|
||||
if metadata['is_custom']:
|
||||
u,v = cc_search_action_with_data(st, x, book_id, metadata, mi, field)
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(u, v, p(x))
|
||||
else:
|
||||
v = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action_with_data(st, x, book_id, field),
|
||||
_('Click to see books with {0}: {1}').format(
|
||||
metadata['name'] or field, a(x)), p(x))
|
||||
else:
|
||||
v = val
|
||||
val = v + add_other_links(field, val)
|
||||
elif metadata['datatype'] == 'enumeration':
|
||||
u,v = cc_search_action_with_data(field, x, book_id, metadata, mi, field)
|
||||
val = '<a href="{}" title="{}">{}</a>'.format(u, v, p(x)) + add_other_links(field, val)
|
||||
elif metadata['datatype'] == 'bool':
|
||||
val = '<a href="{}" title="{}">{}</a>'.format(
|
||||
search_action_with_data(field, val, book_id, None), a(
|
||||
|
@ -1479,6 +1479,9 @@ class BookDetails(DetailsLayout, DropMixin): # {{{
|
||||
if dt == 'search':
|
||||
field = data.get('field')
|
||||
search_term(data['term'], data['value'])
|
||||
elif dt == 'cc_url':
|
||||
if data['url']:
|
||||
browse(data['url'])
|
||||
elif dt == 'author':
|
||||
url = data['url']
|
||||
if url == 'calibre':
|
||||
|
@ -26,11 +26,13 @@ from qt.core import (
|
||||
QRadioButton,
|
||||
QSpinBox,
|
||||
Qt,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
|
||||
from calibre.utils.date import UNDEFINED_DATE, parse_date
|
||||
from calibre.utils.localization import ngettext
|
||||
@ -231,6 +233,7 @@ class CreateCustomColumn(QDialog):
|
||||
elif ct == '*text':
|
||||
self.is_names.setChecked(c['display'].get('is_names', False))
|
||||
self.description_box.setText(c['display'].get('description', ''))
|
||||
self.web_search_template.setText(c['display'].get('web_search_template', ''))
|
||||
self.decimals_box.setValue(min(9, max(1, int(c['display'].get('decimals', 2)))))
|
||||
|
||||
all_colors = [str(s) for s in list(QColor.colorNames())]
|
||||
@ -486,6 +489,7 @@ class CreateCustomColumn(QDialog):
|
||||
l.addWidget(cch)
|
||||
l.addStretch()
|
||||
add_row(None, l)
|
||||
|
||||
l = QHBoxLayout()
|
||||
self.composite_in_comments_box = cmc = QCheckBox(_('Show with comments in Book details'))
|
||||
cmc.setToolTip('<p>' + _('If you check this box then the column contents '
|
||||
@ -533,9 +537,45 @@ class CreateCustomColumn(QDialog):
|
||||
'Rating columns enter a number between 0 and 5.') + '</p>')
|
||||
self.default_value_label = add_row(_('&Default value:'), dv)
|
||||
|
||||
l = QHBoxLayout()
|
||||
self.web_search_label = QLabel(_('Search tem&plate:'))
|
||||
l.addWidget(self.web_search_label)
|
||||
wst = self.web_search_template = QLineEdit()
|
||||
wst.setToolTip('<p>' + _(
|
||||
"Fill in this box if you want clicking on the value in book details to do a "
|
||||
"web search instead of searching your calibre library. The book's metadata are "
|
||||
"available to the template. An additional field 'item_value' is available to the "
|
||||
"template. For multiple-valued (tags-like) columns it is the value being examined, "
|
||||
"telling you which value to use to generate the link.") + '</p>')
|
||||
l.addWidget(wst)
|
||||
self.web_search_label.setBuddy(wst)
|
||||
wst_tb = self.web_search_toolbutton = QToolButton()
|
||||
wst_tb.setIcon(QIcon.ic('edit_input.png'))
|
||||
l.addWidget(wst_tb)
|
||||
wst_tb.clicked.connect(self.cws_template_button_clicked)
|
||||
add_row(None, l)
|
||||
|
||||
self.resize(self.sizeHint())
|
||||
# }}}
|
||||
|
||||
def cws_template_button_clicked(self):
|
||||
db = self.gui.current_db.new_api
|
||||
lv = self.gui.library_view
|
||||
rows = lv.selectionModel().selectedRows()
|
||||
if not self.editing_col or not rows:
|
||||
vals = [{'value': _('Value'), 'lookup_name': _('Lookup name'), 'author': _('Author'),
|
||||
'title': _('Title'), 'author_sort': _('Author sort')}]
|
||||
else:
|
||||
vals = []
|
||||
for row in rows:
|
||||
book_id = lv.model().id(row)
|
||||
mi = db.new_api.get_metadata(book_id)
|
||||
mi.set('item_value', 'Item Value')
|
||||
vals.append(mi)
|
||||
d = TemplateDialog(parent=self, text=self.web_search_template.text(), mi=vals)
|
||||
if d.exec() == QDialog.DialogCode.Accepted:
|
||||
self.web_search_template.setText(d.rule[1])
|
||||
|
||||
def bool_radio_button_clicked(self, button, clicked):
|
||||
if clicked:
|
||||
self.bool_button_group.setFocusProxy(button)
|
||||
@ -617,7 +657,7 @@ class CreateCustomColumn(QDialog):
|
||||
'after the decimal point and thousands separated by commas.') + '</p>'
|
||||
)
|
||||
self.format_label.setText(l), self.format_default_label.setText(dl)
|
||||
for x in ('in_comments_box', 'heading_position', 'heading_position_label'):
|
||||
for x in ('in_comments_box', 'heading_position', 'heading_position_label',):
|
||||
getattr(self, 'composite_'+x).setVisible(col_type == 'composite')
|
||||
for x in ('box', 'default_label', 'label', 'sort_by', 'sort_by_label',
|
||||
'make_category', 'contains_html'):
|
||||
@ -638,6 +678,13 @@ class CreateCustomColumn(QDialog):
|
||||
self.comments_heading_position_label.setVisible(is_comments)
|
||||
self.comments_type.setVisible(is_comments)
|
||||
self.comments_type_label.setVisible(is_comments)
|
||||
|
||||
has_url_template = not is_comments and col_type in ('text', '*text', 'composite', '*composite',
|
||||
'series', 'enumeration')
|
||||
self.web_search_label.setVisible(has_url_template)
|
||||
self.web_search_template.setVisible(has_url_template)
|
||||
self.web_search_toolbutton.setVisible(has_url_template)
|
||||
|
||||
self.allow_half_stars.setVisible(col_type == 'rating')
|
||||
|
||||
is_bool = col_type == 'bool'
|
||||
@ -811,6 +858,7 @@ class CreateCustomColumn(QDialog):
|
||||
display_dict['default_value'] = default_val
|
||||
|
||||
display_dict['description'] = self.description_box.text().strip()
|
||||
display_dict['web_search_template'] = self.web_search_template.text().strip()
|
||||
|
||||
if not self.editing_col:
|
||||
self.caller.custcols[key] = {
|
||||
|
@ -7,14 +7,17 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import json
|
||||
|
||||
from qt.core import QAbstractListModel, QComboBox, QFormLayout, QIcon, QItemSelectionModel, QLineEdit, Qt, QVBoxLayout, QWidget, pyqtSignal
|
||||
from qt.core import QAbstractListModel, QComboBox, QDialog, QFormLayout, QHBoxLayout, QIcon, QItemSelectionModel, QLineEdit, Qt, QToolButton, QVBoxLayout, QWidget, pyqtSignal
|
||||
|
||||
from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK
|
||||
from calibre.ebooks.metadata.search_internet import qquote
|
||||
from calibre.gui2 import choose_files, choose_save_file, error_dialog
|
||||
from calibre.gui2.book_details import get_field_list
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
from calibre.gui2.preferences import LazyConfigWidgetBase
|
||||
from calibre.gui2.preferences.coloring import EditRules
|
||||
from calibre.gui2.ui import get_gui
|
||||
from calibre.utils.formatter import EvalFormatter
|
||||
|
||||
|
||||
class DefaultAuthorLink(QWidget):
|
||||
@ -44,6 +47,7 @@ class DefaultAuthorLink(QWidget):
|
||||
]:
|
||||
c.addItem(text, data)
|
||||
l.addRow(_('Clicking on &author names should:'), c)
|
||||
ul = QHBoxLayout()
|
||||
self.custom_url = u = QLineEdit(self)
|
||||
u.setToolTip(_(
|
||||
'Enter the URL to search. It should contain the string {0}'
|
||||
@ -51,8 +55,14 @@ class DefaultAuthorLink(QWidget):
|
||||
'\n{1}').format('{author}', 'https://en.wikipedia.org/w/index.php?search={author}'))
|
||||
u.textChanged.connect(self.changed_signal)
|
||||
u.setPlaceholderText(_('Enter the URL'))
|
||||
ul.addWidget(u)
|
||||
u = self.custom_url_button = QToolButton()
|
||||
u.setIcon(QIcon.ic('edit_input.png'))
|
||||
u.setToolTip(_('Click this button to open the template tester'))
|
||||
u.clicked.connect(self.open_template_tester)
|
||||
ul.addWidget(u)
|
||||
c.currentIndexChanged.connect(self.current_changed)
|
||||
l.addRow(u)
|
||||
l.addRow(ul)
|
||||
self.current_changed()
|
||||
c.currentIndexChanged.connect(self.changed_signal)
|
||||
|
||||
@ -71,9 +81,29 @@ class DefaultAuthorLink(QWidget):
|
||||
self.custom_url.setText(val)
|
||||
self.choices.setCurrentIndex(i)
|
||||
|
||||
def open_template_tester(self):
|
||||
gui = get_gui()
|
||||
db = gui.current_db.new_api
|
||||
lv = gui.library_view
|
||||
rows = lv.selectionModel().selectedRows()
|
||||
if not rows:
|
||||
vals = [{'author': qquote(_('Author')), 'title': _('Title'), 'author_sort': _('Author sort')}]
|
||||
else:
|
||||
vals = []
|
||||
for row in rows:
|
||||
book_id = lv.model().id(row)
|
||||
mi = db.new_api.get_proxy_metadata(book_id)
|
||||
vals.append({'author': qquote(mi.authors[0]),
|
||||
'title': qquote(mi.title),
|
||||
'author_sort': qquote(mi.author_sort_map.get(mi.authors[0]))})
|
||||
d = TemplateDialog(parent=self, text=self.custom_url.text(), mi=vals, formatter=EvalFormatter)
|
||||
if d.exec() == QDialog.DialogCode.Accepted:
|
||||
self.custom_url.setText(d.rule[1])
|
||||
|
||||
def current_changed(self):
|
||||
k = self.choices.currentData()
|
||||
self.custom_url.setVisible(k == 'url')
|
||||
self.custom_url_button.setVisible(k == 'url')
|
||||
|
||||
def restore_defaults(self):
|
||||
self.value = DEFAULT_AUTHOR_LINK
|
||||
|
Loading…
x
Reference in New Issue
Block a user