From e08fb5f2312cea68a9b90be935d1e09244d7ab97 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Wed, 30 Sep 2020 22:50:26 +0100 Subject: [PATCH] Commit for testing after making a tabbed window and embedding the template tester --- src/calibre/customize/builtins.py | 24 +- .../gui2/actions/show_stored_templates.py | 10 +- src/calibre/gui2/dialogs/template_dialog.py | 32 +- src/calibre/gui2/dialogs/template_dialog.ui | 97 +++++- .../gui2/preferences/stored_templates.py | 211 ------------ .../gui2/preferences/template_functions.py | 126 ++++++- .../gui2/preferences/template_functions.ui | 310 ++++++++++++------ 7 files changed, 450 insertions(+), 360 deletions(-) delete mode 100644 src/calibre/gui2/preferences/stored_templates.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 7c10ede34d..b542593a79 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -935,10 +935,10 @@ class ActionTemplateTester(InterfaceActionBase): description = _('Show an editor for testing templates') -class ActionStoredTemplates(InterfaceActionBase): - name = 'Stored Templates' - actual_plugin = 'calibre.gui2.actions.show_stored_templates:ShowStoredTemplatesAction' - description = _('Show a dialog for creating and managing stored templates') +class ActionTemplateFunctions(InterfaceActionBase): + name = 'Template Functions' + actual_plugin = 'calibre.gui2.actions.show_stored_templates:ShowTemplateFunctionsAction' + description = _('Show a dialog for creating and managing template functions and stored templates') class ActionSaveToDisk(InterfaceActionBase): @@ -1105,7 +1105,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore, ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy, ActionMarkBooks, ActionEmbed, ActionTemplateTester, ActionTagMapper, ActionAuthorMapper, - ActionVirtualLibrary, ActionBrowseAnnotations, ActionStoredTemplates] + ActionVirtualLibrary, ActionBrowseAnnotations, ActionTemplateFunctions] # }}} @@ -1278,18 +1278,6 @@ class TemplateFunctions(PreferencesPlugin): description = _('Create your own template functions') -class StoredTemplates(PreferencesPlugin): - name = 'StoredTemplates' - icon = I('template_funcs.png') - gui_name = _('Stored templates') - category = 'Advanced' - gui_category = _('Advanced') - category_order = 5 - name_order = 6 - config_widget = 'calibre.gui2.preferences.stored_templates' - description = _('Create stored calibre templates') - - class Email(PreferencesPlugin): name = 'Email' icon = I('mail.png') @@ -1394,7 +1382,7 @@ class Misc(PreferencesPlugin): plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions, CommonOptions, OutputOptions, Adding, Saving, Sending, Plugboard, Email, Server, Plugins, Tweaks, Misc, TemplateFunctions, - StoredTemplates, MetadataSources, Keyboard, IgnoredDevices] + MetadataSources, Keyboard, IgnoredDevices] # }}} diff --git a/src/calibre/gui2/actions/show_stored_templates.py b/src/calibre/gui2/actions/show_stored_templates.py index 9164a7d5ba..33fe1f8d38 100644 --- a/src/calibre/gui2/actions/show_stored_templates.py +++ b/src/calibre/gui2/actions/show_stored_templates.py @@ -11,20 +11,20 @@ from calibre.gui2.actions import InterfaceAction from calibre.gui2.preferences.main import Preferences -class ShowStoredTemplatesAction(InterfaceAction): +class ShowTemplateFunctionsAction(InterfaceAction): - name = 'Stored Template' - action_spec = (_('Stored Templates'), 'debug.png', None, ()) + name = 'Template Functions' + action_spec = (_('Template Functions'), 'debug.png', None, ()) dont_add_to = frozenset(('context-menu-device',)) action_type = 'current' def genesis(self): - self.previous_text = _('Manage stored templates') + self.previous_text = _('Manage template functions') self.first_time = True self.qaction.triggered.connect(self.show_template_editor) def show_template_editor(self, *args): - d = Preferences(self.gui, initial_plugin=('Advanced', 'StoredTemplates'), + d = Preferences(self.gui, initial_plugin=('Advanced', 'TemplateFunctions'), close_after_initial=True) d.exec_() diff --git a/src/calibre/gui2/dialogs/template_dialog.py b/src/calibre/gui2/dialogs/template_dialog.py index 81706fbb40..89d88e3c19 100644 --- a/src/calibre/gui2/dialogs/template_dialog.py +++ b/src/calibre/gui2/dialogs/template_dialog.py @@ -213,7 +213,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): def __init__(self, parent, text, mi=None, fm=None, color_field=None, icon_field_key=None, icon_rule_kind=None, doing_emblem=False, - text_is_placeholder=False): + text_is_placeholder=False, dialog_is_st_editor=False): QDialog.__init__(self, parent) Ui_TemplateDialog.__init__(self) self.setupUi(self) @@ -221,6 +221,7 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.coloring = color_field is not None self.iconing = icon_field_key is not None self.embleming = doing_emblem + self.dialog_is_st_editor = dialog_is_st_editor cols = [] if fm is not None: @@ -273,6 +274,14 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.icon_kind.setCurrentIndex(dex) self.icon_field.setCurrentIndex(self.icon_field.findData(icon_field_key)) + if dialog_is_st_editor: + self.buttonBox.setVisible(False) + else: + self.new_doc_label.setVisible(False) + self.new_doc.setVisible(False) + self.template_name_label.setVisible(False) + self.template_name.setVisible(False) + if mi: self.mi = mi else: @@ -467,6 +476,27 @@ class TemplateDialog(QDialog, Ui_TemplateDialog): self.rule = ('', txt) QDialog.accept(self) + def reject(self): + QDialog.reject(self) + if self.dialog_is_st_editor: + parent = self.parent() + while True: + if hasattr(parent, 'reject'): + parent.reject() + break + parent = parent.parent() + if parent is None: + break + + +class EmbeddedTemplateDialog(TemplateDialog): + + def __init__(self, parent): + TemplateDialog.__init__(self, parent, "Foo", text_is_placeholder=True, + dialog_is_st_editor=True) + self.setParent(parent) + self.setWindowFlags(Qt.Widget) + if __name__ == '__main__': app = QApplication([]) diff --git a/src/calibre/gui2/dialogs/template_dialog.ui b/src/calibre/gui2/dialogs/template_dialog.ui index 9f6d38399c..6c5b45f666 100644 --- a/src/calibre/gui2/dialogs/template_dialog.ui +++ b/src/calibre/gui2/dialogs/template_dialog.ui @@ -141,11 +141,68 @@ - - - + + + + Template &name: + + + template_name + + + + + + + true + + + The name of the callable template + + + + + + + T&emplate: + + + textbox + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + The template program text + + + + + + + D&ocumentation: + + + new_doc + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Documentation for the function being defined or edited + + + @@ -173,7 +230,7 @@ - + @@ -203,14 +260,24 @@ - + QFrame::HLine - + + + + Template Function Reference + + + function + + + + Function &name: @@ -220,10 +287,10 @@ - + - + &Function type: @@ -236,14 +303,14 @@ - + true - + &Documentation: @@ -256,7 +323,7 @@ - + &Code: @@ -269,7 +336,7 @@ - + @@ -279,17 +346,17 @@ - + - + true - + true diff --git a/src/calibre/gui2/preferences/stored_templates.py b/src/calibre/gui2/preferences/stored_templates.py deleted file mode 100644 index e687de142c..0000000000 --- a/src/calibre/gui2/preferences/stored_templates.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai - - -__license__ = 'GPL v3' -__copyright__ = '2010, Kovid Goyal ' -__docformat__ = 'restructuredtext en' - -import traceback - -from PyQt5.Qt import (Qt, QGridLayout, QLabel, QSpacerItem, QSizePolicy, - QComboBox, QTextEdit, QHBoxLayout, QPushButton) - -from calibre.gui2 import error_dialog -from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor -from calibre.gui2.preferences import ConfigWidgetBase, test_widget -from calibre.utils.formatter_functions import (formatter_functions, - compile_user_function, compile_user_template_functions, - load_user_template_functions, function_pref_is_python, - function_pref_name) -from polyglot.builtins import native_string_type, unicode_type - - -class ConfigWidget(ConfigWidgetBase): - - def genesis(self, gui): - self.gui = gui - self.db = gui.library_view.model().db - - help_text = '

