Restore link styling and clickability
Some checks failed
Lint / test (push) Waiting to run
Trivy / test (push) Waiting to run
CI / test (push) Has been cancelled

This commit is contained in:
Miguel Jacq 2025-11-15 12:32:45 +11:00
parent 97e723ce34
commit 22901d0e51
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
4 changed files with 93 additions and 10 deletions

View file

@ -3,6 +3,7 @@
* Make it possible to add a tag from the Tag Browser * Make it possible to add a tag from the Tag Browser
* Add a statistics dialog with heatmap * Add a statistics dialog with heatmap
* Remove export to .txt (just use .md) * Remove export to .txt (just use .md)
* Restore link styling and clickability
# 0.3 # 0.3

View file

@ -440,6 +440,7 @@ class MainWindow(QMainWindow):
return self._create_new_tab(date) return self._create_new_tab(date)
def _create_new_tab(self, date: QDate | None = None) -> MarkdownEditor: def _create_new_tab(self, date: QDate | None = None) -> MarkdownEditor:
"""Create a new editor tab and return the editor instance."""
if date is None: if date is None:
date = self.calendar.selectedDate() date = self.calendar.selectedDate()
@ -449,7 +450,6 @@ class MainWindow(QMainWindow):
self.tab_widget.setCurrentIndex(existing) self.tab_widget.setCurrentIndex(existing)
return self.tab_widget.widget(existing) return self.tab_widget.widget(existing)
"""Create a new editor tab and return the editor instance."""
editor = MarkdownEditor(self.themes) editor = MarkdownEditor(self.themes)
# Set up the editor's event connections # Set up the editor's event connections
@ -1129,6 +1129,15 @@ class MainWindow(QMainWindow):
self._load_selected_date() self._load_selected_date()
self._refresh_calendar_marks() self._refresh_calendar_marks()
# ------------ Statistics handler --------------- #
def _open_statistics(self):
if not getattr(self, "db", None) or self.db.conn is None:
return
dlg = StatisticsDialog(self.db, self)
dlg.exec()
# ------------ Window positioning --------------- # # ------------ Window positioning --------------- #
def _restore_window_position(self): def _restore_window_position(self):
geom = self.settings.value("main/geometry", None) geom = self.settings.value("main/geometry", None)
@ -1434,11 +1443,3 @@ class MainWindow(QMainWindow):
super().changeEvent(ev) super().changeEvent(ev)
if ev.type() == QEvent.ActivationChange and self.isActiveWindow(): if ev.type() == QEvent.ActivationChange and self.isActiveWindow():
QTimer.singleShot(0, self._focus_editor_now) QTimer.singleShot(0, self._focus_editor_now)
def _open_statistics(self):
# If the DB isn't ready for some reason, just do nothing
if not getattr(self, "db", None) or self.db.conn is None:
return
dlg = StatisticsDialog(self.db, self)
dlg.exec()

View file

