diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 08e441a722..7e32b8c6c8 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -588,22 +588,27 @@ class EditMetadataAction(InterfaceAction): 'merge_too_many_books', self.gui) def books_dropped(self, merge_map): + covers_replaced = False for dest_id, src_ids in iteritems(merge_map): if not self.confirm_large_merge(len(src_ids) + 1): continue from calibre.gui2.dialogs.confirm_merge import merge_drop - merge_metadata, merge_formats, delete_books = merge_drop(dest_id, src_ids, self.gui) - if merge_metadata is None: + d = merge_drop(dest_id, src_ids, self.gui) + if d is None: return - if merge_formats: + if d.merge_formats: self.add_formats(dest_id, self.formats_for_ids(list(src_ids))) - if merge_metadata: - self.merge_metadata(dest_id, src_ids) - if delete_books: + if d.merge_metadata: + self.merge_metadata(dest_id, src_ids, replace_cover=d.replace_cover) + if d.replace_cover: + covers_replaced = True + if d.delete_books: self.delete_books_after_merge(src_ids) # leave the selection highlight on the target book row = self.gui.library_view.ids_to_rows([dest_id])[dest_id] self.gui.library_view.set_current_row(row) + if covers_replaced: + self.gui.refresh_cover_browser() def merge_books(self, safe_merge=False, merge_only_formats=False): ''' @@ -724,12 +729,12 @@ class EditMetadataAction(InterfaceAction): def delete_books_after_merge(self, ids_to_delete): self.gui.library_view.model().delete_books_by_id(ids_to_delete) - def merge_metadata(self, dest_id, src_ids): + def merge_metadata(self, dest_id, src_ids, replace_cover=False): db = self.gui.library_view.model().db dest_mi = db.get_metadata(dest_id, index_is_id=True) merged_identifiers = db.get_identifiers(dest_id, index_is_id=True) orig_dest_comments = dest_mi.comments - dest_cover = db.cover(dest_id, index_is_id=True) + dest_cover = orig_dest_cover = db.cover(dest_id, index_is_id=True) had_orig_cover = bool(dest_cover) def is_null_date(x): @@ -754,10 +759,11 @@ class EditMetadataAction(InterfaceAction): dest_mi.tags = src_mi.tags else: dest_mi.tags.extend(src_mi.tags) - if not dest_cover: + if not dest_cover or replace_cover: src_cover = db.cover(src_id, index_is_id=True) if src_cover: dest_cover = src_cover + replace_cover = False if not dest_mi.publisher: dest_mi.publisher = src_mi.publisher if not dest_mi.rating: @@ -776,7 +782,7 @@ class EditMetadataAction(InterfaceAction): dest_mi.set_identifiers(merged_identifiers) db.set_metadata(dest_id, dest_mi, ignore_errors=False) - if not had_orig_cover and dest_cover: + if dest_cover and (not had_orig_cover or dest_cover is not orig_dest_cover): db.set_cover(dest_id, dest_cover) for key in db.field_metadata: # loop thru all defined fields diff --git a/src/calibre/gui2/dialogs/confirm_merge.py b/src/calibre/gui2/dialogs/confirm_merge.py index 268b9b21ca..58e4c47a99 100644 --- a/src/calibre/gui2/dialogs/confirm_merge.py +++ b/src/calibre/gui2/dialogs/confirm_merge.py @@ -4,6 +4,8 @@ __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' +from typing import NamedTuple + from qt.core import ( QCheckBox, QDialog, QDialogButtonBox, QLabel, QSplitter, Qt, QTextBrowser, QVBoxLayout, QWidget, @@ -13,7 +15,7 @@ from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata.book.base import field_metadata from calibre.gui2 import dynamic, gprefs from calibre.gui2.dialogs.confirm_delete import confirm_config_name -from calibre.gui2.widgets2 import Dialog +from calibre.gui2.widgets2 import Dialog, FlowLayout from calibre.startup import connect_lambda from calibre.utils.config import tweaks from calibre.utils.date import format_date @@ -35,10 +37,12 @@ class Target(QTextBrowser): {fm[pubdate][name]}:{published} {fm[formats][name]}:{formats} {fm[series][name]}:{series} +{has_cover_title}:{has_cover} '''.format( mb=_('Target book'), title=mi.title, + has_cover_title=_('Has cover'), has_cover=_('Yes') if mi.has_cover else _('No'), authors=authors_to_string(mi.authors), date=format_date(mi.timestamp, tweaks['gui_timestamp_display_format']), fm=fm, published=(format_date(mi.pubdate, tweaks['gui_pubdate_display_format']) if mi.pubdate else ''), @@ -112,10 +116,12 @@ class ChooseMerge(Dialog): s.addWidget(w) w.l = l = QVBoxLayout(w) l.setContentsMargins(0, 0, 0, 0) + w.fl = fl = FlowLayout() + l.addLayout(fl) def cb(name, text, tt=''): ans = QCheckBox(text) - l.addWidget(ans) + fl.addWidget(ans) prefs_key = ans.prefs_key = 'choose-merge-cb-' + name ans.setChecked(gprefs.get(prefs_key, True)) connect_lambda(ans.stateChanged, self, lambda self, state: self.state_changed(getattr(self, name), state), type=Qt.ConnectionType.QueuedConnection) @@ -130,6 +136,8 @@ class ChooseMerge(Dialog): 'Merge the book files of the selected books into the target book')) cb('delete_books', _('Delete merged books'), _( 'Delete the selected books after merging')) + cb('replace_cover', _('Replace existing cover'), _( + 'Replace the cover in the target book with the dragged cover')) l.addStretch(10) self.msg = la = QLabel(self) la.setWordWrap(True) @@ -151,21 +159,26 @@ class ChooseMerge(Dialog): mm = self.merge_metadata.isChecked() mf = self.merge_formats.isChecked() rm = self.delete_books.isChecked() + rc = self.replace_cover.isChecked() msg = '

' if mm and mf: msg += _( 'Book formats and metadata from the selected books' ' will be merged into the target book ({title}).') + if rc or not self.mi.has_cover: + msg += ' ' + _('The dragged cover will be used.') elif mf: msg += _('Book formats from the selected books ' 'will be merged into to the target book ({title}).' - ' Metadata in the target book will not be changed.') + ' Metadata and cover in the target book will not be changed.') elif mm: msg += _('Metadata from the selected books ' 'will be merged into to the target book ({title}).' ' Formats will not be merged.') + if rc or not self.mi.has_cover: + msg += ' ' + _('The dragged cover will be used.') msg += '
' - msg += _('All book formats of the first selected book will be kept.') + '

' + msg += _('All book formats of the target book will be kept.') + '

' if rm: msg += _('After being merged, the selected books will be deleted.') if mf: @@ -185,11 +198,21 @@ class ChooseMerge(Dialog): @property def merge_type(self): - return self.merge_metadata.isChecked(), self.merge_formats.isChecked(), self.delete_books.isChecked() + return MergeData( + self.merge_metadata.isChecked(), self.merge_formats.isChecked(), self.delete_books.isChecked(), + self.replace_cover.isChecked(), + ) + + +class MergeData(NamedTuple): + merge_metadata: bool = False + merge_formats: bool = False + delete_books: bool = False + replace_cover: bool = False def merge_drop(dest_id, src_ids, gui): d = ChooseMerge(dest_id, src_ids, gui) if d.exec() != QDialog.DialogCode.Accepted: - return None, None, None + return None return d.merge_type