From 54f774c375676ffb21c84b046fcfe817510a0deb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 10 Jul 2014 09:35:35 +0530 Subject: [PATCH] Edit book: Highlight broken links using an error style --- src/calibre/gui2/tweak_book/__init__.py | 10 ++++++++++ src/calibre/gui2/tweak_book/editor/syntax/base.py | 12 ++++++++---- src/calibre/gui2/tweak_book/editor/syntax/css.py | 11 +++++++++-- src/calibre/gui2/tweak_book/editor/syntax/html.py | 11 +++++++++-- src/calibre/gui2/tweak_book/editor/text.py | 4 ++-- src/calibre/gui2/tweak_book/editor/themes.py | 3 +++ src/calibre/gui2/tweak_book/editor/widget.py | 6 +++--- 7 files changed, 44 insertions(+), 13 deletions(-) diff --git a/src/calibre/gui2/tweak_book/__init__.py b/src/calibre/gui2/tweak_book/__init__.py index eba535372f..1e41f35c78 100644 --- a/src/calibre/gui2/tweak_book/__init__.py +++ b/src/calibre/gui2/tweak_book/__init__.py @@ -107,3 +107,13 @@ def set_book_locale(lang): dictionaries.default_locale = dictionaries.ui_locale from calibre.gui2.tweak_book.editor.syntax.html import refresh_spell_check_status refresh_spell_check_status() + +def verify_link(url, name=None): + if _current_container is None or name is None: + return None + target = _current_container.href_to_name(url, name) + if _current_container.has_name(target): + return True + if url.partition(':')[0] in {'http', 'https', 'mailto'}: + return True + return False diff --git a/src/calibre/gui2/tweak_book/editor/syntax/base.py b/src/calibre/gui2/tweak_book/editor/syntax/base.py index 33ed2c5017..775845538c 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/base.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/base.py @@ -58,9 +58,11 @@ class SimpleUserData(QTextBlockUserData): def __init__(self): QTextBlockUserData.__init__(self) self.state = SimpleState() + self.doc_name = None - def clear(self, state=None): + def clear(self, state=None, doc_name=None): self.state = SimpleState() if state is None else state + self.doc_name = doc_name class SyntaxHighlighter(object): @@ -71,6 +73,7 @@ class SyntaxHighlighter(object): def __init__(self): self.doc = None + self.doc_name = None self.requests = deque() self.ignore_requests = False @@ -86,7 +89,7 @@ class SyntaxHighlighter(object): def create_formats(self): self.formats = self.create_formats_func() - def set_document(self, doc): + def set_document(self, doc, doc_name=None): old_doc = self.doc if old_doc is not None: old_doc.contentsChange.disconnect(self.reformat_blocks) @@ -97,9 +100,10 @@ class SyntaxHighlighter(object): blk.layout().clearAdditionalFormats() blk = blk.next() c.endEditBlock() - self.doc = None + self.doc = self.doc_name = None if doc is not None: self.doc = doc + self.doc_name = doc_name doc.contentsChange.connect(self.reformat_blocks) self.rehighlight() @@ -207,7 +211,7 @@ class SyntaxHighlighter(object): start_state = start_state.state.copy() else: start_state = self.user_data_factory().state - ud.clear(state=start_state) # Ensure no stale user data lingers + ud.clear(state=start_state, doc_name=self.doc_name) # Ensure no stale user data lingers formats = [] for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode(block.text())): if fmt is not None: diff --git a/src/calibre/gui2/tweak_book/editor/syntax/css.py b/src/calibre/gui2/tweak_book/editor/syntax/css.py index c94fc2bacb..1ca6571ef3 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/css.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/css.py @@ -10,6 +10,7 @@ import re from PyQt4.Qt import QTextBlockUserData +from calibre.gui2.tweak_book import verify_link from calibre.gui2.tweak_book.editor import syntax_text_char_format, LINK_PROPERTY from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter @@ -158,9 +159,11 @@ class CSSUserData(QTextBlockUserData): def __init__(self): QTextBlockUserData.__init__(self) self.state = CSSState() + self.doc_name = None - def clear(self, state=None): + def clear(self, state=None, doc_name=None): self.state = CSSState() if state is None else state + self.doc_name = doc_name def normal(state, text, i, formats, user_data): ' The normal state (outside content blocks {})' @@ -220,7 +223,8 @@ def content(state, text, i, formats, user_data): prefix += main[0] suffix = main[-1] + suffix main = main[1:-1] - return [(len(prefix), formats[fmt]), (len(main), formats['link']), (len(suffix), formats[fmt])] + h = 'bad_link' if verify_link(main, user_data.doc_name) is False else 'link' + return [(len(prefix), formats[fmt]), (len(main), formats[h]), (len(suffix), formats[fmt])] return [(len(m.group()), formats[fmt])] return [(len(text) - i, formats['unknown-normal'])] @@ -282,6 +286,9 @@ def create_formats(highlighter): formats['link'] = syntax_text_char_format(theme['Link']) formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link')) formats['link'].setProperty(LINK_PROPERTY, True) + formats['bad_link'] = syntax_text_char_format(theme['BadLink']) + formats['bad_link'].setProperty(LINK_PROPERTY, True) + formats['bad_link'].setToolTip(_('This link points to a file that is not present in the book')) return formats diff --git a/src/calibre/gui2/tweak_book/editor/syntax/html.py b/src/calibre/gui2/tweak_book/editor/syntax/html.py index a1d47656cb..55d524fd78 100644 --- a/src/calibre/gui2/tweak_book/editor/syntax/html.py +++ b/src/calibre/gui2/tweak_book/editor/syntax/html.py @@ -15,7 +15,7 @@ from PyQt4.Qt import QFont, QTextBlockUserData, QTextCharFormat from calibre.ebooks.oeb.polish.spell import html_spell_tags, xml_spell_tags from calibre.spell.dictionary import parse_lang_code from calibre.spell.break_iterator import split_into_words_and_positions -from calibre.gui2.tweak_book import dictionaries, tprefs +from calibre.gui2.tweak_book import dictionaries, tprefs, verify_link from calibre.gui2.tweak_book.editor import ( syntax_text_char_format, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY) from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter, run_loop @@ -219,10 +219,12 @@ class HTMLUserData(QTextBlockUserData): self.attributes = [] self.state = State() self.css_user_data = None + self.doc_name = None - def clear(self, state=None): + def clear(self, state=None, doc_name=None): self.tags, self.attributes = [], [] self.state = State() if state is None else state + self.doc_name = doc_name @classmethod def tag_ok_for_spell(cls, name): @@ -444,6 +446,8 @@ def quoted_val(state, text, i, formats, user_data): is_link = state.attribute_name in LINK_ATTRS if is_link: + if verify_link(text[i:i+num - 1], user_data.doc_name) is False: + return [(num - 1, formats['bad_link']), (1, formats['string'])] return [(num - 1, formats['link']), (1, formats['string'])] return [(num, formats['string'])] @@ -531,6 +535,9 @@ def create_formats(highlighter, add_css=True): formats['link'] = syntax_text_char_format(t['Link']) formats['link'].setProperty(LINK_PROPERTY, True) formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link')) + formats['bad_link'] = syntax_text_char_format(t['BadLink']) + formats['bad_link'].setProperty(LINK_PROPERTY, True) + formats['bad_link'].setToolTip(_('This link points to a file that is not present in the book')) return formats diff --git a/src/calibre/gui2/tweak_book/editor/text.py b/src/calibre/gui2/tweak_book/editor/text.py index 4319936ffd..96e59a1576 100644 --- a/src/calibre/gui2/tweak_book/editor/text.py +++ b/src/calibre/gui2/tweak_book/editor/text.py @@ -209,11 +209,11 @@ class TextEdit(PlainTextEdit): self.highlight_cursor_line() # }}} - def load_text(self, text, syntax='html', process_template=False): + def load_text(self, text, syntax='html', process_template=False, doc_name=None): self.syntax = syntax self.highlighter = get_highlighter(syntax)() self.highlighter.apply_theme(self.theme) - self.highlighter.set_document(self.document()) + self.highlighter.set_document(self.document(), doc_name=doc_name) sclass = {'html':HTMLSmarts, 'xml':HTMLSmarts, 'css':CSSSmarts}.get(syntax, None) if sclass is not None: self.smarts = sclass(self) diff --git a/src/calibre/gui2/tweak_book/editor/themes.py b/src/calibre/gui2/tweak_book/editor/themes.py index e77a1adb51..4733303462 100644 --- a/src/calibre/gui2/tweak_book/editor/themes.py +++ b/src/calibre/gui2/tweak_book/editor/themes.py @@ -70,6 +70,7 @@ SOLARIZED = \ SpellError us=wave uc={orange} Tooltip fg=black bg=ffffed Link fg={blue} + BadLink fg={cyan} us=wave uc={red} DiffDelete bg={base02} fg={red} DiffInsert bg={base02} fg={green} @@ -112,6 +113,7 @@ THEMES = { SpellError us=wave uc=orange SpecialCharacter bg={cursor_loc} Link fg=cyan + BadLink fg={string} us=wave uc=red DiffDelete bg=341414 fg=642424 DiffInsert bg=143414 fg=246424 @@ -159,6 +161,7 @@ THEMES = { Error us=wave uc=red SpellError us=wave uc=magenta Link fg=blue + BadLink fg={string} us=wave uc=red DiffDelete bg=rgb(255,180,200) fg=rgb(200,80,110) DiffInsert bg=rgb(180,255,180) fg=rgb(80,210,80) diff --git a/src/calibre/gui2/tweak_book/editor/widget.py b/src/calibre/gui2/tweak_book/editor/widget.py index 3d065bfe4d..b896a1454e 100644 --- a/src/calibre/gui2/tweak_book/editor/widget.py +++ b/src/calibre/gui2/tweak_book/editor/widget.py @@ -17,7 +17,7 @@ from calibre import prints from calibre.constants import DEBUG from calibre.ebooks.chardet import replace_encoding_declarations from calibre.gui2 import error_dialog -from calibre.gui2.tweak_book import actions, current_container, tprefs, dictionaries, editor_toolbar_actions +from calibre.gui2.tweak_book import actions, current_container, tprefs, dictionaries, editor_toolbar_actions, editor_name from calibre.gui2.tweak_book.editor import SPELL_PROPERTY from calibre.gui2.tweak_book.editor.text import TextEdit from calibre.utils.icu import utf16_length @@ -153,11 +153,11 @@ class Editor(QMainWindow): self.data = ans return ans.encode('utf-8') def fset(self, val): - self.editor.load_text(val, syntax=self.syntax) + self.editor.load_text(val, syntax=self.syntax, doc_name=editor_name(self)) return property(fget=fget, fset=fset) def init_from_template(self, template): - self.editor.load_text(template, syntax=self.syntax, process_template=True) + self.editor.load_text(template, syntax=self.syntax, process_template=True, doc_name=editor_name(self)) def get_raw_data(self): # The EPUB spec requires NFC normalization, see section 1.3.6 of