Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The documentation editing tool with changes to support it. #2518

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions src/calibre/gui2/dialogs/ff_doc_editor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#!/usr/bin/env python


__license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

'''
Created on 12 Nov 2024

@author: chaley
'''

from qt.core import (QApplication, QCheckBox, QComboBox, QFrame, QLabel, QGridLayout,
QHBoxLayout, QPlainTextEdit, QPushButton, QSize, QTimer)

from calibre.constants import iswindows
from calibre.gui2 import gprefs
from calibre.gui2.widgets2 import Dialog, HTMLDisplay
from calibre.utils.ffml_processor import FFMLProcessor
from calibre.utils.formatter_functions import formatter_functions


class FFDocEditor(Dialog):

def __init__(self, can_copy_back=False, parent=None):
self.ffml = FFMLProcessor()
self.can_copy_back = can_copy_back
self.last_operation = None
super().__init__(title=_('Template function documentation editor'),
name='template_function_doc_editor_dialog', parent=parent)

def sizeHint(self):
return QSize(800, 600)

def set_document_text(self, text):
self.editable_text_widget.setPlainText(text)

def document_text(self):
return self.editable_text_widget.toPlainText()

def copy_text(self):
QApplication.instance().clipboard().setText(self.document_text())

def html_widget(self, layout, row, column):
e = HTMLDisplay()
e.setFrameStyle(QFrame.Shape.Box)
if iswindows:
e.setDefaultStyleSheet('pre { font-family: "Segoe UI Mono", "Consolas", monospace; }')
layout.addWidget(e, row, column, 1, 1)
return e

def text_widget(self, read_only, layout, row, column):
e = QPlainTextEdit()
e.setReadOnly(read_only)
e.setFrameStyle(QFrame.Shape.Box)
layout.addWidget(e, row, column, 1, 1)
return e

def label_widget(self, text, layout, row, column, colspan=None):
e = QLabel(text)
layout.addWidget(e, row, column, 1, colspan if colspan is not None else 1)
return e

def setup_ui(self):
gl = QGridLayout(self)
hl = QHBoxLayout()

so = self.show_original_cb = QCheckBox(_('Show documentation for function'))
so.setChecked(gprefs.get('template_function_doc_editor_show_original', False))
so.stateChanged.connect(self.first_row_checkbox_changed)
hl.addWidget(so)

f = self.functions_box = QComboBox()
self.builtins = formatter_functions().get_builtins()
f.addItem('')
f.addItems(self.builtins.keys())
hl.addWidget(f)
f.currentIndexChanged.connect(self.functions_box_index_changed)

so = self.show_in_english_cb = QCheckBox(_('Show original English'))
so.stateChanged.connect(self.first_row_checkbox_changed)
hl.addWidget(so)

so = self.show_formatted_cb = QCheckBox(_('Show with placeholders replaced'))
so.stateChanged.connect(self.first_row_checkbox_changed)
hl.addWidget(so)

hl.addStretch()
gl.addLayout(hl, 0, 0, 1, 2)

self.original_doc_label = self.label_widget(
_('Raw documentation for the selected function'), gl, 1, 0)
w = self.original_doc_html_label = self.label_widget(
_('Documentation for the selected function in HTML'), gl, 1, 1)
w.setVisible(so.isChecked())
w = self.original_text_widget = self.text_widget(True, gl, 2, 0)
w.setVisible(so.isChecked())
w = self.original_text_result = self.html_widget(gl, 2, 1)
w.setVisible(so.isChecked())

self.label_widget(_('Document being edited'), gl, 3, 0)
l = QHBoxLayout()
l.addWidget(QLabel(_('Document in HTML')))
cb = self.doc_show_formatted_cb = QCheckBox(_('Show with placeholders replaced'))
cb.setToolTip(_('This requires the original function documentation to be visible above'))
cb.stateChanged.connect(self._editable_box_changed)
l.addWidget(cb)
l.addStretch()
gl.addLayout(l, 3, 1)

