WIP: playlists refactoring

This commit is contained in:
Keith Edmunds 2023-03-05 14:31:30 +00:00
parent 530ee60015
commit 15f4bec197

View File

@ -5,7 +5,7 @@ import threading
from collections import namedtuple from collections import namedtuple
from datetime import datetime, timedelta 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 ( from PyQt5.QtCore import (
QEvent, QEvent,
@ -25,6 +25,7 @@ from PyQt5.QtGui import (
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemDelegate, QAbstractItemDelegate,
QAbstractItemView, QAbstractItemView,
QAction,
QApplication, QApplication,
QLineEdit, QLineEdit,
QMainWindow, QMainWindow,
@ -134,6 +135,7 @@ class PlaylistTab(QTableWidget):
ROW_DURATION = Qt.UserRole + 1 ROW_DURATION = Qt.UserRole + 1
PLAYLISTROW_ID = Qt.UserRole + 2 PLAYLISTROW_ID = Qt.UserRole + 2
TRACK_PATH = Qt.UserRole + 3 TRACK_PATH = Qt.UserRole + 3
PLAYED = Qt.UserRole + 4
def __init__(self, musicmuster: "Window", def __init__(self, musicmuster: "Window",
session: scoped_session, session: scoped_session,
@ -144,7 +146,7 @@ class PlaylistTab(QTableWidget):
self.signals = signals self.signals = signals
# Set up widget # Set up widget
self.menu: Optional[QMenu] = None self.menu = QMenu()
self.setItemDelegate(NoSelectDelegate(self)) self.setItemDelegate(NoSelectDelegate(self))
self.setEditTriggers(QAbstractItemView.DoubleClicked) self.setEditTriggers(QAbstractItemView.DoubleClicked)
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
@ -255,141 +257,17 @@ class PlaylistTab(QTableWidget):
# Update track times # Update track times
self._update_start_end_times() self._update_start_end_times()
def eventFilter(self, source, event): def _add_context_menu(self, text: str, action: Callable,
"""Used to process context (right-click) menu, which is defined here""" disabled: bool = False) -> QAction:
"""
Add item to self.menu
"""
if (event.type() == QEvent.MouseButtonPress and # noqa W504 menu_item = self.menu.addAction(text)
event.buttons() == Qt.RightButton and # noqa W504 menu_item.setDisabled(disabled)
source is self.viewport()): menu_item.triggered.connect(action)
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
# Mark unplayed / un-next return menu_item
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)
def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
""" """
@ -654,6 +532,8 @@ class PlaylistTab(QTableWidget):
self._update_row_track_info(session, row, plr.track) self._update_row_track_info(session, row, plr.track)
if not played: if not played:
self._set_row_bold(row) self._set_row_bold(row)
else:
_ = self._set_row_userdata(row, self.PLAYED, True)
else: else:
# This is a section header so it must have note text # This is a section header so it must have note text
if plr.note is None: if plr.note is None:
@ -726,8 +606,8 @@ class PlaylistTab(QTableWidget):
if not row: if not row:
return return
self.musicmuster.clear_next() self.musicmuster.clear_next()
self.clear_selection()
self._set_row_colour(row, None) self._set_row_colour(row, None)
self.clear_selection()
def play_started(self, session: scoped_session) -> None: def play_started(self, session: scoped_session) -> None:
""" """
@ -997,12 +877,101 @@ class PlaylistTab(QTableWidget):
session.flush() session.flush()
# Reset row span # Reset row span
for column in range(len(columns)): self.setSpan(row, HEADER_NOTES_COLUMN, 1, 1)
self.setSpan(row, column, 1, 1)
# Update attributes of row # 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._set_row_note(session, row, plr.note)
self._update_row_track_info(session, row, track) 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], def _calculate_end_time(self, start: Optional[datetime],
duration: int) -> Optional[datetime]: duration: int) -> Optional[datetime]:
@ -1036,7 +1005,8 @@ class PlaylistTab(QTableWidget):
def _context_menu(self, pos): def _context_menu(self, pos):
"""Display right-click menu""" """Display right-click menu"""
assert self.menu item = self.itemAt(pos)
self._build_context_menu(item)
self.menu.exec_(self.mapToGlobal(pos)) self.menu.exec_(self.mapToGlobal(pos))
def _copy_path(self, row: int) -> None: def _copy_path(self, row: int) -> None:
@ -1415,18 +1385,23 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503 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 Mark passed row as unplayed in this playlist
""" """
if not plr.row_number: if row_number is None:
return return
_ = self._set_row_userdata(row_number, self.PLAYED, False)
self._set_row_bold(row_number, True)
self.clear_selection()
with Session() as session: with Session() as session:
session.add(plr) plr = self._get_row_plr(session, row_number)
if not plr:
return
plr.played = False plr.played = False
self._set_row_colour(plr.row_number, None)
def _move_row(self, session: scoped_session, plr: PlaylistRows, def _move_row(self, session: scoped_session, plr: PlaylistRows,
new_row_number: int) -> None: new_row_number: int) -> None:
@ -1526,8 +1501,10 @@ class PlaylistTab(QTableWidget):
_ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note) _ = self._set_item_text(row, HEADER_NOTES_COLUMN, plr.note)
note_colour = NoteColours.get_colour(session, plr.note) note_colour = NoteColours.get_colour(session, plr.note)
if note_colour: if not note_colour:
note_colour = Config.COLOUR_NOTES_PLAYLIST
self._set_row_colour(row, QColor(note_colour)) self._set_row_colour(row, QColor(note_colour))
self.clear_selection()
def _rescan(self, row: int, track_id: int) -> None: def _rescan(self, row: int, track_id: int) -> None:
"""Rescan track""" """Rescan track"""
@ -1767,6 +1744,9 @@ class PlaylistTab(QTableWidget):
def _set_played_row(self, session: scoped_session, row: int) -> None: def _set_played_row(self, session: scoped_session, row: int) -> None:
"""Mark this row as played""" """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) plr = self._get_row_plr(session, row)
if not plr: if not plr:
return return
@ -1889,7 +1869,7 @@ class PlaylistTab(QTableWidget):
if section_header: if section_header:
column = HEADER_NOTES_COLUMN column = HEADER_NOTES_COLUMN
else: else:
column - ROW_NOTES column = ROW_NOTES
if not note_text: if not note_text:
note_text = "" note_text = ""