' + _(''' - Here you can add and remove stored templates used in template processing. - You use a stored template in another template with the 'call' template - function, as in 'call(somename, arguments...). Stored templates must use - General Program Mode -- they must begin with the text 'program:'. - In the stored template you get the arguments using the 'arguments()' - template function, as in arguments(var1, var2, ...). The calling arguments - are copied to the named variables. - ''') + '

' - l = QGridLayout(self) - w = QLabel(help_text) - w.setWordWrap(True) - l.addWidget(w, 0, 0, 1, 2) - - lab = QLabel(_('&Name')) - l.addWidget(lab, 1, 0) - self.function_name = w = QComboBox(self) - w.setEditable(True) - lab.setBuddy(w) - w.setToolTip(_('The name of the function, used in a call statement')) - l.addWidget(w, 1, 1) - - lab = QLabel(_('&Template')) - l.addWidget(lab, 2, 0) - lb = QHBoxLayout() - self.program = w = TemplateLineEditor(self) - w.setPlaceholderText(_('The GPM template.')) - lab.setBuddy(w) - lb.addWidget(w, stretch=1) - self.editor_button = b = QPushButton(_('&Open template editor')) - b.clicked.connect(w.open_editor) - lb.addWidget(b) - l.addLayout(lb, 2, 1) - - lab = QLabel(_('&Documentation')) - l.addWidget(lab, 3, 0, Qt.AlignTop) - self.documentation = w = QTextEdit(self) - w.setPlaceholderText(_('A description of the template. Whatever you wish ...')) - lab.setBuddy(w) - l.addWidget(w, 3, 1) - - lb = QHBoxLayout() - lb.addStretch(1) - self.clear_button = w = QPushButton(_('C&lear')) - lb.addWidget(w) - self.delete_button = w = QPushButton(_('&Delete')) - lb.addWidget(w) - self.replace_button = w = QPushButton(_('&Replace')) - lb.addWidget(w) - self.create_button = w = QPushButton(_('&Create')) - lb.addWidget(w) - lb.addStretch(1) - l.addLayout(lb, 9, 1) - - l.addItem(QSpacerItem(10, 10, vPolicy=QSizePolicy.Expanding), 10, 0, -1, -1) - self.setLayout(l) - - - def initialize(self): - self.funcs = {} - for v in self.db.prefs.get('user_template_functions', []): - if not function_pref_is_python(v): - self.funcs.update({function_pref_name(v):compile_user_function(*v)}) - - self.build_function_names_box() - self.function_name.currentIndexChanged[native_string_type].connect(self.function_index_changed) - self.function_name.editTextChanged.connect(self.function_name_edited) - self.documentation.textChanged.connect(self.enable_replace_button) - self.program.textChanged.connect(self.enable_replace_button) - self.create_button.clicked.connect(self.create_button_clicked) - self.delete_button.clicked.connect(self.delete_button_clicked) - self.create_button.setEnabled(False) - self.delete_button.setEnabled(False) - self.replace_button.setEnabled(False) - self.clear_button.clicked.connect(self.clear_button_clicked) - self.replace_button.clicked.connect(self.replace_button_clicked) - - def enable_replace_button(self): - self.replace_button.setEnabled(self.delete_button.isEnabled()) - - def clear_button_clicked(self): - self.build_function_names_box() - self.program.clear() - self.documentation.clear() - self.create_button.setEnabled(False) - self.delete_button.setEnabled(False) - - def build_function_names_box(self, scroll_to=''): - self.function_name.blockSignals(True) - func_names = sorted(self.funcs) - self.function_name.clear() - self.function_name.addItem('') - self.function_name.addItems(func_names) - self.function_name.setCurrentIndex(0) - self.function_name.blockSignals(False) - if scroll_to: - idx = self.function_name.findText(scroll_to) - if idx >= 0: - self.function_name.setCurrentIndex(idx) - - def delete_button_clicked(self): - name = unicode_type(self.function_name.currentText()) - if name in self.funcs: - del self.funcs[name] - self.changed_signal.emit() - self.create_button.setEnabled(True) - self.delete_button.setEnabled(False) - self.build_function_names_box() - self.program.setReadOnly(False) - else: - error_dialog(self.gui, _('Stored templates'), - _('Function not defined'), show=True) - - def create_button_clicked(self, use_name=None): - self.changed_signal.emit() - name = use_name if use_name else unicode_type(self.function_name.currentText()) - for k,v in formatter_functions().get_functions().items(): - if k == name and v.is_python: - error_dialog(self.gui, _('Stored templates'), - _('Name %s is already used for template function')%(name,), show=True) - try: - prog = unicode_type(self.program.text()) - if not prog.startswith('program:'): - error_dialog(self.gui, _('Stored templates'), - _('The stored template must begin with "program:"'), show=True) - - cls = compile_user_function(name, unicode_type(self.documentation.toPlainText()), - 0, prog) - self.funcs[name] = cls - self.build_function_names_box(scroll_to=name) - except: - error_dialog(self.gui, _('Stored templates'), - _('Exception while storing template'), show=True, - det_msg=traceback.format_exc()) - - def function_name_edited(self, txt): - self.documentation.setReadOnly(False) - self.create_button.setEnabled(True) - self.replace_button.setEnabled(False) - self.program.setReadOnly(False) - - def function_index_changed(self, txt): - txt = unicode_type(txt) - self.create_button.setEnabled(False) - if not txt: - self.program.clear() - self.documentation.clear() - self.documentation.setReadOnly(False) - return - func = self.funcs[txt] - self.documentation.setText(func.doc) - self.program.setText(func.program_text) - self.delete_button.setEnabled(True) - self.program.setReadOnly(False) - self.replace_button.setEnabled(False) - - def replace_button_clicked(self): - name = unicode_type(self.function_name.currentText()) - self.delete_button_clicked() - self.create_button_clicked(use_name=name) - - def refresh_gui(self, gui): - pass - - def commit(self): - # formatter_functions().reset_to_builtins() - pref_value = [v for v in self.db.prefs.get('user_template_functions', []) - if function_pref_is_python(v)] - for v in self.funcs.values(): - pref_value.append(v.to_pref()) - self.db.new_api.set_pref('user_template_functions', pref_value) - funcs = compile_user_template_functions(pref_value) - self.db.new_api.set_user_template_functions(funcs) - self.gui.library_view.model().refresh() - load_user_template_functions(self.db.library_id, [], funcs) - return False - - -if __name__ == '__main__': - from PyQt5.Qt import QApplication - app = QApplication([]) - test_widget('Advanced', 'StoredTemplates') diff --git a/src/calibre/gui2/preferences/template_functions.py b/src/calibre/gui2/preferences/template_functions.py index f3fb62fa59..2ba88e676e 100644 --- a/src/calibre/gui2/preferences/template_functions.py +++ b/src/calibre/gui2/preferences/template_functions.py @@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en' import json, traceback -from PyQt5.Qt import QDialogButtonBox +from PyQt5.Qt import Qt, QDialogButtonBox, QSizePolicy from calibre.gui2 import error_dialog, warning_dialog from calibre.gui2.preferences import ConfigWidgetBase, test_widget @@ -78,6 +78,17 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):

