diff --git a/InterceptEscapeWhenEditingTableCell.py b/InterceptEscapeWhenEditingTableCell.py index e9bf50e..40d1490 100755 --- a/InterceptEscapeWhenEditingTableCell.py +++ b/InterceptEscapeWhenEditingTableCell.py @@ -51,7 +51,7 @@ class MyTableWidget(QTableWidget): def __init__(self, parent=None): super().__init__(parent) self.setItemDelegate(EscapeDelegate(self)) - self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) + # self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) class MainWindow(QMainWindow): diff --git a/InterceptEscapeWhenEditingTableCellInView.py b/InterceptEscapeWhenEditingTableCellInView.py new file mode 100755 index 0000000..5469e86 --- /dev/null +++ b/InterceptEscapeWhenEditingTableCellInView.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +from PyQt6.QtCore import Qt, QEvent, QObject, QVariant, QAbstractTableModel +from PyQt6.QtWidgets import ( + QApplication, + QMainWindow, + QMessageBox, + QPlainTextEdit, + QStyledItemDelegate, + QTableView, +) + +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) + print(key_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(QTableView): + def __init__(self, parent=None): + super().__init__(parent) + self.setItemDelegate(EscapeDelegate(self)) + self.setModel(MyModel()) + + +class MyModel(QAbstractTableModel): + + def columnCount(self, index): + return 2 + + def rowCount(self, index): + return 2 + + def data(self, index, role): + if not index.isValid() or not (0 <= index.row() < 2): + return QVariant() + + row = index.row() + column = index.column() + if role == Qt.ItemDataRole.DisplayRole: + return QVariant(f"Row {row}, Col {column}") + return QVariant + + def flags(self, index): + return Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsEditable + + +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/playlistmodel.py b/app/playlistmodel.py index 5773eae..703f6bc 100644 --- a/app/playlistmodel.py +++ b/app/playlistmodel.py @@ -91,11 +91,6 @@ class PlaylistModel(QAbstractTableModel): super().__init__(*args, **kwargs) self.playlist_rows: dict[int, PlaylistRowData] = {} - # self.current_row = None - # self.next_row = None - self.previous_row = None - self.current_row = 2 - self.next_row = 3 self.refresh_data() @@ -214,6 +209,20 @@ class PlaylistModel(QAbstractTableModel): return QVariant() + def flags(self, index: QModelIndex) -> Qt.ItemFlag: + """ + Standard model flags + """ + + if not index.isValid(): + return Qt.ItemFlag.ItemIsEnabled + + default = Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable + if index.column() in [Col.TITLE.value, Col.ARTIST.value, Col.NOTE.value]: + return default | Qt.ItemFlag.ItemIsEditable + + return default + def headerData( self, section: int, @@ -266,3 +275,13 @@ class PlaylistModel(QAbstractTableModel): """Standard function for view""" return len(self.playlist_rows) + + def setData( + self, index: QModelIndex, value: QVariant, role: int = Qt.ItemDataRole.EditRole + ) -> bool: + """ + Update model with edited data + """ + + print(f"setData({index.row()=}, {index.column()=}, {value=}, {role=})") + return True diff --git a/app/playlists_v3.py b/app/playlists_v3.py index ed2fa7b..e6cdbaf 100644 --- a/app/playlists_v3.py +++ b/app/playlists_v3.py @@ -6,16 +6,16 @@ import threading import obsws_python as obs # type: ignore -from collections import namedtuple +# from collections import namedtuple from datetime import datetime, timedelta -from typing import Any, Callable, cast, List, Optional, Tuple, TYPE_CHECKING, Union +from typing import Any, cast, List, Optional, Tuple, TYPE_CHECKING from PyQt6.QtCore import ( QEvent, QModelIndex, QObject, Qt, - QTimer, + # QTimer, ) from PyQt6.QtGui import QAction, QBrush, QColor, QFont, QDropEvent, QKeyEvent from PyQt6.QtWidgets import ( @@ -23,7 +23,7 @@ from PyQt6.QtWidgets import ( QAbstractItemView, QApplication, QHeaderView, - QMenu, + # QMenu, QMessageBox, QPlainTextEdit, QStyledItemDelegate, @@ -52,88 +52,151 @@ from playlistmodel import PlaylistModel if TYPE_CHECKING: from musicmuster import Window, MusicMusterSignals -scene_change_re = re.compile(r"SetScene=\[([^[\]]*)\]") -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") +# scene_change_re = re.compile(r"SetScene=\[([^[\]]*)\]") +# 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"]) -columns = {} -columns["userdata"] = Column(idx=0, heading=Config.COLUMN_NAME_AUTOPLAY) -columns["start_gap"] = Column(idx=1, heading=Config.COLUMN_NAME_LEADING_SILENCE) -columns["title"] = Column(idx=2, heading=Config.COLUMN_NAME_TITLE) -columns["artist"] = Column(idx=3, heading=Config.COLUMN_NAME_ARTIST) -columns["duration"] = Column(idx=4, heading=Config.COLUMN_NAME_LENGTH) -columns["start_time"] = Column(idx=5, heading=Config.COLUMN_NAME_START_TIME) -columns["end_time"] = Column(idx=6, heading=Config.COLUMN_NAME_END_TIME) -columns["lastplayed"] = Column(idx=7, heading=Config.COLUMN_NAME_LAST_PLAYED) -columns["bitrate"] = Column(idx=8, heading=Config.COLUMN_NAME_BITRATE) -columns["row_notes"] = Column(idx=9, heading=Config.COLUMN_NAME_NOTES) +# # Columns +# Column = namedtuple("Column", ["idx", "heading"]) +# columns = {} +# columns["userdata"] = Column(idx=0, heading=Config.COLUMN_NAME_AUTOPLAY) +# columns["start_gap"] = Column(idx=1, heading=Config.COLUMN_NAME_LEADING_SILENCE) +# columns["title"] = Column(idx=2, heading=Config.COLUMN_NAME_TITLE) +# columns["artist"] = Column(idx=3, heading=Config.COLUMN_NAME_ARTIST) +# columns["duration"] = Column(idx=4, heading=Config.COLUMN_NAME_LENGTH) +# columns["start_time"] = Column(idx=5, heading=Config.COLUMN_NAME_START_TIME) +# columns["end_time"] = Column(idx=6, heading=Config.COLUMN_NAME_END_TIME) +# columns["lastplayed"] = Column(idx=7, heading=Config.COLUMN_NAME_LAST_PLAYED) +# columns["bitrate"] = Column(idx=8, heading=Config.COLUMN_NAME_BITRATE) +# columns["row_notes"] = Column(idx=9, heading=Config.COLUMN_NAME_NOTES) -USERDATA = columns["userdata"].idx -START_GAP = columns["start_gap"].idx -TITLE = columns["title"].idx -ARTIST = columns["artist"].idx -DURATION = columns["duration"].idx -START_TIME = columns["start_time"].idx -END_TIME = columns["end_time"].idx -LASTPLAYED = columns["lastplayed"].idx -BITRATE = columns["bitrate"].idx -ROW_NOTES = columns["row_notes"].idx +# USERDATA = columns["userdata"].idx +# START_GAP = columns["start_gap"].idx +# TITLE = columns["title"].idx +# ARTIST = columns["artist"].idx +# DURATION = columns["duration"].idx +# START_TIME = columns["start_time"].idx +# END_TIME = columns["end_time"].idx +# LASTPLAYED = columns["lastplayed"].idx +# BITRATE = columns["bitrate"].idx +# ROW_NOTES = columns["row_notes"].idx 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: + 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 - """ + def createEditor(self, parent, option, index): + return QPlainTextEdit(parent) - 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 eventFilter(self, editor: Optional[QObject], event: Optional[QEvent]) -> bool: + def eventFilter(self, editor: QObject, event: QEvent): """By default, QPlainTextEdit doesn't handle enter or return""" - if editor is None or event is None: - return False - + print(">>EscapeDelegate event handler") if event.type() == QEvent.Type.KeyPress: key_event = cast(QKeyEvent, event) + print(key_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: - 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 return False +# 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): @@ -154,29 +217,25 @@ class PlaylistTab(QTableView): # Set up widget self.setItemDelegate(EscapeDelegate(self)) self.setAlternatingRowColors(True) - self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - self.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) - self.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked) + # 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 - h_header = self.horizontalHeader() - if isinstance(h_header, QHeaderView): - h_header.sectionResized.connect(self.resizeRowsToContents) - # Drag and drop setup - self.setAcceptDrops(True) - viewport = self.viewport() - if viewport: - viewport.setAcceptDrops(True) - self.setDragDropOverwriteMode(False) - self.setDropIndicatorShown(True) - self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) - self.setDragEnabled(False) + # self.setAcceptDrops(True) + # viewport = self.viewport() + # if viewport: + # viewport.setAcceptDrops(True) + # self.setDragDropOverwriteMode(False) + # self.setDropIndicatorShown(True) + # self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) + # self.setDragEnabled(False) # Prepare for context menu - self.menu = QMenu() - self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) - self.customContextMenuRequested.connect(self._context_menu) + # self.menu = QMenu() + # self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) + # self.customContextMenuRequested.connect(self._context_menu) # Connect signals # This dancing is to satisfy mypy @@ -189,7 +248,7 @@ class PlaylistTab(QTableView): self.signals.span_cells_signal.connect(self._span_cells) # Call self.eventFilter() for events - # self.viewport().installEventFilter(self) + # self.installEventFilter(self) # Initialise miscellaneous instance variables self.search_text: str = "" @@ -200,106 +259,106 @@ class PlaylistTab(QTableView): self.setModel(PlaylistModel(playlist_id, signals)) self._set_column_widths() - def __repr__(self) -> str: - return f"" + # kae def __repr__(self) -> str: + # kae return f"" # ########## Events other than cell editing ########## - def dropEvent(self, event: Optional[QDropEvent]) -> None: - """ - Handle drag/drop of rows + # def dropEvent(self, event: Optional[QDropEvent]) -> None: + # """ + # Handle drag/drop of rows - https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget - """ + # https://stackoverflow.com/questions/26227885/drag-and-drop-rows-within-qtablewidget + # """ - if not event: - return + # if not event: + # return - if not event.source() == self: - return # We don't accept external drops + # if not event.source() == self: + # return # We don't accept external drops - top_row = self.rowAt(0) - row_set = set([mi.row() for mi in self.selectedIndexes()]) - targetRow = self.indexAt(event.position().toPoint()).row() - row_set.discard(targetRow) - rows = list(sorted(row_set)) - if not rows: - return - if targetRow == -1: - targetRow = self.rowCount() - for _ in range(len(rows)): - self.insertRow(targetRow) - rowMapping = dict() # Src row to target row. - for idx, row in enumerate(rows): - if row < targetRow: - rowMapping[row] = targetRow + idx - else: - rowMapping[row + len(rows)] = targetRow + idx - colCount = self.columnCount() - for srcRow, tgtRow in sorted(rowMapping.items()): - if self._get_row_track_id(srcRow): - # This is a track row - for col in range(0, colCount): - self.setItem(tgtRow, col, self.takeItem(srcRow, col)) - else: - self.setItem( - tgtRow, - HEADER_NOTES_COLUMN, - self.takeItem(srcRow, HEADER_NOTES_COLUMN), - ) - self.setSpan(tgtRow, HEADER_NOTES_COLUMN, 1, len(columns) - 1) - for row in reversed(sorted(rowMapping.keys())): - self.removeRow(row) - self.resizeRowsToContents() - # Scroll to drop zone - self.scrollToItem( - self.item(top_row, 1), QAbstractItemView.ScrollHint.PositionAtTop - ) - event.accept() + # top_row = self.rowAt(0) + # row_set = set([mi.row() for mi in self.selectedIndexes()]) + # targetRow = self.indexAt(event.position().toPoint()).row() + # row_set.discard(targetRow) + # rows = list(sorted(row_set)) + # if not rows: + # return + # if targetRow == -1: + # targetRow = self.rowCount() + # for _ in range(len(rows)): + # self.insertRow(targetRow) + # rowMapping = dict() # Src row to target row. + # for idx, row in enumerate(rows): + # if row < targetRow: + # rowMapping[row] = targetRow + idx + # else: + # rowMapping[row + len(rows)] = targetRow + idx + # colCount = self.columnCount() + # for srcRow, tgtRow in sorted(rowMapping.items()): + # if self._get_row_track_id(srcRow): + # # This is a track row + # for col in range(0, colCount): + # self.setItem(tgtRow, col, self.takeItem(srcRow, col)) + # else: + # self.setItem( + # tgtRow, + # HEADER_NOTES_COLUMN, + # self.takeItem(srcRow, HEADER_NOTES_COLUMN), + # ) + # self.setSpan(tgtRow, HEADER_NOTES_COLUMN, 1, len(columns) - 1) + # for row in reversed(sorted(rowMapping.keys())): + # self.removeRow(row) + # self.resizeRowsToContents() + # # Scroll to drop zone + # self.scrollToItem( + # self.item(top_row, 1), QAbstractItemView.ScrollHint.PositionAtTop + # ) + # event.accept() - # Reset drag mode to allow row selection by dragging - self.setDragEnabled(False) - # Disable sort undo - self.sort_undo = [] + # # Reset drag mode to allow row selection by dragging + # self.setDragEnabled(False) + # # Disable sort undo + # self.sort_undo = [] - with Session() as session: - self.save_playlist(session) - self._update_start_end_times(session) + # with Session() as session: + # self.save_playlist(session) + # self._update_start_end_times(session) - self.hide_or_show_played_tracks() + # self.hide_or_show_played_tracks() - def _add_context_menu( - self, - text: str, - action: Callable, - disabled: bool = False, - parent_menu: Optional[QMenu] = None, - ) -> Optional[QAction]: - """ - Add item to self.menu - """ + # def _add_context_menu( + # self, + # text: str, + # action: Callable, + # disabled: bool = False, + # parent_menu: Optional[QMenu] = None, + # ) -> Optional[QAction]: + # """ + # Add item to self.menu + # """ - if parent_menu is None: - parent_menu = self.menu + # if parent_menu is None: + # parent_menu = self.menu - menu_item = parent_menu.addAction(text) - if not menu_item: - return None - menu_item.setDisabled(disabled) - menu_item.triggered.connect(action) + # menu_item = parent_menu.addAction(text) + # if not menu_item: + # return None + # menu_item.setDisabled(disabled) + # menu_item.triggered.connect(action) - return menu_item + # return menu_item - def mouseReleaseEvent(self, event): - """ - Enable dragging if rows are selected - """ + # def mouseReleaseEvent(self, event): + # """ + # Enable dragging if rows are selected + # """ - if self.selectedItems(): - self.setDragEnabled(True) - else: - self.setDragEnabled(False) - super().mouseReleaseEvent(event) + # if self.selectedIndexes(): + # self.setDragEnabled(True) + # else: + # self.setDragEnabled(False) + # super().mouseReleaseEvent(event) # ########## Cell editing ########## @@ -320,175 +379,175 @@ class PlaylistTab(QTableView): # _cell_changed() (only if changes made) # closeEditor() - def _cell_changed(self, row: int, column: int) -> None: - """Called when cell content has changed""" + # def _cell_changed(self, row: int, column: int) -> None: + # """Called when cell content has changed""" - # Disable cell changed signal connection as note updates will - # change cell again (metadata) - self.cellChanged.disconnect(self._cell_changed) + # # Disable cell changed signal connection as note updates will + # # change cell again (metadata) + # self.cellChanged.disconnect(self._cell_changed) - cell = self.item(row, column) - if not cell: - return + # cell = self.item(row, column) + # if not cell: + # return - new_text = cell.text().strip() + # new_text = cell.text().strip() - # Update cell with strip()'d text - cell.setText(new_text) + # # Update cell with strip()'d text + # cell.setText(new_text) - track_id = self._get_row_track_id(row) + # track_id = self._get_row_track_id(row) - # Determine cell type changed - with Session() as session: - # Get playlistrow object - plr_id = self._get_row_plr_id(row) - plr_item = session.get(PlaylistRows, plr_id) - if not plr_item: - return + # # Determine cell type changed + # with Session() as session: + # # Get playlistrow object + # plr_id = self._get_row_plr_id(row) + # plr_item = session.get(PlaylistRows, plr_id) + # if not plr_item: + # return - # Note any updates needed to PlaylistTrack objects - update_current = self.musicmuster.current_track.plr_id == plr_id - update_next = self.musicmuster.next_track.plr_id == plr_id + # # Note any updates needed to PlaylistTrack objects + # update_current = self.musicmuster.current_track.plr_id == plr_id + # update_next = self.musicmuster.next_track.plr_id == plr_id - if self.edit_cell_type == ROW_NOTES: - plr_item.note = new_text - if track_id: - self._set_row_note_text(session, row, new_text) - else: - self._set_row_header_text(session, row, new_text) - else: - if track_id: - track = session.get(Tracks, track_id) - if track: - if self.edit_cell_type == TITLE: - track.title = new_text - if update_current: - self.musicmuster.current_track.title = new_text - if update_next: - self.musicmuster.next_track.title = new_text - elif self.edit_cell_type == ARTIST: - track.artist = new_text - if update_current: - self.musicmuster.current_track.artist = new_text - if update_next: - self.musicmuster.next_track.artist = new_text + # if self.edit_cell_type == ROW_NOTES: + # plr_item.note = new_text + # if track_id: + # self._set_row_note_text(session, row, new_text) + # else: + # self._set_row_header_text(session, row, new_text) + # else: + # if track_id: + # track = session.get(Tracks, track_id) + # if track: + # if self.edit_cell_type == TITLE: + # track.title = new_text + # if update_current: + # self.musicmuster.current_track.title = new_text + # if update_next: + # self.musicmuster.next_track.title = new_text + # elif self.edit_cell_type == ARTIST: + # track.artist = new_text + # if update_current: + # self.musicmuster.current_track.artist = new_text + # if update_next: + # self.musicmuster.next_track.artist = new_text - if update_next or update_current: - self.musicmuster.update_headers() + # if update_next or update_current: + # self.musicmuster.update_headers() - if update_current: - self._set_row_colour_current(row) - elif update_next: - self._set_row_colour_next(row) + # if update_current: + # self._set_row_colour_current(row) + # elif update_next: + # self._set_row_colour_next(row) - self.clear_selection() + # self.clear_selection() - def closeEditor( - self, editor: QWidget, hint: QAbstractItemDelegate.EndEditHint - ) -> None: - """ - Override PySide2.QAbstractItemView.closeEditor to enable - play controls and update display. - """ + # def closeEditor( + # self, editor: QWidget, hint: QAbstractItemDelegate.EndEditHint + # ) -> None: + # """ + # Override PySide2.QAbstractItemView.closeEditor to enable + # play controls and update display. + # """ - # If edit was cancelled (eg, by pressing ESC), the signal will - # still be connected - try: - self.cellChanged.disconnect(self._cell_changed) - except TypeError: - pass + # # If edit was cancelled (eg, by pressing ESC), the signal will + # # still be connected + # try: + # self.cellChanged.disconnect(self._cell_changed) + # except TypeError: + # pass - self.edit_cell_type = None - self.musicmuster.enable_play_next_controls() - self.musicmuster.actionSetNext.setEnabled(True) - self.musicmuster.action_Clear_selection.setEnabled(True) + # 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) + # super(PlaylistTab, self).closeEditor(editor, hint) - # Optimise row heights after increasing row height for editing - self.resizeRowsToContents() + # # Optimise row heights after increasing row height for editing + # self.resizeRowsToContents() - # Update start times in case a start time in a note has been - # edited - with Session() as session: - self._update_start_end_times(session) + # # Update start times in case a start time in a note has been + # # edited + # with Session() as session: + # self._update_start_end_times(session) - def edit( - self, - index: QModelIndex, # type: ignore # FIXME - trigger: QAbstractItemView.EditTrigger, - event: QEvent, - ) -> bool: - """ - Override PySide2.QAbstractItemView.edit to catch when editing starts + # def edit( + # self, + # index: QModelIndex, # type: ignore # FIXME + # trigger: QAbstractItemView.EditTrigger, + # event: QEvent, + # ) -> bool: + # """ + # Override PySide2.QAbstractItemView.edit to catch when editing starts - Editing only ever starts with a double click on a cell - """ + # Editing only ever starts with a double click on a cell + # """ - # 'result' will only be true on double-click - result = super(PlaylistTab, self).edit(index, trigger, event) - if result: - row = index.row() - column = index.column() - note_column = 0 - if self._get_row_track_id(row): - # If a track row, we only allow editing of title, artist and - # note. Check that this column is one of those. - if column in [TITLE, ARTIST, ROW_NOTES]: - self.edit_cell_type = column - else: - # Can't edit other columns - return False + # # 'result' will only be true on double-click + # result = super(PlaylistTab, self).edit(index, trigger, event) + # if result: + # row = index.row() + # column = index.column() + # note_column = 0 + # if self._get_row_track_id(row): + # # If a track row, we only allow editing of title, artist and + # # note. Check that this column is one of those. + # if column in [TITLE, ARTIST, ROW_NOTES]: + # self.edit_cell_type = column + # else: + # # Can't edit other columns + # return False - # Check whether we're editing a notes row for later - if self.edit_cell_type == ROW_NOTES: - note_column = ROW_NOTES - else: - # This is a section header. - if column != HEADER_NOTES_COLUMN: - return False - note_column = HEADER_NOTES_COLUMN - self.edit_cell_type = ROW_NOTES + # # Check whether we're editing a notes row for later + # if self.edit_cell_type == ROW_NOTES: + # note_column = ROW_NOTES + # else: + # # This is a section header. + # if column != HEADER_NOTES_COLUMN: + # return False + # note_column = HEADER_NOTES_COLUMN + # self.edit_cell_type = ROW_NOTES - # Disable play controls so that keyboard input doesn't - # disturb playing - self.musicmuster.disable_play_next_controls() - self.musicmuster.actionSetNext.setEnabled(False) - self.musicmuster.action_Clear_selection.setEnabled(False) + # # Disable play controls so that keyboard input doesn't + # # 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 - # from database. note_column will only be non-zero if we are - # editing a note. - if note_column: - with Session() as session: - plr_item = self._get_row_plr(session, row) - if not plr_item: - return False - if note_column == ROW_NOTES: - self._set_row_note_text(session, row, plr_item.note) - else: - self._set_row_header_text(session, row, plr_item.note) + # # 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 + # # from database. note_column will only be non-zero if we are + # # editing a note. + # if note_column: + # with Session() as session: + # plr_item = self._get_row_plr(session, row) + # if not plr_item: + # return False + # if note_column == ROW_NOTES: + # self._set_row_note_text(session, row, plr_item.note) + # else: + # self._set_row_header_text(session, row, plr_item.note) - # Connect signal so we know when cell has changed. - self.cellChanged.connect(self._cell_changed) + # # Connect signal so we know when cell has changed. + # self.cellChanged.connect(self._cell_changed) - return result + # return result # # ########## Externally called functions ########## - def clear_next(self) -> None: - """ - Unmark next track - """ + # def clear_next(self) -> None: + # """ + # Unmark next track + # """ - row_number = self._get_next_track_row_number() - if not row_number: - return - self._set_row_colour_default(row_number) - self.clear_selection() + # row_number = self._get_next_track_row_number() + # if not row_number: + # return + # self._set_row_colour_default(row_number) + # self.clear_selection() - self.musicmuster.set_next_plr_id(None, self) + # self.musicmuster.set_next_plr_id(None, self) def clear_selection(self) -> None: """Unselect all tracks and reset drag mode""" @@ -496,379 +555,384 @@ class PlaylistTab(QTableView): self.clearSelection() self.setDragEnabled(False) - def get_new_row_number(self) -> int: - """ - Return the selected row or the row count if no row selected - (ie, new row will be appended) - """ - - if self.selectionModel().hasSelection(): - return self.currentRow() - else: - return self.rowCount() - - def get_selected_playlistrow_ids(self) -> list: - """ - Return a list of PlaylistRow ids of the selected rows - """ - - return [self._get_row_plr_id(a) for a in self._get_selected_rows()] - - def get_selected_playlistrows(self, session: scoped_session) -> List[PlaylistRows]: - """ - Return a list of PlaylistRows of the selected rows - """ - - plr_ids = self.get_selected_playlistrow_ids() - if not plr_ids: - return [] - plrs = [session.get(PlaylistRows, a) for a in plr_ids] - - return [plr for plr in plrs if plr is not None] - - def get_selected_row_track_path(self) -> Optional[str]: - """ - Return the path of the first selected row or - None if no rows are selected or first selected row doesn't - have a track. - """ - - first_selected_row = self._get_selected_row() - if first_selected_row is None: - return None - path = self._get_row_track_path(first_selected_row) - if not path: - return None - - return path - - def hide_or_show_played_tracks(self) -> None: - """ - Hide or show played tracks. - - Never hide current or next track - """ - - current_next = [ - self._get_current_track_row_number(), - self._get_next_track_row_number(), - ] - - for row_number in range(self.rowCount()): - if row_number in current_next: - continue - - if self._get_row_userdata(row_number, self.PLAYED): - if self.musicmuster.hide_played_tracks: - self.hideRow(row_number) - else: - self.showRow(row_number) - - # This causes scrolling, so ensure current track is visible - self.scroll_current_to_top() - - def insert_header(self, session: scoped_session, note: str) -> None: - """ - Insert section header into playlist tab. - - If a row is selected, add header above. Otherwise, add to end of - playlist. - - We simply build a PlaylistRows object and pass it to insert_row() - to do the heavy lifing. - """ - - row_number = self.get_new_row_number() - plr = PlaylistRows(session, self.playlist_id, None, row_number, note) - self.insert_row(session, plr) - self._set_row_header_text(session, row_number, note) - self.save_playlist(session) - self._update_start_end_times(session) - - def insert_row( - self, - session: scoped_session, - plr: PlaylistRows, - update_track_times: bool = True, - played=False, - ) -> None: - """ - Insert passed playlist row (plr) into playlist tab. - """ - - row_number = plr.plr_rownum - bold = True - self.insertRow(row_number) - _ = self._set_row_plr_id(row_number, plr.id) - - if plr.track: - self._update_row_track_info(session, row_number, plr.track) - if played: - bold = False - _ = self._set_row_userdata(row_number, self.PLAYED, True) - self._set_row_note_text(session, row_number, plr.note) - else: - # This is a section header so it must have note text - if plr.note is None: - log.debug(f"insert_row({plr=}) with no track_id and no note") - return - - # Use one QTableWidgetItem to span all columns from column 1 - self._set_row_header_text(session, row_number, plr.note) - self.setSpan(row_number, HEADER_NOTES_COLUMN, 1, len(columns) - 1) - - # Save (or clear) track_id - _ = self._set_row_track_id(row_number, 0) - - # Set bold as needed - self._set_row_bold(row_number, bold) - - def insert_track( - self, - session: scoped_session, - track: Tracks, - note: str = "", - repaint: bool = True, - target_row: Optional[int] = None, - ) -> None: - """ - Insert track into playlist tab. - - If a row is selected, add track above. Otherwise, add to end of - playlist. - - We simply build a PlaylistRows object and pass it to insert_row() - to do the heavy lifing. - """ - - if not track: - log.debug( - f"insert_track(session={hex(id(Session))}, {note=}, {repaint=}" - " called with no track" - ) - return - - if target_row: - row_number = target_row - else: - row_number = self.get_new_row_number() - - # Check to see whether track is already in playlist - existing_plr = PlaylistRows.get_track_plr(session, track.id, self.playlist_id) - if existing_plr and ask_yes_no( - "Duplicate row", - "Track already in playlist. " "Move to new location?", - default_yes=True, - ): - # Yes it is and we should reuse it - # If we've been passed a note, we need to add that to the - # existing track - if note: - existing_plr.append_note(note) - return self._move_row(session, existing_plr, row_number) - - # Build playlist_row object - plr = PlaylistRows(session, self.playlist_id, track.id, row_number, note) - self.insert_row(session, plr) - self.save_playlist(session) - self._update_start_end_times(session) - - def lookup_row_in_songfacts(self) -> None: - """ - If there is a selected row and it is a track row, - look up its title in songfacts. - - If multiple rows are selected, only consider the first one. - - Otherwise return. - """ - - self._look_up_row(website="songfacts") - - def lookup_row_in_wikipedia(self) -> None: - """ - If there is a selected row and it is a track row, - look up its title in wikipedia. - - If multiple rows are selected, only consider the first one. - - Otherwise return. - """ - - self._look_up_row(website="wikipedia") - - def play_ended(self) -> None: - """ - Called by musicmuster when play has ended. - - current_track points to track that's just finished - """ - - row_number = self._get_current_track_row_number() - if row_number is None: - return - - self._set_row_colour_default(row_number) - self.clear_selection() - self._set_row_last_played_time( - row_number, self.musicmuster.current_track.start_time - ) - - with Session() as session: - self._set_row_note_colour(session, row_number) - - def play_started(self, session: scoped_session) -> None: - """ - Notification from musicmuster that track has started playing. - - Actions required: - - Mark current row as played - - Set next track - - Display track as current - - Update start/stop times - - Change OBS scene if needed - - Update hidden tracks - """ - - current_row = self._get_current_track_row_number() - if current_row is None: - if os.environ["MM_ENV"] == "PRODUCTION": - send_mail( - Config.ERRORS_TO, - Config.ERRORS_FROM, - "playlists:play_started:current_row is None", - stackprinter.format(), - ) - print("playlists:play_started:current_row is None") - # stackprinter.show(add_summary=True, style="darkbg") - return - - # Mark current row as played - self._set_played_row(session, current_row) - - # Set next track - next_row = self._find_next_track_row(session, current_row + 1) - if next_row: - self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row), self) - - # Display row as current track - self._set_row_colour_current(current_row) - - # Update start/stop times - self._update_start_end_times(session) - - # Change OBS scene if needed - self._obs_change_scene(current_row) - - # Update hidden tracks - QTimer.singleShot( - Config.HIDE_AFTER_PLAYING_OFFSET, self.hide_or_show_played_tracks - ) - - def populate_display( - self, session: scoped_session, playlist_id: int, scroll_to_top: bool = True - ) -> None: - """ - Populate display from the associated playlist ID - """ - - # Sanity check row numbering before we load - PlaylistRows.fixup_rownumbers(session, playlist_id) - - # Clear playlist - self.setRowCount(0) - - # Get played tracks - played_rows = self._get_played_rows(session) - - # Add the rows - playlist = session.get(Playlists, playlist_id) - if not playlist: - if os.environ["MM_ENV"] == "PRODUCTION": - send_mail( - Config.ERRORS_TO, - Config.ERRORS_FROM, - "playlists:populate_display:no playlist", - stackprinter.format(), - ) - print("playlists:populate_display:no playlist") - # stackprinter.show(add_summary=True, style="darkbg") - return - - for plr in PlaylistRows.deep_rows(session, playlist_id): - self.insert_row( - session, - plr, - update_track_times=False, - played=plr.plr_rownum in played_rows, - ) - - # Scroll to top - if scroll_to_top: - row0_item = self.item(0, 0) - if row0_item: - self.scrollToItem(row0_item, QAbstractItemView.ScrollHint.PositionAtTop) - - # Queue up time calculations to take place after UI has - # updated - self._update_start_end_times(session) - # It's possible that the current/next tracks are in this - # playlist, so check and set. - current_row = self._get_current_track_row_number() - if current_row is not None: - self._set_row_colour_current(current_row) - next_row = self._get_next_track_row_number() - if next_row is not None: - self._set_row_colour_next(next_row) - # Needed to wrap notes column correctly - add to event queue so - # that it's processed after list is populated - QTimer.singleShot(0, self.tab_visible) - - def remove_rows(self, row_numbers: List[int]) -> None: - """Remove passed rows from display""" - - # Remove rows from display. Do so in reverse order so that - # row numbers remain valid. - for row in sorted(row_numbers, reverse=True): - self.removeRow(row) - - def save_playlist(self, session: scoped_session) -> None: - """ - Get the PlaylistRow objects for each row in the display. Correct - the row_number and playlist_id if necessary. Remove any row - numbers in the database that are higher than the last row in - the display. - """ - - # Ensure all row plrs have correct row number and playlist_id - for row_number in range(self.rowCount()): - plr = self._get_row_plr(session, row_number) - if not plr: - continue - plr.plr_rownum = row_number - plr.playlist_id = self.playlist_id - - # Any rows in the database for this playlist that has a row - # number equal to or greater than the row count needs to be - # removed. - PlaylistRows.delete_higher_rows(session, self.playlist_id, self.rowCount() - 1) - - # Get changes into db - session.flush() - - def scroll_current_to_top(self) -> None: - """Scroll currently-playing row to top""" - - current_row = self._get_current_track_row_number() - if current_row is not None: - self._scroll_to_top(current_row) - - def scroll_next_to_top(self) -> None: - """Scroll nextly-playing row to top""" - - next_row = self._get_next_track_row_number() - if next_row is not None: - self._scroll_to_top(next_row) + # def get_new_row_number(self) -> int: + # """ + # Return the selected row or the row count if no row selected + # (ie, new row will be appended) + # """ + + # if self.selectionModel().hasSelection(): + # return self.currentRow() + # else: + # return self.rowCount() + + # def get_selected_playlistrow_ids(self) -> list: + # """ + # Return a list of PlaylistRow ids of the selected rows + # """ + + # return [self._get_row_plr_id(a) for a in self._get_selected_rows()] + + # def get_selected_playlistrows(self, session: scoped_session) -> List[PlaylistRows]: + # """ + # Return a list of PlaylistRows of the selected rows + # """ + + # plr_ids = self.get_selected_playlistrow_ids() + # if not plr_ids: + # return [] + # plrs = [session.get(PlaylistRows, a) for a in plr_ids] + + # return [plr for plr in plrs if plr is not None] + + # def get_selected_row_track_path(self) -> Optional[str]: + # """ + # Return the path of the first selected row or + # None if no rows are selected or first selected row doesn't + # have a track. + # """ + + # first_selected_row = self._get_selected_row() + # if first_selected_row is None: + # return None + # path = self._get_row_track_path(first_selected_row) + # if not path: + # return None + + # return path + + # def hide_or_show_played_tracks(self) -> None: + # """ + # Hide or show played tracks. + + # Never hide current or next track + # """ + + # current_next = [ + # self._get_current_track_row_number(), + # self._get_next_track_row_number(), + # ] + + # for row_number in range(self.rowCount()): + # if row_number in current_next: + # continue + + # if self._get_row_userdata(row_number, self.PLAYED): + # if self.musicmuster.hide_played_tracks: + # self.hideRow(row_number) + # else: + # self.showRow(row_number) + + # # This causes scrolling, so ensure current track is visible + # self.scroll_current_to_top() + + # def insert_header(self, session: scoped_session, note: str) -> None: + # """ + # Insert section header into playlist tab. + + # If a row is selected, add header above. Otherwise, add to end of + # playlist. + + # We simply build a PlaylistRows object and pass it to insert_row() + # to do the heavy lifing. + # """ + + # row_number = self.get_new_row_number() + # plr = PlaylistRows(session, self.playlist_id, None, row_number, note) + # self.insert_row(session, plr) + # self._set_row_header_text(session, row_number, note) + # self.save_playlist(session) + # self._update_start_end_times(session) + + # def insert_row( + # self, + # session: scoped_session, + # plr: PlaylistRows, + # update_track_times: bool = True, + # played=False, + # ) -> None: + # """ + # Insert passed playlist row (plr) into playlist tab. + # """ + + # row_number = plr.plr_rownum + # bold = True + # self.insertRow(row_number) + # _ = self._set_row_plr_id(row_number, plr.id) + + # if plr.track: + # self._update_row_track_info(session, row_number, plr.track) + # if played: + # bold = False + # _ = self._set_row_userdata(row_number, self.PLAYED, True) + # self._set_row_note_text(session, row_number, plr.note) + # else: + # # This is a section header so it must have note text + # if plr.note is None: + # log.debug(f"insert_row({plr=}) with no track_id and no note") + # return + + # # Use one QTableWidgetItem to span all columns from column 1 + # self._set_row_header_text(session, row_number, plr.note) + # self.setSpan(row_number, HEADER_NOTES_COLUMN, 1, len(columns) - 1) + + # # Save (or clear) track_id + # _ = self._set_row_track_id(row_number, 0) + + # # Set bold as needed + # self._set_row_bold(row_number, bold) + + # def insert_track( + # self, + # session: scoped_session, + # track: Tracks, + # note: str = "", + # repaint: bool = True, + # target_row: Optional[int] = None, + # ) -> None: + # """ + # Insert track into playlist tab. + + # If a row is selected, add track above. Otherwise, add to end of + # playlist. + + # We simply build a PlaylistRows object and pass it to insert_row() + # to do the heavy lifing. + # """ + + # if not track: + # log.debug( + # f"insert_track(session={hex(id(Session))}, {note=}, {repaint=}" + # " called with no track" + # ) + # return + + # if target_row: + # row_number = target_row + # else: + # row_number = self.get_new_row_number() + + # # Check to see whether track is already in playlist + # existing_plr = PlaylistRows.get_track_plr(session, track.id, self.playlist_id) + # if existing_plr and ask_yes_no( + # "Duplicate row", + # "Track already in playlist. " "Move to new location?", + # default_yes=True, + # ): + # # Yes it is and we should reuse it + # # If we've been passed a note, we need to add that to the + # # existing track + # if note: + # existing_plr.append_note(note) + # return self._move_row(session, existing_plr, row_number) + + # # Build playlist_row object + # plr = PlaylistRows(session, self.playlist_id, track.id, row_number, note) + # self.insert_row(session, plr) + # self.save_playlist(session) + # self._update_start_end_times(session) + + # def lookup_row_in_songfacts(self) -> None: + # """ + # If there is a selected row and it is a track row, + # look up its title in songfacts. + + # If multiple rows are selected, only consider the first one. + + # Otherwise return. + # """ + + # self._look_up_row(website="songfacts") + + # def lookup_row_in_wikipedia(self) -> None: + # """ + # If there is a selected row and it is a track row, + # look up its title in wikipedia. + + # If multiple rows are selected, only consider the first one. + + # Otherwise return. + # """ + + # self._look_up_row(website="wikipedia") + + # def play_ended(self) -> None: + # """ + # Called by musicmuster when play has ended. + + # current_track points to track that's just finished + # """ + + # row_number = self._get_current_track_row_number() + # if row_number is None: + # return + + # self._set_row_colour_default(row_number) + # self.clear_selection() + # self._set_row_last_played_time( + # row_number, self.musicmuster.current_track.start_time + # ) + + # with Session() as session: + # self._set_row_note_colour(session, row_number) + + # def play_started(self, session: scoped_session) -> None: + # """ + # Notification from musicmuster that track has started playing. + + # Actions required: + # - Mark current row as played + # - Set next track + # - Display track as current + # - Update start/stop times + # - Change OBS scene if needed + # - Update hidden tracks + # """ + + # print("playlists_v3:play_starter()") + # return + + # # current_row = self._get_current_track_row_number() + # # if current_row is None: + # # if os.environ["MM_ENV"] == "PRODUCTION": + # # send_mail( + # # Config.ERRORS_TO, + # # Config.ERRORS_FROM, + # # "playlists:play_started:current_row is None", + # # stackprinter.format(), + # # ) + # # print("playlists:play_started:current_row is None") + # # # stackprinter.show(add_summary=True, style="darkbg") + # # return + + # # # Mark current row as played + # # self._set_played_row(session, current_row) + + # # # Set next track + # # next_row = self._find_next_track_row(session, current_row + 1) + # # if next_row: + # # self.musicmuster.set_next_plr_id(self._get_row_plr_id(next_row), self) + + # # # Display row as current track + # # self._set_row_colour_current(current_row) + + # # # Update start/stop times + # # self._update_start_end_times(session) + + # # # Change OBS scene if needed + # # self._obs_change_scene(current_row) + + # # # Update hidden tracks + # # QTimer.singleShot( + # # Config.HIDE_AFTER_PLAYING_OFFSET, self.hide_or_show_played_tracks + # # ) + + # def populate_display( + # self, session: scoped_session, playlist_id: int, scroll_to_top: bool = True + # ) -> None: + # """ + # Populate display from the associated playlist ID + # """ + + # print("playlists_v3:populate_display()") + # return + # # # Sanity check row numbering before we load + # # PlaylistRows.fixup_rownumbers(session, playlist_id) + + # # # Clear playlist + # # self.setRowCount(0) + + # # # Get played tracks + # # played_rows = self._get_played_rows(session) + + # # # Add the rows + # # playlist = session.get(Playlists, playlist_id) + # # if not playlist: + # # if os.environ["MM_ENV"] == "PRODUCTION": + # # send_mail( + # # Config.ERRORS_TO, + # # Config.ERRORS_FROM, + # # "playlists:populate_display:no playlist", + # # stackprinter.format(), + # # ) + # # print("playlists:populate_display:no playlist") + # # # stackprinter.show(add_summary=True, style="darkbg") + # # return + + # # for plr in PlaylistRows.deep_rows(session, playlist_id): + # # self.insert_row( + # # session, + # # plr, + # # update_track_times=False, + # # played=plr.plr_rownum in played_rows, + # # ) + + # # # Scroll to top + # # if scroll_to_top: + # # row0_item = self.item(0, 0) + # # if row0_item: + # # self.scrollToItem(row0_item, QAbstractItemView.ScrollHint.PositionAtTop) + + # # # Queue up time calculations to take place after UI has + # # # updated + # # self._update_start_end_times(session) + # # # It's possible that the current/next tracks are in this + # # # playlist, so check and set. + # # current_row = self._get_current_track_row_number() + # # if current_row is not None: + # # self._set_row_colour_current(current_row) + # # next_row = self._get_next_track_row_number() + # # if next_row is not None: + # # self._set_row_colour_next(next_row) + # # # Needed to wrap notes column correctly - add to event queue so + # # # that it's processed after list is populated + # # QTimer.singleShot(0, self.tab_visible) + + # def remove_rows(self, row_numbers: List[int]) -> None: + # """Remove passed rows from display""" + + # # Remove rows from display. Do so in reverse order so that + # # row numbers remain valid. + # for row in sorted(row_numbers, reverse=True): + # self.removeRow(row) + + # def save_playlist(self, session: scoped_session) -> None: + # """ + # Get the PlaylistRow objects for each row in the display. Correct + # the row_number and playlist_id if necessary. Remove any row + # numbers in the database that are higher than the last row in + # the display. + # """ + + # # Ensure all row plrs have correct row number and playlist_id + # for row_number in range(self.rowCount()): + # plr = self._get_row_plr(session, row_number) + # if not plr: + # continue + # plr.plr_rownum = row_number + # plr.playlist_id = self.playlist_id + + # # Any rows in the database for this playlist that has a row + # # number equal to or greater than the row count needs to be + # # removed. + # PlaylistRows.delete_higher_rows(session, self.playlist_id, self.rowCount() - 1) + + # # Get changes into db + # session.flush() + + # def scroll_current_to_top(self) -> None: + # """Scroll currently-playing row to top""" + + # current_row = self._get_current_track_row_number() + # if current_row is not None: + # self._scroll_to_top(current_row) + + # def scroll_next_to_top(self) -> None: + # """Scroll nextly-playing row to top""" + + # next_row = self._get_next_track_row_number() + # if next_row is not None: + # self._scroll_to_top(next_row) def set_search(self, text: str) -> None: """Set search text and find first match""" @@ -879,115 +943,115 @@ class PlaylistTab(QTableView): return self._search(next=True) - def search_next(self) -> None: - """ - Select next row containg self.search_string. - """ + # def search_next(self) -> None: + # """ + # Select next row containg self.search_string. + # """ - self._search(next=True) + # self._search(next=True) - def search_previous(self) -> None: - """ - Select previous row containg self.search_string. - """ + # def search_previous(self) -> None: + # """ + # Select previous row containg self.search_string. + # """ - self._search(next=False) + # self._search(next=False) - def select_next_row(self) -> None: - """ - Select next or first row. Don't select section headers. + # def select_next_row(self) -> None: + # """ + # Select next or first row. Don't select section headers. - Wrap at last row. - """ + # Wrap at last row. + # """ - selected_rows = self._get_selected_rows() - # we will only handle zero or one selected rows - if len(selected_rows) > 1: - return - # select first row if none selected - if len(selected_rows) == 0: - row_number = 0 - else: - row_number = selected_rows[0] + 1 - if row_number >= self.rowCount(): - row_number = 0 + # selected_rows = self._get_selected_rows() + # # we will only handle zero or one selected rows + # if len(selected_rows) > 1: + # return + # # select first row if none selected + # if len(selected_rows) == 0: + # row_number = 0 + # else: + # row_number = selected_rows[0] + 1 + # if row_number >= self.rowCount(): + # row_number = 0 - # Don't select section headers - wrapped = False - track_id = self._get_row_track_id(row_number) - while not track_id: - row_number += 1 - if row_number >= self.rowCount(): - if wrapped: - # we're already wrapped once, so there are no - # non-headers - return - row_number = 0 - wrapped = True - track_id = self._get_row_track_id(row_number) + # # Don't select section headers + # wrapped = False + # track_id = self._get_row_track_id(row_number) + # while not track_id: + # row_number += 1 + # if row_number >= self.rowCount(): + # if wrapped: + # # we're already wrapped once, so there are no + # # non-headers + # return + # row_number = 0 + # wrapped = True + # track_id = self._get_row_track_id(row_number) - self.selectRow(row_number) + # self.selectRow(row_number) - def select_previous_row(self) -> None: - """ - Select previous or last track. Don't select section headers. - Wrap at first row. - """ + # def select_previous_row(self) -> None: + # """ + # Select previous or last track. Don't select section headers. + # Wrap at first row. + # """ - selected_rows = self._get_selected_rows() - # we will only handle zero or one selected rows - if len(selected_rows) > 1: - return - # select last row if none selected - last_row = self.rowCount() - 1 - if len(selected_rows) == 0: - row_number = last_row - else: - row_number = selected_rows[0] - 1 - if row_number < 0: - row_number = last_row + # selected_rows = self._get_selected_rows() + # # we will only handle zero or one selected rows + # if len(selected_rows) > 1: + # return + # # select last row if none selected + # last_row = self.rowCount() - 1 + # if len(selected_rows) == 0: + # row_number = last_row + # else: + # row_number = selected_rows[0] - 1 + # if row_number < 0: + # row_number = last_row - # Don't select section headers - wrapped = False - track_id = self._get_row_track_id(row_number) - while not track_id: - row_number -= 1 - if row_number < 0: - if wrapped: - # we're already wrapped once, so there are no - # non-notes - return - row_number = last_row - wrapped = True - track_id = self._get_row_track_id(row_number) + # # Don't select section headers + # wrapped = False + # track_id = self._get_row_track_id(row_number) + # while not track_id: + # row_number -= 1 + # if row_number < 0: + # if wrapped: + # # we're already wrapped once, so there are no + # # non-notes + # return + # row_number = last_row + # wrapped = True + # track_id = self._get_row_track_id(row_number) - self.selectRow(row_number) + # self.selectRow(row_number) - def select_rows(self, rows: List[int]) -> None: - """ - Select rows that are passed - """ + # def select_rows(self, rows: List[int]) -> None: + # """ + # Select rows that are passed + # """ - # Clear any selected rows to avoid confustion - self.clear_selection() - # We need to be in MultiSelection mode - self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) - # Select the rows - for row in rows: - self.selectRow(row) - # Reset selection mode - self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + # # Clear any selected rows to avoid confustion + # self.clear_selection() + # # We need to be in MultiSelection mode + # self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection) + # # Select the rows + # for row in rows: + # self.selectRow(row) + # # Reset selection mode + # self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - def tab_visible(self) -> None: - """Called when tab becomes visible""" + # def tab_visible(self) -> None: + # """Called when tab becomes visible""" - # Set row heights - self.resizeRowsToContents() - self.setColumnWidth(len(columns) - 1, 0) - # Hide/show rows - self.hide_or_show_played_tracks() + # # Set row heights + # self.resizeRowsToContents() + # self.setColumnWidth(len(columns) - 1, 0) + # # Hide/show rows + # self.hide_or_show_played_tracks() - # # ########## Internally called functions ########## + # # # ########## Internally called functions ########## def _add_track(self, row_number: int) -> None: """Add a track to a section header making it a normal track row""" @@ -1021,115 +1085,115 @@ class PlaylistTab(QTableView): # Update times once display updated self._update_start_end_times(session) - def _build_context_menu(self, item: QTableWidgetItem) -> None: - """Used to process context (right-click) menu, which is defined here""" + # def _build_context_menu(self, item: QTableWidgetItem) -> None: + # """Used to process context (right-click) menu, which is defined here""" - self.menu.clear() - row_number = item.row() - track_id = self._get_row_track_id(row_number) - track_row = bool(track_id) - header_row = not track_row - current = row_number == self._get_current_track_row_number() - next_row = row_number == self._get_next_track_row_number() + # self.menu.clear() + # row_number = item.row() + # track_id = self._get_row_track_id(row_number) + # track_row = bool(track_id) + # header_row = not track_row + # current = row_number == self._get_current_track_row_number() + # next_row = row_number == self._get_next_track_row_number() - # Play with mplayer - if track_row and not current: - self._add_context_menu( - "Play with mplayer", lambda: self._mplayer_play(row_number) - ) + # # Play with mplayer + # if track_row and not current: + # self._add_context_menu( + # "Play with mplayer", lambda: self._mplayer_play(row_number) + # ) - # Paste - self._add_context_menu( - "Paste", - lambda: self.musicmuster.paste_rows(), - self.musicmuster.selected_plrs is None, - ) + # # Paste + # self._add_context_menu( + # "Paste", + # lambda: self.musicmuster.paste_rows(), + # self.musicmuster.selected_plrs is None, + # ) - # Open in Audacity - if track_row and not current: - self._add_context_menu( - "Open in Audacity", lambda: self._open_in_audacity(row_number) - ) + # # Open in Audacity + # if track_row and not current: + # self._add_context_menu( + # "Open in Audacity", lambda: self._open_in_audacity(row_number) + # ) - # Rescan - if track_row and not current: - self._add_context_menu( - "Rescan track", lambda: self._rescan(row_number, track_id) - ) + # # Rescan + # if track_row and not current: + # self._add_context_menu( + # "Rescan track", lambda: self._rescan(row_number, track_id) + # ) - # ---------------------- - self.menu.addSeparator() + # # ---------------------- + # self.menu.addSeparator() - # Remove row - if not current and not next_row: - self._add_context_menu("Delete row", self._delete_rows) + # # Remove row + # if not current and not next_row: + # self._add_context_menu("Delete row", self._delete_rows) - # Move to playlist - if not current and not next_row: - self._add_context_menu( - "Move to playlist...", self.musicmuster.move_selected - ) + # # Move to playlist + # if not current and not next_row: + # self._add_context_menu( + # "Move to playlist...", self.musicmuster.move_selected + # ) - # ---------------------- - self.menu.addSeparator() + # # ---------------------- + # self.menu.addSeparator() - # Remove track from row - if track_row and not current and not next_row: - self._add_context_menu( - "Remove track from row", lambda: self._remove_track(row_number) - ) + # # Remove track from row + # if track_row and not current and not next_row: + # self._add_context_menu( + # "Remove track from row", lambda: self._remove_track(row_number) + # ) - # Add track to section header (ie, make this a track row) - if header_row: - self._add_context_menu("Add a track", lambda: self._add_track(row_number)) + # # Add track to section header (ie, make this a track row) + # if header_row: + # self._add_context_menu("Add a track", lambda: self._add_track(row_number)) - # Mark unplayed - if self._get_row_userdata(row_number, self.PLAYED): - self._add_context_menu("Mark unplayed", self._mark_unplayed) + # # Mark unplayed + # if self._get_row_userdata(row_number, self.PLAYED): + # self._add_context_menu("Mark unplayed", self._mark_unplayed) - # Unmark as next - if next_row: - self._add_context_menu("Unmark as next track", self.clear_next) + # # Unmark as next + # if next_row: + # self._add_context_menu("Unmark as next track", self.clear_next) - # ---------------------- - self.menu.addSeparator() + # # ---------------------- + # self.menu.addSeparator() - # Sort - sort_menu = self.menu.addMenu("Sort") - self._add_context_menu( - "by title", lambda: self._sort_selection(TITLE), parent_menu=sort_menu - ) - self._add_context_menu( - "by artist", lambda: self._sort_selection(ARTIST), parent_menu=sort_menu - ) - self._add_context_menu( - "by duration", lambda: self._sort_selection(DURATION), parent_menu=sort_menu - ) - self._add_context_menu( - "by last played", - lambda: self._sort_selection(LASTPLAYED), - parent_menu=sort_menu, - ) - if sort_menu: - sort_menu.setEnabled(self._sortable()) - self._add_context_menu("Undo sort", self._sort_undo, not bool(self.sort_undo)) + # # Sort + # sort_menu = self.menu.addMenu("Sort") + # self._add_context_menu( + # "by title", lambda: self._sort_selection(TITLE), parent_menu=sort_menu + # ) + # self._add_context_menu( + # "by artist", lambda: self._sort_selection(ARTIST), parent_menu=sort_menu + # ) + # self._add_context_menu( + # "by duration", lambda: self._sort_selection(DURATION), parent_menu=sort_menu + # ) + # self._add_context_menu( + # "by last played", + # lambda: self._sort_selection(LASTPLAYED), + # parent_menu=sort_menu, + # ) + # if sort_menu: + # sort_menu.setEnabled(self._sortable()) + # self._add_context_menu("Undo sort", self._sort_undo, not bool(self.sort_undo)) - # Build submenu + # # Build submenu - # ---------------------- - self.menu.addSeparator() + # # ---------------------- + # self.menu.addSeparator() - # Info - if track_row: - self._add_context_menu("Info", lambda: self._info_row(track_id)) + # # Info + # if track_row: + # self._add_context_menu("Info", lambda: self._info_row(track_id)) - # Track path - if track_row: - self._add_context_menu( - "Copy track path", lambda: self._copy_path(row_number) - ) + # # Track path + # if track_row: + # self._add_context_menu( + # "Copy track path", lambda: self._copy_path(row_number) + # ) - # return super(PlaylistTab, self).eventFilter(source, event) + # # return super(PlaylistTab, self).eventFilter(source, event) def _calculate_end_time( self, start: Optional[datetime], duration: int @@ -1158,12 +1222,12 @@ class PlaylistTab(QTableView): record = Settings.get_int_settings(session, attr_name) record.f_int = self.columnWidth(column_number) - def _context_menu(self, pos): - """Display right-click menu""" + # def _context_menu(self, pos): + # """Display right-click menu""" - item = self.itemAt(pos) - self._build_context_menu(item) - self.menu.exec(self.mapToGlobal(pos)) + # item = self.itemAt(pos) + # self._build_context_menu(item) + # self.menu.exec(self.mapToGlobal(pos)) def _copy_path(self, row_number: int) -> None: """ @@ -1501,25 +1565,27 @@ class PlaylistTab(QTableView): Otherwise return. """ - selected_row = self._get_selected_row() - if not selected_row: - return + print("playlists_v3:_look_up_row()") + return + # selected_row = self._get_selected_row() + # if not selected_row: + # return - if not self._get_row_track_id(selected_row): - return + # if not self._get_row_track_id(selected_row): + # return - title = self._get_row_title(selected_row) + # title = self._get_row_title(selected_row) - if website == "wikipedia": - QTimer.singleShot( - 0, lambda: self.musicmuster.tabInfolist.open_in_wikipedia(title) - ) - elif website == "songfacts": - QTimer.singleShot( - 0, lambda: self.musicmuster.tabInfolist.open_in_songfacts(title) - ) - else: - return + # if website == "wikipedia": + # QTimer.singleShot( + # 0, lambda: self.musicmuster.tabInfolist.open_in_wikipedia(title) + # ) + # elif website == "songfacts": + # QTimer.singleShot( + # 0, lambda: self.musicmuster.tabInfolist.open_in_songfacts(title) + # ) + # else: + # return def _mark_unplayed(self) -> None: """ @@ -1857,28 +1923,28 @@ class PlaylistTab(QTableView): if match_row is not None: self.selectRow(row_number) - def _select_event(self) -> None: - """ - Called when item selection changes. - If multiple rows are selected, display sum of durations in status bar. - """ + # kae def _select_event(self) -> None: + # kae """ + # kae Called when item selection changes. + # kae If multiple rows are selected, display sum of durations in status bar. + # kae """ - selected_rows = self._get_selected_rows() - # If no rows are selected, we have nothing to do - if len(selected_rows) == 0: - self.musicmuster.lblSumPlaytime.setText("") - return + # kae selected_rows = self._get_selected_rows() + # kae # If no rows are selected, we have nothing to do + # kae if len(selected_rows) == 0: + # kae self.musicmuster.lblSumPlaytime.setText("") + # kae return - ms = 0 - for row_number in selected_rows: - ms += self._get_row_duration(row_number) + # kae ms = 0 + # kae for row_number in selected_rows: + # kae ms += self._get_row_duration(row_number) - if ms > 0: - self.musicmuster.lblSumPlaytime.setText( - f"Selected duration: {ms_to_mmss(ms)}" - ) - else: - self.musicmuster.lblSumPlaytime.setText("") + # kae if ms > 0: + # kae self.musicmuster.lblSumPlaytime.setText( + # kae f"Selected duration: {ms_to_mmss(ms)}" + # kae ) + # kae else: + # kae self.musicmuster.lblSumPlaytime.setText("") # def _set_cell_colour( # self, row_number: int, column: int, colour: Optional[str] = None