mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-31 14:33:54 -04:00
Merge branch 'master' of https://github.com/cbhaley/calibre
This commit is contained in:
commit
0871ba81aa
@ -7,6 +7,7 @@ __license__ = 'GPL v3'
|
|||||||
|
|
||||||
import json, os, traceback, re
|
import json, os, traceback, re
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
import sys
|
||||||
|
|
||||||
from qt.core import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont,
|
from qt.core import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont,
|
||||||
QApplication, QTextCharFormat, QColor, QCursor,
|
QApplication, QTextCharFormat, QColor, QCursor,
|
||||||
@ -389,6 +390,18 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
self.builtins = (builtin_functions if builtin_functions else
|
self.builtins = (builtin_functions if builtin_functions else
|
||||||
formatter_functions().get_builtins_and_aliases())
|
formatter_functions().get_builtins_and_aliases())
|
||||||
|
|
||||||
|
# Set up the breakpoint bar
|
||||||
|
s = gprefs.get('template_editor_break_on_print', False)
|
||||||
|
self.go_button.setEnabled(s)
|
||||||
|
self.remove_all_button.setEnabled(s)
|
||||||
|
self.set_all_button.setEnabled(s)
|
||||||
|
self.toggle_button.setEnabled(s)
|
||||||
|
self.breakpoint_line_box.setEnabled(s)
|
||||||
|
self.breakpoint_line_box_label.setEnabled(s)
|
||||||
|
self.break_box.setChecked(s)
|
||||||
|
self.break_box.stateChanged.connect(self.break_box_changed)
|
||||||
|
self.go_button.clicked.connect(self.go_button_pressed)
|
||||||
|
|
||||||
# Set up the display table
|
# Set up the display table
|
||||||
self.table_column_widths = None
|
self.table_column_widths = None
|
||||||
try:
|
try:
|
||||||
@ -451,16 +464,6 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
'<a href="{}">{}</a>'.format(
|
'<a href="{}">{}</a>'.format(
|
||||||
localize_user_manual_link('https://manual.calibre-ebook.com/generated/en/template_ref.html'), tt))
|
localize_user_manual_link('https://manual.calibre-ebook.com/generated/en/template_ref.html'), tt))
|
||||||
|
|
||||||
s = gprefs.get('template_editor_break_on_print', False)
|
|
||||||
self.go_button.setEnabled(s)
|
|
||||||
self.remove_all_button.setEnabled(s)
|
|
||||||
self.set_all_button.setEnabled(s)
|
|
||||||
self.toggle_button.setEnabled(s)
|
|
||||||
self.breakpoint_line_box.setEnabled(s)
|
|
||||||
self.breakpoint_line_box_label.setEnabled(s)
|
|
||||||
self.break_box.setChecked(s)
|
|
||||||
self.break_box.stateChanged.connect(self.break_box_changed)
|
|
||||||
self.go_button.clicked.connect(self.go_button_pressed)
|
|
||||||
self.textbox.setFocus()
|
self.textbox.setFocus()
|
||||||
self.set_up_font_boxes()
|
self.set_up_font_boxes()
|
||||||
self.toggle_button.clicked.connect(self.toggle_button_pressed)
|
self.toggle_button.clicked.connect(self.toggle_button_pressed)
|
||||||
@ -546,11 +549,19 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
|
|||||||
for r in range(0, len(mi)):
|
for r in range(0, len(mi)):
|
||||||
w = QLineEdit(tv)
|
w = QLineEdit(tv)
|
||||||
w.setReadOnly(True)
|
w.setReadOnly(True)
|
||||||
|
w.setText(mi[r].title)
|
||||||
tv.setCellWidget(r, 0, w)
|
tv.setCellWidget(r, 0, w)
|
||||||
w = QLineEdit(tv)
|
w = QLineEdit(tv)
|
||||||
w.setReadOnly(True)
|
w.setReadOnly(True)
|
||||||
tv.setCellWidget(r, 1, w)
|
tv.setCellWidget(r, 1, w)
|
||||||
self.display_values('')
|
self.set_waiting_message()
|
||||||
|
|
||||||
|
def set_waiting_message(self):
|
||||||
|
if self.break_box.isChecked():
|
||||||
|
for i in range(len(self.mi)):
|
||||||
|
self.template_value.cellWidget(i, 1).setText('')
|
||||||
|
self.template_value.cellWidget(0, 1).setText(
|
||||||
|
_("*** Breakpoints are enabled. Waiting for the 'Go' button to be pressed"))
|
||||||
|
|
||||||
def show_context_menu(self, point):
|
def show_context_menu(self, point):
|
||||||
m = self.textbox.createStandardContextMenu()
|
m = self.textbox.createStandardContextMenu()
|
||||||
@ -678,6 +689,8 @@ def evaluate(book, context):
|
|||||||
self.breakpoint_line_box_label.setEnabled(new_state != 0)
|
self.breakpoint_line_box_label.setEnabled(new_state != 0)
|
||||||
if new_state == 0:
|
if new_state == 0:
|
||||||
self.display_values(str(self.textbox.toPlainText()))
|
self.display_values(str(self.textbox.toPlainText()))
|
||||||
|
else:
|
||||||
|
self.set_waiting_message()
|
||||||
|
|
||||||
def go_button_pressed(self):
|
def go_button_pressed(self):
|
||||||
self.display_values(str(self.textbox.toPlainText()))
|
self.display_values(str(self.textbox.toPlainText()))
|
||||||
@ -760,14 +773,17 @@ def evaluate(book, context):
|
|||||||
c = app.clipboard()
|
c = app.clipboard()
|
||||||
c.setText(str(self.icon_files.currentText()))
|
c.setText(str(self.icon_files.currentText()))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_python(self):
|
||||||
|
return self.textbox.toPlainText().startswith('python:')
|
||||||
|
|
||||||
def textbox_changed(self):
|
def textbox_changed(self):
|
||||||
cur_text = str(self.textbox.toPlainText())
|
cur_text = str(self.textbox.toPlainText())
|
||||||
if cur_text.startswith('python:'):
|
if self.is_python:
|
||||||
if self.highlighting_gpm is True:
|
if self.highlighting_gpm is True:
|
||||||
self.highlighter.initialize_rules(self.builtins, True)
|
self.highlighter.initialize_rules(self.builtins, True)
|
||||||
self.highlighting_gpm = False
|
self.highlighting_gpm = False
|
||||||
self.break_box.setChecked(False)
|
self.break_box.setEnabled(True)
|
||||||
self.break_box.setEnabled(False)
|
|
||||||
elif not self.highlighting_gpm:
|
elif not self.highlighting_gpm:
|
||||||
self.highlighter.initialize_rules(self.builtins, False)
|
self.highlighter.initialize_rules(self.builtins, False)
|
||||||
self.highlighting_gpm = True
|
self.highlighting_gpm = True
|
||||||
@ -776,8 +792,33 @@ def evaluate(book, context):
|
|||||||
self.last_text = cur_text
|
self.last_text = cur_text
|
||||||
self.highlighter.regenerate_paren_positions()
|
self.highlighter.regenerate_paren_positions()
|
||||||
self.text_cursor_changed()
|
self.text_cursor_changed()
|
||||||
if self.break_box.checkState() == Qt.CheckState.Unchecked:
|
if not self.break_box.isChecked():
|
||||||
self.display_values(cur_text)
|
self.display_values(cur_text)
|
||||||
|
else:
|
||||||
|
self.set_waiting_message()
|
||||||
|
|
||||||
|
def trace_lines(self, frame, event, arg):
|
||||||
|
if event != 'line':
|
||||||
|
return
|
||||||
|
# Only respond to events in the "string" which is the template
|
||||||
|
if frame.f_code.co_filename != '<string>':
|
||||||
|
return
|
||||||
|
# Check that there is a breakpoint at the line
|
||||||
|
if frame.f_lineno not in self.textbox.clicked_line_numbers:
|
||||||
|
return
|
||||||
|
l = self.template_value.selectionModel().selectedRows()
|
||||||
|
mi_to_use = self.mi[0 if len(l) == 0 else l[0].row()]
|
||||||
|
self.break_reporter_dialog = PythonBreakReporter(self, mi_to_use, frame)
|
||||||
|
if not self.break_reporter_dialog.exec():
|
||||||
|
raise StopException()
|
||||||
|
|
||||||
|
def trace_calls(self, frame, event, arg):
|
||||||
|
if event != 'call':
|
||||||
|
return
|
||||||
|
# If this is the "string" file (the template), return the trace_lines function
|
||||||
|
if frame.f_code.co_filename == '<string>':
|
||||||
|
return self.trace_lines
|
||||||
|
return None
|
||||||
|
|
||||||
def display_values(self, txt):
|
def display_values(self, txt):
|
||||||
tv = self.template_value
|
tv = self.template_value
|
||||||
@ -787,6 +828,11 @@ def evaluate(book, context):
|
|||||||
w = tv.cellWidget(r, 0)
|
w = tv.cellWidget(r, 0)
|
||||||
w.setText(mi.title)
|
w.setText(mi.title)
|
||||||
w.setCursorPosition(0)
|
w.setCursorPosition(0)
|
||||||
|
if self.break_box.isChecked() and r == break_on_mi and self.is_python:
|
||||||
|
sys.settrace(self.trace_calls)
|
||||||
|
else:
|
||||||
|
sys.settrace(None)
|
||||||
|
try:
|
||||||
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION:'),
|
v = SafeFormat().safe_format(txt, mi, _('EXCEPTION:'),
|
||||||
mi, global_vars=self.global_vars,
|
mi, global_vars=self.global_vars,
|
||||||
template_functions=self.all_functions,
|
template_functions=self.all_functions,
|
||||||
@ -795,6 +841,8 @@ def evaluate(book, context):
|
|||||||
w = tv.cellWidget(r, 1)
|
w = tv.cellWidget(r, 1)
|
||||||
w.setText(v.translate(translate_table))
|
w.setText(v.translate(translate_table))
|
||||||
w.setCursorPosition(0)
|
w.setCursorPosition(0)
|
||||||
|
finally:
|
||||||
|
sys.settrace(None)
|
||||||
|
|
||||||
def text_cursor_changed(self):
|
def text_cursor_changed(self):
|
||||||
cursor = self.textbox.textCursor()
|
cursor = self.textbox.textCursor()
|
||||||
@ -896,20 +944,21 @@ class BreakReporterItem(QTableWidgetItem):
|
|||||||
|
|
||||||
def __init__(self, txt):
|
def __init__(self, txt):
|
||||||
super().__init__(txt.translate(translate_table) if txt else txt)
|
super().__init__(txt.translate(translate_table) if txt else txt)
|
||||||
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable|Qt.ItemFlag.ItemIsSelectable))
|
self.setFlags(self.flags() & ~(Qt.ItemFlag.ItemIsEditable))
|
||||||
|
|
||||||
|
|
||||||
class BreakReporter(QDialog):
|
class BreakReporterBase(QDialog):
|
||||||
|
|
||||||
def __init__(self, parent, mi, op_label, op_value, locals_, line_number):
|
def setup_ui(self, mi, line_number, locals_, leading_rows):
|
||||||
super().__init__(parent)
|
|
||||||
self.mi = mi
|
self.mi = mi
|
||||||
|
self.leading_rows = leading_rows
|
||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
l = QVBoxLayout(self)
|
l = QVBoxLayout(self)
|
||||||
t = self.table = QTableWidget(self)
|
t = self.table = QTableWidget(self)
|
||||||
|
t.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
t.setColumnCount(2)
|
t.setColumnCount(2)
|
||||||
t.setHorizontalHeaderLabels((_('Name'), _('Value')))
|
t.setHorizontalHeaderLabels((_('Name'), _('Value')))
|
||||||
t.setRowCount(2)
|
t.setRowCount(leading_rows)
|
||||||
l.addWidget(t)
|
l.addWidget(t)
|
||||||
|
|
||||||
self.table_column_widths = None
|
self.table_column_widths = None
|
||||||
@ -935,35 +984,34 @@ class BreakReporter(QDialog):
|
|||||||
bb.accepted.connect(self.accept)
|
bb.accepted.connect(self.accept)
|
||||||
bb.rejected.connect(self.reject)
|
bb.rejected.connect(self.reject)
|
||||||
self.setLayout(l)
|
self.setLayout(l)
|
||||||
|
|
||||||
self.setWindowTitle(_('Break: line {0}, book {1}').format(line_number, self.mi.title))
|
self.setWindowTitle(_('Break: line {0}, book {1}').format(line_number, self.mi.title))
|
||||||
|
|
||||||
local_names = sorted(locals_.keys())
|
|
||||||
rows = len(local_names)
|
|
||||||
self.table.setRowCount(rows+2)
|
|
||||||
self.table.setItem(0, 0, BreakReporterItem(op_label))
|
|
||||||
self.table.item(0,0).setToolTip(_('The name of the template language operation'))
|
|
||||||
self.table.setItem(0, 1, BreakReporterItem(op_value))
|
|
||||||
|
|
||||||
self.mi_combo = QComboBox()
|
self.mi_combo = QComboBox()
|
||||||
t.setCellWidget(1, 0, self.mi_combo)
|
t.setCellWidget(leading_rows-1, 0, self.mi_combo)
|
||||||
self.mi_combo.addItems(self.get_field_keys())
|
self.mi_combo.addItems(self.get_field_keys())
|
||||||
self.mi_combo.setToolTip('Choose a book metadata field to display')
|
self.mi_combo.setToolTip('Choose a book metadata field to display')
|
||||||
self.mi_combo.setCurrentIndex(-1)
|
|
||||||
self.mi_combo.currentTextChanged.connect(self.get_field_value)
|
self.mi_combo.currentTextChanged.connect(self.get_field_value)
|
||||||
for i,k in enumerate(local_names):
|
self.mi_combo.setCurrentIndex(self.mi_combo.findText('title'))
|
||||||
itm = BreakReporterItem(k)
|
|
||||||
itm.setToolTip(_('A variable in the template'))
|
|
||||||
self.table.setItem(i+2, 0, itm)
|
|
||||||
itm = BreakReporterItem(locals_[k])
|
|
||||||
itm.setToolTip(_('The value of the variable'))
|
|
||||||
self.table.setItem(i+2, 1, itm)
|
|
||||||
|
|
||||||
self.restore_geometry(gprefs, 'template_editor_break_geometry')
|
self.restore_geometry(gprefs, 'template_editor_break_geometry')
|
||||||
|
self.setup_locals(locals_)
|
||||||
|
|
||||||
|
def setup_locals(self, locals_):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def add_local_line(self, locals, row, key):
|
||||||
|
itm = BreakReporterItem(key)
|
||||||
|
itm.setToolTip(_('A variable in the template'))
|
||||||
|
self.table.setItem(row, 0, itm)
|
||||||
|
itm = BreakReporterItem(repr(locals[key]))
|
||||||
|
itm.setToolTip(_('The value of the variable'))
|
||||||
|
self.table.setItem(row, 1, itm)
|
||||||
|
|
||||||
def get_field_value(self, field):
|
def get_field_value(self, field):
|
||||||
val = self.mi.format_field('timestamp' if field == 'date' else field)[1]
|
val = self.displayable_field_value(self.mi, field)
|
||||||
self.table.setItem(1, 1, BreakReporterItem(val))
|
self.table.setItem(self.leading_rows-1, 1, BreakReporterItem(val))
|
||||||
|
|
||||||
|
def displayable_field_value(self, mi, field):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
def table_column_resized(self, col, old, new):
|
def table_column_resized(self, col, old, new):
|
||||||
self.table_column_widths = []
|
self.table_column_widths = []
|
||||||
@ -992,6 +1040,49 @@ class BreakReporter(QDialog):
|
|||||||
QDialog.accept(self)
|
QDialog.accept(self)
|
||||||
|
|
||||||
|
|
||||||
|
class BreakReporter(BreakReporterBase):
|
||||||
|
|
||||||
|
def __init__(self, parent, mi, op_label, op_value, locals_, line_number):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setup_ui(mi, line_number, locals_, leading_rows=2)
|
||||||
|
self.table.setItem(0, 0, BreakReporterItem(op_label))
|
||||||
|
self.table.item(0,0).setToolTip(_('The name of the template language operation'))
|
||||||
|
self.table.setItem(0, 1, BreakReporterItem(op_value))
|
||||||
|
|
||||||
|
def setup_locals(self, locals):
|
||||||
|
local_names = sorted(locals.keys())
|
||||||
|
rows = len(local_names)
|
||||||
|
self.table.setRowCount(rows+2)
|
||||||
|
for i,k in enumerate(local_names, start=2):
|
||||||
|
self.add_local_line(locals, i, k)
|
||||||
|
|
||||||
|
def displayable_field_value(self, mi, field):
|
||||||
|
return self.mi.format_field('timestamp' if field == 'date' else field)[1]
|
||||||
|
|
||||||
|
|
||||||
|
class PythonBreakReporter(BreakReporterBase):
|
||||||
|
|
||||||
|
def __init__(self, parent, mi, frame):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.frame = frame
|
||||||
|
line_number = frame.f_lineno
|
||||||
|
locals = frame.f_locals
|
||||||
|
self.setup_ui(mi, line_number, locals, leading_rows=1)
|
||||||
|
|
||||||
|
def setup_locals(self, locals):
|
||||||
|
locals = self.frame.f_locals
|
||||||
|
local_names = sorted(k for k in locals.keys() if k not in ('book', 'context'))
|
||||||
|
rows = len(local_names)
|
||||||
|
self.table.setRowCount(rows+1)
|
||||||
|
|
||||||
|
for i,k in enumerate(local_names, start=1):
|
||||||
|
if k in ('book', 'context'): continue
|
||||||
|
self.add_local_line(locals, i, k)
|
||||||
|
|
||||||
|
def displayable_field_value(self, mi, field):
|
||||||
|
return repr(self.mi.get('timestamp' if field == 'date' else field))
|
||||||
|
|
||||||
|
|
||||||
class EmbeddedTemplateDialog(TemplateDialog):
|
class EmbeddedTemplateDialog(TemplateDialog):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
|
@ -1231,10 +1231,12 @@ class _Interpreter:
|
|||||||
if (self.break_reporter):
|
if (self.break_reporter):
|
||||||
self.break_reporter(prog.node_name, res, prog.line_number)
|
self.break_reporter(prog.node_name, res, prog.line_number)
|
||||||
return res
|
return res
|
||||||
|
except StopException:
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
self.error(_("Unknown field '{0}'").format(name), prog.line_number)
|
self.error(_("Unknown field '{0}'").format(name), prog.line_number)
|
||||||
except (StopException, ValueError) as e:
|
except (StopException, ValueError):
|
||||||
raise e
|
raise
|
||||||
except:
|
except:
|
||||||
self.error(_("Unknown field '{0}'").format('internal parse error'),
|
self.error(_("Unknown field '{0}'").format('internal parse error'),
|
||||||
prog.line_number)
|
prog.line_number)
|
||||||
@ -1690,6 +1692,8 @@ class TemplateFormatter(string.Formatter):
|
|||||||
formatter=self,
|
formatter=self,
|
||||||
funcs=self._caller)
|
funcs=self._caller)
|
||||||
rslt = compiled_template(self.book, self.python_context_object)
|
rslt = compiled_template(self.book, self.python_context_object)
|
||||||
|
except StopException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stack = traceback.extract_tb(exc_info()[2])
|
stack = traceback.extract_tb(exc_info()[2])
|
||||||
ss = stack[-1]
|
ss = stack[-1]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user