1
from __future__ import with_statement
2
1
__license__ = 'GPL v3'
3
2
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
5
5
The dialog used to edit meta information for a book as well as
8
import os, time, traceback
10
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread
11
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog, QCompleter
13
from datetime import datetime
15
from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate
16
from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog
14
18
from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
15
choose_files, pixmap_to_data, choose_images, ResizableDialog
19
choose_files, choose_images, ResizableDialog
16
20
from calibre.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
17
21
from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
18
22
from calibre.gui2.dialogs.tag_editor import TagEditor
76
80
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
77
81
text, parent, QListWidgetItem.UserType)
79
class AuthorCompleter(QCompleter):
81
def __init__(self, db):
82
all_authors = db.all_authors()
83
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
84
QCompleter.__init__(self, [x[1] for x in all_authors])
86
83
class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
88
85
COVER_FETCH_TIMEOUT = 240 # seconds
91
88
pix = QPixmap(':/images/book.svg')
92
89
self.cover.setPixmap(pix)
93
90
self.cover_changed = True
91
self.cover_data = None
95
93
def select_cover(self, checked):
96
94
files = choose_images(self, 'change cover dialog',
159
158
row = self.formats.currentRow()
160
159
fmt = self.formats.item(row)
162
error_dialog(self, _('No format selected'),
161
if self.formats.count() == 1:
162
fmt = self.formats.item(0)
164
error_dialog(self, _('No format selected'),
163
165
_('No format selected')).exec_()
165
167
ext = fmt.ext.lower()
166
168
if fmt.path is None:
167
169
stream = self.db.format(self.row, ext, as_file=True)
191
193
self.cover.setPixmap(pix)
192
194
self.cover_changed = True
193
195
self.cpixmap = pix
196
self.cover_data = cdata
195
198
def sync_formats(self):
196
199
old_extensions, new_extensions, paths = set(), set(), {}
226
229
self.cover_changed = False
227
230
self.cpixmap = None
228
231
self.cover.setAcceptDrops(True)
229
self._author_completer = AuthorCompleter(self.db)
230
self.authors.setCompleter(self._author_completer)
231
self.connect(self.cover, SIGNAL('cover_changed()'), self.cover_dropped)
232
self.pubdate.setMinimumDate(QDate(100,1,1))
233
self.connect(self.cover, SIGNAL('cover_changed(PyQt_PyObject)'), self.cover_dropped)
232
234
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), \
233
235
self.select_cover)
234
236
QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), \
246
248
self.remove_unused_series)
247
249
QObject.connect(self.auto_author_sort, SIGNAL('clicked()'),
248
250
self.deduce_author_sort)
251
self.connect(self.formats, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
249
253
self.connect(self.button_set_cover, SIGNAL('clicked()'), self.set_cover)
250
254
self.connect(self.reset_cover, SIGNAL('clicked()'), self.do_reset_cover)
251
255
self.connect(self.swap_button, SIGNAL('clicked()'), self.swap_title_author)
257
261
self.isbn.setText(isbn)
258
au = self.db.authors(row)
260
au = [a.strip().replace('|', ',') for a in au.split(',')]
261
self.authors.setText(authors_to_string(au))
263
self.authors.setText('')
264
262
aus = self.db.author_sort(row)
265
263
self.author_sort.setText(aus if aus else '')
266
264
tags = self.db.tags(row)
267
self.tags.setText(tags if tags else '')
265
self.tags.setText(', '.join(tags.split(',')) if tags else '')
266
self.tags.update_tags_cache(self.db.all_tags())
268
267
rating = self.db.rating(row)
270
269
self.rating.setValue(int(rating/2.))
271
270
comments = self.db.comments(row)
272
271
self.comments.setPlainText(comments if comments else '')
273
272
cover = self.db.cover(row)
273
pubdate = db.pubdate(self.id, index_is_id=True)
274
self.pubdate.setDate(QDate(pubdate.year, pubdate.month,
275
277
exts = self.db.formats(row)
282
284
Format(self.formats, ext, size)
285
self.initialize_series_and_publisher()
287
self.series_index.setValue(self.db.series_index(row))
287
self.initialize_combos()
288
si = self.db.series_index(row)
292
self.series_index.setValue(float(si))
294
self.series_index.setValue(1.0)
288
295
QObject.connect(self.series, SIGNAL('currentIndexChanged(int)'), self.enable_series_index)
289
296
QObject.connect(self.series, SIGNAL('editTextChanged(QString)'), self.enable_series_index)
299
306
pm.loadFromData(cover)
300
307
if not pm.isNull():
301
308
self.cover.setPixmap(pm)
309
self.cover_data = cover
311
def show_format(self, item, *args):
313
self.emit(SIGNAL('view_format(PyQt_PyObject)'), fmt)
303
315
def deduce_author_sort(self):
304
316
au = unicode(self.authors.text())
317
au = re.sub(r'\s+et al\.$', '', au)
305
318
authors = string_to_authors(au)
306
319
self.author_sort.setText(authors_to_sort_string(authors))
311
324
self.authors.setText(title)
312
325
self.author_sort.setText('')
314
def cover_dropped(self):
327
def cover_dropped(self, paths):
315
328
self.cover_changed = True
329
self.cover_data = self.cover.cover_data
331
def initialize_combos(self):
332
self.initalize_authors()
333
self.initialize_series()
334
self.initialize_publisher()
336
self.layout().activate()
338
def initalize_authors(self):
339
all_authors = self.db.all_authors()
340
all_authors.sort(cmp=lambda x, y : cmp(x[1], y[1]))
341
for i in all_authors:
343
name = [name.strip().replace('|', ',') for n in name.split(',')]
344
self.authors.addItem(authors_to_string(name))
346
au = self.db.authors(self.row)
349
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
350
self.authors.setEditText(au)
317
352
def initialize_series(self):
318
353
self.series.setSizeAdjustPolicy(self.series.AdjustToContentsOnFirstShow)
332
367
self.series.setCurrentIndex(idx)
333
368
self.enable_series_index()
335
def initialize_series_and_publisher(self):
336
self.initialize_series()
370
def initialize_publisher(self):
337
371
all_publishers = self.db.all_publishers()
338
372
all_publishers.sort(cmp=lambda x, y : cmp(x[1], y[1]))
339
373
publisher_id = self.db.publisher_id(self.row)
349
383
if idx is not None:
350
384
self.publisher.setCurrentIndex(idx)
353
self.layout().activate()
355
386
def edit_tags(self):
356
387
d = TagEditor(self, self.db, self.row)
358
389
if d.result() == QDialog.Accepted:
359
390
tag_string = ', '.join(d.tags)
360
391
self.tags.setText(tag_string)
392
self.tags.update_tags_cache(self.db.all_tags())
362
394
def fetch_cover(self):
363
isbn = unicode(self.isbn.text()).strip()
395
isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())).strip()
364
396
self.fetch_cover_button.setEnabled(False)
365
397
self.setCursor(Qt.WaitCursor)
366
398
title, author = map(unicode, (self.title.text(), self.authors.text()))
415
448
def fetch_metadata(self):
416
isbn = qstring_to_unicode(self.isbn.text())
449
isbn = re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text()))
417
450
title = qstring_to_unicode(self.title.text())
418
451
author = string_to_authors(unicode(self.authors.text()))[0]
419
452
publisher = qstring_to_unicode(self.publisher.currentText())
429
462
if book.author_sort: self.author_sort.setText(book.author_sort)
430
463
if book.publisher: self.publisher.setEditText(book.publisher)
431
464
if book.isbn: self.isbn.setText(book.isbn)
467
self.pubdate.setDate(QDate(d.year, d.month, d.day))
432
468
summ = book.comments
434
470
prefix = qstring_to_unicode(self.comments.toPlainText())
458
494
def accept(self):
459
if self.formats_changed:
461
title = qstring_to_unicode(self.title.text())
496
if self.formats_changed:
498
title = unicode(self.title.text())
500
if err.errno == 13: # Permission denied
501
fname = err.filename if err.filename else 'file'
502
return error_dialog(self, _('Permission denied'),
503
_('Could not open %s. Is it being used by another'
504
' program?')%fname, show=True)
462
506
self.db.set_title(self.id, title, notify=False)
463
507
au = unicode(self.authors.text())
466
510
aus = qstring_to_unicode(self.author_sort.text())
468
512
self.db.set_author_sort(self.id, aus, notify=False)
469
self.db.set_isbn(self.id, qstring_to_unicode(self.isbn.text()), notify=False)
513
self.db.set_isbn(self.id,
514
re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())), notify=False)
470
515
self.db.set_rating(self.id, 2*self.rating.value(), notify=False)
471
516
self.db.set_publisher(self.id, qstring_to_unicode(self.publisher.currentText()), notify=False)
472
517
self.db.set_tags(self.id, qstring_to_unicode(self.tags.text()).split(','), notify=False)
473
518
self.db.set_series(self.id, qstring_to_unicode(self.series.currentText()), notify=False)
474
519
self.db.set_series_index(self.id, self.series_index.value(), notify=False)
475
520
self.db.set_comment(self.id, qstring_to_unicode(self.comments.toPlainText()), notify=False)
476
if self.cover_changed:
477
self.db.set_cover(self.id, pixmap_to_data(self.cover.pixmap()))
521
d = self.pubdate.date()
522
self.db.set_pubdate(self.id, datetime(d.year(), d.month(), d.day()))
524
if self.cover_changed and self.cover_data is not None:
525
self.db.set_cover(self.id, self.cover_data)
478
526
QDialog.accept(self)
479
527
if callable(self.accepted_callback):
480
528
self.accepted_callback(self.id)
530
def reject(self, *args):
531
cf = getattr(self, 'cover_fetcher', None)
532
if cf is not None and hasattr(cf, 'terminate'):
536
QDialog.reject(self, *args)