V3 WIP: ESC works in editing

This commit is contained in:
Keith Edmunds 2023-10-21 11:03:03 +01:00
parent b75dc4256a
commit 93d780f75a
4 changed files with 90 additions and 109 deletions

View File

@ -50,8 +50,6 @@ class MyTableWidget(QTableView):
super().__init__(parent) super().__init__(parent)
self.setItemDelegate(EscapeDelegate(self)) self.setItemDelegate(EscapeDelegate(self))
self.setModel(MyModel()) self.setModel(MyModel())
self.resizeColumnsToContents()
self.resizeRowsToContents()
class MyModel(QAbstractTableModel): class MyModel(QAbstractTableModel):
@ -82,6 +80,8 @@ class MainWindow(QMainWindow):
self.table_widget = MyTableWidget(self) self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget) self.setCentralWidget(self.table_widget)
self.table_widget.resizeColumnsToContents()
self.table_widget.resizeRowsToContents()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -67,7 +67,7 @@ import icons_rc # noqa F401
import music import music
from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks from models import Base, Carts, Playdates, PlaylistRows, Playlists, Settings, Tracks
from config import Config 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_cart_ui import Ui_DialogCartEdit # type: ignore
from ui.dlg_search_database_ui import Ui_Dialog # type: ignore from ui.dlg_search_database_ui import Ui_Dialog # type: ignore
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # 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) set_next_track_signal = pyqtSignal(int, int)
span_cells_signal = pyqtSignal(int, int, int, int)
enable_escape_signal = pyqtSignal(bool)
class PlaylistTrack: class PlaylistTrack:
@ -327,7 +329,7 @@ class PlaylistTrack:
class Window(QMainWindow, Ui_MainWindow): class Window(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs) -> None: def __init__(self, parent=None, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.timer10: QTimer = QTimer() self.timer10: QTimer = QTimer()
@ -690,6 +692,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.tabBar.tabMoved.connect(self.move_tab) self.tabBar.tabMoved.connect(self.move_tab)
self.txtSearch.returnPressed.connect(self.search_playlist_return) 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.timer10.timeout.connect(self.tick_10ms)
self.timer500.timeout.connect(self.tick_500ms) self.timer500.timeout.connect(self.tick_500ms)
self.timer1000.timeout.connect(self.tick_1000ms) self.timer1000.timeout.connect(self.tick_1000ms)
@ -810,6 +814,16 @@ class Window(QMainWindow, Ui_MainWindow):
else: else:
self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False) 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: def enable_play_next_controls(self) -> None:
""" """
Enable "play next" keyboard controls Enable "play next" keyboard controls

View File

@ -168,6 +168,20 @@ class PlaylistModel(QAbstractTableModel):
# Fall through to no-op # Fall through to no-op
return QVariant() 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: def display_role(self, row: int, column: int, prd: PlaylistRowData) -> QVariant:
""" """
Return text for display Return text for display

View File

@ -56,7 +56,7 @@ if TYPE_CHECKING:
# section_header_cleanup_re = re.compile(r"(@\d\d:\d\d:\d\d.*)?(\+)?") # 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") # start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
# HEADER_NOTES_COLUMN = 2 HEADER_NOTES_COLUMN = 2
# # Columns # # Columns
# Column = namedtuple("Column", ["idx", "heading"]) # Column = namedtuple("Column", ["idx", "heading"])
@ -85,131 +85,81 @@ if TYPE_CHECKING:
class EscapeDelegate(QStyledItemDelegate): 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) super().__init__(parent)
self.signals = signals
def createEditor(self, parent, option, index): def createEditor(
return QPlainTextEdit(parent) 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""" """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: if event.type() == QEvent.Type.KeyPress:
key_event = cast(QKeyEvent, event) key_event = cast(QKeyEvent, event)
print(key_event) print(key_event.key())
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):
print(">>>save data")
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:
# print("discard edits") discard_edits = QMessageBox.question(
# discard_edits = QMessageBox.question( self.parent(), "Abandon edit", "Discard changes?"
# self.parent(), "Abandon edit", "Discard changes?" )
# ) if discard_edits == QMessageBox.StandardButton.Yes:
# if discard_edits == QMessageBox.StandardButton.Yes: self.closeEditor.emit(editor)
# print("abandon edit") return True
# self.closeEditor.emit(editor)
# return True
return False return False
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
print("setEditorData()")
value = index.model().data(index, Qt.ItemDataRole.EditRole) value = index.model().data(index, Qt.ItemDataRole.EditRole)
editor.setPlainText(value.value()) editor.setPlainText(value.value())
def setModelData(self, editor, model, index): def setModelData(self, editor, model, index):
print("setModelData called") print("setModelData")
value = editor.toPlainText() value = editor.toPlainText()
model.setData(index, value, Qt.ItemDataRole.EditRole) model.setData(index, value, Qt.ItemDataRole.EditRole)
def updateEditorGeometry(self, editor, option, index): def updateEditorGeometry(self, editor, option, index):
print("updateEditorGeometry")
editor.setGeometry(option.rect) 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): class PlaylistTab(QTableView):
def __init__( def __init__(
@ -226,10 +176,10 @@ class PlaylistTab(QTableView):
self.signals = signals self.signals = signals
# Set up widget # Set up widget
self.setItemDelegate(EscapeDelegate(self)) self.setItemDelegate(EscapeDelegate(self, self.signals))
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
# self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
# self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
# self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) # self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked)
self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
# This dancing is to satisfy mypy # 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 # 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 # a change to the view and thus it refreshes the data which
# again calls us here. # 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 return
self.setSpan(row, column, rowSpan, columnSpan) self.setSpan(row, column, rowSpan, columnSpan)