Here is the action you asked for. It is mostly a copy/paste from single field edit.
Code:
from PyQt5.Qt import (QApplication, Qt, QWidget, QVBoxLayout, QHBoxLayout,
QGroupBox, QComboBox, QPushButton, QRadioButton)
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre_plugins.action_chains.actions.base import ChainAction
from calibre_plugins.action_chains.templates import check_template, TEMPLATE_ERROR
from calibre_plugins.action_chains.templates.dialogs import TemplateBox
def get_possible_cols(db):
fm = db.field_metadata.custom_field_metadata()
cols = [ col for col, cmeta in fm.items() if cmeta['is_multiple'] ]
cols = [ col for col in cols if fm[col]['datatype'] not in ['composite',None] ]
cols.insert(0, 'tags')
return cols
class ItemEditorConfig(QWidget):
def __init__(self, plugin_action):
QWidget.__init__(self)
self.plugin_action = plugin_action
self.gui = plugin_action.gui
self.db = self.gui.current_db
self.possible_cols = self.get_possible_cols()
self.template = ''
self._init_controls()
def _init_controls(self):
self.blockSignals(True)
l = self.l = QVBoxLayout()
self.setLayout(l)
col_box = QGroupBox(_('Choose column:'))
l.addWidget(col_box)
col_box_layout = QVBoxLayout()
col_box.setLayout(col_box_layout)
name_layout = QHBoxLayout()
col_box_layout.addLayout(name_layout)
name_opt = self.name_opt = QRadioButton(_('By column name'))
name_layout.addWidget(name_opt)
name_opt.setChecked(True)
self.col_combobox = QComboBox()
self.col_combobox.addItems(self.possible_cols)
self.col_combobox.setCurrentText('tags')
name_layout.addWidget(self.col_combobox)
highlighted_opt = self.highlighted_opt = QRadioButton(_('Currently highlighted column'))
col_box_layout.addWidget(highlighted_opt)
template_layout = QHBoxLayout()
col_box_layout.addLayout(template_layout)
template_opt = self.template_opt = QRadioButton(_('By template'))
template_layout.addWidget(template_opt)
self.template_button = QPushButton(_('Add template'))
self.template_button.clicked.connect(self._on_template_button_clicked)
template_layout.addWidget(self.template_button)
l.addStretch(1)
self.setMinimumSize(400,200)
self.blockSignals(False)
def get_possible_cols(self):
return get_possible_cols(self.db)
def _on_template_button_clicked(self):
d = TemplateBox(self, self.plugin_action, template_text=self.template)
if d.exec_() == d.Accepted:
self.template = d.template
self.template_button.setText(_('Edit template'))
def load_settings(self, settings):
if settings:
if settings['col_opt'] == 'name':
self.name_opt.setChecked(True)
self.col_combobox.setCurrentText(settings['col_name'])
elif settings['col_opt'] == 'highlighted':
self.highlighted_opt.setChecked(True)
elif settings['col_opt'] == 'template':
self.template_opt.setChecked(True)
self.template = settings['template']
if self.template:
self.template_button.setText('Edit template')
def save_settings(self):
settings = {}
if self.name_opt.isChecked():
settings['col_opt'] = 'name'
settings['col_name'] = self.col_combobox.currentText()
elif self.highlighted_opt.isChecked():
settings['col_opt'] = 'highlighted'
else:
settings['col_opt'] = 'template'
settings['template'] = self.template
return settings
class ItemEditorAction(ChainAction):
name = 'Item Editor'
def run(self, gui, settings, chain):
db = gui.current_db
rows = gui.current_view().selectionModel().selectedRows()
book_ids = [ gui.library_view.model().db.id(row.row()) for row in rows ]
if settings['col_opt'] == 'name':
col_name = settings['col_name']
elif settings['col_opt'] == 'highlighted':
index = gui.library_view.currentIndex()
column_map = gui.library_view.model().column_map
col_name = column_map[index.column()]
else:
col_name = chain.evaluate_template(settings['template'])
if col_name not in get_possible_cols(db):
return
if col_name == 'tags':
key = None
else:
key = col_name
if len(book_ids) == 0:
return
elif len(book_ids) == 1:
book_id = book_ids[0]
else:
book_id = None
d = TagEditor(gui, db, book_id, key)
QApplication.setOverrideCursor(Qt.ArrowCursor)
try:
d.exec_()
finally:
QApplication.restoreOverrideCursor()
if d.result() == d.Accepted:
val = d.tags
id_map = {book_id: val for book_id in book_ids}
db.new_api.set_field(col_name, id_map)
del d
def config_widget(self):
return ItemEditorConfig
def validate(self, settings):
gui = self.plugin_action.gui
db = gui.current_db
if not settings:
return (_('Settings Error'), _('You must configure this action before running it'))
if settings['col_opt'] == 'name':
col_name = settings['col_name']
if not col_name in get_possible_cols(db):
return (_('Column Error'), _('Cannot find a column with name "{}" in current library'.format(col_name)))
elif settings['col_opt'] == 'template':
if not settings.get('template'):
return (_('Empty Template Error'), _('Template for column name cannot be empty'))
is_template_valid = check_template(settings['template'], self.plugin_action, print_error=False)
if is_template_valid is not True:
return is_template_valid
return True
Warning: If you apply this action on multiple selected books, you will lose all preexisting tags on these books, as they will be overwritten by the new ones.
Edit: you will need to configure this action before running it to choose the column.
Edit2: This is not tested. Use at your own risk.