Ensure checkbox only can get checked on/off if it is clicked right on its block position, not any click on the whole line

This commit is contained in:
Miguel Jacq 2025-11-10 08:25:51 +11:00
parent dfde0d6e6c
commit bc9fa86281
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9

View file

@ -8,6 +8,7 @@ from PySide6.QtGui import (
QColor, QColor,
QFont, QFont,
QFontDatabase, QFontDatabase,
QFontMetrics,
QImage, QImage,
QPalette, QPalette,
QGuiApplication, QGuiApplication,
@ -17,7 +18,7 @@ from PySide6.QtGui import (
QSyntaxHighlighter, QSyntaxHighlighter,
QTextImageFormat, QTextImageFormat,
) )
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QRect
from PySide6.QtWidgets import QTextEdit from PySide6.QtWidgets import QTextEdit
from .theme import ThemeManager, Theme from .theme import ThemeManager, Theme
@ -525,34 +526,73 @@ class MarkdownEditor(QTextEdit):
super().keyPressEvent(event) super().keyPressEvent(event)
def mousePressEvent(self, event): def mousePressEvent(self, event):
"""Handle mouse clicks - check for checkbox clicking.""" """Toggle a checkbox only when the click lands on its icon."""
if event.button() == Qt.MouseButton.LeftButton: if event.button() == Qt.LeftButton:
cursor = self.cursorForPosition(event.pos()) pt = event.pos()
cursor.select(QTextCursor.SelectionType.LineUnderCursor)
line = cursor.selectedText()
# Check if clicking on a checkbox line # Cursor and block under the mouse
if ( cur = self.cursorForPosition(pt)
f"{self._CHECK_UNCHECKED_DISPLAY} " in line block = cur.block()
or f"{self._CHECK_CHECKED_DISPLAY} " in line text = block.text()
):
# Toggle the checkbox # The display tokens, e.g. "☐ " / "☑ " (icon + trailing space)
if f"{self._CHECK_UNCHECKED_DISPLAY} " in line: unchecked = f"{self._CHECK_UNCHECKED_DISPLAY} "
new_line = line.replace( checked = f"{self._CHECK_CHECKED_DISPLAY} "
f"{self._CHECK_UNCHECKED_DISPLAY} ",
f"{self._CHECK_CHECKED_DISPLAY} ", # Helper: rect for a single character at a given doc position
def char_rect_at(doc_pos, ch):
c = QTextCursor(self.document())
c.setPosition(doc_pos)
start_rect = self.cursorRect(
c
) # caret rect at char start (viewport coords)
# Use the actual font at this position for an accurate width
fmt_font = (
c.charFormat().font() if c.charFormat().isValid() else self.font()
) )
fm = QFontMetrics(fmt_font)
w = max(1, fm.horizontalAdvance(ch))
return QRect(start_rect.x(), start_rect.y(), w, start_rect.height())
# Scan the line for any checkbox icons; toggle the one we clicked
i = 0
while i < len(text):
icon = None
if text.startswith(unchecked, i):
icon = self._CHECK_UNCHECKED_DISPLAY
elif text.startswith(checked, i):
icon = self._CHECK_CHECKED_DISPLAY
if icon:
doc_pos = (
block.position() + i
) # absolute document position of the icon
r = char_rect_at(doc_pos, icon)
if r.contains(pt):
# Build the replacement: swap ☐ <-> ☑ (keep trailing space)
new_icon = (
self._CHECK_CHECKED_DISPLAY
if icon == self._CHECK_UNCHECKED_DISPLAY
else self._CHECK_UNCHECKED_DISPLAY
)
edit = QTextCursor(self.document())
edit.beginEditBlock()
edit.setPosition(doc_pos)
edit.movePosition(
QTextCursor.Right, QTextCursor.KeepAnchor, len(icon) + 1
) # icon + space
edit.insertText(f"{new_icon} ")
edit.endEditBlock()
return # handled
# advance past this token
i += len(icon) + 1
else: else:
new_line = line.replace( i += 1
f"{self._CHECK_CHECKED_DISPLAY} ",
f"{self._CHECK_UNCHECKED_DISPLAY} ",
)
cursor.insertText(new_line) # Default handling for anything else
# Don't call super() - we handled the click
return
# Default handling for non-checkbox clicks
super().mousePressEvent(event) super().mousePressEvent(event)
# ------------------------ Toolbar action handlers ------------------------ # ------------------------ Toolbar action handlers ------------------------