Restore link styling and clickability
This commit is contained in:
parent
97e723ce34
commit
22901d0e51
4 changed files with 93 additions and 10 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue