diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 19b662c072..6663950efa 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -8,6 +8,7 @@ __copyright__ = '2014, Kovid Goyal ' import os, cPickle from functools import partial +from urllib import quote_plus from binascii import hexlify from calibre import prepare_string_for_xml, force_unicode @@ -23,6 +24,12 @@ from calibre.utils.localization import calibre_langcode_to_name default_sort = ('title', 'title_sort', 'authors', 'author_sort', 'series', 'rating', 'pubdate', 'tags', 'publisher', 'identifiers') +def qquote(val): + if not isinstance(val, bytes): + val = val.encode('utf-8') + return quote_plus(val).decode('utf-8') + + def field_sort(mi, name): try: title = mi.metadata_for_field(name)['name'] @@ -55,6 +62,34 @@ def search_href(search_term, value): return prepare_string_for_xml('search:' + hexlify(search.encode('utf-8')), True) +DEFAULT_AUTHOR_LINK = 'search-goodreads' + + +def author_search_href(which, title=None, author=None): + if which == 'calibre': + return search_href('authors', author), _('Search the calibre library for books by %s') % author + tt_map = getattr(author_search_href, 'tt_map', None) + if tt_map is None: + tt = _('Search {0} for the author: {1}') + tt_map = author_search_href.tt_map = { + 'goodreads': tt.format('Goodreads', author), + 'wikipedia': tt.format('Wikipedia', author), + 'goodreads-book': _('Search Goodreads for the book: {0} by the author {1}').format(title, author) + } + tt = tt_map.get(which) + if tt is None: + which = DEFAULT_AUTHOR_LINK.partition('-')[2] + tt = tt_map[which] + link_map = getattr(author_search_href, 'link_map', None) + if link_map is None: + link_map = author_search_href.link_map = { + 'goodreads': 'https://www.goodreads.com/search?q={author}&search%5Bfield%5D=author&search%5Bsource%5D=goodreads&search_type=people&tab=people', + 'wikipedia': 'https://en.wikipedia.org/w/index.php?search={author}', + 'goodreads-book': 'https://www.goodreads.com/search?q={author}+{title}&search%5Bsource%5D=goodreads&search_type=books&tab=books' + } + return link_map[which].format(title=qquote(title), author=qquote(author)), tt + + def item_data(field_name, value, book_id): return hexlify(cPickle.dumps((field_name, value, book_id), -1)) @@ -175,27 +210,29 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers= links = u', '.join(links) if links: ans.append((field, row % (_('Ids')+':', links))) - elif field == 'authors' and not isdevice: + elif field == 'authors': authors = [] formatter = EvalFormatter() for aut in mi.authors: link = '' - if mi.author_link_map[aut]: + if mi.author_link_map.get(aut): link = lt = mi.author_link_map[aut] elif default_author_link: - if default_author_link == 'search-calibre': - link = search_href('authors', aut) - lt = a(_('Search the calibre library for books by %s') % aut) + if isdevice and default_author_link == 'search-calibre': + default_author_link = DEFAULT_AUTHOR_LINK + if default_author_link.startswith('search-'): + which_src = default_author_link.partition('-')[2] + link, lt = author_search_href(which_src, title=mi.title, author=aut) else: - vals = {'author': aut.replace(' ', '+')} + vals = {'author': qquote(aut), 'title': qquote(mi.title)} try: - vals['author_sort'] = mi.author_sort_map[aut].replace(' ', '+') - except: - vals['author_sort'] = aut.replace(' ', '+') - link = lt = a(formatter.safe_format(default_author_link, vals, '', vals)) + vals['author_sort'] = qquote(mi.author_sort_map[aut]) + except KeyError: + vals['author_sort'] = qquote(aut) + link = lt = formatter.safe_format(default_author_link, vals, '', vals) aut = p(aut) if link: - authors.append(u'%s'%(lt, link, aut)) + authors.append(u'%s'%(a(lt), a(link), aut)) else: authors.append(aut) ans.append((field, row % (name, u' & '.join(authors)))) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 53ade5a5e5..7238c97148 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -108,7 +108,6 @@ def create_defs(): defs['tags_browser_collapse_at'] = 100 defs['tag_browser_dont_collapse'] = [] defs['edit_metadata_single_layout'] = 'default' - defs['default_author_link'] = 'https://en.wikipedia.org/w/index.php?search={author}' defs['preserve_date_on_ctl'] = True defs['manual_add_auto_convert'] = False defs['auto_convert_same_fmt'] = False @@ -273,6 +272,15 @@ QSettings.setPath(QSettings.IniFormat, QSettings.SystemScope, config_dir) QSettings.setDefaultFormat(QSettings.IniFormat) +def default_author_link(): + from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK + ans = gprefs.get('default_author_link') + if ans == 'https://en.wikipedia.org/w/index.php?search={author}': + # The old default value for this setting + ans = DEFAULT_AUTHOR_LINK + return ans or DEFAULT_AUTHOR_LINK + + def available_heights(): desktop = QCoreApplication.instance().desktop() return map(lambda x: x.height(), map(desktop.availableGeometry, range(desktop.screenCount()))) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 3e89cbd6df..bbc5298679 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -21,7 +21,7 @@ from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files, from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.book.base import (field_metadata, Metadata) from calibre.ebooks.metadata.book.render import mi_to_html -from calibre.gui2 import (config, open_url, pixmap_to_data, gprefs, rating_font, NO_URL_FORMATTING) +from calibre.gui2 import (config, open_url, pixmap_to_data, gprefs, rating_font, NO_URL_FORMATTING, default_author_link) from calibre.utils.config import tweaks from calibre.utils.img import image_from_x, blend_image from calibre.utils.localization import is_rtl @@ -127,7 +127,7 @@ def render_data(mi, use_roman_numbers=True, all_fields=False): 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, rtl=is_rtl(), - rating_font=rating_font(), default_author_link=gprefs.get('default_author_link')) + rating_font=rating_font(), default_author_link=default_author_link()) # }}} diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index c5cb9af352..c58ded2e76 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -15,11 +15,13 @@ from PyQt5.Qt import ( QApplication, QFont, QFontInfo, QFontDialog, QColorDialog, QPainter, QAbstractListModel, Qt, QIcon, QKeySequence, QColor, pyqtSignal, QCursor, QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout, - QTableWidget, QTableWidgetItem, QLabel, QFormLayout, QLineEdit + QTableWidget, QTableWidgetItem, QLabel, QFormLayout, QLineEdit, QComboBox ) from calibre import human_readable +from calibre.ebooks.metadata.book.render import DEFAULT_AUTHOR_LINK from calibre.ebooks.metadata.sources.prefs import msprefs +from calibre.gui2 import default_author_link 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 @@ -42,6 +44,56 @@ class BusyCursor(object): def __exit__(self, *args): QApplication.restoreOverrideCursor() + +class DefaultAuthorLink(QWidget): # {{{ + + changed_signal = pyqtSignal() + + def __init__(self, parent): + QWidget.__init__(self, parent) + l = QVBoxLayout(parent) + l.addWidget(self) + l.setContentsMargins(0, 0, 0, 0) + l = QFormLayout(self) + l.setContentsMargins(0, 0, 0, 0) + l.setFieldGrowthPolicy(l.AllNonFixedFieldsGrow) + self.choices = c = QComboBox() + c.setMinimumContentsLength(30) + for text, data in [ + (_('Search for the author on Goodreads'), 'search-goodreads'), + (_('Search for the author in your calibre library'), 'search-calibre'), + (_('Search for the author on Wikipedia'), 'search-wikipedia'), + (_('Search for the book on Goodreads'), 'search-goodreads-book'), + (_('Use a custom search URL'), 'url'), + ]: + c.addItem(text, data) + l.addRow(_('Clicking on &author names should:'), c) + self.custom_url = u = QLineEdit(self) + c.currentIndexChanged.connect(self.current_changed) + l.addRow(u) + self.current_changed() + c.currentIndexChanged.connect(self.changed_signal) + + @property + def value(self): + k = self.choices.currentData() + if k == 'url': + return self.custom_url.text() + return k if k != DEFAULT_AUTHOR_LINK else None + + @value.setter + def value(self, val): + i = self.choices.findData(val) + if i < 0: + i = self.choices.findData('url') + self.custom_url.setText(val) + self.choices.setCurrentIndex(i) + + def current_changed(self): + k = self.choices.currentData() + self.custom_url.setVisible(k == 'url') +# }}} + # IdLinksEditor {{{ @@ -280,6 +332,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.icon_theme.setText(_('Icon theme: %s') % self.icon_theme_title) self.commit_icon_theme = None self.icon_theme_button.clicked.connect(self.choose_icon_theme) + self.default_author_link = DefaultAuthorLink(self.default_author_link_container) + self.default_author_link.changed_signal.connect(self.changed_signal) r('gui_layout', config, restart_required=True, choices=[(_('Wide'), 'wide'), (_('Narrow'), 'narrow')]) r('ui_style', gprefs, restart_required=True, choices=[(_('System default'), 'system'), (_('calibre style'), 'calibre')]) @@ -351,12 +405,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): (_('Partitioned'), 'partition')] r('tags_browser_partition_method', gprefs, choices=choices) r('tags_browser_collapse_at', gprefs) - r('default_author_link', gprefs) r('tag_browser_dont_collapse', gprefs, setting=CommaSeparatedList) - self.search_library_for_author_button.clicked.connect( - lambda : self.opt_default_author_link.setText('search-calibre')) - choices = set([k for k in db.field_metadata.all_field_keys() if (db.field_metadata[k]['is_category'] and (db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and @@ -483,6 +533,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def initialize(self): ConfigWidgetBase.initialize(self) + self.default_author_link.value = default_author_link() font = gprefs['font'] if font is not None: font = list(font) @@ -536,6 +587,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def restore_defaults(self): ConfigWidgetBase.restore_defaults(self) + self.default_author_link.value = DEFAULT_AUTHOR_LINK ofont = self.current_font self.current_font = None if ofont is not None: @@ -637,6 +689,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): if self.commit_icon_theme is not None: self.commit_icon_theme() rr = True + gprefs['default_author_link'] = self.default_author_link.value return rr def refresh_gui(self, gui): diff --git a/src/calibre/gui2/preferences/look_feel.ui b/src/calibre/gui2/preferences/look_feel.ui index 1e08d4720b..1301dd6a3b 100644 --- a/src/calibre/gui2/preferences/look_feel.ui +++ b/src/calibre/gui2/preferences/look_feel.ui @@ -269,7 +269,7 @@ - Control the Cover grid view. You can enable this view by clicking the "Cover grid" button in the bottom right corner of the main calibre window. + Control the Cover grid view. You can enable this view by clicking the "Cover grid" button in the bottom right corner of the main calibre window. true @@ -773,41 +773,6 @@ A value of zero means calculate automatically. - - - - - - Default author &link template: - - - opt_default_author_link - - - - - - - <p>Enter a template to be used to create a link for -an author in the books information dialog. This template will -be used when no link has been provided for the author using -Manage Authors. You can use the values {author} and -{author_sort}, and any template function. - - - - - - - Search the calibre library for the author, instead of opening a link - - - Search &calibre library for author - - - - - @@ -815,6 +780,16 @@ Manage Authors. You can use the values {author} and + + + + + 0 + 0 + + + + diff --git a/src/calibre/gui2/viewer/ui.py b/src/calibre/gui2/viewer/ui.py index 5e36055560..51d0ee3c5b 100644 --- a/src/calibre/gui2/viewer/ui.py +++ b/src/calibre/gui2/viewer/ui.py @@ -14,7 +14,7 @@ from PyQt5.Qt import ( QRegExpValidator, QRegExp, QPalette, QColor, QBrush, QPainter, QDockWidget, QSize, QWebView, QLabel, QVBoxLayout) -from calibre.gui2 import rating_font, error_dialog +from calibre.gui2 import rating_font, error_dialog, open_url from calibre.gui2.main_window import MainWindow from calibre.gui2.search_box import SearchBox2 from calibre.gui2.viewer.documentview import DocumentView @@ -74,21 +74,30 @@ class Metadata(QWebView): # {{{ s = self.settings() s.setAttribute(s.JavascriptEnabled, False) self.page().setLinkDelegationPolicy(self.page().DelegateAllLinks) + self.page().linkClicked.connect(self.link_clicked) self.setAttribute(Qt.WA_OpaquePaintEvent, False) palette = self.palette() palette.setBrush(QPalette.Base, Qt.transparent) self.page().setPalette(palette) self.setVisible(False) + def link_clicked(self, qurl): + if qurl.scheme() in ('http', 'https'): + return open_url(qurl) + def update_layout(self): self.setGeometry(0, 0, self.parent().width(), self.parent().height()) def show_metadata(self, mi, ext=''): + from calibre.gui2 import default_author_link from calibre.gui2.book_details import render_html, css 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(), rtl=is_rtl()) + return mi_to_html( + mi, use_roman_numbers=use_roman_numbers, rating_font=rating_font(), rtl=is_rtl(), + default_author_link=default_author_link() + ) html = render_html(mi, css(), True, self, render_data_func=render_data) self.setHtml(html)