Edit book: Highlight broken links using an error style

This commit is contained in:
Kovid Goyal 2014-07-10 09:35:35 +05:30
parent 3964715d56
commit 54f774c375
7 changed files with 44 additions and 13 deletions

View File

@ -107,3 +107,13 @@ def set_book_locale(lang):
dictionaries.default_locale = dictionaries.ui_locale dictionaries.default_locale = dictionaries.ui_locale
from calibre.gui2.tweak_book.editor.syntax.html import refresh_spell_check_status from calibre.gui2.tweak_book.editor.syntax.html import refresh_spell_check_status
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

View File

@ -58,9 +58,11 @@ class SimpleUserData(QTextBlockUserData):
def __init__(self): def __init__(self):
QTextBlockUserData.__init__(self) QTextBlockUserData.__init__(self)
self.state = SimpleState() 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.state = SimpleState() if state is None else state
self.doc_name = doc_name
class SyntaxHighlighter(object): class SyntaxHighlighter(object):
@ -71,6 +73,7 @@ class SyntaxHighlighter(object):
def __init__(self): def __init__(self):
self.doc = None self.doc = None
self.doc_name = None
self.requests = deque() self.requests = deque()
self.ignore_requests = False self.ignore_requests = False
@ -86,7 +89,7 @@ class SyntaxHighlighter(object):
def create_formats(self): def create_formats(self):
self.formats = self.create_formats_func() self.formats = self.create_formats_func()
def set_document(self, doc): def set_document(self, doc, doc_name=None):
old_doc = self.doc old_doc = self.doc
if old_doc is not None: if old_doc is not None:
old_doc.contentsChange.disconnect(self.reformat_blocks) old_doc.contentsChange.disconnect(self.reformat_blocks)
@ -97,9 +100,10 @@ class SyntaxHighlighter(object):
blk.layout().clearAdditionalFormats() blk.layout().clearAdditionalFormats()
blk = blk.next() blk = blk.next()
c.endEditBlock() c.endEditBlock()
self.doc = None self.doc = self.doc_name = None
if doc is not None: if doc is not None:
self.doc = doc self.doc = doc
self.doc_name = doc_name
doc.contentsChange.connect(self.reformat_blocks) doc.contentsChange.connect(self.reformat_blocks)
self.rehighlight() self.rehighlight()
@ -207,7 +211,7 @@ class SyntaxHighlighter(object):
start_state = start_state.state.copy() start_state = start_state.state.copy()
else: else:
start_state = self.user_data_factory().state 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 = [] formats = []
for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode(block.text())): for i, num, fmt in run_loop(ud, self.state_map, self.formats, unicode(block.text())):
if fmt is not None: if fmt is not None:

View File

@ -10,6 +10,7 @@ import re
from PyQt4.Qt import QTextBlockUserData 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 import syntax_text_char_format, LINK_PROPERTY
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
@ -158,9 +159,11 @@ class CSSUserData(QTextBlockUserData):
def __init__(self): def __init__(self):
QTextBlockUserData.__init__(self) QTextBlockUserData.__init__(self)
self.state = CSSState() 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.state = CSSState() if state is None else state
self.doc_name = doc_name
def normal(state, text, i, formats, user_data): def normal(state, text, i, formats, user_data):
' The normal state (outside content blocks {})' ' The normal state (outside content blocks {})'
@ -220,7 +223,8 @@ def content(state, text, i, formats, user_data):
prefix += main[0] prefix += main[0]
suffix = main[-1] + suffix suffix = main[-1] + suffix
main = main[1:-1] 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(m.group()), formats[fmt])]
return [(len(text) - i, formats['unknown-normal'])] 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'] = syntax_text_char_format(theme['Link'])
formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link')) formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link'))
formats['link'].setProperty(LINK_PROPERTY, True) 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 return formats

View File

