Book details panel: Make series and tags clickable. Clicking on them will search the calibre library for all books in the same series/ having the same tag

Fixes #1341297 [[Enhancement] make series name (in book details) a link/clickable](https://bugs.launchpad.net/calibre/+bug/1341297)
This commit is contained in:
Kovid Goyal 2014-07-14 13:40:50 +05:30
parent d60337d4ab
commit 689d9f6e60
4 changed files with 41 additions and 7 deletions

View File

@ -31,12 +31,12 @@ table.fields td.title {
} }
/* /*
The HTML that this styleshhet applies to looks like this: The HTML that this stylesheet applies to looks like this:
<table class="fields"> <table class="fields">
<tr id="formats" class="datatype_text"><td class="title">Formats:</td><td><a href="format:572:EPUB">EPUB</a>, <a href="format:572:LIT">LIT</a></td></tr> <tr id="formats" class="datatype_text"><td class="title">Formats:</td><td><a href="format:572:EPUB">EPUB</a>, <a href="format:572:LIT">LIT</a></td></tr>
<tr id="series" class="datatype_series"><td class="title">Series:</td><td>Book II of <span class="series_name">The Sea Beggars</span></td></tr> <tr id="series" class="datatype_series"><td class="title">Series:</td><td>Book II of <a href="..."><span class="series_name">The Sea Beggars</span></a></td></tr>
<tr id="tags" class="datatype_text"><td class="title">Tags:</td><td>Fantasy, Fiction</td></tr> <tr id="tags" class="datatype_text"><td class="title">Tags:</td><td><a href="...">Fantasy</a>, <a href="...">Fiction</a></td></tr>
<tr id="path" class="datatype_text"><td class="title">Path:</td><td><a href="path:572" title="/home/kovid/test library/Paul Kearney/This Forsaken Earth (572)">Click to open</a></td></tr> <tr id="path" class="datatype_text"><td class="title">Path:</td><td><a href="path:572" title="/home/kovid/test library/Paul Kearney/This Forsaken Earth (572)">Click to open</a></td></tr>
</table> </table>

View File

@ -8,6 +8,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import os import os
from functools import partial from functools import partial
from binascii import hexlify
from calibre import prepare_string_for_xml, force_unicode from calibre import prepare_string_for_xml, force_unicode
from calibre.ebooks.metadata import fmt_sidx from calibre.ebooks.metadata import fmt_sidx
@ -45,6 +46,10 @@ def get_field_list(mi):
for field in sorted(displayable_field_keys(mi), key=partial(field_sort, mi)): for field in sorted(displayable_field_keys(mi), key=partial(field_sort, mi)):
yield field, True yield field, True
def search_href(search_term, value):
search = '%s:"=%s"' % (search_term, value.replace('"', '\\"'))
return prepare_string_for_xml('search:' + hexlify(search.encode('utf-8')), True)
def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=True, rating_font='Liberation Serif'): 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: if field_list is None:
field_list = get_field_list(mi) field_list = get_field_list(mi)
@ -166,13 +171,36 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
sidx = mi.get(field+'_index') sidx = mi.get(field+'_index')
if sidx is None: if sidx is None:
sidx = 1.0 sidx = 1.0
val = _('Book %(sidx)s of <span class="series_name">%(series)s</span>')%dict( try:
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), st = metadata['search_terms'][0]
series=p(getattr(mi, field))) except Exception:
st = field
series = getattr(mi, field)
val = _(
'Book %(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_href(st, series),
tt=p(_('Click to see books in this series')))
elif metadata['datatype'] == 'datetime': elif metadata['datatype'] == 'datetime':
aval = getattr(mi, field) aval = getattr(mi, field)
if is_date_undefined(aval): if is_date_undefined(aval):
continue continue
elif metadata['datatype'] == 'text' and metadata['is_multiple']:
try:
st = metadata['search_terms'][0]
except Exception:
st = field
links = ['<a href="%s" title="%s">%s</a>' % (
search_href(st, x), _('Click to see books with {0}: {1}').format(metadata['name'], x), x)
for x in mi.get(field)]
val = metadata['is_multiple']['list_to_ui'].join(links)
elif metadata['datatype'] == 'enumeration':
try:
st = metadata['search_terms'][0]
except Exception:
st = field
val = '<a href="%s" title="%s">%s</a>' % (search_href(st, val), _('Click to see books with {0}: {1}').format(metadata['name'], val), val)
ans.append((field, row % (name, val))) ans.append((field, row % (name, val)))

View File

@ -5,6 +5,8 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from binascii import unhexlify
from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, QIcon, from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, QIcon,
QPropertyAnimation, QEasingCurve, QApplication, QFontInfo, QAction, QPropertyAnimation, QEasingCurve, QApplication, QFontInfo, QAction,
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu, QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu,
@ -500,6 +502,7 @@ class BookDetails(QWidget): # {{{
show_book_info = pyqtSignal() show_book_info = pyqtSignal()
open_containing_folder = pyqtSignal(int) open_containing_folder = pyqtSignal(int)
view_specific_format = pyqtSignal(int, object) view_specific_format = pyqtSignal(int, object)
search_requested = pyqtSignal(object)
remove_specific_format = pyqtSignal(int, object) remove_specific_format = pyqtSignal(int, object)
save_specific_format = pyqtSignal(int, object) save_specific_format = pyqtSignal(int, object)
restore_specific_format = pyqtSignal(int, object) restore_specific_format = pyqtSignal(int, object)
@ -581,7 +584,7 @@ class BookDetails(QWidget): # {{{
self.setCursor(Qt.PointingHandCursor) self.setCursor(Qt.PointingHandCursor)
def handle_click(self, link): def handle_click(self, link):
typ, _, val = link.partition(':') typ, val = link.partition(':')[0::2]
if typ == 'path': if typ == 'path':
self.open_containing_folder.emit(int(val)) self.open_containing_folder.emit(int(val))
elif typ == 'format': elif typ == 'format':
@ -589,6 +592,8 @@ class BookDetails(QWidget): # {{{
self.view_specific_format.emit(int(id_), fmt) self.view_specific_format.emit(int(id_), fmt)
elif typ == 'devpath': elif typ == 'devpath':
self.view_device_book.emit(val) self.view_device_book.emit(val)
elif typ == 'search':
self.search_requested.emit(unhexlify(val).decode('utf-8'))
else: else:
try: try:
open_url(QUrl(link, QUrl.TolerantMode)) open_url(QUrl(link, QUrl.TolerantMode))

View File

@ -445,6 +445,7 @@ class LayoutMixin(object): # {{{
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id) self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id)
self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id)
self.book_details.search_requested.connect(self.search.set_search_string)
self.book_details.remove_specific_format.connect( self.book_details.remove_specific_format.connect(
self.iactions['Remove Books'].remove_format_by_id) self.iactions['Remove Books'].remove_format_by_id)
self.book_details.save_specific_format.connect( self.book_details.save_specific_format.connect(