Put cursor at click position on edit

Fixes #150
This commit is contained in:
Keith Edmunds 2024-12-01 15:31:43 +00:00
parent 8c33db170d
commit ecd5c65695

View File

@ -1,9 +1,9 @@
# Standard library imports # Standard library imports
from typing import Callable, cast, List, Optional, TYPE_CHECKING from typing import Callable, cast, List, Optional, TYPE_CHECKING
import time
# PyQt imports # PyQt imports
from PyQt6.QtCore import ( from PyQt6.QtCore import (
QAbstractItemModel,
QEvent, QEvent,
QModelIndex, QModelIndex,
QObject, QObject,
@ -57,40 +57,46 @@ class EscapeDelegate(QStyledItemDelegate):
- increases the height of a row when editing to make editing easier - increases the height of a row when editing to make editing easier
- closes the edit on control-return - closes the edit on control-return
- checks with user before abandoning edit on Escape - checks with user before abandoning edit on Escape
- positions cursor where double-click occurs
""" """
def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None: def __init__(self, parent: QWidget, source_model: PlaylistModel) -> None:
super().__init__(parent) super().__init__(parent)
self.source_model = source_model self.source_model = source_model
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.click_position = None # Store the mouse click position
def createEditor( def createEditor(
self, self,
parent: Optional[QWidget], parent: Optional[QWidget],
option: QStyleOptionViewItem, option: QStyleOptionViewItem,
index: QModelIndex, index: QModelIndex,
) -> Optional[QWidget]: ) -> Optional[QDoubleSpinBox | QPlainTextEdit]:
""" """
Intercept createEditor call and make row just a little bit taller Intercept createEditor call and make row just a little bit taller
""" """
self.editor: QDoubleSpinBox | QPlainTextEdit editor: QDoubleSpinBox | QPlainTextEdit
self.signals = MusicMusterSignals() self.signals = MusicMusterSignals()
self.signals.enable_escape_signal.emit(False) self.signals.enable_escape_signal.emit(False)
if isinstance(self.parent(), PlaylistTab): if isinstance(self.parent(), PlaylistTab):
p = cast(PlaylistTab, self.parent()) p = cast(PlaylistTab, self.parent())
if index.column() == Col.INTRO.value: if index.column() == Col.INTRO.value:
self.editor = QDoubleSpinBox(parent) editor = QDoubleSpinBox(parent)
self.editor.setDecimals(1) editor.setDecimals(1)
self.editor.setSingleStep(0.1) editor.setSingleStep(0.1)
return self.editor return editor
elif isinstance(index.data(), str): elif isinstance(index.data(), str):
self.editor = QPlainTextEdit(parent) editor = QPlainTextEdit(parent)
editor.setGeometry(option.rect) # Match the cell geometry
row = index.row() row = index.row()
row_height = p.rowHeight(row) row_height = p.rowHeight(row)
p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT) p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT)
return self.editor return editor
return super().createEditor(parent, option, index) return super().createEditor(parent, option, index)
def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None: def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None:
@ -101,31 +107,53 @@ class EscapeDelegate(QStyledItemDelegate):
self.signals.enable_escape_signal.emit(True) self.signals.enable_escape_signal.emit(True)
return super().destroyEditor(editor, index) return super().destroyEditor(editor, index)
def editorEvent(
self, event: QEvent, model: QAbstractItemModel, option, index: QModelIndex
) -> bool:
"""Capture mouse click position."""
if event.type() == QEvent.Type.MouseButtonPress:
self.click_position = event.pos()
return super().editorEvent(event, model, option, index)
def eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool: def eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool:
"""By default, QPlainTextEdit doesn't handle enter or return""" """By default, QPlainTextEdit doesn't handle enter or return"""
if editor is None or event is None: if editor is None or event is None:
return False return super().eventFilter(editor, event)
if event.type() == QEvent.Type.KeyPress: if event.type() == QEvent.Type.Show:
if self.click_position and isinstance(editor, QPlainTextEdit):
# Map click position to editor's local space
local_click_position = editor.mapFromParent(self.click_position)
# Move cursor to the calculated position
cursor = editor.cursorForPosition(local_click_position)
editor.setTextCursor(cursor)
# Reset click position
self.click_position = None
return super().eventFilter(editor, event)
elif event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event) key_event = cast(QKeyEvent, event)
if key_event.key() == Qt.Key.Key_Return: if key_event.key() == Qt.Key.Key_Return:
if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier): if key_event.modifiers() == (Qt.KeyboardModifier.ControlModifier):
self.commitData.emit(editor) self.commitData.emit(editor)
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
elif key_event.key() == Qt.Key.Key_Escape: elif key_event.key() == Qt.Key.Key_Escape:
# Close editor if no changes have been made # Close editor if no changes have been made
data_modified = False data_modified = False
if isinstance(self.editor, QPlainTextEdit): if isinstance(editor, QPlainTextEdit):
data_modified = self.original_model_data != editor.toPlainText()
elif isinstance(editor, QDoubleSpinBox):
data_modified = ( data_modified = (
self.original_model_data == self.editor.toPlainText() self.original_model_data != int(editor.value()) * 1000
) )
elif isinstance(self.editor, QDoubleSpinBox): if not data_modified:
data_modified = (
self.original_model_data == int(self.editor.value()) * 1000
)
if data_modified:
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
@ -135,6 +163,7 @@ class EscapeDelegate(QStyledItemDelegate):
if discard_edits == QMessageBox.StandardButton.Yes: if discard_edits == QMessageBox.StandardButton.Yes:
self.closeEditor.emit(editor) self.closeEditor.emit(editor)
return True return True
return False return False
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
@ -153,9 +182,9 @@ class EscapeDelegate(QStyledItemDelegate):
proxy_model = index.model() proxy_model = index.model()
edit_index = proxy_model.mapToSource(index) edit_index = proxy_model.mapToSource(index)
if isinstance(self.editor, QPlainTextEdit): if isinstance(editor, QPlainTextEdit):
value = editor.toPlainText().strip() value = editor.toPlainText().strip()
elif isinstance(self.editor, QDoubleSpinBox): elif isinstance(editor, QDoubleSpinBox):
value = editor.value() value = editor.value()
self.source_model.setData(edit_index, value, Qt.ItemDataRole.EditRole) self.source_model.setData(edit_index, value, Qt.ItemDataRole.EditRole)