mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Implement a metadata comparison/merging tool
This commit is contained in:
parent
7755ff72c3
commit
890b5e4c57
@ -66,6 +66,7 @@ class EditorWidget(QWebView): # {{{
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWebView.__init__(self, parent)
|
||||
self.readonly = False
|
||||
|
||||
self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)
|
||||
|
||||
@ -163,7 +164,11 @@ class EditorWidget(QWebView): # {{{
|
||||
self.page().linkClicked.connect(self.link_clicked)
|
||||
|
||||
self.setHtml('')
|
||||
self.page().setContentEditable(True)
|
||||
self.set_readonly(False)
|
||||
|
||||
def set_readonly(self, what):
|
||||
self.readonly = what
|
||||
self.page().setContentEditable(not self.readonly)
|
||||
|
||||
def clear_text(self, *args):
|
||||
us = self.page().undoStack()
|
||||
@ -313,7 +318,7 @@ class EditorWidget(QWebView): # {{{
|
||||
# toList() is needed because PyQt on Debian is old/broken
|
||||
for body in self.page().mainFrame().documentElement().findAll('body').toList():
|
||||
body.setAttribute('style', style)
|
||||
self.page().setContentEditable(True)
|
||||
self.page().setContentEditable(not self.readonly)
|
||||
|
||||
def keyPressEvent(self, ev):
|
||||
if ev.key() in (Qt.Key_Tab, Qt.Key_Escape, Qt.Key_Backtab):
|
||||
@ -585,6 +590,7 @@ class Editor(QWidget): # {{{
|
||||
self.tabs.addTab(self.code_edit, _('HTML Source'))
|
||||
self.tabs.currentChanged[int].connect(self.change_tab)
|
||||
self.highlighter = Highlighter(self.code_edit.document())
|
||||
self.layout().setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
# toolbar1 {{{
|
||||
self.toolbar1.addAction(self.editor.action_undo)
|
||||
@ -666,6 +672,12 @@ class Editor(QWidget): # {{{
|
||||
self.toolbar2.setVisible(False)
|
||||
self.toolbar3.setVisible(False)
|
||||
|
||||
def set_readonly(self, what):
|
||||
self.editor.set_readonly(what)
|
||||
|
||||
def hide_tabs(self):
|
||||
self.tabs.tabBar().setVisible(False)
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -101,7 +101,7 @@ class TitleEdit(EnLineEdit):
|
||||
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False,
|
||||
commit=False)
|
||||
except (IOError, OSError) as err:
|
||||
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
|
||||
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
|
||||
import traceback
|
||||
fname = getattr(err, 'filename', None)
|
||||
p = 'Locked file: %s\n\n'%fname if fname else ''
|
||||
@ -273,7 +273,7 @@ class AuthorsEdit(EditWithComplete):
|
||||
self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
|
||||
allow_case_change=True)
|
||||
except (IOError, OSError) as err:
|
||||
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
|
||||
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
|
||||
import traceback
|
||||
fname = getattr(err, 'filename', None)
|
||||
p = 'Locked file: %s\n\n'%fname if fname else ''
|
||||
@ -485,7 +485,7 @@ class SeriesEdit(EditWithComplete):
|
||||
def initialize(self, db, id_):
|
||||
self.books_to_refresh = set([])
|
||||
all_series = db.all_series()
|
||||
all_series.sort(key=lambda x : sort_key(x[1]))
|
||||
all_series.sort(key=lambda x: sort_key(x[1]))
|
||||
self.update_items_cache([x[1] for x in all_series])
|
||||
series_id = db.series_id(id_, index_is_id=True)
|
||||
inval = ''
|
||||
@ -586,7 +586,7 @@ class SeriesIndexEdit(QDoubleSpinBox):
|
||||
|
||||
# }}}
|
||||
|
||||
class BuddyLabel(QLabel): # {{{
|
||||
class BuddyLabel(QLabel): # {{{
|
||||
|
||||
def __init__(self, buddy):
|
||||
QLabel.__init__(self, buddy.LABEL)
|
||||
@ -698,11 +698,11 @@ class FormatsManager(QWidget):
|
||||
self.formats.setIconSize(QSize(32, 32))
|
||||
self.formats.setMaximumWidth(200)
|
||||
|
||||
l.addWidget(self.cover_from_format_button, 0, 0, 1, 1)
|
||||
l.addWidget(self.cover_from_format_button, 0, 0, 1, 1)
|
||||
l.addWidget(self.metadata_from_format_button, 2, 0, 1, 1)
|
||||
l.addWidget(self.add_format_button, 0, 2, 1, 1)
|
||||
l.addWidget(self.remove_format_button, 2, 2, 1, 1)
|
||||
l.addWidget(self.formats, 0, 1, 3, 1)
|
||||
l.addWidget(self.add_format_button, 0, 2, 1, 1)
|
||||
l.addWidget(self.remove_format_button, 2, 2, 1, 1)
|
||||
l.addWidget(self.formats, 0, 1, 3, 1)
|
||||
|
||||
self.temp_files = []
|
||||
|
||||
@ -882,7 +882,7 @@ class FormatsManager(QWidget):
|
||||
self.temp_files = []
|
||||
# }}}
|
||||
|
||||
class Cover(ImageView): # {{{
|
||||
class Cover(ImageView): # {{{
|
||||
|
||||
download_cover = pyqtSignal()
|
||||
|
||||
@ -1052,7 +1052,7 @@ class Cover(ImageView): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class CommentsEdit(Editor): # {{{
|
||||
class CommentsEdit(Editor): # {{{
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
@ -1076,7 +1076,7 @@ class CommentsEdit(Editor): # {{{
|
||||
return True
|
||||
# }}}
|
||||
|
||||
class RatingEdit(QSpinBox): # {{{
|
||||
class RatingEdit(QSpinBox): # {{{
|
||||
LABEL = _('&Rating:')
|
||||
TOOLTIP = _('Rating of this book. 0-5 stars')
|
||||
|
||||
@ -1120,7 +1120,7 @@ class RatingEdit(QSpinBox): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class TagsEdit(EditWithComplete): # {{{
|
||||
class TagsEdit(EditWithComplete): # {{{
|
||||
LABEL = _('Ta&gs:')
|
||||
TOOLTIP = '<p>'+_('Tags categorize the book. This is particularly '
|
||||
'useful while searching. <br><br>They can be any words '
|
||||
@ -1174,7 +1174,6 @@ class TagsEdit(EditWithComplete): # {{{
|
||||
self.current_val = d.tags
|
||||
self.all_items = db.all_tags()
|
||||
|
||||
|
||||
def commit(self, db, id_):
|
||||
self.books_to_refresh |= db.set_tags(
|
||||
id_, self.current_val, notify=False, commit=False,
|
||||
@ -1183,7 +1182,7 @@ class TagsEdit(EditWithComplete): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class LanguagesEdit(LE): # {{{
|
||||
class LanguagesEdit(LE): # {{{
|
||||
|
||||
LABEL = _('&Languages:')
|
||||
TOOLTIP = _('A comma separated list of languages for this book')
|
||||
@ -1194,8 +1193,10 @@ class LanguagesEdit(LE): # {{{
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
def fget(self): return self.lang_codes
|
||||
def fset(self, val): self.lang_codes = val
|
||||
def fget(self):
|
||||
return self.lang_codes
|
||||
def fset(self, val):
|
||||
self.lang_codes = val
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def initialize(self, db, id_):
|
||||
@ -1221,7 +1222,7 @@ class LanguagesEdit(LE): # {{{
|
||||
return True
|
||||
# }}}
|
||||
|
||||
class IdentifiersEdit(QLineEdit): # {{{
|
||||
class IdentifiersEdit(QLineEdit): # {{{
|
||||
LABEL = _('I&ds:')
|
||||
BASE_TT = _('Edit the identifiers for this book. '
|
||||
'For example: \n\n%s')%(
|
||||
@ -1309,7 +1310,7 @@ class IdentifiersEdit(QLineEdit): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class ISBNDialog(QDialog) : # {{{
|
||||
class ISBNDialog(QDialog): # {{{
|
||||
|
||||
def __init__(self, parent, txt):
|
||||
QDialog.__init__(self, parent)
|
||||
@ -1320,7 +1321,7 @@ class ISBNDialog(QDialog) : # {{{
|
||||
l.addWidget(w, 0, 0, 1, 2)
|
||||
w = QLabel(_('ISBN:'))
|
||||
l.addWidget(w, 1, 0, 1, 1)
|
||||
self.line_edit = w = QLineEdit();
|
||||
self.line_edit = w = QLineEdit()
|
||||
w.setText(txt)
|
||||
w.selectAll()
|
||||
w.textChanged.connect(self.checkText)
|
||||
@ -1361,7 +1362,7 @@ class ISBNDialog(QDialog) : # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class PublisherEdit(EditWithComplete): # {{{
|
||||
class PublisherEdit(EditWithComplete): # {{{
|
||||
LABEL = _('&Publisher:')
|
||||
|
||||
def __init__(self, parent):
|
||||
@ -1388,7 +1389,7 @@ class PublisherEdit(EditWithComplete): # {{{
|
||||
def initialize(self, db, id_):
|
||||
self.books_to_refresh = set([])
|
||||
all_publishers = db.all_publishers()
|
||||
all_publishers.sort(key=lambda x : sort_key(x[1]))
|
||||
all_publishers.sort(key=lambda x: sort_key(x[1]))
|
||||
self.update_items_cache([x[1] for x in all_publishers])
|
||||
publisher_id = db.publisher_id(id_, index_is_id=True)
|
||||
inval = ''
|
||||
@ -1421,7 +1422,7 @@ class DateEdit(QDateTimeEdit):
|
||||
ATTR = 'timestamp'
|
||||
TWEAK = 'gui_timestamp_display_format'
|
||||
|
||||
def __init__(self, parent):
|
||||
def __init__(self, parent, create_clear_button=True):
|
||||
QDateTimeEdit.__init__(self, parent)
|
||||
self.setToolTip(self.TOOLTIP)
|
||||
self.setWhatsThis(self.TOOLTIP)
|
||||
@ -1435,10 +1436,11 @@ class DateEdit(QDateTimeEdit):
|
||||
self.setCalendarWidget(self.cw)
|
||||
self.setMinimumDateTime(UNDEFINED_QDATETIME)
|
||||
self.setSpecialValueText(_('Undefined'))
|
||||
self.clear_button = QToolButton(parent)
|
||||
self.clear_button.setIcon(QIcon(I('trash.png')))
|
||||
self.clear_button.setToolTip(_('Clear date'))
|
||||
self.clear_button.clicked.connect(self.reset_date)
|
||||
if create_clear_button:
|
||||
self.clear_button = QToolButton(parent)
|
||||
self.clear_button.setIcon(QIcon(I('trash.png')))
|
||||
self.clear_button.setToolTip(_('Clear date'))
|
||||
self.clear_button.clicked.connect(self.reset_date)
|
||||
|
||||
def reset_date(self, *args):
|
||||
self.current_val = None
|
||||
|
@ -6,5 +6,477 @@ from __future__ import (unicode_literals, division, absolute_import,
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import os
|
||||
from collections import OrderedDict, namedtuple
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QDialog, QWidget, QGridLayout, QLineEdit, QLabel, QToolButton, QIcon,
|
||||
QVBoxLayout, QDialogButtonBox, QApplication, pyqtSignal, QFont, QPixmap,
|
||||
QSize, QPainter, Qt, QColor, QPen, QSizePolicy, QScrollArea, QFrame)
|
||||
|
||||
from calibre import fit_image
|
||||
from calibre.ebooks.metadata import title_sort, authors_to_sort_string
|
||||
from calibre.gui2 import pixmap_to_data, gprefs
|
||||
from calibre.gui2.comments_editor import Editor
|
||||
from calibre.gui2.metadata.basic_widgets import PubdateEdit, RatingEdit
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.date import UNDEFINED_DATE
|
||||
|
||||
Widgets = namedtuple('Widgets', 'new old label button')
|
||||
|
||||
# Widgets {{{
|
||||
|
||||
class LineEdit(QLineEdit):
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, field, is_new, parent, metadata, extra):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.is_new = is_new
|
||||
self.field = field
|
||||
self.metadata = metadata
|
||||
if not is_new:
|
||||
self.setReadOnly(True)
|
||||
self.textChanged.connect(self.changed)
|
||||
|
||||
def from_mi(self, mi):
|
||||
val = mi.get(self.field, default='') or ''
|
||||
ism = self.metadata['is_multiple']
|
||||
if ism:
|
||||
if not val:
|
||||
val = ''
|
||||
else:
|
||||
val = ism['list_to_ui'].join(val)
|
||||
self.setText(val)
|
||||
self.setCursorPosition(0)
|
||||
|
||||
def to_mi(self, mi):
|
||||
val = unicode(self.text()).strip()
|
||||
ism = self.metadata['is_multiple']
|
||||
if ism:
|
||||
if not val:
|
||||
val = []
|
||||
else:
|
||||
val = [x.strip() for x in val.split(ism['list_to_ui']) if x.strip()]
|
||||
mi.set(self.field, val)
|
||||
if self.field == 'title':
|
||||
mi.set('title_sort', title_sort(val, lang=mi.language))
|
||||
elif self.field == 'authors':
|
||||
mi.set('author_sort', authors_to_sort_string(val))
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
def fget(self):
|
||||
return unicode(self.text())
|
||||
def fset(self, val):
|
||||
self.setText(val)
|
||||
self.setCursorPosition(0)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
@property
|
||||
def is_blank(self):
|
||||
return not self.current_val.strip()
|
||||
|
||||
class RatingsEdit(RatingEdit):
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, field, is_new, parent, metadata, extra):
|
||||
RatingEdit.__init__(self, parent)
|
||||
self.is_new = is_new
|
||||
self.field = field
|
||||
self.metadata = metadata
|
||||
self.valueChanged.connect(self.changed)
|
||||
if not is_new:
|
||||
self.setReadOnly(True)
|
||||
|
||||
def from_mi(self, mi):
|
||||
val = (mi.get(self.field, default=0) or 0)/2
|
||||
self.setValue(val)
|
||||
|
||||
def to_mi(self, mi):
|
||||
mi.set(self.field, self.value() * 2)
|
||||
|
||||
@property
|
||||
def is_blank(self):
|
||||
return self.value() == 0
|
||||
|
||||
class DateEdit(PubdateEdit):
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, field, is_new, parent, metadata, extra):
|
||||
PubdateEdit.__init__(self, parent, create_clear_button=False)
|
||||
self.is_new = is_new
|
||||
self.field = field
|
||||
self.metadata = metadata
|
||||
self.setDisplayFormat(extra)
|
||||
self.dateTimeChanged.connect(self.changed)
|
||||
if not is_new:
|
||||
self.setReadOnly(True)
|
||||
|
||||
def from_mi(self, mi):
|
||||
self.current_val = mi.get(self.field, default=None)
|
||||
|
||||
def to_mi(self, mi):
|
||||
mi.set(self.field, self.current_val)
|
||||
|
||||
@property
|
||||
def is_blank(self):
|
||||
return self.current_val == UNDEFINED_DATE
|
||||
|
||||
class SeriesEdit(LineEdit):
|
||||
|
||||
def from_mi(self, mi):
|
||||
series = mi.get(self.field, default='')
|
||||
series_index = mi.get(self.field + '_index', default=1.0)
|
||||
val = ''
|
||||
if series:
|
||||
val = '%s [%s]' % (series, mi.format_series_index(series_index))
|
||||
self.setText(val)
|
||||
self.setCursorPosition(0)
|
||||
|
||||
def to_mi(self, mi):
|
||||
val = unicode(self.text()).strip()
|
||||
try:
|
||||
series_index = float(val.rpartition('[')[-1].rstrip(']').strip())
|
||||
except:
|
||||
series_index = 1.0
|
||||
series = val.rpartition('[')[0].strip() or None
|
||||
mi.set(self.field, series)
|
||||
mi.set(self.field + '_index', series_index)
|
||||
|
||||
class IdentifiersEdit(LineEdit):
|
||||
|
||||
def from_mi(self, mi):
|
||||
val = ('%s:%s' % (k, v) for k, v in mi.identifiers.iteritems())
|
||||
self.setText(', '.join(val))
|
||||
|
||||
def to_mi(self, mi):
|
||||
parts = (x.strip() for x in self.current_val.split(',') if x.strip())
|
||||
val = {x.partition(':')[0].strip():x.partition(':')[-1].strip() for x in parts}
|
||||
mi.set_identifiers({k:v for k, v in val.iteritems() if k and v})
|
||||
|
||||
class CommentsEdit(Editor):
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, field, is_new, parent, metadata, extra):
|
||||
Editor.__init__(self, parent, one_line_toolbar=False)
|
||||
self.is_new = is_new
|
||||
self.field = field
|
||||
self.metadata = metadata
|
||||
self.hide_tabs()
|
||||
if not is_new:
|
||||
self.hide_toolbars()
|
||||
self.set_readonly(True)
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
def fget(self):
|
||||
return self.html
|
||||
def fset(self, val):
|
||||
self.html = val or ''
|
||||
self.changed.emit()
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def from_mi(self, mi):
|
||||
val = mi.get(self.field, default='')
|
||||
self.current_val = val
|
||||
|
||||
def to_mi(self, mi):
|
||||
mi.set(self.field, self.current_val)
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(450, 200)
|
||||
|
||||
@property
|
||||
def is_blank(self):
|
||||
return not self.current_val.strip()
|
||||
|
||||
class CoverView(QWidget):
|
||||
|
||||
changed = pyqtSignal()
|
||||
|
||||
def __init__(self, field, is_new, parent, metadata, extra):
|
||||
QWidget.__init__(self, parent)
|
||||
self.is_new = is_new
|
||||
self.field = field
|
||||
self.metadata = metadata
|
||||
self.pixmap = None
|
||||
self.blank = QPixmap(I('blank.png'))
|
||||
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.GrowFlag|QSizePolicy.ExpandFlag)
|
||||
self.sizePolicy().setHeightForWidth(True)
|
||||
|
||||
@property
|
||||
def is_blank(self):
|
||||
return self.pixmap is None
|
||||
|
||||
@dynamic_property
|
||||
def current_val(self):
|
||||
def fget(self):
|
||||
return self.pixmap
|
||||
def fset(self, val):
|
||||
self.pixmap = val
|
||||
self.changed.emit()
|
||||
self.update()
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def from_mi(self, mi):
|
||||
p = getattr(mi, 'cover', None)
|
||||
if p and os.path.exists(p):
|
||||
pmap = QPixmap()
|
||||
with open(p, 'rb') as f:
|
||||
pmap.loadFromData(f.read())
|
||||
if not pmap.isNull():
|
||||
self.pixmap = pmap
|
||||
self.update()
|
||||
self.changed.emit()
|
||||
return
|
||||
cd = getattr(mi, 'cover_data', (None, None))
|
||||
if cd and cd[1]:
|
||||
pmap = QPixmap()
|
||||
pmap.loadFromData(cd[1])
|
||||
if not pmap.isNull():
|
||||
self.pixmap = pmap
|
||||
self.update()
|
||||
self.changed.emit()
|
||||
return
|
||||
self.pixmap = None
|
||||
self.update()
|
||||
self.changed.emit()
|
||||
|
||||
def to_mi(self, mi):
|
||||
mi.cover, mi.cover_data = None, (None, None)
|
||||
if self.pixmap is not None and not self.pixmap.isNull():
|
||||
with PersistentTemporaryFile('.jpg') as pt:
|
||||
pt.write(pixmap_to_data(self.pixmap))
|
||||
mi.cover = pt.name
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(225, 300)
|
||||
|
||||
def paintEvent(self, event):
|
||||
pmap = self.blank if self.pixmap is None or self.pixmap.isNull() else self.pixmap
|
||||
target = self.rect()
|
||||
scaled, width, height = fit_image(pmap.width(), pmap.height(), target.width(), target.height())
|
||||
target.setRect(target.x(), target.y(), width, height)
|
||||
p = QPainter(self)
|
||||
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
|
||||
p.drawPixmap(target, pmap)
|
||||
|
||||
if self.pixmap is not None and not self.pixmap.isNull():
|
||||
sztgt = target.adjusted(0, 0, 0, -4)
|
||||
f = p.font()
|
||||
f.setBold(True)
|
||||
p.setFont(f)
|
||||
sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
|
||||
flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
|
||||
szrect = p.boundingRect(sztgt, flags, sz)
|
||||
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
|
||||
p.setPen(QPen(QColor(255,255,255)))
|
||||
p.drawText(sztgt, flags, sz)
|
||||
p.end()
|
||||
# }}}
|
||||
|
||||
class CompareSingle(QWidget):
|
||||
|
||||
def __init__(
|
||||
self, field_metadata, parent=None, revert_tooltip=None,
|
||||
datetime_fmt='MMMM yyyy', blank_as_equal=True,
|
||||
fields=('title', 'authors', 'series', 'tags', 'rating', 'publisher', 'pubdate', 'identifiers', 'comments', 'cover')):
|
||||
QWidget.__init__(self, parent)
|
||||
self.l = l = QGridLayout()
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(l)
|
||||
revert_tooltip = revert_tooltip or _('Revert %s')
|
||||
self.current_mi = None
|
||||
self.changed_font = QFont(QApplication.font())
|
||||
self.changed_font.setBold(True)
|
||||
self.changed_font.setItalic(True)
|
||||
self.blank_as_equal = blank_as_equal
|
||||
|
||||
self.widgets = OrderedDict()
|
||||
row = 0
|
||||
|
||||
for field in fields:
|
||||
m = field_metadata[field]
|
||||
dt = m['datatype']
|
||||
extra = None
|
||||
if 'series' in {field, dt}:
|
||||
cls = SeriesEdit
|
||||
elif field == 'identifiers':
|
||||
cls = IdentifiersEdit
|
||||
elif 'comments' in {field, dt}:
|
||||
cls = CommentsEdit
|
||||
elif 'rating' in {field, dt}:
|
||||
cls = RatingsEdit
|
||||
elif dt == 'datetime':
|
||||
extra = datetime_fmt
|
||||
cls = DateEdit
|
||||
elif field == 'cover':
|
||||
cls = CoverView
|
||||
elif dt in {'text', 'enum'}:
|
||||
cls = LineEdit
|
||||
else:
|
||||
continue
|
||||
neww = cls(field, True, self, m, extra)
|
||||
neww.changed.connect(partial(self.changed, field))
|
||||
oldw = cls(field, False, self, m, extra)
|
||||
newl = QLabel('&%s:' % m['name'])
|
||||
newl.setBuddy(neww)
|
||||
button = QToolButton(self)
|
||||
button.setIcon(QIcon(I('back.png')))
|
||||
button.clicked.connect(partial(self.revert, field))
|
||||
button.setToolTip(revert_tooltip % m['name'])
|
||||
self.widgets[field] = Widgets(neww, oldw, newl, button)
|
||||
for i, w in enumerate((newl, neww, button, oldw)):
|
||||
c = i if i < 2 else i + 1
|
||||
if w is oldw:
|
||||
c += 1
|
||||
l.addWidget(w, row, c)
|
||||
row += 1
|
||||
|
||||
self.sep = f = QFrame(self)
|
||||
f.setFrameShape(f.VLine)
|
||||
l.addWidget(f, 0, 2, row, 1)
|
||||
self.sep2 = f = QFrame(self)
|
||||
f.setFrameShape(f.VLine)
|
||||
l.addWidget(f, 0, 4, row, 1)
|
||||
|
||||
def changed(self, field):
|
||||
w = self.widgets[field]
|
||||
if w.new.current_val != w.old.current_val and (not self.blank_as_equal or not w.new.is_blank):
|
||||
w.label.setFont(self.changed_font)
|
||||
else:
|
||||
w.label.setFont(QApplication.font())
|
||||
|
||||
def revert(self, field):
|
||||
widgets = self.widgets[field]
|
||||
neww, oldw = widgets[:2]
|
||||
neww.current_val = oldw.current_val
|
||||
|
||||
def __call__(self, oldmi, newmi):
|
||||
self.current_mi = newmi
|
||||
self.initial_vals = {}
|
||||
for field, widgets in self.widgets.iteritems():
|
||||
widgets.old.from_mi(oldmi)
|
||||
widgets.new.from_mi(newmi)
|
||||
self.initial_vals[field] = widgets.new.current_val
|
||||
|
||||
def apply_changes(self):
|
||||
changed = False
|
||||
for field, widgets in self.widgets.iteritems():
|
||||
val = widgets.new.current_val
|
||||
if val != self.initial_vals[field]:
|
||||
widgets.new.to_mi(self.current_mi)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
class CompareMany(QDialog):
|
||||
|
||||
def __init__(self, ids, get_metadata, field_metadata, parent=None, window_title=None, skip_button_tooltip=None,
|
||||
accept_all_tooltip=None, reject_all_tooltip=None, **kwargs):
|
||||
QDialog.__init__(self, parent)
|
||||
self.l = l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
self.setWindowIcon(QIcon(I('auto_author_sort.png')))
|
||||
self.get_metadata = get_metadata
|
||||
self.ids = list(ids)
|
||||
self.total = len(self.ids)
|
||||
self.accepted = OrderedDict()
|
||||
self.window_title = window_title or _('Compare metadata')
|
||||
|
||||
self.compare_widget = CompareSingle(field_metadata, parent=parent, **kwargs)
|
||||
self.sa = sa = QScrollArea()
|
||||
l.addWidget(sa)
|
||||
sa.setWidget(self.compare_widget)
|
||||
sa.setWidgetResizable(True)
|
||||
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.aarb = b = bb.addButton(_('&Accept all remaining'), bb.YesRole)
|
||||
if accept_all_tooltip:
|
||||
b.setToolTip(accept_all_tooltip)
|
||||
b.clicked.connect(self.accept_all_remaining)
|
||||
self.rarb = b = bb.addButton(_('Re&ject all remaining'), bb.NoRole)
|
||||
if reject_all_tooltip:
|
||||
b.setToolTip(reject_all_tooltip)
|
||||
b.clicked.connect(self.reject_all_remaining)
|
||||
self.sb = b = bb.addButton(_('&Reject'), bb.ActionRole)
|
||||
b.clicked.connect(partial(self.next_item, False))
|
||||
if skip_button_tooltip:
|
||||
b.setToolTip(skip_button_tooltip)
|
||||
self.nb = b = bb.addButton(_('&Next'), bb.ActionRole)
|
||||
b.setIcon(QIcon(I('forward.png')))
|
||||
b.clicked.connect(partial(self.next_item, True))
|
||||
b.setDefault(True)
|
||||
l.addWidget(bb)
|
||||
|
||||
self.next_item(True)
|
||||
|
||||
desktop = QApplication.instance().desktop()
|
||||
geom = desktop.availableGeometry(parent or self)
|
||||
width = max(700, min(950, geom.width()-50))
|
||||
height = max(650, min(1000, geom.height()-100))
|
||||
self.resize(QSize(width, height))
|
||||
geom = gprefs.get('diff_dialog_geom', None)
|
||||
if geom is not None:
|
||||
self.restoreGeometry(geom)
|
||||
|
||||
def accept(self):
|
||||
gprefs.set('diff_dialog_geom', bytearray(self.saveGeometry()))
|
||||
super(CompareMany, self).accept()
|
||||
|
||||
def reject(self):
|
||||
gprefs.set('diff_dialog_geom', bytearray(self.saveGeometry()))
|
||||
super(CompareMany, self).reject()
|
||||
|
||||
@property
|
||||
def current_mi(self):
|
||||
return self.compare_widget.current_mi
|
||||
|
||||
def next_item(self, accept):
|
||||
if not self.ids:
|
||||
return self.accept()
|
||||
if self.current_mi is not None:
|
||||
changed = self.compare_widget.apply_changes()
|
||||
self.setWindowTitle(self.window_title + _(' [%(num)d of %(tot)d]') % dict(
|
||||
num=(self.total - len(self.ids) + 1), tot=self.total))
|
||||
oldmi, newmi = self.get_metadata(self.ids[0])
|
||||
old_id = self.ids.pop(0)
|
||||
if self.current_mi is not None:
|
||||
self.accepted[old_id] = (changed, self.current_mi) if accept else (False, None)
|
||||
self.compare_widget(oldmi, newmi)
|
||||
|
||||
def accept_all_remaining(self):
|
||||
self.next_item(True)
|
||||
for id_ in self.ids:
|
||||
oldmi, newmi = self.get_metadata(id_)
|
||||
self.accepted[id_] = (False, newmi)
|
||||
self.ids = []
|
||||
self.accept()
|
||||
|
||||
def reject_all_remaining(self):
|
||||
self.next_item(False)
|
||||
for id_ in self.ids:
|
||||
oldmi, newmi = self.get_metadata(id_)
|
||||
self.accepted[id_] = (False, None)
|
||||
self.ids = []
|
||||
self.accept()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
from calibre.library import db
|
||||
db = db()
|
||||
ids = sorted(db.all_ids(), reverse=True)
|
||||
ids = [(x, ids[i+1]) for i, x in enumerate(ids[0::2])]
|
||||
gm = partial(db.get_metadata, index_is_id=True, get_cover=True, cover_as_data=True)
|
||||
get_metadata = lambda x:map(gm, ids[x])
|
||||
d = CompareMany(list(xrange(len(ids))), get_metadata, db.field_metadata)
|
||||
if d.exec_() == d.Accepted:
|
||||
for changed, mi in d.accepted.itervalues():
|
||||
if changed and mi is not None:
|
||||
print (mi)
|
||||
|
||||
|
@ -252,7 +252,7 @@ class FieldMetadata(dict):
|
||||
'datatype':'int',
|
||||
'is_multiple':{},
|
||||
'kind':'field',
|
||||
'name':None,
|
||||
'name':_('Cover'),
|
||||
'search_terms':['cover'],
|
||||
'is_custom':False,
|
||||
'is_category':False,
|
||||
|
Loading…
x
Reference in New Issue
Block a user