Add a Tag Editor for tags like custom columns to the edit metadata dialog

This commit is contained in:
Kovid Goyal 2012-01-13 18:39:43 +05:30
commit ff9af90409
3 changed files with 139 additions and 8 deletions

View File

@ -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):

View File

@ -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:
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,6 +38,9 @@ class TagEditor(QDialog, Ui_TagEditor):
self.tags = 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)
@ -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:
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))

View File

@ -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):