mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit book: Highlight broken links using an error style
This commit is contained in:
parent
3964715d56
commit
54f774c375
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user