diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index d624d5320d..6bd2f3c77a 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -11,7 +11,7 @@ from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ - QPushButton, QCoreApplication + QPushButton from calibre.utils.date import qt_to_dt, now from calibre.gui2.widgets import TagsLineEdit, EnComboBox @@ -32,8 +32,13 @@ class Base(object): val = self.normalize_db_val(val) self.setter(val) + @property + def gui_val(self): + return self.getter() + + def commit(self, book_id, notify=False): - val = self.getter() + val = self.gui_val val = self.normalize_ui_val(val) if val != self.initial_val: self.db.set_custom(book_id, val, num=self.col_id, notify=notify) @@ -284,10 +289,14 @@ class Series(Base): if idx is not None: self.widgets[1].setCurrentIndex(idx) + def getter(self): + n = unicode(self.name_widget.currentText()).strip() + i = self.idx_widget.value() + return n, i + def commit(self, book_id, notify=False): - val = unicode(self.name_widget.currentText()).strip() + val, s_index = self.gui_val val = self.normalize_ui_val(val) - s_index = self.idx_widget.value() if val != self.initial_val or s_index != self.initial_index: if s_index == 0.0: if tweaks['series_index_auto_increment'] == 'next': @@ -378,6 +387,13 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa class BulkBase(Base): + @property + def gui_val(self): + if not hasattr(self, '_cached_gui_val_'): + self._cached_gui_val_ = self.getter() + return self._cached_gui_val_ + + def get_initial_value(self, book_ids): values = set([]) for book_id in book_ids: @@ -400,11 +416,10 @@ class BulkBase(Base): self.setter(val) def commit(self, book_ids, notify=False): - val = self.getter() + val = self.gui_val val = self.normalize_ui_val(val) if val != self.initial_val: for book_id in book_ids: - QCoreApplication.processEvents() self.db.set_custom(book_id, val, num=self.col_id, notify=notify) class BulkBool(BulkBase, Bool): @@ -443,13 +458,16 @@ class BulkSeries(BulkBase): self.name_widget.addItem(c) self.name_widget.setEditText('') + def getter(self): + n = unicode(self.name_widget.currentText()).strip() + i = self.idx_widget.checkState() + return n, i + def commit(self, book_ids, notify=False): - val = unicode(self.name_widget.currentText()).strip() + val, update_indices = self.gui_val val = self.normalize_ui_val(val) - update_indices = self.idx_widget.checkState() if val != '': for book_id in book_ids: - QCoreApplication.processEvents() if update_indices: if tweaks['series_index_auto_increment'] == 'next': s_index = self.db.get_next_cc_series_num_for\ @@ -526,41 +544,35 @@ class BulkText(BulkBase): def commit(self, book_ids, notify=False): if self.col_metadata['is_multiple']: + remove_all, adding, rtext = self.gui_val remove = set() - if self.removing_widget.checkbox.isChecked(): + if remove_all: for book_id in book_ids: remove |= set(self.db.get_custom(book_id, num=self.col_id, index_is_id=True)) else: - txt = unicode(self.removing_widget.tags_box.text()) + txt = rtext if txt: remove = set([v.strip() for v in txt.split(',')]) - txt = unicode(self.adding_widget.text()) + txt = adding if txt: add = set([v.strip() for v in txt.split(',')]) else: add = set() self.db.set_custom_bulk(book_ids, add=add, remove=remove, num=self.col_id) else: - val = self.getter() + val = self.gui_val val = self.normalize_ui_val(val) if val != self.initial_val: for book_id in book_ids: - QCoreApplication.processEvents() self.db.set_custom(book_id, val, num=self.col_id, notify=notify) - def getter(self, original_value = None): + def getter(self): if self.col_metadata['is_multiple']: - if self.removing_widget.checkbox.isChecked(): - ans = set() - else: - ans = set(original_value) - ans -= set([v.strip() for v in - unicode(self.removing_widget.tags_box.text()).split(',')]) - txt = unicode(self.adding_widget.text()) - if txt: - ans |= set([v.strip() for v in txt.split(',')]) - return ans # returning a set instead of a list works, for now at least. + return self.removing_widget.checkbox.isChecked(), \ + unicode(self.adding_widget.text()), \ + unicode(self.removing_widget.tags_box.text()) + val = unicode(self.widgets[1].currentText()).strip() if not val: val = None diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 0139d0aee2..c216463941 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -4,14 +4,33 @@ __copyright__ = '2008, Kovid Goyal ' '''Dialog to edit metadata in bulk''' from PyQt4.Qt import SIGNAL, QObject, QDialog, QGridLayout, \ - QCoreApplication + QThread, Qt from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, \ authors_to_string from calibre.gui2.custom_column_widgets import populate_metadata_page -from calibre.gui2.dialogs.progress import ProgressDialog +from calibre.gui2.dialogs.progress import BlockingBusy +from calibre.gui2 import error_dialog + +class Worker(QThread): + + def __init__(self, func, parent=None): + QThread.__init__(self, parent) + self.func = func + self.error = None + + def run(self): + try: + self.func() + except Exception, err: + import traceback + try: + err = unicode(err) + except: + err = repr(err) + self.error = (err, traceback.format_exc()) class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): @@ -107,35 +126,29 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if len(self.ids) < 1: return QDialog.accept(self) - pd = ProgressDialog(_('Working'), - _('Applying changes to %d books. This may take a while.')%len(self.ids), - 0, 0, self, cancelable=False) - pd.setModal(True) - pd.show() - def upd(): - QCoreApplication.processEvents() + remove = unicode(self.remove_tags.text()).strip().split(',') + add = unicode(self.tags.text()).strip().split(',') + au = unicode(self.authors.text()) + aus = unicode(self.author_sort.text()) + do_aus = self.author_sort.isEnabled() + rating = self.rating.value() + pub = unicode(self.publisher.text()) + do_series = self.write_series + series = unicode(self.series.currentText()).strip() + do_autonumber = self.autonumber_series.isChecked() + do_remove_format = self.remove_format.currentIndex() > -1 + remove_format = unicode(self.remove_format.currentText()) + do_swap_ta = self.swap_title_and_author.isChecked() + do_remove_conv = self.remove_conversion_settings.isChecked() + do_auto_author = self.auto_author_sort.isChecked() + self.changed = bool(self.ids) + # Cache values from GUI so that Qt widgets are not used in + # non GUI thread + for w in getattr(self, 'custom_column_widgets', []): + w.gui_val - try: - remove = unicode(self.remove_tags.text()).strip().split(',') - add = unicode(self.tags.text()).strip().split(',') - au = unicode(self.authors.text()) - aus = unicode(self.author_sort.text()) - do_aus = self.author_sort.isEnabled() - rating = self.rating.value() - pub = unicode(self.publisher.text()) - do_series = self.write_series - series = unicode(self.series.currentText()).strip() - do_autonumber = self.autonumber_series.isChecked() - do_remove_format = self.remove_format.currentIndex() > -1 - remove_format = unicode(self.remove_format.currentText()) - do_swap_ta = self.swap_title_and_author.isChecked() - do_remove_conv = self.remove_conversion_settings.isChecked() - do_auto_author = self.auto_author_sort.isChecked() - - upd() - self.changed = bool(self.ids) + def doit(): for id in self.ids: - upd() if do_swap_ta: title = self.db.title(id, index_is_id=True) aum = self.db.authors(id, index_is_id=True) @@ -146,55 +159,54 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if title: new_authors = string_to_authors(title) self.db.set_authors(id, new_authors, notify=False) - upd() if au: self.db.set_authors(id, string_to_authors(au), notify=False) - upd() if do_auto_author: x = self.db.author_sort_from_book(id, index_is_id=True) if x: self.db.set_author_sort(id, x, notify=False) - upd() if aus and do_aus: self.db.set_author_sort(id, aus, notify=False) - upd() if rating != -1: self.db.set_rating(id, 2*rating, notify=False) if pub: self.db.set_publisher(id, pub, notify=False) - upd() if do_series: next = self.db.get_next_series_num_for(series) self.db.set_series(id, series, notify=False) num = next if do_autonumber and series else 1.0 self.db.set_series_index(id, num, notify=False) - upd() if do_remove_format: self.db.remove_format(id, remove_format, index_is_id=True, notify=False) - upd() if do_remove_conv: self.db.delete_conversion_options(id, 'PIPE') - upd() for w in getattr(self, 'custom_column_widgets', []): w.commit(self.ids) self.db.bulk_modify_tags(self.ids, add=add, remove=remove, notify=False) - upd() + self.db.clean() + self.worker = Worker(doit, self) + self.worker.start() - finally: - pd.hide() + bb = BlockingBusy(_('Applying changes to %d books. This may take a while.') + %len(self.ids), parent=self) + self.worker.finished.connect(bb.accept, type=Qt.QueuedConnection) + bb.exec_() - self.db.clean() + if self.worker.error is not None: + return error_dialog(self, _('Failed'), + self.worker.error[0], det_msg=self.worker.error[1], + show=True) return QDialog.accept(self)