From 15f4bec197ae610d0f70fe0bfb2c3357bccf5cee Mon Sep 17 00:00:00 2001 From: Keith Edmunds Date: Sun, 5 Mar 2023 14:31:30 +0000 Subject: [PATCH] WIP: playlists refactoring --- app/playlists.py | 272 ++++++++++++++++++++++------------------------- 1 file changed, 126 insertions(+), 146 deletions(-) diff --git a/app/playlists.py b/app/playlists.py index 5e8da7f..dea8c24 100644 --- a/app/playlists.py +++ b/app/playlists.py @@ -5,7 +5,7 @@ import threading from collections import namedtuple from datetime import datetime, timedelta -from typing import cast, List, Optional, TYPE_CHECKING, Union +from typing import Callable, cast, List, Optional, TYPE_CHECKING, Union from PyQt5.QtCore import ( QEvent, @@ -25,6 +25,7 @@ from PyQt5.QtGui import ( from PyQt5.QtWidgets import ( QAbstractItemDelegate, QAbstractItemView, + QAction, QApplication, QLineEdit, QMainWindow, @@ -134,6 +135,7 @@ class PlaylistTab(QTableWidget): ROW_DURATION = Qt.UserRole + 1 PLAYLISTROW_ID = Qt.UserRole + 2 TRACK_PATH = Qt.UserRole + 3 + PLAYED = Qt.UserRole + 4 def __init__(self, musicmuster: "Window", session: scoped_session, @@ -144,7 +146,7 @@ class PlaylistTab(QTableWidget): self.signals = signals # Set up widget - self.menu: Optional[QMenu] = None + self.menu = QMenu() self.setItemDelegate(NoSelectDelegate(self)) self.setEditTriggers(QAbstractItemView.DoubleClicked) self.setAlternatingRowColors(True) @@ -255,141 +257,17 @@ class PlaylistTab(QTableWidget): # Update track times self._update_start_end_times() - def eventFilter(self, source, event): - """Used to process context (right-click) menu, which is defined here""" + def _add_context_menu(self, text: str, action: Callable, + disabled: bool = False) -> QAction: + """ + Add item to self.menu + """ - if (event.type() == QEvent.MouseButtonPress and # noqa W504 - event.buttons() == Qt.RightButton and # noqa W504 - source is self.viewport()): - self.menu = QMenu(self) - item = self.itemAt(event.pos()) - if item is not None: - with Session() as session: - row_number = item.row() - plr = self._get_row_plr(session, row_number) - track_id = plr.track_id - track_row = track_id is not None - header_row = not track_row - if track_row: - current = ( - row_number == self._get_current_track_row_number() - ) - next_row = ( - row_number == self._get_next_track_row_number()) - else: - current = next_row = False + menu_item = self.menu.addAction(text) + menu_item.setDisabled(disabled) + menu_item.triggered.connect(action) - # Mark unplayed / un-next - sep = False - if plr.played: - sep = True - act_unplay = self.menu.addAction("Mark unplayed") - act_unplay.triggered.connect( - lambda: self._mark_unplayed(plr)) - if next_row: - sep = True - act_unnext = self.menu.addAction( - "Unmark as next track") - act_unnext.triggered.connect( - lambda: self.mark_unnext()) - if sep: - self.menu.addSeparator() - - # Cut/paste - act_cut = self.menu.addAction( - "Mark for moving") - act_cut.triggered.connect( - lambda: self.musicmuster.cut_rows()) - - act_paste = self.menu.addAction( - "Paste") - act_paste.setDisabled( - self.musicmuster.selected_plrs is None) - act_paste.triggered.connect( - lambda: self.musicmuster.paste_rows()) - - self.menu.addSeparator() - - if track_row: - # Info - act_info = self.menu.addAction('Info') - act_info.triggered.connect( - lambda: self._info_row(track_id) - ) - act_copypath = self.menu.addAction("Copy track path") - act_copypath.triggered.connect( - lambda: self._copy_path(row_number)) - - self.menu.addSeparator() - - # Play with mplayer - act_mplayer = self.menu.addAction( - "Play with mplayer") - act_mplayer.triggered.connect( - lambda: self._mplayer_play(track_id)) - - # Set next - if not current and not next_row: - act_setnext = self.menu.addAction("Set next") - act_setnext.triggered.connect( - lambda: self._set_next(session, row_number)) - - if not current: - # Open in Audacity - act_audacity = self.menu.addAction( - "Open in Audacity") - act_audacity.triggered.connect( - lambda: self._open_in_audacity(track_id)) - - # Rescan - act_rescan = self.menu.addAction("Rescan") - act_rescan.triggered.connect( - lambda: self._rescan(row_number, track_id) - ) - - # Remove track - act_remove_track = self.menu.addAction( - 'Remove track') - act_remove_track.triggered.connect( - lambda: self._remove_track(row_number) - ) - self.menu.addSeparator() - - # Look up in wikipedia - act_wikip = self.menu.addAction("Wikipedia") - act_wikip.triggered.connect( - lambda: self._wikipedia(row_number) - ) - - # Look up in songfacts - act_songfacts = self.menu.addAction("Songfacts") - act_songfacts.triggered.connect( - lambda: self._songfacts(row_number) - ) - - self.menu.addSeparator() - - if header_row: - # Add track to section header (ie, make this a track - # row) - act_add_track = self.menu.addAction('Add track') - act_add_track.triggered.connect( - lambda: self._add_track(row_number)) - - if not current and not next_row: - # Remove row - act_delete = self.menu.addAction('Remove row') - act_delete.triggered.connect(self._delete_rows) - - self.menu.addSeparator() - - if not current and not next_row: - act_move = self.menu.addAction('Move to playlist...') - act_move.triggered.connect( - self.musicmuster.move_selected) - self.menu.addSeparator() - - return super(PlaylistTab, self).eventFilter(source, event) + return menu_item def mouseReleaseEvent(self, event): """ @@ -654,6 +532,8 @@ class PlaylistTab(QTableWidget): self._update_row_track_info(session, row, plr.track) if not played: self._set_row_bold(row) + else: + _ = self._set_row_userdata(row, self.PLAYED, True) else: # This is a section header so it must have note text if plr.note is None: @@ -726,8 +606,8 @@ class PlaylistTab(QTableWidget): if not row: return self.musicmuster.clear_next() - self.clear_selection() self._set_row_colour(row, None) + self.clear_selection() def play_started(self, session: scoped_session) -> None: """ @@ -997,12 +877,101 @@ class PlaylistTab(QTableWidget): session.flush() # Reset row span - for column in range(len(columns)): - self.setSpan(row, column, 1, 1) + self.setSpan(row, HEADER_NOTES_COLUMN, 1, 1) # Update attributes of row + self._set_row_bold(row) + self._set_row_colour(plr.row_number, None) _ = self._set_row_note(session, row, plr.note) self._update_row_track_info(session, row, track) + self.clear_selection() + + 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() + + # Play with mplayer + if track_row and not current: + self._add_context_menu("Play with mplayer", + lambda: self._mplayer_play(track_id)) + + # 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(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() + + # Remove row + if track_row and not current and not next_row: + self._add_context_menu('Delete row', self._delete_rows) + + # Move to playlist + if track_row and not current and not next_row: + self._add_context_menu('Move to playlist...', + self.musicmuster.move_selected) + + # ---------------------- + self.menu.addSeparator() + + # Look up in songfacts + if track_row: + self._add_context_menu("Songfacts", + lambda: self._songfacts(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)) + + # Mark unplayed + if self._get_row_userdata(row_number, self.PLAYED): + self._add_context_menu("Mark unplayed", + lambda: self._mark_unplayed(row_number)) + + # Unmark as next + if next_row: + self._add_context_menu("Unmark as next track", + self.mark_unnext) + + # ---------------------- + self.menu.addSeparator() + + # 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)) + + # return super(PlaylistTab, self).eventFilter(source, event) def _calculate_end_time(self, start: Optional[datetime], duration: int) -> Optional[datetime]: @@ -1036,7 +1005,8 @@ class PlaylistTab(QTableWidget): def _context_menu(self, pos): """Display right-click menu""" - assert self.menu + item = self.itemAt(pos) + self._build_context_menu(item) self.menu.exec_(self.mapToGlobal(pos)) def _copy_path(self, row: int) -> None: @@ -1415,18 +1385,23 @@ class PlaylistTab(QTableWidget): and pos.y() >= rect.center().y() # noqa W503 ) - def _mark_unplayed(self, plr: PlaylistRows) -> None: + def _mark_unplayed(self, row_number: int) -> None: """ Mark passed row as unplayed in this playlist """ - if not plr.row_number: + if row_number is None: return + _ = self._set_row_userdata(row_number, self.PLAYED, False) + self._set_row_bold(row_number, True) + self.clear_selection() + with Session() as session: - session.add(plr) + plr = self._get_row_plr(session, row_number) + if not plr: + return plr.played = False - self._set_row_colour(plr.row_number, None) def _move_row(self, session: scoped_session, plr: PlaylistRows, new_row_number: int) -> None: @@ -1526,8 +1501,10 @@ class PlaylistTab(QTableWidget): _ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note) note_colour = NoteColours.get_colour(session, plr.note) - if note_colour: - self._set_row_colour(row, QColor(note_colour)) + if not note_colour: + note_colour = Config.COLOUR_NOTES_PLAYLIST + self._set_row_colour(row, QColor(note_colour)) + self.clear_selection() def _rescan(self, row: int, track_id: int) -> None: """Rescan track""" @@ -1767,6 +1744,9 @@ class PlaylistTab(QTableWidget): def _set_played_row(self, session: scoped_session, row: int) -> None: """Mark this row as played""" + _ = self._set_row_userdata(row, self.PLAYED, True) + self._set_row_bold(row, False) + plr = self._get_row_plr(session, row) if not plr: return @@ -1889,7 +1869,7 @@ class PlaylistTab(QTableWidget): if section_header: column = HEADER_NOTES_COLUMN else: - column - ROW_NOTES + column = ROW_NOTES if not note_text: note_text = ""