WIP: playlists refactoring
This commit is contained in:
parent
530ee60015
commit
15f4bec197
272
app/playlists.py
272
app/playlists.py
@ -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:
|
||||||
self._set_row_colour(row, QColor(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:
|
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 = ""
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user