w = self.editable_text_widget = self.text_widget(False, gl, 4, 0)
w.textChanged.connect(self.editable_box_changed)
self.editable_text_result = self.html_widget(gl, 4, 1)
if self.can_copy_back:
self.label_widget(_('Text will be stored with the saved template/function'), gl, 5, 0)
else:
self.label_widget(_('You must copy the text then paste it where it is needed'), gl, 5, 0, colspan=2)

l = QHBoxLayout()
b = QPushButton(_('&Copy text'))
b.clicked.connect(self.copy_text)
l.addWidget(b)
l.addStretch()
gl.addLayout(l, 6, 0)
gl.addWidget(self.bb, 6, 1)

self.changed_timer = QTimer()
self.fill_in_top_row()

def editable_box_changed(self):
self.changed_timer.stop()
t = self.changed_timer = QTimer()
t.timeout.connect(self._editable_box_changed)
t.setSingleShot(True)
t.setInterval(250)
t.start()

def _editable_box_changed(self):
name = self.functions_box.currentText()
if name and self.doc_show_formatted_cb.isVisible() and self.doc_show_formatted_cb.isChecked():
doc = self.builtins[name].doc
self.editable_text_result.setHtml(
self.ffml.document_to_html(doc.format_again(
self.editable_text_widget.toPlainText()), 'edited text'))
else:
self.editable_text_result.setHtml(
self.ffml.document_to_html(self.editable_text_widget.toPlainText(), 'edited text'))

def fill_in_top_row(self):
to_show = self.show_original_cb.isChecked()
self.original_doc_label.setVisible(to_show)
self.original_doc_html_label.setVisible(to_show)
self.show_in_english_cb.setVisible(to_show)
self.show_formatted_cb.setVisible(to_show)
self.original_text_widget.setVisible(to_show)
self.original_text_result.setVisible(to_show)
if not to_show:
self.doc_show_formatted_cb.setVisible(False)
self._editable_box_changed()
return
name = self.functions_box.currentText()
if name in self.builtins:
doc = self.builtins[name].doc
if not self.can_copy_back:
self.doc_show_formatted_cb.setVisible(True)
if self.show_in_english_cb.isChecked():
html = doc.formatted_english if self.show_formatted_cb.isChecked() else doc.raw_english
self.original_text_widget.setPlainText(doc.raw_english.lstrip())
self.original_text_result.setHtml(self.ffml.document_to_html(html, name))
else:
html = doc.formatted_other if self.show_formatted_cb.isChecked() else doc.raw_other
self.original_text_widget.setPlainText(doc.raw_other.lstrip())
self.original_text_result.setHtml(self.ffml.document_to_html(html, name))
else:
self.original_text_widget.setPlainText('')
self.original_text_result.setHtml(self.ffml.document_to_html('', name))
self.doc_show_formatted_cb.setVisible(False)
self._editable_box_changed()

def first_row_checkbox_changed(self):
gprefs['template_function_doc_editor_show_original'] = self.show_original_cb.isChecked()
self.fill_in_top_row()

def functions_box_index_changed(self, idx):
self.show_original_cb.setChecked(True)
self.fill_in_top_row()

if __name__ == '__main__':
from tempfile import TemporaryDirectory
from calibre.db.legacy import LibraryDatabase
from calibre.gui2 import Application

with TemporaryDirectory() as tdir:
app = Application([])
db = LibraryDatabase(tdir) # needed to load formatter_funcs
d = FFDocEditor(None)
d.exec()
del app
2 changes: 1 addition & 1 deletion src/calibre/gui2/dialogs/template_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def show_all_functions(self):
self.last_operation = self.show_all_functions
result = []
a = result.append
for name in sorted(self.builtins):
for name in sorted(self.builtins, key=sort_key):
a(self.header_line(name))
try:
doc = self.get_doc(self.builtins[name])
Expand Down
19 changes: 19 additions & 0 deletions src/calibre/gui2/preferences/template_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from qt.core import QDialog, QDialogButtonBox

