mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow creation of rules to convert arbitrary identifiers into clickable links in the book details panel
This commit is contained in:
parent
9269e62723
commit
b20610910e
@ -14,6 +14,7 @@ from threading import Thread
|
||||
from io import BytesIO
|
||||
from operator import attrgetter
|
||||
from urlparse import urlparse
|
||||
from urllib import quote
|
||||
|
||||
from calibre.customize.ui import metadata_plugins, all_metadata_plugins
|
||||
from calibre.ebooks.metadata import check_issn
|
||||
@ -25,6 +26,7 @@ from calibre.utils.date import utc_tz, as_utc
|
||||
from calibre.utils.html2text import html2text
|
||||
from calibre.utils.icu import lower
|
||||
from calibre.utils.date import UNDEFINED_DATE
|
||||
from calibre.utils.formatter import EvalFormatter
|
||||
|
||||
# Download worker {{{
|
||||
class Worker(Thread):
|
||||
@ -477,9 +479,9 @@ def identify(log, abort, # {{{
|
||||
if f == 'series':
|
||||
result.series_index = dummy.series_index
|
||||
result.relevance_in_source = i
|
||||
result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable
|
||||
and plugin.get_cached_cover_url(result.identifiers) is not
|
||||
None)
|
||||
result.has_cached_cover_url = (
|
||||
plugin.cached_cover_url_is_reliable and
|
||||
plugin.get_cached_cover_url(result.identifiers) is not None)
|
||||
result.identify_plugin = plugin
|
||||
if msprefs['txt_comments']:
|
||||
if plugin.has_html_comments and result.comments:
|
||||
@ -524,6 +526,20 @@ def identify(log, abort, # {{{
|
||||
def urls_from_identifiers(identifiers): # {{{
|
||||
identifiers = {k.lower():v for k, v in identifiers.iteritems()}
|
||||
ans = []
|
||||
rules = msprefs['id_link_rules']
|
||||
if rules:
|
||||
formatter = EvalFormatter()
|
||||
for k, val in identifiers.iteritems():
|
||||
vals = {'id':quote(val)}
|
||||
items = rules.get(k) or ()
|
||||
for name, template in items:
|
||||
try:
|
||||
url = formatter.safe_format(template, vals, '', vals)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.format_exc()
|
||||
continue
|
||||
ans.append((name, k, val, url))
|
||||
for plugin in all_metadata_plugins():
|
||||
try:
|
||||
for id_type, id_val, url in plugin.get_book_urls(identifiers):
|
||||
|
@ -20,6 +20,7 @@ msprefs.defaults['fewer_tags'] = True
|
||||
msprefs.defaults['find_first_edition_date'] = False
|
||||
msprefs.defaults['append_comments'] = False
|
||||
msprefs.defaults['tag_map_rules'] = []
|
||||
msprefs.defaults['id_link_rules'] = {}
|
||||
|
||||
# Google covers are often poor quality (scans/errors) but they have high
|
||||
# resolution, so they trump covers from better sources. So make sure they
|
||||
|
@ -7,19 +7,23 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import json
|
||||
|
||||
from collections import defaultdict
|
||||
from threading import Thread
|
||||
from functools import partial
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QApplication, QFont, QFontInfo, QFontDialog, QColorDialog, QPainter,
|
||||
QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal, QCursor,
|
||||
QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout)
|
||||
QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout,
|
||||
QTableWidget, QTableWidgetItem, QLabel, QFormLayout, QLineEdit
|
||||
)
|
||||
|
||||
from calibre import human_readable
|
||||
from calibre.ebooks.metadata.sources.prefs import msprefs
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList
|
||||
from calibre.gui2.preferences.look_feel_ui import Ui_Form
|
||||
from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog
|
||||
from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog, error_dialog
|
||||
from calibre.utils.localization import (available_translations,
|
||||
get_language, get_lang)
|
||||
from calibre.utils.config import prefs
|
||||
@ -27,6 +31,7 @@ from calibre.utils.icu import sort_key
|
||||
from calibre.gui2.book_details import get_field_list
|
||||
from calibre.gui2.preferences.coloring import EditRules
|
||||
from calibre.gui2.library.alternate_views import auto_height, CM_TO_INCH
|
||||
from calibre.gui2.widgets2 import Dialog
|
||||
|
||||
class BusyCursor(object):
|
||||
|
||||
@ -36,6 +41,115 @@ class BusyCursor(object):
|
||||
def __exit__(self, *args):
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
# IdLinksEditor {{{
|
||||
|
||||
class IdLinksRuleEdit(Dialog):
|
||||
|
||||
def __init__(self, key='', name='', template='', parent=None):
|
||||
title = _('Edit rule') if key else _('Create a new rule')
|
||||
Dialog.__init__(self, title=title, name='id-links-rule-editor', parent=parent)
|
||||
self.key.setText(key), self.nw.setText(name), self.template.setText(template or 'http://example.com/{id}')
|
||||
|
||||
@property
|
||||
def rule(self):
|
||||
return self.key.text().lower(), self.nw.text(), self.template.text()
|
||||
|
||||
def setup_ui(self):
|
||||
self.l = l = QFormLayout(self)
|
||||
l.setFieldGrowthPolicy(l.AllNonFixedFieldsGrow)
|
||||
l.addRow(QLabel(_(
|
||||
'The key of the identifier, for example, in isbn:XXX, they key is isbn')))
|
||||
self.key = k = QLineEdit(self)
|
||||
l.addRow(_('&Key:'), k)
|
||||
l.addRow(QLabel(_(
|
||||
'The name that will appear in the book details panel')))
|
||||
self.nw = n = QLineEdit(self)
|
||||
l.addRow(_('&Name:'), n)
|
||||
la = QLabel(_(
|
||||
'The template used to create the link. The placeholder {id} in the template will be replaced with the actual identifier value.'))
|
||||
la.setWordWrap(True)
|
||||
l.addRow(la)
|
||||
self.template = t = QLineEdit(self)
|
||||
l.addRow(_('&Template:'), t)
|
||||
t.selectAll()
|
||||
t.setFocus(Qt.OtherFocusReason)
|
||||
l.addWidget(self.bb)
|
||||
|
||||
def accept(self):
|
||||
r = self.rule
|
||||
for i, which in enumerate([_('Key'), _('Name'), _('Template')]):
|
||||
if not r[i]:
|
||||
return error_dialog(self, _('Value needed'), _(
|
||||
'The %s field cannot be empty') % which, show=True)
|
||||
Dialog.accept(self)
|
||||
|
||||
class IdLinksEditor(Dialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
Dialog.__init__(self, title=_('Create rules for identifiers'), name='id-links-rules-editor', parent=parent)
|
||||
|
||||
def setup_ui(self):
|
||||
self.l = l = QVBoxLayout(self)
|
||||
self.la = la = QLabel(_(
|
||||
'Create rules to convert identifiers into links.'))
|
||||
la.setWordWrap(True)
|
||||
l.addWidget(la)
|
||||
items = []
|
||||
for k, lx in msprefs['id_link_rules'].iteritems():
|
||||
for n, t in lx:
|
||||
items.append((k, n, t))
|
||||
items.sort(key=lambda x:sort_key(x[1]))
|
||||
self.table = t = QTableWidget(len(items), 3, self)
|
||||
t.setHorizontalHeaderLabels([_('Key'), _('Name'), _('Template')])
|
||||
for r, (key, val, template) in enumerate(items):
|
||||
t.setItem(r, 0, QTableWidgetItem(key))
|
||||
t.setItem(r, 1, QTableWidgetItem(val))
|
||||
t.setItem(r, 2, QTableWidgetItem(template))
|
||||
l.addWidget(t)
|
||||
t.horizontalHeader().setSectionResizeMode(2, t.horizontalHeader().Stretch)
|
||||
self.cb = b = QPushButton(QIcon(I('plus.png')), _('&Add rule'), self)
|
||||
b.clicked.connect(lambda : self.edit_rule())
|
||||
self.bb.addButton(b, self.bb.ActionRole)
|
||||
self.rb = b = QPushButton(QIcon(I('minus.png')), _('&Remove rule'), self)
|
||||
b.clicked.connect(lambda : self.remove_rule())
|
||||
self.bb.addButton(b, self.bb.ActionRole)
|
||||
self.eb = b = QPushButton(QIcon(I('modified.png')), _('&Edit rule'), self)
|
||||
b.clicked.connect(lambda : self.edit_rule(self.table.currentRow()))
|
||||
self.bb.addButton(b, self.bb.ActionRole)
|
||||
l.addWidget(self.bb)
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(700, 550)
|
||||
|
||||
def accept(self):
|
||||
rules = defaultdict(list)
|
||||
for r in range(self.table.rowCount()):
|
||||
def item(c):
|
||||
return self.table.item(r, c).text()
|
||||
rules[item(0)].append([item(1), item(2)])
|
||||
msprefs['id_link_rules'] = dict(rules)
|
||||
Dialog.accept(self)
|
||||
|
||||
def edit_rule(self, r=-1):
|
||||
key = name = template = ''
|
||||
if r > -1:
|
||||
key, name, template = map(lambda c: self.table.item(r, c).text(), range(3))
|
||||
d = IdLinksRuleEdit(key, name, template, self)
|
||||
if d.exec_() == d.Accepted:
|
||||
if r < 0:
|
||||
self.table.setRowCount(self.table.rowCount() + 1)
|
||||
r = self.table.rowCount() - 1
|
||||
rule = d.rule
|
||||
for c in range(3):
|
||||
self.table.setItem(r, c, QTableWidgetItem(rule[c]))
|
||||
self.table.scrollToItem(self.table.item(r, 0))
|
||||
|
||||
def remove_rule(self):
|
||||
r = self.table.currentRow()
|
||||
if r > -1:
|
||||
self.table.removeRow(r)
|
||||
# }}}
|
||||
|
||||
class DisplayedFields(QAbstractListModel): # {{{
|
||||
|
||||
def __init__(self, db, parent=None):
|
||||
@ -178,6 +292,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
(_('Left'), 'left'), (_('Top'), 'top'), (_('Right'), 'right'), (_('Bottom'), 'bottom')])
|
||||
r('book_list_extra_row_spacing', gprefs)
|
||||
self.cover_browser_title_template_button.clicked.connect(self.edit_cb_title_template)
|
||||
self.id_links_button.clicked.connect(self.edit_id_link_rules)
|
||||
|
||||
def get_esc_lang(l):
|
||||
if l == 'en':
|
||||
@ -310,6 +425,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.icon_theme.setText(_('Icon theme: <b>%s</b>') % self.icon_theme_title)
|
||||
self.changed_signal.emit()
|
||||
|
||||
def edit_id_link_rules(self):
|
||||
if IdLinksEditor(self).exec_() == Dialog.Accepted:
|
||||
self.changed_signal.emit()
|
||||
|
||||
@property
|
||||
def current_cover_size(self):
|
||||
cval = self.opt_cover_grid_height.value()
|
||||
@ -519,5 +638,3 @@ if __name__ == '__main__':
|
||||
from calibre.gui2 import Application
|
||||
app = Application([])
|
||||
test_widget('Interface', 'Look & Feel')
|
||||
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>820</width>
|
||||
<width>843</width>
|
||||
<height>546</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -661,7 +661,7 @@ A value of zero means calculate automatically.</string>
|
||||
<string>Book Details</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_12">
|
||||
<item row="2" column="1">
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Note that <b>comments</b> will always be displayed at the end, regardless of the position you assign here.</string>
|
||||
@ -671,7 +671,7 @@ A value of zero means calculate automatically.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" rowspan="2">
|
||||
<item row="3" column="0" rowspan="2">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Select displayed metadata</string>
|
||||
@ -801,6 +801,13 @@ Manage Authors. You can use the values {author} and
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="id_links_button">
|
||||
<property name="text">
|
||||
<string>Create rules to convert &identifiers into links</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
@ -900,15 +907,15 @@ then the tags will be displayed each on their own line.</string>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_tag_browser_hide_empty_categories">
|
||||
<property name="text">
|
||||
<string>Hi&de empty categories (columns) in the tag browser</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>When checked, calibre will automatically hide any category
|
||||
(a column, custom or standard) that has no items to show. For example, some
|
||||
categories might not have values when using virtual libraries. Checking this
|
||||
box will cause these empty categories to be hidden.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Hi&de empty categories (columns) in the tag browser</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
@ -956,7 +963,7 @@ if you never want subcategories</string>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="opt_cover_browser_title_template">
|
||||
<property name="toolTip">
|
||||
<string>The template used to generate the text below the covers. Uses the same syntax as save templates. Defaults to just the book title. Note that this setting is per-library, which means that you have to set it again for every different calibre library you use.</string>
|
||||
<string>The template used to generate the text below the covers. Uses the same syntax as save templates. Defaults to just the book title. Note that this setting is per-library, which means that you have to set it again for every different calibre library you use.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
Loading…
x
Reference in New Issue
Block a user