diff --git a/src/calibre/ebooks/metadata/book/render.py b/src/calibre/ebooks/metadata/book/render.py
index 59e704b908..1c73fd55b4 100644
--- a/src/calibre/ebooks/metadata/book/render.py
+++ b/src/calibre/ebooks/metadata/book/render.py
@@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal '
-import os
+import os, cPickle
from functools import partial
from binascii import hexlify
@@ -50,6 +50,9 @@ 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 item_data(field_name, value, book_id):
+ return hexlify(cPickle.dumps((field_name, value, book_id), -1))
+
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:
field_list = get_field_list(mi)
@@ -144,7 +147,7 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
ans.append((field, row % (name, u', '.join(fmts))))
elif field == 'identifiers':
urls = urls_from_identifiers(mi.identifiers)
- links = [u'%s' % (a(url), a(id_typ), a(id_val), p(namel))
+ links = [u'%s' % (a(url), a(id_typ), a(id_val), a(item_data(field, id_typ, mi.id)), p(namel))
for namel, id_typ, id_val, url in urls]
links = u', '.join(links)
if links:
@@ -181,8 +184,9 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
elif field == 'publisher':
if not mi.publisher:
continue
- val = '%s' % (
- search_href('publisher', mi.publisher), _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)), p(mi.publisher))
+ val = '%s' % (
+ search_href('publisher', mi.publisher), _('Click to see books with {0}: {1}').format(metadata['name'], a(mi.publisher)),
+ a(item_data('publisher', mi.publisher, mi.id)), p(mi.publisher))
ans.append((field, row % (name, val)))
else:
val = mi.format_field(field)[-1]
@@ -199,10 +203,11 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
st = field
series = getattr(mi, field)
val = _(
- '%(sidx)s of '
+ '%(sidx)s of '
'%(series)s') % dict(
sidx=fmt_sidx(sidx, use_roman=use_roman_numbers), cls="series_name",
series=p(series), href=search_href(st, series),
+ data=a(item_data(field, series, mi.id)),
tt=p(_('Click to see books in this series')))
elif metadata['datatype'] == 'datetime':
aval = getattr(mi, field)
@@ -216,8 +221,8 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
all_vals = mi.get(field)
if field == 'tags':
all_vals = sorted(all_vals, key=sort_key)
- links = ['%s' % (
- search_href(st, x), _('Click to see books with {0}: {1}').format(metadata['name'], a(x)), p(x))
+ links = ['%s' % (
+ search_href(st, x), _('Click to see books with {0}: {1}').format(metadata['name'], a(x)), a(item_data(field, x, mi.id)), p(x))
for x in all_vals]
val = metadata['is_multiple']['list_to_ui'].join(links)
elif metadata['datatype'] == 'enumeration':
@@ -225,7 +230,9 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
st = metadata['search_terms'][0]
except Exception:
st = field
- val = '%s' % (search_href(st, val), _('Click to see books with {0}: {1}').format(metadata['name'], val), val)
+ val = '%s' % (
+ search_href(st, val), a(_('Click to see books with {0}: {1}').format(metadata['name'], val)),
+ a(item_data(field, val, mi.id)), p(val))
ans.append((field, row % (name, val)))
@@ -246,5 +253,3 @@ def mi_to_html(mi, field_list=None, default_author_link=None, use_roman_numbers=
classname(fieldl), html) for fieldl, html in ans]
# print '\n'.join(ans)
return u''%(u'\n'.join(ans)), comment_fields
-
-
diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py
index 4e80bf622e..d7eff04e97 100644
--- a/src/calibre/gui2/actions/edit_metadata.py
+++ b/src/calibre/gui2/actions/edit_metadata.py
@@ -322,18 +322,22 @@ class EditMetadataAction(InterfaceAction):
m.refresh_rows(rows_to_refresh)
if changed:
- m.refresh_ids(list(changed))
- current = self.gui.library_view.currentIndex()
- if self.gui.cover_flow:
- self.gui.cover_flow.dataChanged()
- m.current_changed(current, previous)
- self.gui.tags_view.recount()
+ self.refresh_books_after_metadata_edit(changed, previous)
if self.gui.library_view.alternate_views.current_view is view:
if hasattr(view, 'restore_hpos'):
view.restore_hpos(hpos)
else:
view.horizontalScrollBar().setValue(hpos)
+ def refresh_books_after_metadata_edit(self, book_ids, previous=None):
+ m = self.gui.library_view.model()
+ m.refresh_ids(list(book_ids))
+ current = self.gui.library_view.currentIndex()
+ if self.gui.cover_flow:
+ self.gui.cover_flow.dataChanged()
+ m.current_changed(current, previous or current)
+ self.gui.tags_view.recount()
+
def do_edit_metadata(self, row_list, current_row, editing_multiple):
from calibre.gui2.metadata.single import edit_metadata
db = self.gui.library_view.model().db
@@ -418,8 +422,8 @@ class EditMetadataAction(InterfaceAction):
show=True)
if len(rows) > 5:
if not confirm(''+_('You are about to merge more than 5 books. '
- 'Are you sure you want to proceed?')
- +'
', 'merge_too_many_books', self.gui):
+ 'Are you sure you want to proceed?') +
+ '
', 'merge_too_many_books', self.gui):
return
dest_id, src_ids = self.books_to_merge(rows)
@@ -430,8 +434,8 @@ class EditMetadataAction(InterfaceAction):
'will be added to the first selected book (%s).
'
'The second and subsequently selected books will not '
'be deleted or changed.
'
- 'Please confirm you want to proceed.')%title
- +'', 'merge_books_safe', self.gui):
+ 'Please confirm you want to proceed.')%title +
+ '', 'merge_books_safe', self.gui):
return
self.add_formats(dest_id, self.formats_for_books(rows))
self.merge_metadata(dest_id, src_ids)
@@ -446,8 +450,8 @@ class EditMetadataAction(InterfaceAction):
'All book formats of the first selected book will be kept '
'and any duplicate formats in the second and subsequently selected books '
'will be permanently deleted from your calibre library.
'
- 'Are you sure you want to proceed?')%title
- +'', 'merge_only_formats', self.gui):
+ 'Are you sure you want to proceed?')%title +
+ '', 'merge_only_formats', self.gui):
return
self.add_formats(dest_id, self.formats_for_books(rows))
self.delete_books_after_merge(src_ids)
@@ -460,8 +464,8 @@ class EditMetadataAction(InterfaceAction):
'All book formats of the first selected book will be kept '
'and any duplicate formats in the second and subsequently selected books '
'will be permanently deleted from your calibre library.
'
- 'Are you sure you want to proceed?')%title
- +'', 'merge_books', self.gui):
+ 'Are you sure you want to proceed?')%title +
+ '', 'merge_books', self.gui):
return
self.add_formats(dest_id, self.formats_for_books(rows))
self.merge_metadata(dest_id, src_ids)
@@ -758,3 +762,19 @@ class EditMetadataAction(InterfaceAction):
# }}}
+ def remove_metadata_item(self, book_id, field, value):
+ db = self.gui.current_db.new_api
+ fm = db.field_metadata[field]
+ affected_books = set()
+ if field == 'identifiers':
+ identifiers = db.field_for(field, book_id)
+ if identifiers.pop(value, False) is not False:
+ affected_books = db.set_field(field, {book_id:identifiers})
+ elif fm['is_multiple']:
+ item_id = db.get_item_id(field, value)
+ if item_id is not None:
+ affected_books = db.remove_items(field, (item_id,), {book_id})
+ else:
+ affected_books = db.set_field(field, {book_id:''})
+ if affected_books:
+ self.refresh_books_after_metadata_edit(affected_books)
diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py
index 5f4681ec42..96cce15b9e 100644
--- a/src/calibre/gui2/book_details.py
+++ b/src/calibre/gui2/book_details.py
@@ -5,6 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+import cPickle
from binascii import unhexlify
from functools import partial
@@ -164,6 +165,7 @@ def details_context_menu_event(view, ev, book_info): # {{{
menu.addAction(ac)
else:
el = r.linkElement()
+ data = el.attribute('data-item')
author = el.toPlainText() if unicode(el.attribute('calibre-data')) == u'authors' else None
if not url.startswith('search:'):
for a, t in [('copy', _('&Copy Link')),
@@ -179,6 +181,16 @@ def details_context_menu_event(view, ev, book_info): # {{{
ac.current_fmt = author
ac.setText(_('Manage %s') % author)
menu.addAction(ac)
+ if data:
+ try:
+ field, value, book_id = cPickle.loads(unhexlify(data))
+ except Exception:
+ field = value = book_id = None
+ if field:
+ ac = book_info.remove_item_action
+ ac.data = (field, value, book_id)
+ ac.setText(_('Remove %s from this book') % value)
+ menu.addAction(ac)
if len(menu.actions()) > 0:
menu.exec_(ev.globalPos())
@@ -385,6 +397,7 @@ class BookInfo(QWebView):
link_clicked = pyqtSignal(object)
remove_format = pyqtSignal(int, object)
+ remove_item = pyqtSignal(int, object, object)
save_format = pyqtSignal(int, object)
restore_format = pyqtSignal(int, object)
compare_format = pyqtSignal(int, object)
@@ -415,8 +428,16 @@ class BookInfo(QWebView):
ac.current_url = None
ac.triggered.connect(getattr(self, '%s_triggerred'%x))
setattr(self, '%s_action'%x, ac)
+ self.remove_item_action = ac = QAction(QIcon(I('minus.png')), '...', self)
+ ac.data = (None, None, None)
+ ac.triggered.connect(self.remove_item_triggered)
self.setFocusPolicy(Qt.NoFocus)
+ def remove_item_triggered(self):
+ field, value, book_id = self.remove_item_action.data
+ if field:
+ self.remove_item.emit(book_id, field, value)
+
def context_action_triggered(self, which):
f = getattr(self, '%s_action'%which).current_fmt
url = getattr(self, '%s_action'%which).current_url
@@ -579,6 +600,7 @@ class BookDetails(QWidget): # {{{
view_specific_format = pyqtSignal(int, object)
search_requested = pyqtSignal(object)
remove_specific_format = pyqtSignal(int, object)
+ remove_metadata_item = pyqtSignal(int, object, object)
save_specific_format = pyqtSignal(int, object)
restore_specific_format = pyqtSignal(int, object)
compare_specific_format = pyqtSignal(int, object)
@@ -654,6 +676,7 @@ class BookDetails(QWidget): # {{{
self._layout.addWidget(self.book_info)
self.book_info.link_clicked.connect(self.handle_click)
self.book_info.remove_format.connect(self.remove_specific_format)
+ self.book_info.remove_item.connect(self.remove_metadata_item)
self.book_info.open_fmt_with.connect(self.open_fmt_with)
self.book_info.save_format.connect(self.save_specific_format)
self.book_info.restore_format.connect(self.restore_specific_format)
diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py
index 92c386400e..53952ae415 100644
--- a/src/calibre/gui2/init.py
+++ b/src/calibre/gui2/init.py
@@ -503,6 +503,8 @@ class LayoutMixin(object): # {{{
self.book_details.search_requested.connect(self.search.set_search_string)
self.book_details.remove_specific_format.connect(
self.iactions['Remove Books'].remove_format_by_id)
+ self.book_details.remove_metadata_item.connect(
+ self.iactions['Edit Metadata'].remove_metadata_item)
self.book_details.save_specific_format.connect(
self.iactions['Save To Disk'].save_library_format_by_ids)
self.book_details.restore_specific_format.connect(