mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
Add a Tag Editor for tags like custom columns to the edit metadata dialog
This commit is contained in:
commit
ff9af90409
@ -10,12 +10,13 @@ from functools import partial
|
|||||||
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \
|
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \
|
||||||
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \
|
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \
|
||||||
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
|
||||||
QPushButton
|
QPushButton, QMessageBox, QToolButton
|
||||||
|
|
||||||
from calibre.utils.date import qt_to_dt, now
|
from calibre.utils.date import qt_to_dt, now
|
||||||
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
|
||||||
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
from calibre.gui2.comments_editor import Editor as CommentsEditor
|
||||||
from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
|
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.config import tweaks
|
||||||
from calibre.utils.icu import sort_key
|
from calibre.utils.icu import sort_key
|
||||||
from calibre.library.comments import comments_to_html
|
from calibre.library.comments import comments_to_html
|
||||||
@ -226,18 +227,71 @@ class Comments(Base):
|
|||||||
val = None
|
val = None
|
||||||
return val
|
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):
|
class Text(Base):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
self.sep = self.col_metadata['multiple_seps']
|
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']:
|
if self.col_metadata['is_multiple']:
|
||||||
w = MultiCompleteLineEdit(parent)
|
w = MultipleWidget(parent)
|
||||||
w.set_separator(self.sep['ui_to_list'])
|
w.set_separator(self.sep['ui_to_list'])
|
||||||
if self.sep['ui_to_list'] == '&':
|
if self.sep['ui_to_list'] == '&':
|
||||||
w.set_space_before_sep(True)
|
w.set_space_before_sep(True)
|
||||||
w.set_add_separator(tweaks['authors_completer_append_separator'])
|
w.set_add_separator(tweaks['authors_completer_append_separator'])
|
||||||
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
w.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
|
||||||
|
w.get_editor_button().clicked.connect(self.edit)
|
||||||
else:
|
else:
|
||||||
w = MultiCompleteComboBox(parent)
|
w = MultiCompleteComboBox(parent)
|
||||||
w.set_separator(None)
|
w.set_separator(None)
|
||||||
@ -248,9 +302,12 @@ class Text(Base):
|
|||||||
def initialize(self, book_id):
|
def initialize(self, book_id):
|
||||||
values = list(self.db.all_custom(num=self.col_id))
|
values = list(self.db.all_custom(num=self.col_id))
|
||||||
values.sort(key=sort_key)
|
values.sort(key=sort_key)
|
||||||
|
self.book_id = book_id
|
||||||
self.widgets[1].clear()
|
self.widgets[1].clear()
|
||||||
self.widgets[1].update_items_cache(values)
|
self.widgets[1].update_items_cache(values)
|
||||||
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
|
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
|
self.initial_val = val
|
||||||
val = self.normalize_db_val(val)
|
val = self.normalize_db_val(val)
|
||||||
|
|
||||||
@ -284,6 +341,31 @@ class Text(Base):
|
|||||||
val = None
|
val = None
|
||||||
return val
|
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):
|
class Series(Base):
|
||||||
|
|
||||||
def setup_ui(self, parent):
|
def setup_ui(self, parent):
|
||||||
|
@ -10,19 +10,26 @@ from calibre.utils.icu import sort_key
|
|||||||
|
|
||||||
class TagEditor(QDialog, Ui_TagEditor):
|
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)
|
QDialog.__init__(self, window)
|
||||||
Ui_TagEditor.__init__(self)
|
Ui_TagEditor.__init__(self)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
self.db = db
|
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
|
self.index = db.row(id_) if id_ is not None else None
|
||||||
if self.index is not 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:
|
else:
|
||||||
tags = []
|
tags = []
|
||||||
if tags:
|
if tags:
|
||||||
tags = [tag.strip() for tag in tags.split(',') if tag.strip()]
|
|
||||||
tags.sort(key=sort_key)
|
tags.sort(key=sort_key)
|
||||||
for tag in tags:
|
for tag in tags:
|
||||||
self.applied_tags.addItem(tag)
|
self.applied_tags.addItem(tag)
|
||||||
@ -31,7 +38,10 @@ class TagEditor(QDialog, Ui_TagEditor):
|
|||||||
|
|
||||||
self.tags = tags
|
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 = list(set(all_tags))
|
||||||
all_tags.sort(key=sort_key)
|
all_tags.sort(key=sort_key)
|
||||||
for tag in all_tags:
|
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_()
|
error_dialog(self, 'No tags selected', 'You must select at least one tag from the list of Available tags.').exec_()
|
||||||
return
|
return
|
||||||
for item in items:
|
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)
|
confirms.append(item)
|
||||||
else:
|
else:
|
||||||
deletes.append(item)
|
deletes.append(item)
|
||||||
@ -73,7 +86,12 @@ class TagEditor(QDialog, Ui_TagEditor):
|
|||||||
deletes += confirms
|
deletes += confirms
|
||||||
|
|
||||||
for item in deletes:
|
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))
|
self.available_tags.takeItem(self.available_tags.row(item))
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,6 +276,37 @@ class CustomColumns(object):
|
|||||||
self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id,))
|
self.conn.execute('DELETE FROM %s WHERE value=?'%lt, (id,))
|
||||||
self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id,))
|
self.conn.execute('DELETE FROM %s WHERE id=?'%table, (id,))
|
||||||
self.conn.commit()
|
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
|
# end convenience methods
|
||||||
|
|
||||||
def get_next_cc_series_num_for(self, series, label=None, num=None):
|
def get_next_cc_series_num_for(self, series, label=None, num=None):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user