diff --git a/recipes/irish_independent.recipe b/recipes/irish_independent.recipe
index 681e62646b..105260fd02 100644
--- a/recipes/irish_independent.recipe
+++ b/recipes/irish_independent.recipe
@@ -25,7 +25,9 @@ class IrishIndependent(BasicNewsRecipe):
]
feeds = [
+ ('Frontpage News', 'http://www.independent.ie/rss'),
('News', 'http://www.independent.ie/rss'),
+ ('World News', 'http://www.independent.ie/world-news/rss'),
('Opinion', 'http://www.independent.ie/opinion/rss'),
('Business', 'http://www.independent.ie/business/rss'),
('Sport', 'http://www.independent.ie/sport/rss'),
diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py
index d2b2b1b62a..c4fa065c01 100644
--- a/src/calibre/db/cache.py
+++ b/src/calibre/db/cache.py
@@ -2401,7 +2401,7 @@ class Cache:
Example: Assume author A has link X, author B has link Y, tag S has link
F, and tag T has link G. If book 1 has author A and tag T,
- this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}
+ this method returns {'authors':{'A':'X'}, 'tags':{'T', 'G'}}.
If book 2's author is neither A nor B and has no tags, this method returns {}.
:param book_id: the book id in question.
diff --git a/src/calibre/gui2/dialogs/edit_authors_dialog.py b/src/calibre/gui2/dialogs/edit_authors_dialog.py
index 7f3dfb657b..0688161703 100644
--- a/src/calibre/gui2/dialogs/edit_authors_dialog.py
+++ b/src/calibre/gui2/dialogs/edit_authors_dialog.py
@@ -88,9 +88,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.buttonBox.button(QDialogButtonBox.StandardButton.Ok).setText(_('&OK'))
self.buttonBox.button(QDialogButtonBox.StandardButton.Cancel).setText(_('&Cancel'))
self.buttonBox.accepted.connect(self.accepted)
+ self.buttonBox.rejected.connect(self.rejected)
self.apply_vl_checkbox.stateChanged.connect(self.use_vl_changed)
- # Set up the heading for sorting
+ self.table.setAlternatingRowColors(True)
self.table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.find_aut_func = find_aut_func
@@ -112,6 +113,10 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
hh.sectionClicked.connect(self.do_sort)
hh.setSortIndicatorShown(True)
+ vh = self.table.verticalHeader()
+ vh.setDefaultSectionSize(gprefs.get('general_category_editor_row_height', vh.defaultSectionSize()))
+ vh.sectionResized.connect(self.row_height_changed)
+
# set up the search & filter boxes
self.find_box.initialize('manage_authors_search')
le = self.find_box.lineEdit()
@@ -271,10 +276,16 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
self.start_find_pos = -1
self.table.blockSignals(False)
+ def row_height_changed(self, row, old, new):
+ self.table.verticalHeader().blockSignals(True)
+ self.table.verticalHeader().setDefaultSectionSize(new)
+ self.table.verticalHeader().blockSignals(False)
+
def save_state(self):
self.table_column_widths = []
for c in range(0, self.table.columnCount()):
self.table_column_widths.append(self.table.columnWidth(c))
+ gprefs['general_category_editor_row_height'] = self.table.verticalHeader().sectionSize(0)
gprefs['manage_authors_table_widths'] = self.table_column_widths
self.save_geometry(gprefs, 'manage_authors_dialog_geometry')
@@ -451,6 +462,9 @@ class EditAuthorsDialog(QDialog, Ui_EditAuthorsDialog):
if orig != v:
self.result.append((id_, orig['name'], v['name'], v['sort'], v['link']))
+ def rejected(self):
+ self.save_state()
+
def do_recalc_author_sort(self):
with self.no_cell_changed():
for row in range(0,self.table.rowCount()):
diff --git a/src/calibre/gui2/dialogs/tag_list_editor.py b/src/calibre/gui2/dialogs/tag_list_editor.py
index 98faaa910a..0c78e25cab 100644
--- a/src/calibre/gui2/dialogs/tag_list_editor.py
+++ b/src/calibre/gui2/dialogs/tag_list_editor.py
@@ -165,12 +165,6 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.setWindowFlags(self.windowFlags()&(~Qt.WindowType.WindowContextHelpButtonHint))
self.setWindowIcon(icon)
- # Get saved geometry info
- try:
- self.table_column_widths = gprefs.get('tag_list_editor_table_widths', None)
- except:
- pass
-
# initialization
self.to_rename = {}
self.to_delete = set()
@@ -370,6 +364,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
# I'm not sure if this is standard Qt behavior or behavior triggered by
# something in this class, but replacing the table fixes it.
if self.table is not None:
+ self.save_geometry()
self.gridlayout.removeWidget(self.table)
sip.delete(self.table)
self.table = TleTableWidget(self)
@@ -386,9 +381,11 @@ class TagListEditor(QDialog, Ui_TagListEditor):
hh.sectionClicked.connect(self.record_sort)
hh.setSortIndicatorShown(True)
+ vh = self.table.verticalHeader()
+ vh.setDefaultSectionSize(gprefs.get('general_category_editor_row_height', vh.defaultSectionSize()))
+ vh.sectionResized.connect(self.row_height_changed)
+
self.table.setColumnCount(4)
- for col,width in enumerate(self.table_column_widths):
- self.table.setColumnWidth(col, width)
self.edit_delegate = EditColumnDelegate(self.table, self.check_for_deleted_items)
self.edit_delegate.editing_finished.connect(self.stop_editing)
@@ -416,11 +413,21 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.not_found_label_timer_event, type=Qt.ConnectionType.QueuedConnection)
self.table.setEditTriggers(QAbstractItemView.EditTrigger.EditKeyPressed)
+
self.restore_geometry(gprefs, 'tag_list_editor_dialog_geometry')
+ self.table_column_widths = gprefs.get('tag_list_editor_table_widths', None)
+ if self.table_column_widths is not None:
+ for col,width in enumerate(self.table_column_widths):
+ self.table.setColumnWidth(col, width)
self.table.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.table.customContextMenuRequested.connect(self.show_context_menu)
+ def row_height_changed(self, row, old, new):
+ self.table.verticalHeader().blockSignals(True)
+ self.table.verticalHeader().setDefaultSectionSize(new)
+ self.table.verticalHeader().blockSignals(False)
+
def fill_in_table(self, tags, tag_to_match, ttm_is_first_letter):
self.create_table()
@@ -532,7 +539,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
def do_filter(self):
self.fill_in_table(None, None, False)
- def table_column_resized(self, col, old, new):
+ def table_column_resized(self, *args):
self.table_column_widths = []
for c in range(0, self.table.columnCount()):
self.table_column_widths.append(self.table.columnWidth(c))
@@ -552,6 +559,7 @@ class TagListEditor(QDialog, Ui_TagListEditor):
self.table.setColumnWidth(c, w)
def save_geometry(self):
+ gprefs['general_category_editor_row_height'] = self.table.verticalHeader().sectionSize(0)
gprefs['tag_list_editor_table_widths'] = self.table_column_widths
super().save_geometry(gprefs, 'tag_list_editor_dialog_geometry')
diff --git a/src/calibre/gui2/markdown_editor.py b/src/calibre/gui2/markdown_editor.py
index 37bcd43a30..6d946c746c 100644
--- a/src/calibre/gui2/markdown_editor.py
+++ b/src/calibre/gui2/markdown_editor.py
@@ -183,7 +183,7 @@ if __name__ == '__main__':
w.setWindowFlag(Qt.WindowType.Dialog)
w.show()
w.markdown = '''\
-test *italic* **bold** ***bold-italic*** `code` [link](https://calibre-ebook.com) span
+normal& *italic&* **bold&** ***bold-italic*** `code` [link](https://calibre-ebook.com) span
> Blockquotes
diff --git a/src/calibre/gui2/markdown_syntax_highlighter.py b/src/calibre/gui2/markdown_syntax_highlighter.py
index adb280a7e4..693f43d6b9 100644
--- a/src/calibre/gui2/markdown_syntax_highlighter.py
+++ b/src/calibre/gui2/markdown_syntax_highlighter.py
@@ -20,19 +20,20 @@ class MarkdownHighlighter(QSyntaxHighlighter):
'uBold': re.compile(r'(?__)(?P.+?)(?P=delim)'),
'uItalic': re.compile(r'(?_)(?!_)(?P([^_]{2,}?|[^_]))(?___)(?P([^_]{2,}?|[^_]))(?+[ \t]?'),
+ 'Link': re.compile(r'(?+[ \t]?'),
+ 'CodeBlock': re.compile(r'^([ ]{4,}|[ ]*\t).*'),
'CodeSpan': re.compile(r'(?`+).+?(?P=delim)'),
'HeaderLine': re.compile(r'(?u)^(-|=)+\s*$'),
'HR': re.compile(r'(?u)^(\s*(\*|-|_)\s*){3,}$'),
- 'Html': re.compile(r'<.+?(?')
+ 'Html': re.compile(r'(?u)?[^/\s].*?(?'),
+ 'Entity': re.compile(r'&([A-z]{2,7}|#\d{1,7}|#x[\dA-Fa-f]{1,6});'),
}
key_theme_maps = {
@@ -55,6 +56,7 @@ class MarkdownHighlighter(QSyntaxHighlighter):
'CodeSpan': "codespan",
'HR': "line",
'Html': "html",
+ 'Entity': "entity",
}
light_theme = {
@@ -70,7 +72,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
"codespan": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
"codeblock": {"color":"#ff5800", "font-weight":"normal", "font-style":"normal"},
"line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
- "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"}
+ "html": {"color":"#c000c0", "font-weight":"normal", "font-style":"normal"},
+ "entity": {"color":"#006496"},
}
dark_theme = {
@@ -86,7 +89,8 @@ class MarkdownHighlighter(QSyntaxHighlighter):
"codespan": {"color":"#90ee90", "font-weight":"normal", "font-style":"normal"},
"codeblock": {"color":"#ff9900", "font-weight":"normal", "font-style":"normal"},
"line": {"color":"#2aa198", "font-weight":"normal", "font-style":"normal"},
- "html": {"color":"#F653A6", "font-weight":"normal", "font-style":"normal"}
+ "html": {"color":"#f653a6", "font-weight":"normal", "font-style":"normal"},
+ "entity": {"color":"#ff82ac"},
}
def __init__(self, parent):
@@ -98,6 +102,16 @@ class MarkdownHighlighter(QSyntaxHighlighter):
self.theme = theme
self.MARKDOWN_KWS_FORMAT = {}
+ for k in ['Bold', 'Italic','BoldItalic']:
+ # generate dynamically keys and theme for EntityBold, EntityItalic, EntityBoldItalic
+ t = self.key_theme_maps[k]
+ newtheme = theme['entity'].copy()
+ newtheme.update(theme[t])
+ newthemekey = 'entity'+t
+ newmapkey = 'Entity'+k
+ theme[newthemekey] = newtheme
+ self.key_theme_maps[newmapkey] = newthemekey
+
for k,t in self.key_theme_maps.items():
subtheme = theme[t]
format = QTextCharFormat()
@@ -141,13 +155,15 @@ class MarkdownHighlighter(QSyntaxHighlighter):
self.highlightImage(text, cursor, bf)
+ self.highlightEntity(text, cursor, bf)
+
self.highlightCodeSpan(text, cursor, bf)
self.highlightCodeBlock(text, cursor, bf)
def highlightBlockQuote(self, text, cursor, bf):
found = False
- mo = re.search(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
+ mo = re.match(self.MARKDOWN_KEYS_REGEX['BlockQuote'],text)
if mo:
self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['BlockQuote'])
self.offset += mo.end()
@@ -290,11 +306,29 @@ class MarkdownHighlighter(QSyntaxHighlighter):
found = False
for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['CodeBlock'],text):
stripped = text.lstrip()
- if stripped[0] not in ('*','-','+','>') and not re.match(r'\d+\.', stripped):
+ if stripped[0] not in ('*','-','+') and not re.match(self.MARKDOWN_KEYS_REGEX['OrderedList'], stripped):
self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), self.MARKDOWN_KWS_FORMAT['CodeBlock'])
found = True
return found
+ def highlightEntity(self, text, cursor, bf):
+ found = False
+ for mo in re.finditer(self.MARKDOWN_KEYS_REGEX['Entity'],text):
+ charformat = self.format(self.offset+ mo.start())
+ charbold = charformat.fontWeight() == QFont.Weight.Bold
+ charitalic = charformat.fontItalic()
+ if charbold and charitalic:
+ format = self.MARKDOWN_KWS_FORMAT['EntityBoldItalic']
+ elif charbold and not charitalic:
+ format = self.MARKDOWN_KWS_FORMAT['EntityBold']
+ elif not charbold and charitalic:
+ format = self.MARKDOWN_KWS_FORMAT['EntityItalic']
+ else:
+ format = self.MARKDOWN_KWS_FORMAT['Entity']
+ self.setFormat(self.offset+ mo.start(), mo.end() - mo.start(), format)
+ found = True
+ return found
+
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'])
diff --git a/src/calibre/gui2/toc/main.py b/src/calibre/gui2/toc/main.py
index 9f422c9365..476ea6512c 100644
--- a/src/calibre/gui2/toc/main.py
+++ b/src/calibre/gui2/toc/main.py
@@ -1070,7 +1070,7 @@ class TOCEditor(QDialog): # {{{
def workaround_macos_mouse_with_webview_bug(self):
# macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639
# needed as of Qt 6.4.2
- d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False)
+ d = info_dialog(self, _('Loading...'), _('Loading table of contents view, please wait...'), show_copy_button=False)
QTimer.singleShot(0, d.reject)
d.exec()
diff --git a/src/calibre/gui2/trash.py b/src/calibre/gui2/trash.py
index 7ca44dacc3..aa210d3b6b 100644
--- a/src/calibre/gui2/trash.py
+++ b/src/calibre/gui2/trash.py
@@ -250,7 +250,7 @@ class TrashView(Dialog):
return
det_msg = []
for (entry, exc, tb) in failures:
- det_msg.append(_('Failed for {} with error:').format(entry.title))
+ det_msg.append(_('Failed for the book {} with error:').format(entry.title))
det_msg.append(tb)
det_msg.append('-' * 40)
det_msg.append('')
diff --git a/src/calibre/gui2/tweak_book/toc.py b/src/calibre/gui2/tweak_book/toc.py
index 5f085f15dd..19097a8e29 100644
--- a/src/calibre/gui2/tweak_book/toc.py
+++ b/src/calibre/gui2/tweak_book/toc.py
@@ -68,7 +68,7 @@ class TOCEditor(QDialog):
def workaround_macos_mouse_with_webview_bug(self):
# macOS is weird: https://bugs.launchpad.net/calibre/+bug/2004639
# needed as of Qt 6.4.2
- d = info_dialog(self, _('Loading...'), _('Loading view, please wait...'), show_copy_button=False)
+ d = info_dialog(self, _('Loading...'), _('Loading table of contents view, please wait...'), show_copy_button=False)
QTimer.singleShot(0, d.reject)
d.exec()
diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py
index 9e192620de..dae26bf0cd 100644
--- a/src/calibre/library/catalogs/csv_xml.py
+++ b/src/calibre/library/catalogs/csv_xml.py
@@ -177,72 +177,75 @@ class CSV_XML(CatalogPlugin):
else:
root = E.calibredb()
for r in data:
- record = E.record()
- root.append(record)
+ try:
+ record = E.record()
+ root.append(record)
- for field in fields:
- if field.startswith('#'):
- val = db.get_field(r['id'], field, index_is_id=True)
- if not isinstance(val, str):
- val = str(val)
- item = getattr(E, field.replace('#', '_'))(val)
- record.append(item)
+ for field in fields:
+ if field.startswith('#'):
+ val = db.get_field(r['id'], field, index_is_id=True)
+ if not isinstance(val, str):
+ val = str(val)
+ item = getattr(E, field.replace('#', '_'))(val)
+ record.append(item)
- for field in ('id', 'uuid', 'publisher', 'rating', 'size',
- 'isbn', 'ondevice', 'identifiers'):
- if field in fields:
- val = r[field]
- if not val:
- continue
- if not isinstance(val, (bytes, str)):
- if (fm.get(field, {}).get('datatype', None) ==
- 'rating' and val):
- val = '%.2g' % (val / 2)
- val = str(val)
- item = getattr(E, field)(val)
- record.append(item)
+ for field in ('id', 'uuid', 'publisher', 'rating', 'size',
+ 'isbn', 'ondevice', 'identifiers'):
+ if field in fields:
+ val = r[field]
+ if not val:
+ continue
+ if not isinstance(val, (bytes, str)):
+ if (fm.get(field, {}).get('datatype', None) ==
+ 'rating' and val):
+ val = '%.2g' % (val / 2)
+ val = str(val)
+ item = getattr(E, field)(val)
+ record.append(item)
- if 'title' in fields:
- title = E.title(r['title'], sort=r['sort'])
- record.append(title)
+ if 'title' in fields:
+ title = E.title(r['title'], sort=r['sort'])
+ record.append(title)
- if 'authors' in fields:
- aus = E.authors(sort=r['author_sort'])
- for au in r['authors']:
- aus.append(E.author(au))
- record.append(aus)
+ if 'authors' in fields:
+ aus = E.authors(sort=r['author_sort'])
+ for au in r['authors']:
+ aus.append(E.author(au))
+ record.append(aus)
- for field in ('timestamp', 'pubdate'):
- if field in fields:
- record.append(getattr(E, field)(isoformat(r[field], as_utc=False)))
+ for field in ('timestamp', 'pubdate'):
+ if field in fields:
+ record.append(getattr(E, field)(isoformat(r[field], as_utc=False)))
- if 'tags' in fields and r['tags']:
- tags = E.tags()
- for tag in r['tags']:
- tags.append(E.tag(tag))
- record.append(tags)
+ if 'tags' in fields and r['tags']:
+ tags = E.tags()
+ for tag in r['tags']:
+ tags.append(E.tag(tag))
+ record.append(tags)
- if 'comments' in fields and r['comments']:
- record.append(E.comments(r['comments']))
+ if 'comments' in fields and r['comments']:
+ record.append(E.comments(r['comments']))
- if 'series' in fields and r['series']:
- record.append(E.series(r['series'],
- index=str(r['series_index'])))
+ if 'series' in fields and r['series']:
+ record.append(E.series(r['series'],
+ index=str(r['series_index'])))
- if 'languages' in fields and r['languages']:
- record.append(E.languages(r['languages']))
+ if 'languages' in fields and r['languages']:
+ record.append(E.languages(r['languages']))
- if 'cover' in fields and r['cover']:
- record.append(E.cover(r['cover'].replace(os.sep, '/')))
+ if 'cover' in fields and r['cover']:
+ record.append(E.cover(r['cover'].replace(os.sep, '/')))
- if 'formats' in fields and r['formats']:
- fmt = E.formats()
- for f in r['formats']:
- fmt.append(E.format(f.replace(os.sep, '/')))
- record.append(fmt)
+ if 'formats' in fields and r['formats']:
+ fmt = E.formats()
+ for f in r['formats']:
+ fmt.append(E.format(f.replace(os.sep, '/')))
+ record.append(fmt)
- if 'library_name' in fields:
- record.append(E.library_name(current_library))
+ if 'library_name' in fields:
+ record.append(E.library_name(current_library))
+ except Exception as e:
+ raise Exception('Failed to convert {} to XML with error: {}'.format(r['title'], e)) from e
with open(path_to_output, 'wb') as f:
f.write(etree.tostring(root, encoding='utf-8',
diff --git a/src/calibre/utils/run_tests.py b/src/calibre/utils/run_tests.py
index a38815d5b2..65c432a518 100644
--- a/src/calibre/utils/run_tests.py
+++ b/src/calibre/utils/run_tests.py
@@ -53,7 +53,7 @@ class TestResult(unittest.TextTestResult):
def find_tests_in_package(package, excludes=('main.py',)):
- items = list(importlib.resources.contents(package))
+ items = [path.name for path in importlib.resources.files(package).iterdir()]
suits = []
excludes = set(excludes) | {x + 'c' for x in excludes}
seen = set()