@ -14,8 +14,9 @@ from PySide6.QtGui import (
QTextFormat, QTextFormat,
QTextBlockFormat, QTextBlockFormat,
QTextImageFormat, QTextImageFormat,
QDesktopServices,
) )
from PySide6.QtCore import Qt, QRect, QTimer from PySide6.QtCore import Qt, QRect, QTimer, QUrl
from PySide6.QtWidgets import QTextEdit from PySide6.QtWidgets import QTextEdit
from .theme import ThemeManager from .theme import ThemeManager
@ -32,6 +33,9 @@ class MarkdownEditor(QTextEdit):
self.theme_manager = theme_manager self.theme_manager = theme_manager
# Track hyperlink under click
self._clicked_link: str | None = None
# Setup tab width # Setup tab width
tab_w = 4 * self.fontMetrics().horizontalAdvance(" ") tab_w = 4 * self.fontMetrics().horizontalAdvance(" ")
self.setTabStopDistance(tab_w) self.setTabStopDistance(tab_w)
@ -70,6 +74,11 @@ class MarkdownEditor(QTextEdit):
# Enable mouse tracking for checkbox clicking # Enable mouse tracking for checkbox clicking
self.viewport().setMouseTracking(True) self.viewport().setMouseTracking(True)
# Also mark links as mouse-accessible
flags = self.textInteractionFlags()
self.setTextInteractionFlags(
flags | Qt.TextInteractionFlag.LinksAccessibleByMouse
)
def setDocument(self, doc): def setDocument(self, doc):
super().setDocument(doc) super().setDocument(doc)
@ -400,6 +409,28 @@ class MarkdownEditor(QTextEdit):
return (None, "") return (None, "")
def _url_at_pos(self, pos) -> str | None:
"""
Return the URL under the given widget position, or None if there isn't one.
"""
cursor = self.cursorForPosition(pos)
block = cursor.block()
text = block.text()
if not text:
return None
# Position of the cursor inside this block
pos_in_block = cursor.position() - block.position()
# Same pattern as in MarkdownHighlighter
url_pattern = re.compile(r"(https?://[^\s<>()]+)")
for m in url_pattern.finditer(text):
start, end = m.span(1)
if start <= pos_in_block < end:
return m.group(1)
return None
def keyPressEvent(self, event): def keyPressEvent(self, event):
"""Handle special key events for markdown editing.""" """Handle special key events for markdown editing."""
@ -622,6 +653,37 @@ class MarkdownEditor(QTextEdit):
# Default handling # Default handling
super().keyPressEvent(event) super().keyPressEvent(event)
def mouseMoveEvent(self, event):
# Change cursor when hovering a link
url = self._url_at_pos(event.pos())
if url:
self.viewport().setCursor(Qt.PointingHandCursor)
else:
self.viewport().setCursor(Qt.IBeamCursor)
super().mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
# Let QTextEdit handle caret/selection first
super().mouseReleaseEvent(event)
if event.button() != Qt.LeftButton:
return
# If the user dragged to select text, don't treat it as a click
if self.textCursor().hasSelection():
return
url_str = self._url_at_pos(event.pos())
if not url_str:
return
url = QUrl(url_str)
if not url.scheme():
url.setScheme("https")
QDesktopServices.openUrl(url)
def mousePressEvent(self, event): def mousePressEvent(self, event):
"""Toggle a checkbox only when the click lands on its icon.""" """Toggle a checkbox only when the click lands on its icon."""
if event.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:

View file

@ -91,6 +91,13 @@ class MarkdownHighlighter(QSyntaxHighlighter):
self.h3_format.setFontPointSize(14.0) self.h3_format.setFontPointSize(14.0)
self.h3_format.setFontWeight(QFont.Weight.Bold) self.h3_format.setFontWeight(QFont.Weight.Bold)
# Hyperlinks
self.link_format = QTextCharFormat()
link_color = pal.color(QPalette.Link)
self.link_format.setForeground(link_color)
self.link_format.setFontUnderline(True)
self.link_format.setAnchor(True)
# Markdown syntax (the markers themselves) - make invisible # Markdown syntax (the markers themselves) - make invisible
self.syntax_format = QTextCharFormat() self.syntax_format = QTextCharFormat()
# Make the markers invisible by setting font size to 0.1 points # Make the markers invisible by setting font size to 0.1 points
@ -243,3 +250,15 @@ class MarkdownHighlighter(QSyntaxHighlighter):
self.setFormat(start, 1, self.syntax_format) self.setFormat(start, 1, self.syntax_format)
self.setFormat(end - 1, 1, self.syntax_format) self.setFormat(end - 1, 1, self.syntax_format)
self.setFormat(content_start, content_end - content_start, self.code_format) self.setFormat(content_start, content_end - content_start, self.code_format)
# Hyperlinks
url_pattern = re.compile(r"(https?://[^\s<>()]+)")
for m in url_pattern.finditer(text):
start, end = m.span(1)
url = m.group(1)
# Clone link format so we can attach a per-link href
fmt = QTextCharFormat(self.link_format)
fmt.setAnchorHref(url)
# Overlay link attributes on top of whatever formatting is already there
self._overlay_range(start, end - start, fmt)