From 93d780f75ab4892db84deedf4c208c9b2192b717 Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sat, 21 Oct 2023 11:03:03 +0100 Subject: [PATCH] V3 WIP: ESC works in editing --- InterceptEscapeWhenEditingTableCellInView.py | 4 +- app/musicmuster.py | 18 +- app/playlistmodel.py | 14 ++ app/playlists_v3.py | 163 +++++++------------ 4 files changed, 90 insertions(+), 109 deletions(-) diff --git a/InterceptEscapeWhenEditingTableCellInView.py b/InterceptEscapeWhenEditingTableCellInView.py index 1c8b543..71bbac6 100755 --- a/InterceptEscapeWhenEditingTableCellInView.py +++ b/InterceptEscapeWhenEditingTableCellInView.py @@ -50,8 +50,6 @@ class MyTableWidget(QTableView): super().__init__(parent) self.setItemDelegate(EscapeDelegate(self)) self.setModel(MyModel()) - self.resizeColumnsToContents() - self.resizeRowsToContents() class MyModel(QAbstractTableModel): @@ -82,6 +80,8 @@ class MainWindow(QMainWindow): self.table_widget = MyTableWidget(self) self.setCentralWidget(self.table_widget) + self.table_widget.resizeColumnsToContents() + self.table_widget.resizeRowsToContents() if __name__ == "__main__": diff --git a/app/musicmuster.py b/app/musicmuster.py index 4f51af7..0170126 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -67,7 +67,7 @@ import icons_rc # noqa F401 import music from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks from config import Config -from playlists import PlaylistTab +from playlists_v3 import PlaylistTab from ui.dlg_cart_ui import Ui_DialogCartEdit # type: ignore from ui.dlg_search_database_ui import Ui_Dialog # type: ignore from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore @@ -245,6 +245,8 @@ class MusicMusterSignals(QObject): """ set_next_track_signal = pyqtSignal(int, int) + span_cells_signal = pyqtSignal(int, int, int, int) + enable_escape_signal = pyqtSignal(bool) class PlaylistTrack: @@ -327,7 +329,7 @@ class PlaylistTrack: class Window(QMainWindow, Ui_MainWindow): def __init__(self, parent=None, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) + super().__init__(parent) self.setupUi(self) self.timer10: QTimer = QTimer() @@ -690,6 +692,8 @@ class Window(QMainWindow, Ui_MainWindow): self.tabBar.tabMoved.connect(self.move_tab) self.txtSearch.returnPressed.connect(self.search_playlist_return) + self.signals.enable_escape_signal.connect(self.enable_escape) + self.timer10.timeout.connect(self.tick_10ms) self.timer500.timeout.connect(self.tick_500ms) self.timer1000.timeout.connect(self.tick_1000ms) @@ -810,6 +814,16 @@ class Window(QMainWindow, Ui_MainWindow): else: self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False) + def enable_escape(self, enabled: bool) -> None: + """ + Manage signal to enable/disable handling ESC character. + + Needed because we want to use ESC when editing playlist in place, + so we need to disable it here while editing. + """ + + self.action_Clear_selection.setEnabled(enabled) + def enable_play_next_controls(self) -> None: """ Enable "play next" keyboard controls diff --git a/app/playlistmodel.py b/app/playlistmodel.py index 91b73b4..6016cfc 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -168,6 +168,20 @@ class PlaylistModel(QAbstractTableModel): # Fall through to no-op return QVariant() + def edit_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant: + """ + Return text for editing + """ + + if column == Col.TITLE.value: + return QVariant(prd.title) + if column == Col.ARTIST.value: + return QVariant(prd.artist) + if column == Col.NOTE.value: + return QVariant(prd.note) + + return QVariant() + def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant: """ Return text for display diff --git a/app/playlists_v3.py b/app/playlists_v3.py index b8ab402..d887dc5 100644 --- a/app/playlists_v3.py +++ b/app/playlists_v3.py @@ -56,7 +56,7 @@ if TYPE_CHECKING: # section_header_cleanup_re = re.compile(r"(@\d\d:\d\d:\d\d.*)?(\+)?") # start_time_re = re.compile(r"@\d\d:\d\d:\d\d") -# HEADER_NOTES_COLUMN = 2 +HEADER_NOTES_COLUMN = 2 # # Columns # Column = namedtuple("Column", ["idx", "heading"]) @@ -85,131 +85,81 @@ if TYPE_CHECKING: class EscapeDelegate(QStyledItemDelegate): - def __init__(self, parent=None): + """ + - 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, signals: "MusicMusterSignals") -> None: super().__init__(parent) + self.signals = signals - def createEditor(self, parent, option, index): - return QPlainTextEdit(parent) + def createEditor( + self, + parent: Optional[QWidget], + option: QStyleOptionViewItem, + index: QModelIndex, + ): + """ + Intercept createEditor call and make row just a little bit taller + """ - def eventFilter(self, editor: QObject, event: QEvent): + self.signals.enable_escape_signal.emit(False) + if isinstance(self.parent(), PlaylistTab): + p = cast(PlaylistTab, self.parent()) + if isinstance(index.data(), str): + row = index.row() + row_height = p.rowHeight(row) + p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT) + return QPlainTextEdit(parent) + return super().createEditor(parent, option, index) + + def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None: + """ + Intercept editor destroyment + """ + + self.signals.enable_escape_signal.emit(True) + return super().destroyEditor(editor, 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 + if event.type() == QEvent.Type.KeyPress: key_event = cast(QKeyEvent, event) - print(key_event) + print(key_event.key()) 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: - # print("discard edits") - # 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 + 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 def setEditorData(self, editor, index): + print("setEditorData()") value = index.model().data(index, Qt.ItemDataRole.EditRole) editor.setPlainText(value.value()) def setModelData(self, editor, model, index): - print("setModelData called") + print("setModelData") value = editor.toPlainText() model.setData(index, value, Qt.ItemDataRole.EditRole) def updateEditorGeometry(self, editor, option, index): + print("updateEditorGeometry") editor.setGeometry(option.rect) -# 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 -# """ - -# def __init__(self, parent) -> None: -# super().__init__(parent) - -# def createEditor( -# self, -# parent: Optional[QWidget], -# option: QStyleOptionViewItem, -# index: QModelIndex, -# ): -# """ -# Intercept createEditor call and make row just a little bit taller -# """ -# return QPlainTextEdit(parent) - -# # kae if isinstance(self.parent(), PlaylistTab): -# # kae p = cast(PlaylistTab, self.parent()) -# # kae if isinstance(index.data(), str): -# # kae row = index.row() -# # kae row_height = p.rowHeight(row) -# # kae p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT) -# # kae return QPlainTextEdit(parent) -# # kae return super().createEditor(parent, option, index) - -# def eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool: -# """By default, QPlainTextEdit doesn't handle enter or return""" - -# # kae if editor is None or event is None: -# # kae print("event filter returning") -# # kae return False - -# # kae print(event.type()) - -# 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): -# self.commitData.emit(editor) -# self.closeEditor.emit(editor) -# return True -# elif key_event.key() == Qt.Key.Key_Escape: -# print("Escape pressed") -# discard_edits = QMessageBox.question( -# self.parent(), "Abandon edit", "Discard changes?" -# ) -# if discard_edits == QMessageBox.StandardButton.Yes: -# self.closeEditor.emit(editor) -# return True -# 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 -# # return False - class PlaylistTab(QTableView): def __init__( @@ -226,10 +176,10 @@ class PlaylistTab(QTableView): self.signals = signals # Set up widget - self.setItemDelegate(EscapeDelegate(self)) + self.setItemDelegate(EscapeDelegate(self, self.signals)) self.setAlternatingRowColors(True) - # self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - # self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) # self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) # This dancing is to satisfy mypy @@ -2453,7 +2403,10 @@ class PlaylistTab(QTableView): # Don't set spanning if already in place because that is seen as # a change to the view and thus it refreshes the data which # again calls us here. - if self.rowSpan(row, column) == rowSpan and self.columnSpan(row, column) == columnSpan: + if ( + self.rowSpan(row, column) == rowSpan + and self.columnSpan(row, column) == columnSpan + ): return self.setSpan(row, column, rowSpan, columnSpan)