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'%s
'%(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(