diff --git a/novelwriter/formats/shared.py b/novelwriter/formats/shared.py index b7d7e7cc..f097e99a 100644 --- a/novelwriter/formats/shared.py +++ b/novelwriter/formats/shared.py @@ -46,6 +46,7 @@ class TextDocumentTheme: text: QColor = QColor(0, 0, 0) highlight: QColor = QColor(255, 255, 166) head: QColor = QColor(66, 113, 174) + link: QColor = QColor(66, 113, 174) comment: QColor = QColor(100, 100, 100) note: QColor = QColor(129, 55, 9) code: QColor = QColor(66, 113, 174) diff --git a/novelwriter/formats/tokenizer.py b/novelwriter/formats/tokenizer.py index f341cb1e..3ae28f63 100644 --- a/novelwriter/formats/tokenizer.py +++ b/novelwriter/formats/tokenizer.py @@ -1117,6 +1117,13 @@ class Tokenizer(ABC): for n, fmt in enumerate(fmts) if fmt > 0 ) + # Match URLs + for res in REGEX_PATTERNS.url.finditer(text): + s = res.start(0) + e = res.end(0) + temp.append((s, s, TextFmt.HRF_B, res.group(0))) + temp.append((e, e, TextFmt.HRF_E, "")) + # Match Shortcodes for res in REGEX_PATTERNS.shortcodePlain.finditer(text): temp.append(( diff --git a/novelwriter/formats/toqdoc.py b/novelwriter/formats/toqdoc.py index 295c9745..838ffb9f 100644 --- a/novelwriter/formats/toqdoc.py +++ b/novelwriter/formats/toqdoc.py @@ -29,7 +29,7 @@ from pathlib import Path from PyQt5.QtCore import QMarginsF, QSizeF from PyQt5.QtGui import ( - QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat, + QColor, QFont, QFontMetricsF, QPageSize, QTextBlockFormat, QTextCharFormat, QTextCursor, QTextDocument ) from PyQt5.QtPrintSupport import QPrinter @@ -281,8 +281,9 @@ class ToQTextDocument(Tokenizer): ) -> None: """Apply formatting tags to text.""" cFmt = QTextCharFormat(dFmt) - start = 0 temp = text.replace("\n", nwUnicode.U_LSEP) + start = 0 + primary: QColor | None = None for pos, fmt, data in tFmt: # Insert buffer with previous format @@ -320,20 +321,25 @@ class ToQTextDocument(Tokenizer): elif fmt == TextFmt.COL_B: if color := self._classes.get(data): cFmt.setForeground(color) + primary = color elif fmt == TextFmt.COL_E: cFmt.setForeground(self._theme.text) + primary = None elif fmt == TextFmt.ANM_B: cFmt.setAnchor(True) cFmt.setAnchorNames([data]) elif fmt == TextFmt.ANM_E: cFmt.setAnchor(False) elif fmt == TextFmt.HRF_B: + cFmt.setForeground(primary or self._theme.link) cFmt.setFontUnderline(True) cFmt.setAnchor(True) cFmt.setAnchorHref(data) elif fmt == TextFmt.HRF_E: + cFmt.setForeground(primary or self._theme.text) cFmt.setFontUnderline(False) cFmt.setAnchor(False) + cFmt.setAnchorHref("") elif fmt == TextFmt.FNOTE: xFmt = QTextCharFormat(self._charFmt) xFmt.setForeground(self._theme.code) diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index de25da81..698ad8bc 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -29,7 +29,10 @@ from time import time from typing import TYPE_CHECKING from PyQt5.QtCore import Qt, QTimer, QUrl, pyqtSignal, pyqtSlot -from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent, QTextDocument +from PyQt5.QtGui import ( + QCloseEvent, QColor, QCursor, QDesktopServices, QFont, QPalette, + QResizeEvent, QTextDocument +) from PyQt5.QtPrintSupport import QPrinter, QPrintPreviewDialog from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QFormLayout, QGridLayout, QHBoxLayout, @@ -711,6 +714,7 @@ class _PreviewWidget(QTextBrowser): self.setMinimumWidth(40*SHARED.theme.textNWidth) self.setTabStopDistance(CONFIG.getTabWidth()) self.setOpenExternalLinks(False) + self.setOpenLinks(False) self.document().setDocumentMargin(CONFIG.getTextMargin()) self.setPlaceholderText(self.tr( @@ -719,6 +723,9 @@ class _PreviewWidget(QTextBrowser): self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn) + # Signals + self.anchorClicked.connect(self._linkClicked) + # Document Age aPalette = self.palette() aPalette.setColor(QPalette.ColorRole.Window, aPalette.toolTipBase().color()) @@ -855,6 +862,17 @@ class _PreviewWidget(QTextBrowser): # Private Slots ## + @pyqtSlot("QUrl") + def _linkClicked(self, url: QUrl) -> None: + """Process a clicked link in the document.""" + if link := url.url(): + logger.debug("Clicked link: '%s'", link) + if link.startswith("#"): + self.navigateTo(link.lstrip("#")) + elif link.startswith("http"): + QDesktopServices.openUrl(QUrl(url)) + return + @pyqtSlot() def _updateBuildAge(self) -> None: """Update the build time and the fuzzy age."""