mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Two improvements to the enum values editor
1) Rename values in books automatically instead of asking. This avoids leaving invalid values in book metadata. 2) Delete values from books when deleted from the permissible values list. This avoids requiring bouncing between this dialog and the manage dialog. 3) Indicate renames with a "Was" column.
This commit is contained in:
parent
5038fb5994
commit
9acf9aed24
@ -17,7 +17,8 @@ from qt.core import (
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from calibre.gui2 import error_dialog, gprefs
|
||||
from calibre.gui2 import error_dialog, gprefs, question_dialog
|
||||
from calibre.utils.icu import lower
|
||||
from calibre.utils.localization import ngettext
|
||||
|
||||
|
||||
@ -35,6 +36,11 @@ class CountTableWidgetItem(QTableWidgetItem):
|
||||
|
||||
class EnumValuesEdit(QDialog):
|
||||
|
||||
VALUE_COLUMN = 0
|
||||
WAS_COLUMN = 1
|
||||
COLOR_COLUMN = 2
|
||||
COUNT_COLUMN = 3
|
||||
|
||||
def __init__(self, parent, db, key):
|
||||
QDialog.__init__(self, parent)
|
||||
|
||||
@ -46,8 +52,8 @@ class EnumValuesEdit(QDialog):
|
||||
bbox.addStretch(10)
|
||||
self.del_button = QToolButton()
|
||||
self.del_button.setIcon(QIcon.ic('trash.png'))
|
||||
self.del_button.setToolTip(_('Remove the currently selected value. Only '
|
||||
'values with a count of zero can be removed'))
|
||||
self.del_button.setToolTip(_('Remove the currently selected value. The '
|
||||
'value will be removed from all books.'))
|
||||
self.ins_button = QToolButton()
|
||||
self.ins_button.setIcon(QIcon.ic('plus.png'))
|
||||
self.ins_button.setToolTip(_('Add a new permissible value'))
|
||||
@ -65,6 +71,7 @@ class EnumValuesEdit(QDialog):
|
||||
bbox.addStretch(10)
|
||||
l.addItem(bbox, 0, 0)
|
||||
|
||||
self.deleted_values = {}
|
||||
self.del_button.clicked.connect(self.del_line)
|
||||
|
||||
self.all_colors = {str(s) for s in list(QColor.colorNames())}
|
||||
@ -72,14 +79,15 @@ class EnumValuesEdit(QDialog):
|
||||
tl = QVBoxLayout()
|
||||
l.addItem(tl, 0, 1)
|
||||
self.table = t = QTableWidget(parent)
|
||||
t.setColumnCount(3)
|
||||
t.setColumnCount(4)
|
||||
t.setRowCount(1)
|
||||
t.setHorizontalHeaderLabels([_('Value'), _('Color'), _('Count')])
|
||||
t.setHorizontalHeaderLabels([_('Value'), _('Was'), _('Color'), _('Count')])
|
||||
t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||
tl.addWidget(t)
|
||||
|
||||
counts = self.db.new_api.get_usage_count_by_id(key)
|
||||
self.name_to_count = {self.db.new_api.get_item_name(key, item_id):count for item_id,count in counts.items()}
|
||||
self.name_to_count = {lower(self.db.new_api.get_item_name(key, item_id)):count
|
||||
for item_id,count in counts.items()}
|
||||
|
||||
self.key = key
|
||||
self.fm = fm = db.field_metadata[key]
|
||||
@ -93,6 +101,7 @@ class EnumValuesEdit(QDialog):
|
||||
c.setCurrentIndex(c.findText(colors[i]))
|
||||
else:
|
||||
c.setCurrentIndex(0)
|
||||
self.make_was_item(i)
|
||||
self.make_count_item(i, v)
|
||||
|
||||
t.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
|
||||
@ -112,10 +121,21 @@ class EnumValuesEdit(QDialog):
|
||||
|
||||
def cell_changed(self, row, col):
|
||||
if col == 0:
|
||||
item = self.table.item(row, 2)
|
||||
if item is not None and self.table.item(row, 0) is not None:
|
||||
count = self.name_to_count.get(self.table.item(row, 0).text())
|
||||
val_item = self.table.item(row, self.VALUE_COLUMN)
|
||||
if val_item is None:
|
||||
return
|
||||
item = self.table.item(row, self.COUNT_COLUMN)
|
||||
if item is not None:
|
||||
count = self.name_to_count.get(lower(self.table.item(row, self.VALUE_COLUMN).text()))
|
||||
item.set_count(count)
|
||||
txt = val_item.text()
|
||||
orig_txt = str(val_item.data(Qt.ItemDataRole.UserRole))
|
||||
was_item = self.table.item(row, self.WAS_COLUMN)
|
||||
if was_item is not None:
|
||||
if txt != orig_txt:
|
||||
was_item.setText(orig_txt)
|
||||
else:
|
||||
was_item.setText('')
|
||||
|
||||
def sizeHint(self):
|
||||
sz = QDialog.sizeHint(self)
|
||||
@ -126,9 +146,8 @@ class EnumValuesEdit(QDialog):
|
||||
def make_name_item(self, row, txt):
|
||||
it = QTableWidgetItem(txt)
|
||||
it.setData(Qt.ItemDataRole.UserRole, txt)
|
||||
it.setCheckState(Qt.CheckState.Unchecked)
|
||||
it.setToolTip('<p>' + _('Check the box if you change the value and want it renamed in books where it is used') + '</p>')
|
||||
self.table.setItem(row, 0, it)
|
||||
it.setToolTip(_('Changing the value will rename it in all books'))
|
||||
self.table.setItem(row, self.VALUE_COLUMN, it)
|
||||
|
||||
def make_color_combobox(self, row, dex):
|
||||
c = QComboBox(self)
|
||||
@ -136,14 +155,18 @@ class EnumValuesEdit(QDialog):
|
||||
c.addItems(QColor.colorNames())
|
||||
c.setToolTip('<p>' + _('Selects the color of the text when displayed in the book list. '
|
||||
'Either all rows must have a color or no rows have a color') + '</p>')
|
||||
self.table.setCellWidget(row, 1, c)
|
||||
self.table.setCellWidget(row, self.COLOR_COLUMN, c)
|
||||
if dex >= 0:
|
||||
c.setCurrentIndex(dex)
|
||||
return c
|
||||
|
||||
def make_was_item(self, row):
|
||||
it = QTableWidgetItem('')
|
||||
self.table.setItem(row, self.WAS_COLUMN, it)
|
||||
|
||||
def make_count_item(self, row, txt):
|
||||
it = CountTableWidgetItem(self.name_to_count.get(txt))
|
||||
self.table.setItem(row, 2, it)
|
||||
it = CountTableWidgetItem(self.name_to_count.get(lower(txt)))
|
||||
self.table.setItem(row, self.COUNT_COLUMN, it)
|
||||
|
||||
def move_up_clicked(self):
|
||||
row = self.table.currentRow()
|
||||
@ -156,15 +179,17 @@ class EnumValuesEdit(QDialog):
|
||||
self.move_row(row, -1)
|
||||
|
||||
def move_row(self, row, direction):
|
||||
t = self.table.takeItem(row, 0)
|
||||
c = self.table.cellWidget(row, 1).currentIndex()
|
||||
count = self.table.takeItem(row, 2)
|
||||
t = self.table.takeItem(row, self.VALUE_COLUMN)
|
||||
c = self.table.cellWidget(row, self.COLOR_COLUMN).currentIndex()
|
||||
was = self.table.takeItem(row, self.WAS_COLUMN)
|
||||
count = self.table.takeItem(row, self.COUNT_COLUMN)
|
||||
self.table.removeRow(row)
|
||||
row += direction
|
||||
self.table.insertRow(row)
|
||||
self.table.setItem(row, 0, t)
|
||||
self.table.setItem(row, self.VALUE_COLUMN, t)
|
||||
self.make_color_combobox(row, c)
|
||||
self.table.setItem(row, 2, count)
|
||||
self.table.setItem(row, self.WAS_COLUMN, was)
|
||||
self.table.setItem(row, self.COUNT_COLUMN, count)
|
||||
self.table.setCurrentCell(row, 0)
|
||||
|
||||
def move_down_clicked(self):
|
||||
@ -180,16 +205,17 @@ class EnumValuesEdit(QDialog):
|
||||
def del_line(self):
|
||||
row = self.table.currentRow()
|
||||
if row >= 0:
|
||||
txt = self.table.item(row, 0).text()
|
||||
count = self.name_to_count.get(txt, 0)
|
||||
txt = self.table.item(row, self.VALUE_COLUMN).text()
|
||||
count = self.name_to_count.get(lower(txt), 0)
|
||||
if count > 0:
|
||||
error_dialog(self,
|
||||
_('Cannot remove value "{}"').format(txt),
|
||||
ngettext('The value "{0}" is used in {1} book and cannot be removed.',
|
||||
'The value "{0}" is used in {1} books and cannot be removed.',
|
||||
count).format(txt, count),
|
||||
show=True)
|
||||
return
|
||||
r = question_dialog(self,
|
||||
_('Value "{}" is used').format(txt),
|
||||
ngettext('The value "{0}" is used in {1} book. Do you really want to remove it?',
|
||||
'The value "{0}" is used in {1} books. Do you really want to remove it?',
|
||||
count).format(txt, count))
|
||||
if r != QDialog.DialogCode.Accepted:
|
||||
return
|
||||
self.deleted_values[lower(txt)] = txt
|
||||
self.table.removeRow(self.table.currentRow())
|
||||
|
||||
def ins_button_clicked(self):
|
||||
@ -201,6 +227,7 @@ class EnumValuesEdit(QDialog):
|
||||
self.table.insertRow(row)
|
||||
self.make_name_item(row, '')
|
||||
self.make_color_combobox(row, -1)
|
||||
self.make_was_item(row)
|
||||
self.make_count_item(row, '')
|
||||
|
||||
def save_geometry(self):
|
||||
@ -212,21 +239,20 @@ class EnumValuesEdit(QDialog):
|
||||
colors = []
|
||||
id_map = {}
|
||||
for i in range(0, self.table.rowCount()):
|
||||
it = self.table.item(i, 0)
|
||||
it = self.table.item(i, self.VALUE_COLUMN)
|
||||
v = str(it.text())
|
||||
if not v:
|
||||
error_dialog(self, _('Empty value'),
|
||||
_('Empty values are not allowed'), show=True)
|
||||
return
|
||||
ov = str(it.data(Qt.ItemDataRole.UserRole))
|
||||
if v != ov and it.checkState() == Qt.CheckState.Checked:
|
||||
if v != ov:
|
||||
fid = self.db.new_api.get_item_id(self.key, ov)
|
||||
id_map[fid] = v
|
||||
values.append(v)
|
||||
c = str(self.table.cellWidget(i, 1).currentText())
|
||||
c = str(self.table.cellWidget(i, self.COLOR_COLUMN).currentText())
|
||||
if c:
|
||||
colors.append(c)
|
||||
|
||||
l_lower = [v.lower() for v in values]
|
||||
for i,v in enumerate(l_lower):
|
||||
if v in l_lower[i+1:]:
|
||||
@ -241,6 +267,22 @@ class EnumValuesEdit(QDialog):
|
||||
'Either all values or no values must have colors'), show=True)
|
||||
return
|
||||
|
||||
# Process deleted values. It is possible that a value was deleted then
|
||||
# added back, possibly with a different case. If the case is the same then
|
||||
# don't delete it. If the case is different then add it to the rename dict.
|
||||
for v in values:
|
||||
dv = self.deleted_values.get(lower(v))
|
||||
if dv is None:
|
||||
continue
|
||||
self.deleted_values.pop(lower(v))
|
||||
if v != dv:
|
||||
fid = self.db.new_api.get_item_id(self.key, dv)
|
||||
id_map[fid] = v
|
||||
|
||||
ids_to_delete = (self.db.new_api.get_item_id(self.key, v) for v in self.deleted_values.values())
|
||||
if ids_to_delete:
|
||||
self.db.new_api.remove_items(self.key, ids_to_delete)
|
||||
|
||||
disp['enum_values'] = values
|
||||
disp['enum_colors'] = colors
|
||||
self.db.set_custom_column_metadata(self.fm['colnum'], display=disp,
|
||||
|
Loading…
x
Reference in New Issue
Block a user