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