From 18398aafea5877178df334078dc981aab001e1e5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 2 Oct 2019 12:21:16 +0530 Subject: [PATCH] Insert hyperlink should preserve formatting of selected text --- src/calibre/gui2/comments_editor.py | 54 +++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/comments_editor.py b/src/calibre/gui2/comments_editor.py index 864da61521..68851a8971 100644 --- a/src/calibre/gui2/comments_editor.py +++ b/src/calibre/gui2/comments_editor.py @@ -23,10 +23,10 @@ from PyQt5.Qt import ( from calibre import xml_replace_entities from calibre.ebooks.chardet import xml_to_unicode from calibre.gui2 import NO_URL_FORMATTING, choose_files, error_dialog, gprefs +from calibre.gui2.book_details import css from calibre.gui2.widgets import LineEditECM from calibre.utils.config import tweaks from calibre.utils.imghdr import what -from calibre.gui2.book_details import css from polyglot.builtins import filter, iteritems, itervalues, unicode_type # Cleanup Qt markup {{{ @@ -132,6 +132,38 @@ def use_implicit_styling_for_a(a, style_map): break +def merge_contiguous_links(root): + all_hrefs = set(root.xpath('//a/@href')) + for href in all_hrefs: + tags = root.xpath('//a[@href="{}"]'.format(href)) + processed = set() + + def insert_tag(parent, child): + parent.tail = child.tail + if child.text: + children = parent.getchildren() + if children: + children[-1].tail = (children[-1].tail or '') + child.text + else: + parent.text = (parent.text or '') + child.text + for gc in child.iterchildren('*'): + parent.append(gc) + + for a in tags: + if a in processed or a.tail: + continue + processed.add(a) + n = a + remove = [] + while not n.tail and n.getnext() is not None and getattr(n.getnext(), 'tag', None) == 'a' and n.getnext().get('href') == href: + n = n.getnext() + processed.add(n) + remove.append(n) + for n in remove: + insert_tag(a, n) + n.getparent().remove(n) + + def cleanup_qt_markup(root): from calibre.ebooks.docx.cleanup import lift style_map = defaultdict(dict) @@ -158,6 +190,8 @@ def cleanup_qt_markup(root): tag.attrib.pop('style', None) for span in root.xpath('//span[not(@style)]'): lift(span) + + merge_contiguous_links(root) # }}} @@ -539,20 +573,25 @@ class EditorWidget(QTextEdit, LineEditECM): # {{{ url = unicode_type(url.toString(NO_URL_FORMATTING)) self.focus_self() with self.editing_cursor() as c: - selected_text = c.selectedText() if is_image: c.insertImage(url) else: - name = name or url fmt = QTextCharFormat() fmt.setAnchor(True) fmt.setAnchorHref(url) fmt.setFontUnderline(True) fmt.setForeground(QBrush(QColor('blue'))) - prev_fmt = c.charFormat() - c.mergeCharFormat(fmt) - c.insertText(selected_text or url) - c.setCharFormat(prev_fmt) + if name or not c.hasSelection(): + c.mergeCharFormat(fmt) + c.insertText(name or url) + else: + pos, anchor = c.position(), c.anchor() + start, end = min(pos, anchor), max(pos, anchor) + for i in range(start, end): + cur = self.textCursor() + cur.setPosition(i), cur.setPosition(i + 1, c.KeepAnchor) + cur.mergeCharFormat(fmt) + else: error_dialog(self, _('Invalid URL'), _('The url %r is invalid') % link, show=True) @@ -1138,5 +1177,6 @@ if __name__ == '__main__': w.html = '''

Test Heading

Test blockquote

He hadn't set out to have an affair, much less a long-term, devoted one.

hello''' + w.html = '

Testing a link.

' app.exec_() # print w.html