Expang edit box working, code untidy

This commit is contained in:
Keith Edmunds 2024-12-11 15:34:48 +00:00
parent eaac2ef4ca
commit 4860c9f188

View File

@ -8,15 +8,17 @@ from PyQt6.QtCore import (
QModelIndex, QModelIndex,
QObject, QObject,
QItemSelection, QItemSelection,
QSize,
Qt, Qt,
QTimer, QTimer,
) )
from PyQt6.QtGui import QAction, QKeyEvent from PyQt6.QtGui import QAction, QKeyEvent, QTextDocument
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QAbstractItemDelegate, QAbstractItemDelegate,
QAbstractItemView, QAbstractItemView,
QApplication, QApplication,
QDoubleSpinBox, QDoubleSpinBox,
QFrame,
QHeaderView, QHeaderView,
QMenu, QMenu,
QMessageBox, QMessageBox,
@ -28,6 +30,7 @@ from PyQt6.QtWidgets import (
QStyleOptionViewItem, QStyleOptionViewItem,
QTableView, QTableView,
QTableWidgetItem, QTableWidgetItem,
QTextEdit,
QWidget, QWidget,
) )
@ -58,54 +61,119 @@ class EscapeDelegate(QStyledItemDelegate):
- 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 - positions cursor where double-click occurs
Parts inspired by https://stackoverflow.com/questions/69113867/
make-row-of-qtableview-expand-as-editor-grows-in-height
""" """
class EditorDocument(QTextDocument):
def __init__(self, parent):
super().__init__(parent)
self.setDocumentMargin(0)
self.contentsChange.connect(self.contents_change)
self.height = None
parent.setDocument(self)
def contents_change(self, position, chars_removed, chars_added):
def resize_func():
if self.size().height() != self.height:
doc_size = self.size()
self.parent().resize(int(doc_size.width()), int(doc_size.height()))
QTimer.singleShot(0, resize_func)
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 self.click_position = None # Store the mouse click position
self.current_editor = None
def createEditor( def createEditor(
self, self,
parent: Optional[QWidget], parent: Optional[QWidget],
option: QStyleOptionViewItem, option: QStyleOptionViewItem,
index: QModelIndex, index: QModelIndex,
) -> Optional[QDoubleSpinBox | QPlainTextEdit]: ) -> Optional[QDoubleSpinBox | QTextEdit]:
""" """
Intercept createEditor call and make row just a little bit taller Intercept createEditor call and make row just a little bit taller
""" """
editor: QDoubleSpinBox | QPlainTextEdit # editor: QDoubleSpinBox | QTextEdit
class Editor(QTextEdit):
def resizeEvent(self, event):
super().resizeEvent(event)
parent.parent().resizeRowToContents(index.row())
def keyPressEvent(self, event):
if event.modifiers() == Qt.KeyboardModifier.ControlModifier:
if event.key() == Qt.Key.Key_Return:
self.commitData.emit(editor)
# self.closeEditor.emit(editor)
return
# elif event.key() == Qt.Key.Key_Escape:
# # Close editor if no changes have been made
# data_modified = False
# if isinstance(editor, QPlainTextEdit):
# data_modified = self.original_model_data != editor.toPlainText()
# elif isinstance(editor, QDoubleSpinBox):
# data_modified = (
# self.original_model_data != int(editor.value()) * 1000
# )
# if not data_modified:
# self.closeEditor.emit(editor)
# return True
# discard_edits = QMessageBox.question(
# cast(QWidget, self.parent()), "Abandon edit", "Discard changes?"
# )
# if discard_edits == QMessageBox.StandardButton.Yes:
# self.closeEditor.emit(editor)
# return True
super().keyPressEvent(event)
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:
editor = QDoubleSpinBox(parent) # editor = QDoubleSpinBox(parent)
editor.setDecimals(1) # editor.setDecimals(1)
editor.setSingleStep(0.1) # editor.setSingleStep(0.1)
return editor # return editor
elif isinstance(index.data(), str): # elif isinstance(index.data(), str):
editor = QPlainTextEdit(parent) if self.current_editor:
editor.setGeometry(option.rect) # Match the cell geometry editor = self.current_editor
row = index.row() else:
row_height = p.rowHeight(row) editor = Editor(parent)
p.setRowHeight(row, row_height + Config.MINIMUM_ROW_HEIGHT) editor.setVerticalScrollBarPolicy(
return editor Qt.ScrollBarPolicy.ScrollBarAlwaysOff
)
editor.setFrameShape(QFrame.Shape.NoFrame)
self.current_editor = editor
# # 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 super().createEditor(parent, option, index) EscapeDelegate.EditorDocument(editor)
return editor
# return super().createEditor(parent, option, index)
def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None: def destroyEditor(self, editor: Optional[QWidget], index: QModelIndex) -> None:
""" """
Intercept editor destroyment Intercept editor destroyment
""" """
super().destroyEditor(editor, index)
self.current_editor = None
self.parent().resizeRowToContents(index.row())
self.signals.enable_escape_signal.emit(True) self.signals.enable_escape_signal.emit(True)
return super().destroyEditor(editor, index)
def editorEvent( def editorEvent(
self, event: QEvent, model: QAbstractItemModel, option, index: QModelIndex self, event: QEvent, model: QAbstractItemModel, option, index: QModelIndex
@ -123,7 +191,7 @@ class EscapeDelegate(QStyledItemDelegate):
return super().eventFilter(editor, event) return super().eventFilter(editor, event)
if event.type() == QEvent.Type.Show: if event.type() == QEvent.Type.Show:
if self.click_position and isinstance(editor, QPlainTextEdit): if self.click_position and isinstance(editor, QTextEdit):
# Map click position to editor's local space # Map click position to editor's local space
local_click_position = editor.mapFromParent(self.click_position) local_click_position = editor.mapFromParent(self.click_position)
@ -136,36 +204,20 @@ class EscapeDelegate(QStyledItemDelegate):
return super().eventFilter(editor, event) 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(editor, QPlainTextEdit):
data_modified = self.original_model_data != editor.toPlainText()
elif isinstance(editor, QDoubleSpinBox):
data_modified = (
self.original_model_data != int(editor.value()) * 1000
)
if not data_modified:
self.closeEditor.emit(editor)
return True
discard_edits = QMessageBox.question(
cast(QWidget, self.parent()), "Abandon edit", "Discard changes?"
)
if discard_edits == QMessageBox.StandardButton.Yes:
self.closeEditor.emit(editor)
return True
return False return False
def sizeHint(self, option, index):
self.initStyleOption(option, index)
if self.current_editor:
doc = self.current_editor.document()
else:
doc = QTextDocument()
doc.setTextWidth(option.rect.width())
doc.setDefaultFont(option.font)
doc.setDocumentMargin(0)
doc.setHtml(option.text)
return QSize(int(doc.idealWidth()), int(doc.size().height()))
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
proxy_model = index.model() proxy_model = index.model()
edit_index = proxy_model.mapToSource(index) edit_index = proxy_model.mapToSource(index)
@ -182,7 +234,7 @@ 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(editor, QPlainTextEdit): if isinstance(editor, QTextEdit):
value = editor.toPlainText().strip() value = editor.toPlainText().strip()
elif isinstance(editor, QDoubleSpinBox): elif isinstance(editor, QDoubleSpinBox):
value = editor.value() value = editor.value()
@ -264,7 +316,7 @@ class PlaylistTab(QTableView):
# Set up for Audacity # Set up for Audacity
try: try:
self.ac = AudacityController() self.ac: Optional[AudacityController] = AudacityController()
except ApplicationError as e: except ApplicationError as e:
self.ac = None self.ac = None
show_warning(self.musicmuster, "Audacity error", str(e)) show_warning(self.musicmuster, "Audacity error", str(e))
@ -272,9 +324,15 @@ class PlaylistTab(QTableView):
# Stretch last column *after* setting column widths which is # Stretch last column *after* setting column widths which is
# *much* faster # *much* faster
h_header = self.horizontalHeader() h_header = self.horizontalHeader()
if isinstance(h_header, QHeaderView): if h_header:
h_header.sectionResized.connect(self._column_resize) h_header.sectionResized.connect(self._column_resize)
h_header.setStretchLastSection(True) h_header.setStretchLastSection(True)
# Resize on vertical header click
v_header = self.verticalHeader()
if v_header:
v_header.setMinimumSectionSize(5)
v_header.sectionHandleDoubleClicked.disconnect()
v_header.sectionHandleDoubleClicked.connect(self.resizeRowToContents)
# Setting ResizeToContents causes screen flash on load # Setting ResizeToContents causes screen flash on load
self.resize_rows() self.resize_rows()
@ -370,6 +428,16 @@ class PlaylistTab(QTableView):
self.reset() self.reset()
super().mouseReleaseEvent(event) super().mouseReleaseEvent(event)
def resizeRowToContents(self, row):
super().resizeRowToContents(row)
self.verticalHeader().resizeSection(row, self.sizeHintForRow(row))
def resizeRowsToContents(self):
header = self.verticalHeader()
for row in range(self.model().rowCount()):
hint = self.sizeHintForRow(row)
header.resizeSection(row, hint)
def selectionChanged( def selectionChanged(
self, selected: QItemSelection, deselected: QItemSelection self, selected: QItemSelection, deselected: QItemSelection
) -> None: ) -> None: