From 2e0a45737a99ae2c2d204c06a84427eb329e8dec Mon Sep 17 00:00:00 2001 From: Charles Haley Date: Thu, 27 Apr 2023 11:20:23 +0100 Subject: [PATCH 1/2] 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 2/2] 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':