diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 9201cc1062..ce12e7f2e5 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2367,18 +2367,22 @@ class Cache: @read_api def get_all_link_maps_for_book(self, book_id): ''' - Returns all links for all fields referenced by book identified by book_id + Returns all links for all fields referenced by book identified by book_id. + If book_id is None or doesn't exist then the method returns {}. Example: Assume author A has link X, author B has link Y, tag S has link - F, and tag T has link G. IF book 1 has author A and tag T, + F, and tag T has link G. If book 1 has author A and tag T, this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}} If book 2's author is neither A nor B and has no tags, this method returns {} :param book_id: the book id in question. - :return: {field: {field_value, link_value}, ... for all fields that have a non-empty link value for that book + :return: {field: {field_value, link_value}, ... for all fields with a field_value having a non-empty link value for that book ''' + if not self.has_id(book_id): + # Works for book_id is None. + return {} cached = self.link_maps_cache.get(book_id) if cached is not None: return cached diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py index 48a8f43cef..398f0cabc7 100644 --- a/src/calibre/ebooks/metadata/book/render.py +++ b/src/calibre/ebooks/metadata/book/render.py @@ -183,14 +183,14 @@ def mi_to_html( else: if not metadata['is_multiple']: val = '{}'.format( - search_action(field, val), + search_action(field, val, book_id=book_id), _('Click to see books with {0}: {1}').format(metadata['name'], a(val)), 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 = ['{}'.format( - search_action(field, x), _('Click to see books with {0}: {1}').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] else: links = all_vals @@ -210,7 +210,7 @@ def mi_to_html( extra = '
%s'%( prepare_string_for_xml(durl)) if show_links: - link = '{}{}'.format(action(scheme, loc=loc), + link = '{}{}'.format(action(scheme, book_id=book_id, loc=loc), prepare_string_for_xml(path, True), pathstr, extra) else: link = prepare_string_for_xml(path, True) @@ -238,7 +238,7 @@ def mi_to_html( if show_links: links = [ '{}'.format( - action('identifier', url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers', book_id=book_id), + action('identifier', book_id=book_id, url=url, name=namel, id_type=id_typ, value=id_val, field='identifiers'), a(id_typ), a(id_val), p(namel)) for namel, id_typ, id_val, url in urls] links = value_list(', ', links) @@ -266,7 +266,8 @@ def mi_to_html( else: aut = p(aut) if link: - val = '%s'%(a(lt), action('author', url=link, name=aut, title=lt), aut) + val = '%s'%(a(lt), action('author', book_id=book_id, + url=link, name=aut, title=lt), aut) else: val = aut val += add_other_link('authors', aut) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 7a046932d8..99f7d380fa 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -391,6 +391,12 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.data = ('authors', author, book_id) ac.setText(_('Remove %s from this book') % escape_for_menu(author)) menu.addAction(ac) + # See if we need to add a click associated link menu line for the author + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) + link = link_map.get("authors", {}).get(author, None) + if link: + menu.addAction(QIcon.ic('external-link'), _('Open associated link'), + lambda : book_info.link_clicked.emit(link)) elif dt in ('path', 'devpath'): path = data['loc'] ac = book_info.copy_link_action @@ -435,6 +441,12 @@ def add_item_specific_entries(menu, data, book_info, copy_menu, search_menu): ac.data = (field, remove_value, book_id) ac.setText(_('Remove %s from this book') % escape_for_menu(remove_name or data.get('original_value') or value)) menu.addAction(ac) + # See if we need to add a click associated link menu line + link_map = get_gui().current_db.new_api.get_all_link_maps_for_book(data.get('book_id', None)) + link = link_map.get(field, {}).get(value, None) + if link: + menu.addAction(QIcon.ic('external-link'), _('Open associated link'), + lambda : book_info.link_clicked.emit(link)) else: v = data.get('original_value') or data.get('value') copy_menu.addAction(QIcon.ic('edit-copy.png'), _('The text: {}').format(v), diff --git a/src/calibre/gui2/dialogs/tag_categories.py b/src/calibre/gui2/dialogs/tag_categories.py index f498b47005..13172b73c7 100644 --- a/src/calibre/gui2/dialogs/tag_categories.py +++ b/src/calibre/gui2/dialogs/tag_categories.py @@ -126,6 +126,11 @@ class TagCategories(QDialog, Ui_TagCategories): def category_name_tuple(self, key, name): return self.CategoryNameTuple(name, key) + def item_sort_key(self, v): + # Add the key so the order of identical items is predictable. + # The tab ensures that the values sort together regardless of key + return primary_sort_key(v.v + '\t ' + (v.k[1:] if v.k.startswith('#') else v.k)) + def initialize_category_lists(self): cfb = self.category_filter_box current_cat_filter = (self.category_labels[cfb.currentIndex()] @@ -154,11 +159,9 @@ class TagCategories(QDialog, Ui_TagCategories): self.available_items[key] = av sorted_categories.append(self.category_name_tuple(key, self.all_items[key]['name'])) - # Sort the items - self.sorted_items.sort(key=lambda v: primary_sort_key(v.v + v.k)) + self.sorted_items.sort(key=self.item_sort_key) + sorted_categories.sort(key=lambda v: primary_sort_key(v.n)) - # Fill in the category names with visible (not hidden) lookup keys - sorted_categories.sort(key=lambda v: primary_sort_key(v.n + v.k)) cfb.blockSignals(True) cfb.clear() cfb.addItem('', '') @@ -227,7 +230,7 @@ class TagCategories(QDialog, Ui_TagCategories): ccn = self.current_cat_name if ccn: self.applied_items = [v for v in self.user_categories[ccn]] - self.applied_items.sort(key=lambda x:primary_sort_key(x.v + x.k)) + self.applied_items.sort(key=self.item_sort_key) else: self.applied_items = [] self.applied_items_box.clear()