''') self.textBrowser.setHtml(help_text) + help_text = '

' + _(''' + Here you can add and remove stored templates used in template processing. + You use a stored template in another template with the 'call' template + function, as in 'call(somename, arguments...). Stored templates must use + General Program Mode -- they must begin with the text 'program:'. + In the stored template you get the arguments using the 'arguments()' + template function, as in arguments(var1, var2, ...). The calling arguments + are copied to the named variables. + ''') + '

' + self.st_textBrowser.setHtml(help_text) + self.st_textBrowser.adjustSize() def initialize(self): try: @@ -92,6 +103,11 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.builtins = formatter_functions().get_builtins_and_aliases() + self.st_funcs = {} + for v in self.db.prefs.get('user_template_functions', []): + if not function_pref_is_python(v): + self.st_funcs.update({function_pref_name(v):compile_user_function(*v)}) + self.build_function_names_box() self.function_name.currentIndexChanged[native_string_type].connect(self.function_index_changed) self.function_name.editTextChanged.connect(self.function_name_edited) @@ -108,6 +124,24 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.program.setTabStopWidth(20) self.highlighter = PythonHighlighter(self.program.document()) + 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.template_editor.new_doc.textChanged.connect(self.st_enable_replace_button) + self.template_editor.textbox.textChanged.connect(self.st_enable_replace_button) + 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_clear_button.clicked.connect(self.st_clear_button_clicked) + self.st_replace_button.clicked.connect(self.st_replace_button_clicked) + + self.st_button_layout.insertSpacing(0, 90) + self.template_editor.new_doc.setFixedHeight(50) + + # Python funtion tab + def enable_replace_button(self): self.replace_button.setEnabled(self.delete_button.isEnabled()) @@ -228,12 +262,98 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def refresh_gui(self, gui): pass + # Stored template tab + + def st_enable_replace_button(self): + self.st_replace_button.setEnabled(self.st_delete_button.isEnabled()) + + def st_clear_button_clicked(self): + self.st_build_function_names_box() + self.template_editor.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) + 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) + if scroll_to: + idx = self.template_editor.template_name.findText(scroll_to) + if idx >= 0: + self.template_editor.template_name.setCurrentIndex(idx) + + def st_delete_button_clicked(self): + name = unicode_type(self.template_editor.template_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) + 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()) + for k,v in formatter_functions().get_functions().items(): + if k == name and v.is_python: + error_dialog(self.gui, _('Stored templates'), + _('Name %s is already used for template function')%(name,), show=True) + try: + prog = unicode_type(self.template_editor.textbox.toPlainText()) + if not prog.startswith('program:'): + error_dialog(self.gui, _('Stored templates'), + _('The stored template must begin with "program:"'), show=True) + + cls = compile_user_function(name, unicode_type(self.template_editor.new_doc.toPlainText()), + 0, prog) + self.st_funcs[name] = cls + self.st_build_function_names_box(scroll_to=name) + except: + error_dialog(self.gui, _('Stored templates'), + _('Exception while storing template'), show=True, + det_msg=traceback.format_exc()) + + def st_template_name_edited(self, txt): + self.st_create_button.setEnabled(True) + self.st_replace_button.setEnabled(False) + self.template_editor.textbox.setReadOnly(False) + + def st_function_index_changed(self, txt): + txt = unicode_type(txt) + self.st_create_button.setEnabled(False) + if not txt: + self.template_editor.textbox.clear() + self.template_editor.new_doc.clear() + return + func = self.st_funcs[txt] + self.template_editor.new_doc.setPlainText(func.doc) + self.template_editor.textbox.setPlainText(func.program_text) + self.st_delete_button.setEnabled(True) + self.template_editor.textbox.setReadOnly(False) + self.st_replace_button.setEnabled(False) + + def st_replace_button_clicked(self): + name = unicode_type(self.template_editor.template_name.currentText()) + self.st_delete_button_clicked() + self.st_create_button_clicked(use_name=name) + def commit(self): - pref_value = [v for v in self.db.prefs.get('user_template_functions', []) - if not function_pref_is_python(v)] + pref_value = [] for name, cls in iteritems(self.funcs): + print(name) if name not in self.builtins: pref_value.append(cls.to_pref()) + for v in self.st_funcs.values(): + pref_value.append(v.to_pref()) self.db.new_api.set_pref('user_template_functions', pref_value) funcs = compile_user_template_functions(pref_value) self.db.new_api.set_user_template_functions(funcs) diff --git a/src/calibre/gui2/preferences/template_functions.ui b/src/calibre/gui2/preferences/template_functions.ui index 56111e6831..da04514b69 100644 --- a/src/calibre/gui2/preferences/template_functions.ui +++ b/src/calibre/gui2/preferences/template_functions.ui @@ -6,155 +6,251 @@ 0 0 - 798 - 672 + 788 + 663
Form - - - - Qt::Horizontal + + + + 0 - - - - - - - - - - &Function: - - - function_name - + + + &Stored Templates + + + + + + + - - - - Enter the name of the function to create. - - - true - - - - - - - - - - Argument &count: - - - argument_count - - - - - - - Set this to -1 if the function takes a variable number of arguments - - - -1 - - - - - - - - - - &Documentation: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - documentation - - - - - + + - + &Clear - + - &Delete + D&elete - + &Replace - + C&reate + + + + Qt::Vertical + + + + 20 + 0 + + + + - - - - - - - - &Program code (Follow Python indenting rules): + + + + Qt::Vertical - - program - - - - - - + - 400 - 0 + 20 + 400 - - - - - 30 + + + + + + + &Template Functions + + + + + + Qt::Horizontal + + + + + + + + F&unction: + + + function_name + + + + + + + Enter the name of the function to create. + + + true + + + + + + + + + + Argument &count: + + + argument_count + + + + + + + Set this to -1 if the function takes a variable number of arguments + + + -1 + + + + + + + + + + D&ocumentation: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + documentation + + + + + + + + + Clear + + + + + + + Delete + + + + + + + Replace + + + + + + + C&reate + + + + + + + + + + + + + P&rogram code (Follow Python indenting rules): + + + program + + + + + + + + 400 + 0 + + + + + + + 30 + + + + + + + + + + - - - - - + +
- - - + + + EmbeddedTemplateDialog + TemplateDialog +
calibre/gui2/dialogs/template_dialog.h
+
+ + TemplateDialog + QDialog +
calibre/gui2/dialogs/template_dialog.h
+
+
+ \ No newline at end of file