from calibre.gui2 import error_dialog, gprefs, question_dialog, warning_dialog
from calibre.gui2.dialogs.ff_doc_editor import FFDocEditor
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.gui2.preferences import AbortInitialize, ConfigWidgetBase, test_widget
from calibre.gui2.preferences.template_functions_ui import Ui_Form
Expand Down Expand Up @@ -187,6 +188,7 @@ def initialize(self):
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.doc_edit_button.clicked.connect(self.doc_edit_button_clicked)
self.create_button.setEnabled(False)
self.delete_button.setEnabled(False)
self.replace_button.setEnabled(False)
Expand All @@ -206,9 +208,11 @@ def initialize(self):
self.st_delete_button.setEnabled(False)
self.st_replace_button.setEnabled(False)
self.st_test_template_button.setEnabled(False)
self.st_doc_edit_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_doc_edit_button.clicked.connect(self.st_doc_edit_button_clicked)

self.st_current_program_name = ''
self.st_current_program_text = ''
Expand Down Expand Up @@ -239,6 +243,12 @@ def show_only_user_defined_changed(self, state):
def enable_replace_button(self):
self.replace_button.setEnabled(self.delete_button.isEnabled())

def doc_edit_button_clicked(self):
d = FFDocEditor(can_copy_back=True, parent=self)
d.set_document_text(self.documentation.toPlainText())
if d.exec() == QDialog.DialogCode.Accepted:
self.documentation.setPlainText(d.document_text())

def clear_button_clicked(self):
self.build_function_names_box()
self.program.clear()
Expand Down Expand Up @@ -425,6 +435,7 @@ def st_clear_button_clicked(self):
self.template_editor.new_doc.clear()
self.st_create_button.setEnabled(False)
self.st_delete_button.setEnabled(False)
self.st_doc_edit_button.setEnabled(False)

def st_build_function_names_box(self, scroll_to=''):
self.te_name.blockSignals(True)
Expand All @@ -447,6 +458,7 @@ def st_delete_button_clicked(self):
self.changed_signal.emit()
self.st_create_button.setEnabled(True)
self.st_delete_button.setEnabled(False)
self.st_doc_edit_button.setEnabled(False)
self.st_build_function_names_box()
self.te_textbox.setReadOnly(False)
self.st_current_program_name = ''
Expand Down Expand Up @@ -483,6 +495,7 @@ def st_template_name_edited(self, txt):
self.st_delete_button.setEnabled(b)
self.st_test_template_button.setEnabled(b)
self.te_textbox.setReadOnly(False)
self.st_doc_edit_button.setEnabled(True)

def st_function_index_changed(self, idx):
txt = self.te_name.currentText()
Expand Down Expand Up @@ -518,6 +531,12 @@ def st_replace_button_clicked(self):
self.st_delete_button_clicked()
self.st_create_button_clicked(use_name=name)

def st_doc_edit_button_clicked(self):
d = FFDocEditor(can_copy_back=True, parent=self)
d.set_document_text(self.template_editor.new_doc.toPlainText())
if d.exec() == QDialog.DialogCode.Accepted:
self.template_editor.new_doc.setPlainText(d.document_text())

def commit(self):
pref_value = []
for name, cls in iteritems(self.funcs):
Expand Down
72 changes: 61 additions & 11 deletions src/calibre/gui2/preferences/template_functions.ui
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="st_doc_edit_button">
<property name="text">
<string>Doc Editor</string>
</property>
<property name="toolTip">
<string>Open an editor for function documentation</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_22">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="st_test_template_button">
<property name="text">
Expand Down Expand Up @@ -185,17 +208,44 @@ a new related user defined function.&lt;/p&gt;</string>
<widget class="QTextEdit" name="documentation"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>D&amp;ocumentation:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="buddy">
<cstring>documentation</cstring>
</property>
</widget>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>D&amp;ocumentation:</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="buddy">
<cstring>documentation</cstring>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="doc_edit_button">
<property name="text">
<string>Doc Editor</string>
</property>
<property name="toolTip">
<string>Open an editor for function documentation</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>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
Expand Down
2 changes: 1 addition & 1 deletion src/calibre/utils/ffml_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ def parse_document(self, doc, name):
self.document_name = name

node = DocumentNode()
return self._parse_document(node)
return self._parse_document(node) if doc else node

def tree_to_html(self, tree, depth=0):
"""
Expand Down
Loading
Loading