mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Edit Book: Highlight href/src attributes and allow clicking on them while holding down the Ctrl key to jump to the linked to file
This commit is contained in:
parent
ac919b48ca
commit
7ab719cfcd
@ -8,10 +8,11 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import tempfile, shutil, sys, os
|
||||
from functools import partial, wraps
|
||||
from urlparse import urlparse
|
||||
|
||||
from PyQt4.Qt import (
|
||||
QObject, QApplication, QDialog, QGridLayout, QLabel, QSize, Qt,
|
||||
QDialogButtonBox, QIcon, QTimer, QPixmap, QInputDialog)
|
||||
QDialogButtonBox, QIcon, QTimer, QPixmap, QInputDialog, QUrl)
|
||||
|
||||
from calibre import prints, isbytestring
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory
|
||||
@ -25,7 +26,7 @@ from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_re
|
||||
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
|
||||
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc, create_inline_toc
|
||||
from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_cssutils_serialization as scs
|
||||
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
|
||||
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file, open_url
|
||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||
from calibre.gui2.tweak_book import (
|
||||
set_current_container, current_container, tprefs, actions, editors,
|
||||
@ -788,6 +789,21 @@ class Boss(QObject):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def editor_link_clicked(self, url):
|
||||
ed = self.gui.central.current_editor
|
||||
name = editor_name(ed)
|
||||
target = current_container().href_to_name(url, name)
|
||||
frag = url.partition('#')[-1]
|
||||
if current_container().has_name(target):
|
||||
self.link_clicked(target, frag)
|
||||
else:
|
||||
purl = urlparse(url)
|
||||
if purl.scheme not in {'', 'file'}:
|
||||
open_url(QUrl(url))
|
||||
else:
|
||||
error_dialog(self, _('Not found'), _(
|
||||
'No file with the name %s was found in the book') % target, show=True)
|
||||
|
||||
def saved_searches(self):
|
||||
self.gui.saved_searches.show(), self.gui.saved_searches.raise_()
|
||||
|
||||
@ -1092,6 +1108,8 @@ class Boss(QObject):
|
||||
editor.cursor_position_changed.connect(self.update_cursor_position)
|
||||
if hasattr(editor, 'word_ignored'):
|
||||
editor.word_ignored.connect(self.word_ignored)
|
||||
if hasattr(editor, 'link_clicked'):
|
||||
editor.link_clicked.connect(self.editor_link_clicked)
|
||||
if data is not None:
|
||||
if use_template:
|
||||
editor.init_from_template(data)
|
||||
|
@ -36,6 +36,7 @@ def editor_from_syntax(syntax, parent=None):
|
||||
SYNTAX_PROPERTY = QTextCharFormat.UserProperty
|
||||
SPELL_PROPERTY = SYNTAX_PROPERTY + 1
|
||||
SPELL_LOCALE_PROPERTY = SPELL_PROPERTY + 1
|
||||
LINK_PROPERTY = SPELL_LOCALE_PROPERTY + 1
|
||||
|
||||
def syntax_text_char_format(*args):
|
||||
ans = QTextCharFormat(*args)
|
||||
|
@ -16,7 +16,8 @@ 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.editor import syntax_text_char_format, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale
|
||||
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
|
||||
from calibre.gui2.tweak_book.editor.syntax.css import (
|
||||
create_formats as create_css_formats, state_map as css_state_map, CSSState, CSSUserData)
|
||||
@ -428,6 +429,7 @@ def quoted_val(state, text, i, formats, user_data):
|
||||
pos = text.find(quote, i)
|
||||
if pos == -1:
|
||||
num = len(text) - i
|
||||
is_link = False
|
||||
else:
|
||||
num = pos - i + 1
|
||||
state.parse = IN_OPENING_TAG
|
||||
@ -437,6 +439,10 @@ def quoted_val(state, text, i, formats, user_data):
|
||||
except ValueError:
|
||||
pass
|
||||
add_attr_data(user_data, ATTR_VALUE, ATTR_END, i + num)
|
||||
is_link = state.attribute_name in {'href', 'src'}
|
||||
|
||||
if is_link:
|
||||
return [(num - 1, formats['link']), (1, formats['string'])]
|
||||
return [(num, formats['string'])]
|
||||
|
||||
def closing_tag(state, text, i, formats, user_data):
|
||||
@ -502,6 +508,7 @@ def create_formats(highlighter, add_css=True):
|
||||
'preproc': t['PreProc'],
|
||||
'nbsp': t['SpecialCharacter'],
|
||||
'spell': t['SpellError'],
|
||||
'link': t['Link'],
|
||||
}
|
||||
for name, msg in {
|
||||
'<': _('An unescaped < is not allowed. Replace it with <'),
|
||||
@ -520,6 +527,8 @@ def create_formats(highlighter, add_css=True):
|
||||
if add_css:
|
||||
formats['css_sub_formats'] = create_css_formats(highlighter)
|
||||
formats['spell'].setProperty(SPELL_PROPERTY, True)
|
||||
formats['link'].setProperty(LINK_PROPERTY, True)
|
||||
formats['link'].setToolTip(_('Hold down the Ctrl key and click to open this link'))
|
||||
return formats
|
||||
|
||||
|
||||
|
@ -14,11 +14,12 @@ import regex
|
||||
from PyQt4.Qt import (
|
||||
QPlainTextEdit, QFontDatabase, QToolTip, QPalette, QFont, QKeySequence,
|
||||
QTextEdit, QTextFormat, QWidget, QSize, QPainter, Qt, QRect, pyqtSlot,
|
||||
QApplication, QMimeData, QColor, QColorDialog, QTimer)
|
||||
QApplication, QMimeData, QColor, QColorDialog, QTimer, pyqtSignal)
|
||||
|
||||
from calibre import prepare_string_for_xml, xml_entity_to_unicode
|
||||
from calibre.gui2.tweak_book import tprefs, TOP
|
||||
from calibre.gui2.tweak_book.editor import SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale
|
||||
from calibre.gui2.tweak_book.editor import (
|
||||
SYNTAX_PROPERTY, SPELL_PROPERTY, SPELL_LOCALE_PROPERTY, store_locale, LINK_PROPERTY)
|
||||
from calibre.gui2.tweak_book.editor.themes import get_theme, theme_color, theme_format
|
||||
from calibre.gui2.tweak_book.editor.syntax.base import SyntaxHighlighter
|
||||
from calibre.gui2.tweak_book.editor.syntax.html import HTMLHighlighter, XMLHighlighter
|
||||
@ -130,6 +131,8 @@ class PlainTextEdit(QPlainTextEdit):
|
||||
|
||||
class TextEdit(PlainTextEdit):
|
||||
|
||||
link_clicked = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None, expected_geometry=(100, 50)):
|
||||
PlainTextEdit.__init__(self, parent)
|
||||
self.expected_geometry = expected_geometry
|
||||
@ -618,6 +621,21 @@ class TextEdit(PlainTextEdit):
|
||||
ev.ignore()
|
||||
# }}}
|
||||
|
||||
def link_for_position(self, pos):
|
||||
c = self.cursorForPosition(pos)
|
||||
r = self.syntax_range_for_cursor(c)
|
||||
if r is not None and r.format.property(LINK_PROPERTY).toBool():
|
||||
return self.text_for_range(c.block(), r)
|
||||
|
||||
def mousePressEvent(self, ev):
|
||||
if ev.modifiers() & Qt.CTRL:
|
||||
url = self.link_for_position(ev.pos())
|
||||
if url is not None:
|
||||
ev.accept()
|
||||
self.link_clicked.emit(url)
|
||||
return
|
||||
return PlainTextEdit.mousePressEvent(self, ev)
|
||||
|
||||
def get_range_inside_tag(self):
|
||||
c = self.textCursor()
|
||||
left = min(c.anchor(), c.position())
|
||||
|
@ -105,6 +105,7 @@ class Editor(QMainWindow):
|
||||
data_changed = pyqtSignal(object)
|
||||
cursor_position_changed = pyqtSignal()
|
||||
word_ignored = pyqtSignal(object, object)
|
||||
link_clicked = pyqtSignal(object)
|
||||
|
||||
def __init__(self, syntax, parent=None):
|
||||
QMainWindow.__init__(self, parent)
|
||||
@ -126,6 +127,7 @@ class Editor(QMainWindow):
|
||||
self.editor.textChanged.connect(self._data_changed)
|
||||
self.editor.copyAvailable.connect(self._copy_available)
|
||||
self.editor.cursorPositionChanged.connect(self._cursor_position_changed)
|
||||
self.editor.link_clicked.connect(self.link_clicked)
|
||||
|
||||
@dynamic_property
|
||||
def current_line(self):
|
||||
@ -300,12 +302,9 @@ class Editor(QMainWindow):
|
||||
add_action(name, self.format_bar)
|
||||
|
||||
def break_cycles(self):
|
||||
for x in ('modification_state_changed', 'word_ignored', 'link_clicked'):
|
||||
try:
|
||||
self.modification_state_changed.disconnect()
|
||||
except TypeError:
|
||||
pass # in case this signal was never connected
|
||||
try:
|
||||
self.word_ignored.disconnect()
|
||||
getattr(self, x).disconnect()
|
||||
except TypeError:
|
||||
pass # in case this signal was never connected
|
||||
self.undo_redo_state_changed.disconnect()
|
||||
@ -318,6 +317,7 @@ class Editor(QMainWindow):
|
||||
self.editor.textChanged.disconnect()
|
||||
self.editor.copyAvailable.disconnect()
|
||||
self.editor.cursorPositionChanged.disconnect()
|
||||
self.editor.link_clicked.disconnect()
|
||||
self.editor.setPlainText('')
|
||||
self.editor.smarts = None
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user