From 2e0a45737a99ae2c2d204c06a84427eb329e8dec Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 27 Apr 2023 11:20:23 +0100 Subject: [PATCH 01/11] Two related changes: 1) Make F2 on a markdown column in the booklist use the new markdown dialog. 2) Fix some problems in the markdown syntax highlighter that threw exceptions. --- src/calibre/gui2/library/delegates.py | 14 ++++- src/calibre/gui2/markdown_editor.py | 59 +++++++++++++++++-- .../gui2/markdown_syntax_highlighter.py | 8 +-- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/library/delegates.py b/src/calibre/gui2/library/delegates.py index eb9ac48bf1..eda62deb9e 100644 --- a/src/calibre/gui2/library/delegates.py +++ b/src/calibre/gui2/library/delegates.py @@ -5,16 +5,17 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys +import os, sys from qt.core import (Qt, QApplication, QStyle, QIcon, QDoubleSpinBox, QStyleOptionViewItem, - QSpinBox, QStyledItemDelegate, QComboBox, QTextDocument, QMenu, QKeySequence, + QSpinBox, QStyledItemDelegate, QComboBox, QTextDocument, QMenu, QKeySequence, QUrl, QAbstractTextDocumentLayout, QFont, QFontInfo, QDate, QDateTimeEdit, QDateTime, QEvent, QStyleOptionComboBox, QStyleOptionSpinBox, QLocale, QSize, QLineEdit, QDialog, QPalette) from calibre.ebooks.metadata import rating_to_stars, title_sort from calibre.gui2 import UNDEFINED_QDATETIME, rating_font, gprefs from calibre.constants import iswindows +from calibre.gui2.markdown_editor import MarkdownEditDialog from calibre.gui2.widgets import EnLineEdit from calibre.gui2.widgets2 import populate_standard_spinbox_context_menu, RatingEditor, DateTimeEdit as DateTimeEditBase from calibre.gui2.complete2 import EditWithComplete @@ -534,7 +535,14 @@ class CcLongTextDelegate(QStyledItemDelegate): # {{{ text = '' else: text = m.db.data[index.row()][m.custom_columns[col]['rec_index']] - d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name']) + column_format = m.custom_columns[m.column_map[index.column()]]['display'].get('interpret_as') + if column_format == 'markdown': + path = m.db.abspath(index.row(), index_is_id=False) + base_url = QUrl.fromLocalFile(os.path.join(path, 'metadata.html')) if path else None + d = MarkdownEditDialog(parent, text, column_name=m.custom_columns[col]['name'], + base_url=base_url) + else: + d = PlainTextDialog(parent, text, column_name=m.custom_columns[col]['name']) if d.exec() == QDialog.DialogCode.Accepted: m.setData(index, d.text, Qt.ItemDataRole.EditRole) return None diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py index 50dc91e608..efea26fd75 100644 --- a/src/calibre/gui2/markdown_editor.py +++ b/src/calibre/gui2/markdown_editor.py @@ -3,13 +3,14 @@ import os from qt.core import ( - QPlainTextEdit, Qt, QTabWidget, QUrl, QVBoxLayout, QWidget, pyqtSignal, + QDialog, QDialogButtonBox, QPlainTextEdit, QSize, Qt, QTabWidget, QUrl, + QVBoxLayout, QWidget, pyqtSignal, ) -from calibre.gui2 import safe_open_url +from calibre.gui2 import safe_open_url, gprefs from calibre.gui2.book_details import css from calibre.gui2.widgets2 import HTMLDisplay -from calibre.library.comments import markdown +from calibre.library.comments import markdown as get_markdown class Preview(HTMLDisplay): @@ -42,6 +43,56 @@ class MarkdownEdit(QPlainTextEdit): m.exec(ev.globalPos()) +class MarkdownEditDialog(QDialog): + + def __init__(self, parent, text, column_name=None, base_url=None): + QDialog.__init__(self, parent) + self.setObjectName("MarkdownEditDialog") + self.setWindowTitle(_("Edit markdown")) + self.verticalLayout = l = QVBoxLayout(self) + self.textbox = editor = Editor(self) + editor.set_base_url(base_url) + self.buttonBox = bb = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, self) + bb.accepted.connect(self.accept) + bb.rejected.connect(self.reject) + l.addWidget(editor) + l.addWidget(bb) + # Remove help icon on title bar + icon = self.windowIcon() + self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint)) + self.setWindowIcon(icon) + + self.textbox.markdown =text + # self.textbox.wyswyg_dirtied() + + if column_name: + self.setWindowTitle(_('Edit "{0}"').format(column_name)) + self.restore_geometry(gprefs, 'markdown_edit_dialog_geom') + + def sizeHint(self): + return QSize(650, 600) + + def accept(self): + self.save_geometry(gprefs, 'markdown_edit_dialog_geom') + QDialog.accept(self) + + def reject(self): + self.save_geometry(gprefs, 'markdown_edit_dialog_geom') + QDialog.reject(self) + + def closeEvent(self, ev): + self.save_geometry(gprefs, 'markdown_edit_dialog_geom') + return QDialog.closeEvent(self, ev) + + @property + def text(self): + return self.textbox.markdown + + @text.setter + def text(self, val): + self.textbox.markdown = val or '' + + class Editor(QWidget): # {{{ def __init__(self, parent=None): @@ -90,7 +141,7 @@ class Editor(QWidget): # {{{ self.update_preview() def update_preview(self): - html = markdown(self.editor.toPlainText().strip()) + html = get_markdown(self.editor.toPlainText().strip()) val = f'''\ diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index 821c20ac65..eb5bffdaaa 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -237,13 +237,13 @@ class MarkdownHighlighter(QSyntaxHighlighter): prevAscii = str(prev.replace('\u2029','\n')) if prevAscii.strip(): #print "Its a header" - prevCursor.select(QTextCursor.LineUnderCursor) + prevCursor.select(QTextCursor.SelectionType.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] formatRange.length = prevCursor.block().length() formatRange.start = 0 - prevCursor.block().layout().setAdditionalFormats([formatRange]) + prevCursor.block().layout().setFormats([formatRange]) self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text): @@ -253,13 +253,13 @@ class MarkdownHighlighter(QSyntaxHighlighter): prevAscii = str(prev.replace('\u2029','\n')) if prevAscii.strip(): #print "Its a header" - prevCursor.select(QTextCursor.LineUnderCursor) + prevCursor.select(QTextCursor.SelectionType.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] formatRange.length = prevCursor.block().length() formatRange.start = 0 - prevCursor.block().layout().setAdditionalFormats([formatRange]) + prevCursor.block().layout().setFormats([formatRange]) self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) return found From a0739105047c0d5ba819674e3431db6f88c7c8a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 27 Apr 2023 14:58:39 +0530 Subject: [PATCH 02/11] Use an encoded URL for the data files link --- src/calibre/gui2/book_details.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 9fb46360c7..afd60a3080 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -15,7 +15,7 @@ from qt.core import ( from calibre import fit_image, sanitize_file_name from calibre.constants import config_dir, iswindows -from calibre.db.constants import DATA_DIR_NAME +from calibre.db.constants import DATA_DIR_NAME, DATA_FILE_PATTERN from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.book.base import Metadata, field_metadata from calibre.ebooks.metadata.book.render import mi_to_html @@ -487,12 +487,11 @@ def create_copy_links(menu, data=None): link(_('Link to show book in calibre'), f'calibre://show-book/{library_id}/{book_id}') link(_('Link to show book details in a popup window'), f'calibre://book-details/{library_id}/{book_id}') mi = db.new_api.get_proxy_metadata(book_id) - data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME) - with suppress(OSError): - if os.listdir(data_path): - if iswindows: - data_path = '/' + data_path.replace('\\', '/') - link(_("Link to open book's data files folder"), 'file://' + data_path) + with suppress(Exception): + data_files = db.new_api.list_extra_files(book_id, use_cache=True, pattern=DATA_FILE_PATTERN) + if data_files: + data_path = os.path.join(db.backend.library_path, mi.path, DATA_DIR_NAME) + link(_("Link to open book's data files folder"), bytes(QUrl.fromLocalFile(data_path).toEncoded()).decode('utf-8')) if data: field = data.get('field') if data['type'] == 'author': From 685f8310accbd96415efb32b31d6b009941a1e0b Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:54:23 +0200 Subject: [PATCH 03/11] use a map to init the highlighter theme --- .../gui2/markdown_syntax_highlighter.py | 126 +++++------------- 1 file changed, 31 insertions(+), 95 deletions(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index eb5bffdaaa..b6711fe182 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -34,6 +34,27 @@ class MarkdownHighlighter(QSyntaxHighlighter): 'Html': re.compile('<.+?>') } + key_theme_maps = { + 'Bold': "bold", + 'uBold': "bold", + 'Italic': "emphasis", + 'uItalic': "emphasis", + 'Link': "link", + 'Image': "image", + 'HeaderAtx': "header", + 'Header': "header", + 'CodeBlock': "codeblock", + 'UnorderedList': "unorderedlist", + 'UnorderedListStar': "unorderedlist", + 'OrderedList': "orderedlist", + 'BlockQuote': "blockquote", + 'BlockQuoteCount': "blockquote", + 'CodeSpan': "codespan", + 'HR': "line", + 'eHR': "line", + 'Html': "html", + } + light_theme = { "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"}, "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"}, @@ -73,101 +94,16 @@ class MarkdownHighlighter(QSyntaxHighlighter): self.theme = theme self.MARKDOWN_KWS_FORMAT = {} - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['bold']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['bold']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['Bold'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['bold']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['bold']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['bold']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['uBold'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['emphasis']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['Italic'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['emphasis']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['emphasis']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['emphasis']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['uItalic'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['link']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['link']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['link']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['Link'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['image']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['image']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['image']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['Image'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['header']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['header']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['Header'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['header']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['header']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['header']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['HeaderAtx'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['unorderedlist']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['unorderedlist']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['unorderedlist']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['UnorderedList'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['orderedlist']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['orderedlist']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['orderedlist']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['OrderedList'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['blockquote']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['blockquote']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['blockquote']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['BlockQuote'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['codespan']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['codespan']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['codespan']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['CodeSpan'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['codeblock']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['codeblock']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['codeblock']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['CodeBlock'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['line']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['line']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['HR'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['line']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['line']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['line']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['eHR'] = format - - format = QTextCharFormat() - format.setForeground(QBrush(QColor(theme['html']['color']))) - format.setFontWeight(QFont.Weight.Bold if theme['html']['font-weight']=='bold' else QFont.Weight.Normal) - format.setFontItalic(True if theme['html']['font-style']=='italic' else False) - self.MARKDOWN_KWS_FORMAT['HTML'] = format + for k,t in self.key_theme_maps.items(): + subtheme = theme[t] + format = QTextCharFormat() + if 'color' in subtheme: + format.setForeground(QBrush(QColor(subtheme['color']))) + if 'font-weight' in subtheme: + format.setFontWeight(QFont.Weight.Bold if subtheme['font-weight']=='bold' else QFont.Weight.Normal) + if 'font-style' in subtheme: + format.setFontItalic(True if subtheme['font-style']=='italic' else False) + self.MARKDOWN_KWS_FORMAT[k] = format self.rehighlight() From 6fa9d1c9e119376523f7a891b90cd9c75284f989 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:55:15 +0200 Subject: [PATCH 04/11] ... --- src/calibre/gui2/markdown_syntax_highlighter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index b6711fe182..f9f1a56ac5 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -278,4 +278,4 @@ class MarkdownHighlighter(QSyntaxHighlighter): def highlightHtml(self, text): for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Html'], text): - self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HTML']) + self.setFormat(mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['Html']) From 80dace9d4349cf588660c40599bd70f97ab420fe Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 13:57:44 +0200 Subject: [PATCH 05/11] basic bold and emphasis --- src/calibre/gui2/markdown_syntax_highlighter.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index f9f1a56ac5..613f932fee 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -56,8 +56,8 @@ class MarkdownHighlighter(QSyntaxHighlighter): } light_theme = { - "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"}, - "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"}, + "bold": {"font-weight":"bold"}, + "emphasis": {"font-style":"italic"}, "link": {"color":light_link_color.name(), "font-weight":"normal", "font-style":"normal"}, "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"}, "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"}, @@ -71,8 +71,8 @@ class MarkdownHighlighter(QSyntaxHighlighter): } dark_theme = { - "bold": {"color":"#859900", "font-weight":"bold", "font-style":"normal"}, - "emphasis": {"color":"#b58900", "font-weight":"bold", "font-style":"italic"}, + "bold": {"font-weight":"bold"}, + "emphasis": {"font-style":"italic"}, "link": {"color":dark_link_color.name(), "font-weight":"normal", "font-style":"normal"}, "image": {"color":"#cb4b16", "font-weight":"normal", "font-style":"normal"}, "header": {"color":"#2aa198", "font-weight":"bold", "font-style":"normal"}, From e18950ea04d03e2d415c9ff6cd9252cd505a776b Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 14:19:48 +0200 Subject: [PATCH 06/11] improve HorizontalLine/HeaderLine --- .../gui2/markdown_syntax_highlighter.py | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index 613f932fee..755d69772a 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -29,8 +29,8 @@ class MarkdownHighlighter(QSyntaxHighlighter): 'BlockQuote': re.compile(r'(?u)^\s*>+\s*'), 'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'), 'CodeSpan': re.compile('(?P`+).+?(?P=delim)'), - 'HR': re.compile(r'(?u)^(\s*(\*|-)\s*){3,}$'), - 'eHR': re.compile(r'(?u)^(\s*(\*|=)\s*){3,}$'), + 'HeaderLine': re.compile(r'(?u)^(\s*(-|=)\s*){3,}$'), + 'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'), 'Html': re.compile('<.+?>') } @@ -43,6 +43,7 @@ class MarkdownHighlighter(QSyntaxHighlighter): 'Image': "image", 'HeaderAtx': "header", 'Header': "header", + 'HeaderLine': "header", 'CodeBlock': "codeblock", 'UnorderedList': "unorderedlist", 'UnorderedListStar': "unorderedlist", @@ -51,7 +52,6 @@ class MarkdownHighlighter(QSyntaxHighlighter): 'BlockQuoteCount': "blockquote", 'CodeSpan': "codespan", 'HR': "line", - 'eHR': "line", 'Html': "html", } @@ -166,23 +166,8 @@ class MarkdownHighlighter(QSyntaxHighlighter): def highlightHorizontalLine(self, text, cursor, bf, strt): found = False - for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text): - prevBlock = self.currentBlock().previous() - prevCursor = QTextCursor(prevBlock) - prev = prevBlock.text() - prevAscii = str(prev.replace('\u2029','\n')) - if prevAscii.strip(): - #print "Its a header" - prevCursor.select(QTextCursor.SelectionType.LineUnderCursor) - #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) - formatRange = QTextLayout.FormatRange() - formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] - formatRange.length = prevCursor.block().length() - formatRange.start = 0 - prevCursor.block().layout().setFormats([formatRange]) - self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) - for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['eHR'],text): + for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HeaderLine'],text): prevBlock = self.currentBlock().previous() prevCursor = QTextCursor(prevBlock) prev = prevBlock.text() @@ -192,11 +177,16 @@ class MarkdownHighlighter(QSyntaxHighlighter): prevCursor.select(QTextCursor.SelectionType.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() - formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] + formatRange.format = self.MARKDOWN_KWS_FORMAT['HeaderLine'] formatRange.length = prevCursor.block().length() formatRange.start = 0 prevCursor.block().layout().setFormats([formatRange]) + self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HeaderLine']) + return True + + for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['HR'],text): self.setFormat(mo.start()+strt, mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['HR']) + found = True return found def highlightAtxHeader(self, text, cursor, bf, strt): From 6509d584205399248e276eefc12532f086635926 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 14:25:10 +0200 Subject: [PATCH 07/11] fix HeaderLine to match with Markdown result --- src/calibre/gui2/markdown_syntax_highlighter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index 755d69772a..094e09f62e 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -29,7 +29,7 @@ class MarkdownHighlighter(QSyntaxHighlighter): 'BlockQuote': re.compile(r'(?u)^\s*>+\s*'), 'BlockQuoteCount': re.compile('^[ \t]*>[ \t]?'), 'CodeSpan': re.compile('(?P`+).+?(?P=delim)'), - 'HeaderLine': re.compile(r'(?u)^(\s*(-|=)\s*){3,}$'), + 'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'), 'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'), 'Html': re.compile('<.+?>') } From 6c98af26acc26b4ab269036be3d72ff4e17e7919 Mon Sep 17 00:00:00 2001 From: un-pogaz <46523284+un-pogaz@users.noreply.github.com> Date: Thu, 27 Apr 2023 14:35:44 +0200 Subject: [PATCH 08/11] ... --- src/calibre/gui2/markdown_syntax_highlighter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index 094e09f62e..f62c714588 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -177,7 +177,7 @@ class MarkdownHighlighter(QSyntaxHighlighter): prevCursor.select(QTextCursor.SelectionType.LineUnderCursor) #prevCursor.setCharFormat(self.MARKDOWN_KWS_FORMAT['Header']) formatRange = QTextLayout.FormatRange() - formatRange.format = self.MARKDOWN_KWS_FORMAT['HeaderLine'] + formatRange.format = self.MARKDOWN_KWS_FORMAT['Header'] formatRange.length = prevCursor.block().length() formatRange.start = 0 prevCursor.block().layout().setFormats([formatRange]) From 400bc9be54eaed1ea3470ed48d8dcc6856e53cac Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 27 Apr 2023 19:55:49 +0530 Subject: [PATCH 09/11] ... --- src/calibre/gui2/markdown_syntax_highlighter.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py index f62c714588..7079ac6185 100644 --- a/src/calibre/gui2/markdown_syntax_highlighter.py +++ b/src/calibre/gui2/markdown_syntax_highlighter.py @@ -99,10 +99,8 @@ class MarkdownHighlighter(QSyntaxHighlighter): format = QTextCharFormat() if 'color' in subtheme: format.setForeground(QBrush(QColor(subtheme['color']))) - if 'font-weight' in subtheme: - format.setFontWeight(QFont.Weight.Bold if subtheme['font-weight']=='bold' else QFont.Weight.Normal) - if 'font-style' in subtheme: - format.setFontItalic(True if subtheme['font-style']=='italic' else False) + format.setFontWeight(QFont.Weight.Bold if subtheme.get('font-weight') == 'bold' else QFont.Weight.Normal) + format.setFontItalic(subtheme.get('font-style') == 'italic') self.MARKDOWN_KWS_FORMAT[k] = format self.rehighlight() From 6760f583858c1be1469fc3133f4621ae9f1880cf Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 27 Apr 2023 15:47:40 +0100 Subject: [PATCH 10/11] Improvements to new formatter functions has_extra_files() and extra_file_names(). Provide the option of using a regular expression to filter the files before counting or returning the names. --- manual/template_lang.rst | 4 +-- src/calibre/utils/formatter_functions.py | 42 ++++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/manual/template_lang.rst b/manual/template_lang.rst index 5126ce4878..c11df09e88 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -495,7 +495,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``eval(string)`` -- evaluates the string as a program, passing the local variables. This permits using the template processor to construct complex results from local variables. In :ref:`Template Program Mode `, because the `{` and `}` characters are interpreted before the template is evaluated you must use `[[` for the `{` character and `]]` for the ``}`` character. They are converted automatically. Note also that prefixes and suffixes (the `|prefix|suffix` syntax) cannot be used in the argument to this function when using :ref:`Template Program Mode `. * ``extra_file_size(file_name)`` -- returns the size in bytes of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``extra_file_modtime(file_name, format_spec)`` -- returns the modification time of the extra file ``file_name`` in the book's ``data/`` folder if it exists, otherwise ``-1``. The modtime is formatted according to ``format_spec`` (see ``format_date()`` for details). If ``format_spec`` is the empty string, returns the modtime as the floating point number of seconds since the epoch. See also the functions ``has_extra_files()``, ``extra_file_names()`` and ``extra_file_size()``. The epoch is OS dependent. This function can be used only in the GUI. -* ``extra_file_names(sep)`` -- returns a ``sep``-separated list of extra files in the book's ``data/`` folder. See also the functions ``has_extra_files()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. +* ``extra_file_names(sep [, pattern])`` returns a ``sep``-separated list of extra files in the book's ``data/`` folder. If the optional parameter ``pattern``, a regular expression, is supplied then the list is filtered to files that match ``pattern``. The pattern match is case insensitive. See also the functions ``has_extra_files()``, ``extra_file_modtime()`` and ``extra_file_size()``. This function can be used only in the GUI. * ``field(lookup_name)`` -- returns the value of the metadata field with lookup name ``lookup_name``. * ``field_exists(field_name)`` -- checks if a field (column) with the lookup name ``field_name`` exists, returning ``'1'`` if so and the empty string if not. * ``finish_formatting(val, fmt, prefix, suffix)`` -- apply the format, prefix, and suffix to a value in the same way as done in a template like ``{series_index:05.2f| - |- }``. This function is provided to ease conversion of complex single-function- or template-program-mode templates to `GPM` Templates. For example, the following program produces the same output as the above template:: @@ -557,7 +557,7 @@ In `GPM` the functions described in `Single Function Mode` all require an additi * ``formats_sizes()`` -- return a comma-separated list of colon-separated ``FMT:SIZE`` items giving the sizes in bytes of the formats of a book. You can use the select function to get the size for a specific format. Note that format names are always uppercase, as in EPUB. * ``fractional_part(x)`` -- returns the value after the decimal point. For example, ``fractional_part(3.14)`` returns ``0.14``. Throws an exception if ``x`` is not a number. * ``has_cover()`` -- return ``'Yes'`` if the book has a cover, otherwise the empty string. -* ``has_extra_files()`` -- returns ``'Yes'`` if there are any extra files for the book (files in the folder ``data/`` in the book's folder), otherwise ``''`` (the empty string). See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. +* ``has_extra_files([pattern])`` -- returns the count of extra files, otherwise '' (the empty string). If the optional parameter ``pattern`` (a regular expression) is supplied then the list is filtered to files that match ``pattern`` before the files are counted. The pattern match is case insensitive. See also the functions ``extra_file_names()``, ``extra_file_size()`` and ``extra_file_modtime()``. This function can be used only in the GUI. * ``is_marked()`` -- check whether the book is `marked` in calibre. If it is then return the value of the mark, either ``'true'`` (lower case) or a comma-separated list of named marks. Returns ``''`` (the empty string) if the book is not marked. This function works only in the GUI. * ``language_codes(lang_strings)`` -- return the `language codes `_ for the language names passed in `lang_strings`. The strings must be in the language of the current locale. ``Lang_strings`` is a comma-separated list. * ``list_contains(value, separator, [ pattern, found_val, ]* not_found_val)`` -- (Alias of ``in_list``) Interpreting the value as a list of items separated by ``separator``, evaluate the ``pattern`` against each value in the list. If the ``pattern`` matches any value then return ``found_val``, otherwise return ``not_found_val``. The ``pattern`` and ``found_value`` can be repeated as many times as desired, permitting returning different values depending on the search. The patterns are checked in order. The first match is returned. Aliases: ``in_list()``, ``list_contains()`` diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index a9a567a958..6f82cf7dda 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -2391,16 +2391,28 @@ class BuiltinBookValues(BuiltinFormatterFunction): class BuiltinHasExtraFiles(BuiltinFormatterFunction): name = 'has_extra_files' - arg_count = 0 + arg_count = -1 category = 'Template database functions' - __doc__ = doc = _("has_extra_files() -- returns 'Yes' if there are any extra " + __doc__ = doc = _("has_extra_files([pattern]) -- returns the count of extra " "files, otherwise '' (the empty string). " + "If the optional parameter 'pattern' (a regular expression) " + "is supplied then the list is filtered to files that match " + "pattern before the files are counted. The pattern match is " + "case insensitive. " 'This function can be used only in the GUI.') - def evaluate(self, formatter, kwargs, mi, locals): + def evaluate(self, formatter, kwargs, mi, locals, *args): + if len(args) > 1: + raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) + pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try: - return 'Yes' if db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) else '' + files = tuple(f.relpath.partition('/')[-1] for f in + db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) + if pattern: + r = re.compile(pattern, re.IGNORECASE) + files = tuple(filter(r.search, files)) + return len(files) if len(files) > 0 else '' except Exception as e: traceback.print_exc() raise ValueError(e) @@ -2408,17 +2420,27 @@ class BuiltinHasExtraFiles(BuiltinFormatterFunction): class BuiltinExtraFileNames(BuiltinFormatterFunction): name = 'extra_file_names' - arg_count = 1 + arg_count = -1 category = 'Template database functions' - __doc__ = doc = _("extra_file_names(sep) -- returns a sep-separated list of " - "extra files in the book's '{}/' folder. " + __doc__ = doc = _("extra_file_names(sep [, pattern]) -- returns a sep-separated " + "list of extra files in the book's '{}/' folder. If the " + "optional parameter 'pattern', a regular expression, is " + "supplied then the list is filtered to files that match pattern. " + "The pattern match is case insensitive. " 'This function can be used only in the GUI.').format(DATA_DIR_NAME) - def evaluate(self, formatter, kwargs, mi, locals, sep): + def evaluate(self, formatter, kwargs, mi, locals, sep, *args): + if len(args) > 1: + raise ValueError(_('Incorrect number of arguments for function {0}').format('has_extra_files')) + pattern = args[0] if len(args) == 1 else None db = self.get_database(mi).new_api try: - files = db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN) - return sep.join(file.relpath.partition('/')[-1] for file in files) + files = tuple(f.relpath.partition('/')[-1] for f in + db.list_extra_files(mi.id, use_cache=True, pattern=DATA_FILE_PATTERN)) + if pattern: + r = re.compile(pattern, re.IGNORECASE) + files = tuple(filter(r.search, files)) + return sep.join(files) except Exception as e: traceback.print_exc() raise ValueError(e) From 7b0cecdcbd7ac56ffc177d3e75005509be86d889 Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 27 Apr 2023 16:03:13 +0100 Subject: [PATCH 11/11] Back out the change that removed delegates for read-only columns --- src/calibre/gui2/library/views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index aff08c08bd..c2c3e36a5a 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -1153,10 +1153,9 @@ class BooksView(QTableView): # {{{ elif cc['datatype'] == 'enumeration': set_item_delegate(colhead, self.cc_enum_delegate) else: - if colhead in self._model.editable_cols: - dattr = colhead+'_delegate' - delegate = colhead if hasattr(self, dattr) else 'text' - set_item_delegate(colhead, getattr(self, delegate+'_delegate')) + dattr = colhead+'_delegate' + delegate = colhead if hasattr(self, dattr) else 'text' + set_item_delegate(colhead, getattr(self, delegate+'_delegate')) self.restore_state() self.set_ondevice_column_visibility()