mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Stored Template preference editor:
1) Add a 'Test' button that opens a subsidiary template tester, permitting testing a stored template. 2) Fix some bugs triggered when editing a stored template.
This commit is contained in:
parent
2086358ec0
commit
f76423e704
@ -246,6 +246,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.global_vars = global_vars
|
||||
|
||||
cols = []
|
||||
self.fm = fm
|
||||
if fm is not None:
|
||||
for key in sorted(displayable_columns(fm),
|
||||
key=lambda k: sort_key(fm[k]['name'] if k != color_row_key else 0)):
|
||||
@ -305,66 +306,6 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.template_name_label.setVisible(False)
|
||||
self.template_name.setVisible(False)
|
||||
|
||||
if mi:
|
||||
if not isinstance(mi, list):
|
||||
mi = (mi, )
|
||||
else:
|
||||
mi = Metadata(_('Title'), [_('Author')])
|
||||
mi.author_sort = _('Author Sort')
|
||||
mi.series = ngettext('Series', 'Series', 1)
|
||||
mi.series_index = 3
|
||||
mi.rating = 4.0
|
||||
mi.tags = [_('Tag 1'), _('Tag 2')]
|
||||
mi.languages = ['eng']
|
||||
mi.id = 1
|
||||
if fm is not None:
|
||||
mi.set_all_user_metadata(fm.custom_field_metadata())
|
||||
else:
|
||||
# No field metadata. Grab a copy from the current library so
|
||||
# that we can validate any custom column names. The values for
|
||||
# the columns will all be empty, which in some very unusual
|
||||
# cases might cause formatter errors. We can live with that.
|
||||
from calibre.gui2.ui import get_gui
|
||||
mi.set_all_user_metadata(
|
||||
get_gui().current_db.new_api.field_metadata.custom_field_metadata())
|
||||
for col in mi.get_all_user_metadata(False):
|
||||
mi.set(col, (col,), 0)
|
||||
mi = (mi, )
|
||||
self.mi = mi
|
||||
|
||||
# Set up the display table
|
||||
self.table_column_widths = None
|
||||
try:
|
||||
self.table_column_widths = \
|
||||
gprefs.get('template_editor_table_widths', None)
|
||||
except:
|
||||
pass
|
||||
tv = self.template_value
|
||||
tv.setRowCount(len(mi))
|
||||
tv.setColumnCount(2)
|
||||
tv.setHorizontalHeaderLabels((_('Book title'), _('Template value')))
|
||||
tv.horizontalHeader().setStretchLastSection(True)
|
||||
tv.horizontalHeader().sectionResized.connect(self.table_column_resized)
|
||||
# Set the height of the table
|
||||
h = tv.rowHeight(0) * min(len(mi), 5)
|
||||
h += 2 * tv.frameWidth() + tv.horizontalHeader().height()
|
||||
tv.setMinimumHeight(h)
|
||||
tv.setMaximumHeight(h)
|
||||
# Set the size of the title column
|
||||
if self.table_column_widths:
|
||||
tv.setColumnWidth(0, self.table_column_widths[0])
|
||||
else:
|
||||
tv.setColumnWidth(0, tv.fontMetrics().averageCharWidth() * 10)
|
||||
# Use our own widget to get rid of elision. setTextElideMode() doesn't work
|
||||
for r in range(0, len(mi)):
|
||||
w = QLineEdit(tv)
|
||||
w.setReadOnly(True)
|
||||
tv.setCellWidget(r, 0, w)
|
||||
w = QLineEdit(tv)
|
||||
w.setReadOnly(True)
|
||||
tv.setCellWidget(r, 1, w)
|
||||
tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
|
||||
# Remove help icon on title bar
|
||||
icon = self.windowIcon()
|
||||
self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint))
|
||||
@ -374,6 +315,15 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.builtins = (builtin_functions if builtin_functions else
|
||||
formatter_functions().get_builtins_and_aliases())
|
||||
|
||||
# Set up the display table
|
||||
self.table_column_widths = None
|
||||
try:
|
||||
self.table_column_widths = \
|
||||
gprefs.get('template_editor_table_widths', None)
|
||||
except:
|
||||
pass
|
||||
self.set_mi(mi, fm)
|
||||
|
||||
self.last_text = ''
|
||||
self.highlighter = TemplateHighlighter(self.textbox.document(), builtin_functions=self.builtins)
|
||||
self.textbox.cursorPositionChanged.connect(self.text_cursor_changed)
|
||||
@ -415,7 +365,6 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
self.function_type_string(f, longform=False)), f)
|
||||
self.function.setCurrentIndex(0)
|
||||
self.function.currentIndexChanged.connect(self.function_changed)
|
||||
self.display_values(text)
|
||||
self.rule = (None, '')
|
||||
|
||||
tt = _('Template language tutorial')
|
||||
@ -457,6 +406,66 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_mi(self, mi, fm):
|
||||
'''
|
||||
This sets the metadata for the test result books table. It doesn't reset
|
||||
the contents of the field selectors for editing rules.
|
||||
'''
|
||||
self.fm = fm
|
||||
if mi:
|
||||
if not isinstance(mi, list):
|
||||
mi = (mi, )
|
||||
else:
|
||||
mi = Metadata(_('Title'), [_('Author')])
|
||||
mi.author_sort = _('Author Sort')
|
||||
mi.series = ngettext('Series', 'Series', 1)
|
||||
mi.series_index = 3
|
||||
mi.rating = 4.0
|
||||
mi.tags = [_('Tag 1'), _('Tag 2')]
|
||||
mi.languages = ['eng']
|
||||
mi.id = 1
|
||||
if self.fm is not None:
|
||||
mi.set_all_user_metadata(self.fm.custom_field_metadata())
|
||||
else:
|
||||
# No field metadata. Grab a copy from the current library so
|
||||
# that we can validate any custom column names. The values for
|
||||
# the columns will all be empty, which in some very unusual
|
||||
# cases might cause formatter errors. We can live with that.
|
||||
from calibre.gui2.ui import get_gui
|
||||
mi.set_all_user_metadata(
|
||||
get_gui().current_db.new_api.field_metadata.custom_field_metadata())
|
||||
for col in mi.get_all_user_metadata(False):
|
||||
mi.set(col, (col,), 0)
|
||||
mi = (mi, )
|
||||
self.mi = mi
|
||||
tv = self.template_value
|
||||
tv.setColumnCount(2)
|
||||
tv.setHorizontalHeaderLabels((_('Book title'), _('Template value')))
|
||||
tv.horizontalHeader().setStretchLastSection(True)
|
||||
tv.horizontalHeader().sectionResized.connect(self.table_column_resized)
|
||||
tv.setRowCount(len(mi))
|
||||
# Set the height of the table
|
||||
h = tv.rowHeight(0) * min(len(mi), 5)
|
||||
h += 2 * tv.frameWidth() + tv.horizontalHeader().height()
|
||||
tv.setMinimumHeight(h)
|
||||
tv.setMaximumHeight(h)
|
||||
# Set the size of the title column
|
||||
if self.table_column_widths:
|
||||
tv.setColumnWidth(0, self.table_column_widths[0])
|
||||
else:
|
||||
tv.setColumnWidth(0, tv.fontMetrics().averageCharWidth() * 10)
|
||||
tv.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
|
||||
tv.setRowCount(len(mi))
|
||||
# Use our own widget to get rid of elision. setTextElideMode() doesn't work
|
||||
for r in range(0, len(mi)):
|
||||
w = QLineEdit(tv)
|
||||
w.setReadOnly(True)
|
||||
tv.setCellWidget(r, 0, w)
|
||||
w = QLineEdit(tv)
|
||||
w.setReadOnly(True)
|
||||
tv.setCellWidget(r, 1, w)
|
||||
self.display_values('')
|
||||
|
||||
def show_context_menu(self, point):
|
||||
m = self.textbox.createStandardContextMenu()
|
||||
m.addSeparator()
|
||||
|
@ -2,11 +2,11 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import traceback
|
||||
from qt.core import QDialogButtonBox
|
||||
import copy, json, traceback
|
||||
from qt.core import QDialogButtonBox, QDialog
|
||||
|
||||
from calibre.gui2 import error_dialog, warning_dialog
|
||||
from calibre.gui2.dialogs.template_dialog import TemplateDialog
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||
from calibre.gui2.preferences.template_functions_ui import Ui_Form
|
||||
from calibre.gui2.widgets import PythonHighlighter
|
||||
@ -121,21 +121,43 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.program.setTabStopWidth(20)
|
||||
self.highlighter = PythonHighlighter(self.program.document())
|
||||
|
||||
self.te_textbox = self.template_editor.textbox
|
||||
self.te_name = self.template_editor.template_name
|
||||
self.st_build_function_names_box()
|
||||
self.template_editor.template_name.currentIndexChanged[native_string_type].connect(self.st_function_index_changed)
|
||||
self.template_editor.template_name.editTextChanged.connect(self.st_template_name_edited)
|
||||
self.te_name.currentIndexChanged[native_string_type].connect(self.st_function_index_changed)
|
||||
self.te_name.editTextChanged.connect(self.st_template_name_edited)
|
||||
self.st_create_button.clicked.connect(self.st_create_button_clicked)
|
||||
self.st_delete_button.clicked.connect(self.st_delete_button_clicked)
|
||||
self.st_create_button.setEnabled(False)
|
||||
self.st_delete_button.setEnabled(False)
|
||||
self.st_replace_button.setEnabled(False)
|
||||
self.st_test_template_button.setEnabled(False)
|
||||
self.st_clear_button.clicked.connect(self.st_clear_button_clicked)
|
||||
self.st_test_template_button.clicked.connect(self.st_test_template)
|
||||
self.st_replace_button.clicked.connect(self.st_replace_button_clicked)
|
||||
|
||||
self.st_current_program_name = ''
|
||||
self.st_current_program_text = ''
|
||||
self.st_previous_text = ''
|
||||
self.st_first_time = False
|
||||
|
||||
self.st_button_layout.insertSpacing(0, 90)
|
||||
self.template_editor.new_doc.setFixedHeight(50)
|
||||
|
||||
# Python funtion tab
|
||||
# get field metadata and selected books
|
||||
view = self.gui.current_view()
|
||||
rows = view.selectionModel().selectedRows()
|
||||
self.mi = []
|
||||
if rows:
|
||||
db = view.model().db
|
||||
self.fm = db.field_metadata
|
||||
for row in rows:
|
||||
if row.isValid():
|
||||
self.mi.append(db.new_api.get_proxy_metadata(db.data.index_to_id(row.row())))
|
||||
|
||||
self.template_editor.set_mi(self.mi[0], self.fm)
|
||||
|
||||
# Python function tab
|
||||
|
||||
def enable_replace_button(self):
|
||||
self.replace_button.setEnabled(self.delete_button.isEnabled())
|
||||
@ -197,7 +219,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
_('Argument count should be -1 or greater than zero. '
|
||||
'Setting it to zero means that this function cannot '
|
||||
'be used in single function mode.'), det_msg='',
|
||||
show=False)
|
||||
show=False, show_copy_button=False)
|
||||
box.bb.setStandardButtons(box.bb.standardButtons() | QDialogButtonBox.StandardButton.Cancel)
|
||||
box.det_msg_toggle.setVisible(False)
|
||||
if not box.exec_():
|
||||
@ -259,48 +281,66 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
# Stored template tab
|
||||
|
||||
def st_test_template(self):
|
||||
if self.mi:
|
||||
self.st_replace_button_clicked()
|
||||
all_funcs = copy.copy(formatter_functions().get_functions())
|
||||
for n,f in self.st_funcs.items():
|
||||
all_funcs[n] = f
|
||||
t = TemplateDialog(self.gui, self.st_previous_text,
|
||||
mi=self.mi, fm=self.fm, text_is_placeholder=self.st_first_time,
|
||||
all_functions=all_funcs)
|
||||
t.setWindowTitle(_('Template tester'))
|
||||
if t.exec_() == QDialog.DialogCode.Accepted:
|
||||
self.st_previous_text = t.rule[1]
|
||||
self.st_first_time = False
|
||||
else:
|
||||
error_dialog(self.gui, _('Template functions'),
|
||||
_('Cannot "test" when no books are selected'), show=True)
|
||||
|
||||
def st_clear_button_clicked(self):
|
||||
self.st_build_function_names_box()
|
||||
self.template_editor.textbox.clear()
|
||||
self.te_textbox.clear()
|
||||
self.template_editor.new_doc.clear()
|
||||
self.st_create_button.setEnabled(False)
|
||||
self.st_delete_button.setEnabled(False)
|
||||
|
||||
def st_build_function_names_box(self, scroll_to=''):
|
||||
self.template_editor.template_name.blockSignals(True)
|
||||
self.te_name.blockSignals(True)
|
||||
func_names = sorted(self.st_funcs)
|
||||
self.template_editor.template_name.clear()
|
||||
self.template_editor.template_name.addItem('')
|
||||
self.template_editor.template_name.addItems(func_names)
|
||||
self.template_editor.template_name.setCurrentIndex(0)
|
||||
self.template_editor.template_name.blockSignals(False)
|
||||
self.te_name.clear()
|
||||
self.te_name.addItem('')
|
||||
self.te_name.addItems(func_names)
|
||||
self.te_name.setCurrentIndex(0)
|
||||
self.te_name.blockSignals(False)
|
||||
if scroll_to:
|
||||
idx = self.template_editor.template_name.findText(scroll_to)
|
||||
idx = self.te_name.findText(scroll_to)
|
||||
if idx >= 0:
|
||||
self.template_editor.template_name.setCurrentIndex(idx)
|
||||
self.te_name.setCurrentIndex(idx)
|
||||
|
||||
def st_delete_button_clicked(self):
|
||||
name = unicode_type(self.template_editor.template_name.currentText())
|
||||
name = unicode_type(self.te_name.currentText())
|
||||
if name in self.st_funcs:
|
||||
del self.st_funcs[name]
|
||||
self.changed_signal.emit()
|
||||
self.st_create_button.setEnabled(True)
|
||||
self.st_delete_button.setEnabled(False)
|
||||
self.st_build_function_names_box()
|
||||
self.template_editor.textbox.setReadOnly(False)
|
||||
self.te_textbox.setReadOnly(False)
|
||||
self.st_current_program_name = ''
|
||||
else:
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('Function not defined'), show=True)
|
||||
|
||||
def st_create_button_clicked(self, use_name=None):
|
||||
self.changed_signal.emit()
|
||||
name = use_name if use_name else unicode_type(self.template_editor.template_name.currentText())
|
||||
name = use_name if use_name else unicode_type(self.te_name.currentText())
|
||||
for k,v in formatter_functions().get_functions().items():
|
||||
if k == name and v.is_python:
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('The name {} is already used for template function').format(name), show=True)
|
||||
try:
|
||||
prog = unicode_type(self.template_editor.textbox.toPlainText())
|
||||
prog = unicode_type(self.te_textbox.toPlainText())
|
||||
if not prog.startswith('program:'):
|
||||
error_dialog(self.gui, _('Stored templates'),
|
||||
_('The stored template must begin with "program:"'), show=True)
|
||||
@ -319,22 +359,40 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
self.st_create_button.setEnabled(not b)
|
||||
self.st_replace_button.setEnabled(b)
|
||||
self.st_delete_button.setEnabled(b)
|
||||
self.template_editor.textbox.setReadOnly(False)
|
||||
self.st_test_template_button.setEnabled(b)
|
||||
self.te_textbox.setReadOnly(False)
|
||||
|
||||
def st_function_index_changed(self, txt):
|
||||
txt = unicode_type(txt)
|
||||
if self.st_current_program_name:
|
||||
if self.st_current_program_text != self.te_textbox.toPlainText():
|
||||
box = warning_dialog(self.gui, _('Template functions'),
|
||||
_('Changes to the current template will be lost. OK?'), det_msg='',
|
||||
show=False, show_copy_button=False)
|
||||
box.bb.setStandardButtons(box.bb.standardButtons() |
|
||||
QDialogButtonBox.StandardButton.Cancel)
|
||||
box.det_msg_toggle.setVisible(False)
|
||||
if not box.exec_():
|
||||
self.te_name.blockSignals(True)
|
||||
dex = self.te_name.findText(self.st_current_program_name)
|
||||
self.te_name.setCurrentIndex(dex)
|
||||
self.te_name.blockSignals(False)
|
||||
return
|
||||
self.st_create_button.setEnabled(False)
|
||||
self.st_current_program_name = txt
|
||||
if not txt:
|
||||
self.template_editor.textbox.clear()
|
||||
self.te_textbox.clear()
|
||||
self.template_editor.new_doc.clear()
|
||||
return
|
||||
func = self.st_funcs[txt]
|
||||
self.st_current_program_text = func.program_text
|
||||
self.template_editor.new_doc.setPlainText(func.doc)
|
||||
self.template_editor.textbox.setPlainText(func.program_text)
|
||||
self.te_textbox.setPlainText(func.program_text)
|
||||
self.st_template_name_edited(txt)
|
||||
|
||||
def st_replace_button_clicked(self):
|
||||
name = unicode_type(self.template_editor.template_name.currentText())
|
||||
name = unicode_type(self.te_name.currentText())
|
||||
self.st_current_program_text = self.te_textbox.toPlainText()
|
||||
self.st_delete_button_clicked()
|
||||
self.st_create_button_clicked(use_name=name)
|
||||
|
||||
|
@ -80,6 +80,29 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="st_test_template_button">
|
||||
<property name="text">
|
||||
<string>Test</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Open a template tester dialog to use a template to test stored templates</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
Loading…
x
Reference in New Issue
Block a user