diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 486f2c540d..76fdc62d68 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -10,12 +10,13 @@ from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \ QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ - QPushButton + QPushButton, QMessageBox, QToolButton from calibre.utils.date import qt_to_dt, now from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog +from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.utils.config import tweaks from calibre.utils.icu import sort_key from calibre.library.comments import comments_to_html @@ -226,18 +227,71 @@ class Comments(Base): val = None return val +class MultipleWidget(QWidget): + + def __init__(self, parent): + QWidget.__init__(self, parent) + layout = QHBoxLayout() + layout.setSpacing(5) + layout.setContentsMargins(0, 0, 0, 0) + + self.tags_box = MultiCompleteLineEdit(parent) + layout.addWidget(self.tags_box, stretch=1000) + self.editor_button = QToolButton(self) + self.editor_button.setToolTip(_('Open Item Editor')) + self.editor_button.setIcon(QIcon(I('chapters.png'))) + layout.addWidget(self.editor_button) + self.setLayout(layout) + + def get_editor_button(self): + return self.editor_button + + def update_items_cache(self, values): + self.tags_box.update_items_cache(values) + + def clear(self): + self.tags_box.clear() + + def setEditText(self): + self.tags_box.setEditText() + + def addItem(self, itm): + self.tags_box.addItem(itm) + + def set_separator(self, sep): + self.tags_box.set_separator(sep) + + def set_add_separator(self, sep): + self.tags_box.set_add_separator(sep) + + def set_space_before_sep(self, v): + self.tags_box.set_space_before_sep(v) + + def setSizePolicy(self, v1, v2): + self.tags_box.setSizePolicy(v1, v2) + + def setText(self, v): + self.tags_box.setText(v) + + def text(self): + return self.tags_box.text() + class Text(Base): def setup_ui(self, parent): self.sep = self.col_metadata['multiple_seps'] + self.key = self.db.field_metadata.label_to_key(self.col_metadata['label'], + prefer_custom=True) + self.parent = parent if self.col_metadata['is_multiple']: - w = MultiCompleteLineEdit(parent) + w = MultipleWidget(parent) w.set_separator(self.sep['ui_to_list']) if self.sep['ui_to_list'] == '&': w.set_space_before_sep(True) w.set_add_separator(tweaks['authors_completer_append_separator']) w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) + w.get_editor_button().clicked.connect(self.edit) else: w = MultiCompleteComboBox(parent) w.set_separator(None) @@ -248,9 +302,12 @@ class Text(Base): def initialize(self, book_id): values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) + self.book_id = book_id self.widgets[1].clear() self.widgets[1].update_items_cache(values) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) + if isinstance(val, list): + val.sort(key=sort_key) self.initial_val = val val = self.normalize_db_val(val) @@ -284,6 +341,31 @@ class Text(Base): val = None return val + def _save_dialog(self, parent, title, msg, det_msg=''): + d = QMessageBox(parent) + d.setWindowTitle(title) + d.setText(msg) + d.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) + return d.exec_() + + def edit(self): + if self.getter() != self.initial_val: + d = self._save_dialog(self.parent, _('Values changed'), + _('You have changed the values. In order to use this ' + 'editor, you must either discard or apply these ' + 'changes. Apply changes?')) + if d == QMessageBox.Cancel: + return + if d == QMessageBox.Yes: + self.commit(self.book_id) + self.db.commit() + self.initial_val = self.getter() + else: + self.setter(self.initial_val) + d = TagEditor(self.parent, self.db, self.book_id, self.key) + if d.exec_() == TagEditor.Accepted: + self.setter(d.tags) + class Series(Base): def setup_ui(self, parent): diff --git a/src/calibre/gui2/dialogs/tag_editor.py b/src/calibre/gui2/dialogs/tag_editor.py index bf3bb9fd4e..843711713b 100644 --- a/src/calibre/gui2/dialogs/tag_editor.py +++ b/src/calibre/gui2/dialogs/tag_editor.py @@ -10,19 +10,26 @@ from calibre.utils.icu import sort_key class TagEditor(QDialog, Ui_TagEditor): - def __init__(self, window, db, id_=None): + def __init__(self, window, db, id_=None, key=None): QDialog.__init__(self, window) Ui_TagEditor.__init__(self) self.setupUi(self) self.db = db + if key: + key = db.field_metadata.key_to_label(key) + self.key = key self.index = db.row(id_) if id_ is not None else None if self.index is not None: - tags = self.db.tags(self.index) + if key is None: + tags = self.db.tags(self.index) + if tags: + tags = [tag.strip() for tag in tags.split(',') if tag.strip()] + else: + tags = self.db.get_custom(self.index, label=key) else: tags = [] if tags: - tags = [tag.strip() for tag in tags.split(',') if tag.strip()] tags.sort(key=sort_key) for tag in tags: self.applied_tags.addItem(tag) @@ -31,7 +38,10 @@ class TagEditor(QDialog, Ui_TagEditor): self.tags = tags - all_tags = [tag for tag in self.db.all_tags()] + if key: + all_tags = [tag for tag in self.db.all_custom(label=key)] + else: + all_tags = [tag for tag in self.db.all_tags()] all_tags = list(set(all_tags)) all_tags.sort(key=sort_key) for tag in all_tags: @@ -61,7 +71,10 @@ class TagEditor(QDialog, Ui_TagEditor): error_dialog(self, 'No tags selected', 'You must select at least one tag from the list of Available tags.').exec_() return for item in items: - if self.db.is_tag_used(unicode(item.text())): + used = self.db.is_tag_used(unicode(item.text())) \ + if self.key is None else \ + self.db.is_item_used_in_multiple(unicode(item.text()), label=self.key) + if used: confirms.append(item) else: deletes.append(item) @@ -73,7 +86,12 @@ class TagEditor(QDialog, Ui_TagEditor): deletes += confirms for item in deletes: - self.db.delete_tag(unicode(item.text())) + if self.key is None: + self.db.delete_tag(unicode(item.text())) + else: + bks = self.db.delete_item_from_multiple(unicode(item.text()), + label=self.key) + self.db.refresh_ids(bks) self.available_tags.takeItem(self.available_tags.row(item)) diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 9a2a27aecc..a2a39df90f 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -276,6 +276,37 @@ class CustomColumns(object): self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id,)) self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id,)) self.conn.commit() + + def is_item_used_in_multiple(self, item, label=None, num=None): + existing_tags = self.all_custom(label=label, num=num) + return item.lower() in {t.lower() for t in existing_tags} + + def delete_item_from_multiple(self, item, label=None, num=None): + if label is not None: + data = self.custom_column_label_map[label] + if num is not None: + data = self.custom_column_num_map[num] + if data['datatype'] != 'text' or not data['is_multiple']: + raise ValueError('Column %r is not text/multiple'%data['label']) + existing_tags = list(self.all_custom(label=label, num=num)) + lt = [t.lower() for t in existing_tags] + try: + idx = lt.index(item.lower()) + except ValueError: + idx = -1 + books_affected = [] + if idx > -1: + table, lt = self.custom_table_names(data['num']) + id_ = self.conn.get('SELECT id FROM %s where value = ?'%table, + (existing_tags[idx],), all=False) + if id_: + books = self.conn.get('SELECT book FROM %s where value = ?'%lt, (id_,)) + if books: + books_affected = [b[0] for b in books] + self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id_,)) + self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id_,)) + self.conn.commit() + return books_affected # end convenience methods def get_next_cc_series_num_for(self, series, label=None, num=None):