@ -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.ebooks.oeb.polish.spell import html_spell_tags, xml_spell_tags
from calibre.spell.dictionary import parse_lang_code from calibre.spell.dictionary import parse_lang_code
from calibre.spell.break_iterator import split_into_words_and_positions 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 ( from calibre.gui2.tweak_book.editor import (
syntax_text_char_format, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY) 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 from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter, run_loop
@ -219,10 +219,12 @@ class HTMLUserData(QTextBlockUserData):
self.attributes = [] self.attributes = []
self.state = State() self.state = State()
self.css_user_data = None 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.tags, self.attributes = [], []
self.state = State() if state is None else state self.state = State() if state is None else state
self.doc_name = doc_name
@classmethod @classmethod
def tag_ok_for_spell(cls, name): 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 is_link = state.attribute_name in LINK_ATTRS
if is_link: 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 - 1, formats['link']), (1, formats['string'])]
return [(num, 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'] = syntax_text_char_format(t['Link'])
formats['link'].setProperty(LINK_PROPERTY, True) formats['link'].setProperty(LINK_PROPERTY, True)
formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link')) 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 return formats

View File

@ -209,11 +209,11 @@ class TextEdit(PlainTextEdit):
self.highlight_cursor_line() 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.syntax = syntax
self.highlighter = get_highlighter(syntax)() self.highlighter = get_highlighter(syntax)()
self.highlighter.apply_theme(self.theme) 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) sclass = {'html':HTMLSmarts, 'xml':HTMLSmarts, 'css':CSSSmarts}.get(syntax, None)
if sclass is not None: if sclass is not None:
self.smarts = sclass(self) self.smarts = sclass(self)

View File

@ -70,6 +70,7 @@ SOLARIZED = \
SpellError us=wave uc={orange} SpellError us=wave uc={orange}
Tooltip fg=black bg=ffffed Tooltip fg=black bg=ffffed
Link fg={blue} Link fg={blue}
BadLink fg={cyan} us=wave uc={red}
DiffDelete bg={base02} fg={red} DiffDelete bg={base02} fg={red}
DiffInsert bg={base02} fg={green} DiffInsert bg={base02} fg={green}
@ -112,6 +113,7 @@ THEMES = {
SpellError us=wave uc=orange SpellError us=wave uc=orange
SpecialCharacter bg={cursor_loc} SpecialCharacter bg={cursor_loc}
Link fg=cyan Link fg=cyan
BadLink fg={string} us=wave uc=red
DiffDelete bg=341414 fg=642424 DiffDelete bg=341414 fg=642424
DiffInsert bg=143414 fg=246424 DiffInsert bg=143414 fg=246424
@ -159,6 +161,7 @@ THEMES = {
Error us=wave uc=red Error us=wave uc=red
SpellError us=wave uc=magenta SpellError us=wave uc=magenta
Link fg=blue Link fg=blue
BadLink fg={string} us=wave uc=red
DiffDelete bg=rgb(255,180,200) fg=rgb(200,80,110) DiffDelete bg=rgb(255,180,200) fg=rgb(200,80,110)
DiffInsert bg=rgb(180,255,180) fg=rgb(80,210,80) DiffInsert bg=rgb(180,255,180) fg=rgb(80,210,80)

View File

@ -17,7 +17,7 @@ from calibre import prints
from calibre.constants import DEBUG from calibre.constants import DEBUG
from calibre.ebooks.chardet import replace_encoding_declarations from calibre.ebooks.chardet import replace_encoding_declarations
from calibre.gui2 import error_dialog 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 import SPELL_PROPERTY
from calibre.gui2.tweak_book.editor.text import TextEdit from calibre.gui2.tweak_book.editor.text import TextEdit
from calibre.utils.icu import utf16_length from calibre.utils.icu import utf16_length
@ -153,11 +153,11 @@ class Editor(QMainWindow):
self.data = ans self.data = ans
return ans.encode('utf-8') return ans.encode('utf-8')
def fset(self, val): 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) return property(fget=fget, fset=fset)
def init_from_template(self, template): 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): def get_raw_data(self):
# The EPUB spec requires NFC normalization, see section 1.3.6 of # The EPUB spec requires NFC normalization, see section 1.3.6 of