From f07ff569870be1cfa78d3843aef584510f973ee8 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Tue, 18 Apr 2023 21:33:48 +0100 Subject: [PATCH] Intercept ESC on cell edit --- InterceptEscapeWhenEditingTableCell.py | 81 ++++++++++++++++++++++++++ app/playlists.py | 36 +++++++----- 2 files changed, 101 insertions(+), 16 deletions(-) create mode 100755 InterceptEscapeWhenEditingTableCell.py diff --git a/InterceptEscapeWhenEditingTableCell.py b/InterceptEscapeWhenEditingTableCell.py new file mode 100755 index 0000000..d0e3eda --- /dev/null +++ b/InterceptEscapeWhenEditingTableCell.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +from PyQt6.QtCore import Qt, QEvent, QObject +from PyQt6.QtWidgets import ( + QAbstractItemDelegate, + QAbstractItemView, + QApplication, + QMainWindow, + QMessageBox, + QPlainTextEdit, + QStyledItemDelegate, + QStyleOptionViewItem, + QTableWidget, + QTableWidgetItem, +) + +from PyQt6.QtGui import QKeyEvent + +from typing import cast + + +class EscapeDelegate(QStyledItemDelegate): + def __init__(self, parent=None): + super().__init__(parent) + + def createEditor(self, parent, option, index): + return QPlainTextEdit(parent) + + def eventFilter(self, editor: QObject, event: QEvent): + """By default, QPlainTextEdit doesn't handle enter or return""" + + print("EscapeDelegate event handler") + if 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 + ): + print("save data") + self.commitData.emit(editor) + self.closeEditor.emit(editor) + return True + elif key_event.key() == Qt.Key.Key_Escape: + discard_edits = QMessageBox.question( + self.parent(), "Abandon edit", "Discard changes?") + if discard_edits == QMessageBox.StandardButton.Yes: + print("abandon edit") + self.closeEditor.emit(editor) + return True + return False + + +class MyTableWidget(QTableWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.setItemDelegate(EscapeDelegate(self)) + self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) + + +class MainWindow(QMainWindow): + def __init__(self, parent=None): + super().__init__(parent) + self.table_widget = MyTableWidget(self) + self.table_widget.setRowCount(2) + self.table_widget.setColumnCount(2) + for row in range(2): + for col in range(2): + item = QTableWidgetItem() + item.setText(f"Row {row}, Col {col}") + self.table_widget.setItem(row, col, item) + self.setCentralWidget(self.table_widget) + + self.table_widget.resizeColumnsToContents() + self.table_widget.resizeRowsToContents() + + +if __name__ == '__main__': + app = QApplication([]) + window = MainWindow() + window.show() + app.exec() diff --git a/app/playlists.py b/app/playlists.py index bd93e04..0963914 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -99,20 +99,16 @@ BITRATE = columns["bitrate"].idx ROW_NOTES = columns["row_notes"].idx -class NoSelectDelegate(QStyledItemDelegate): +class EscapeDelegate(QStyledItemDelegate): """ - This originally used the following link to not select text on edit; - however, using a QPlainTextBox means a) text isn't selected anyway and - b) it provides a multiline edit. - - https://stackoverflow.com/questions/72790705/ - dont-select-text-in-qtablewidget-cell-when-editing/72792962#72792962 - - Now this: - 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 """ + def __init__(self, parent) -> None: + super().__init__(parent) + def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex): """ @@ -128,7 +124,7 @@ class NoSelectDelegate(QStyledItemDelegate): return QPlainTextEdit(parent) return super().createEditor(parent, option, index) - def eventFilter(self, editor: QObject, event: QEvent): + def eventFilter(self, editor: QObject, event: QEvent) -> bool: """By default, QPlainTextEdit doesn't handle enter or return""" if event.type() == QEvent.Type.KeyPress: @@ -139,8 +135,14 @@ class NoSelectDelegate(QStyledItemDelegate): ): self.commitData.emit(editor) self.closeEditor.emit(editor) - - return super().eventFilter(editor, event) + return True + elif key_event.key() == Qt.Key.Key_Escape: + discard_edits = QMessageBox.question( + self.parent(), "Abandon edit", "Discard changes?") + if discard_edits == QMessageBox.StandardButton.Yes: + self.closeEditor.emit(editor) + return True + return False class PlaylistTab(QTableWidget): @@ -161,13 +163,13 @@ class PlaylistTab(QTableWidget): # Set up widget self.menu = QMenu() - self.setItemDelegate(NoSelectDelegate(self)) - self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) + self.setItemDelegate(EscapeDelegate(self)) self.setAlternatingRowColors(True) self.setSelectionMode( QAbstractItemView.SelectionMode.ExtendedSelection) self.setSelectionBehavior( QAbstractItemView.SelectionBehavior.SelectRows) + self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self.setRowCount(0) self.setColumnCount(len(columns)) @@ -291,8 +293,8 @@ class PlaylistTab(QTableWidget): self.setDragEnabled(False) super().mouseReleaseEvent(event) - # ########## Cell editing ########## - # +# ########## Cell editing ########## + # We only want to allow cell editing on tracks, artists and notes, # although notes may be section headers. # @@ -392,6 +394,7 @@ class PlaylistTab(QTableWidget): self.edit_cell_type = None self.musicmuster.enable_play_next_controls() self.musicmuster.actionSetNext.setEnabled(True) + self.musicmuster.action_Clear_selection.setEnabled(True) super(PlaylistTab, self).closeEditor(editor, hint) @@ -441,6 +444,7 @@ class PlaylistTab(QTableWidget): # disturb playing self.musicmuster.disable_play_next_controls() self.musicmuster.actionSetNext.setEnabled(False) + self.musicmuster.action_Clear_selection.setEnabled(False) # If this is a note cell, we need to remove any existing section # timing so user can't edit that. Keep it simple: refresh text