Playlist drag and drop; track colours

This commit is contained in:
Keith Edmunds 2021-04-02 00:22:29 +01:00
parent d3b739dc36
commit cf52eb3c9c
4 changed files with 208 additions and 24 deletions

View File

@ -4,6 +4,13 @@ import os
class Config(object):
AUDIO_SEGMENT_CHUNK_SIZE = 10
COLOUR_CURRENT_HEADER = "#d4edda"
COLOUR_CURRENT_PLAYLIST = "#28a745"
COLOUR_ODD_PLAYLIST = "#f2f2f2"
COLOUR_EVEN_PLAYLIST = "#d9d9d9"
COLOUR_NEXT_HEADER = "#fff3cd"
COLOUR_NEXT_PLAYLIST = "#ffc107"
COLOUR_PREVIOUS_HEADER = "#f8d7da"
DBFS_FADE = -12
DBFS_SILENCE = -50
DISPLAY_SQL = False

View File

@ -8,8 +8,15 @@ from datetime import datetime, timedelta
from log import DEBUG, ERROR
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow
from PyQt5.QtWidgets import QTableWidgetItem, QFileDialog, QListWidgetItem
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QDialog,
QFileDialog,
QListWidgetItem,
QMainWindow,
QTableWidgetItem,
)
from ui.main_window_ui import Ui_MainWindow
from ui.dlg_search_database_ui import Ui_Dialog
@ -101,6 +108,12 @@ class Music:
except AttributeError:
return ""
def get_next_track_id(self):
try:
return self.next_track['meta'].id
except AttributeError:
return 0
def fade_current(self):
if not self.playing():
return
@ -193,6 +206,19 @@ class Window(QMainWindow, Ui_MainWindow):
db_playlist = Playlists.get_only_playlist()
for track in db_playlist.get_tracks():
self.add_to_playlist(track)
# Set the first playable track as next to play
for row in range(self.playlist.rowCount()):
if self.set_next_track_row(row):
break
pl = self.playlist
row = pl.rowCount()
pl.insertRow(row)
item = QTableWidgetItem("to be spanned")
pl.setItem(row, 1, item)
pl.setSpan(row, 1, 1, 3)
colour = QColor(125, 75, 25)
pl.set_row_colour(row, colour)
self.timer.start(Config.TIMER_MS)
@ -220,14 +246,15 @@ class Window(QMainWindow, Ui_MainWindow):
record.update({'f_int': self.y()})
def connectSignalsSlots(self):
self.action_Clear_selection.triggered.connect(self.clear_selection)
self.actionFade.triggered.connect(self.fade)
self.actionPlay_next.triggered.connect(self.play_next)
self.actionPlay_selected.triggered.connect(self.play_next)
self.actionSearch_database.triggered.connect(self.selectFromDatabase)
self.btnSearchDatabase.clicked.connect(self.selectFromDatabase)
self.btnSetNextTrack.clicked.connect(self.set_next_track)
self.btnSkipNext.clicked.connect(self.play_next)
self.btnStop.clicked.connect(self.fade)
self.playlist.itemSelectionChanged.connect(self.set_next_track)
self.timer.timeout.connect(self.tick)
@ -235,6 +262,9 @@ class Window(QMainWindow, Ui_MainWindow):
dlg = DbDialog(self)
dlg.exec()
def clear_selection(self):
self.playlist.clearSelection()
def fade(self):
self.music.fade_current()
@ -258,6 +288,29 @@ class Window(QMainWindow, Ui_MainWindow):
self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S"))
self.label_fade_length.setText(ms_to_mmss(
silence_at - self.music.get_current_fade_at()))
self.set_playlist_colours()
def set_playlist_colours(self):
self.playlist.clearSelection()
current_track = self.music.get_current_track_id()
next_track = self.music.get_next_track_id()
for row in range(self.playlist.rowCount()):
try:
track_id = int(self.playlist.item(row, 0).text())
if track_id == next_track:
self.playlist.set_row_colour(
row, QColor(Config.COLOUR_NEXT_PLAYLIST))
elif track_id == current_track:
self.playlist.set_row_colour(
row, QColor(Config.COLOUR_CURRENT_PLAYLIST))
else:
if row % 2:
colour = QColor(Config.COLOUR_ODD_PLAYLIST)
else:
colour = QColor(Config.COLOUR_EVEN_PLAYLIST)
self.playlist.set_row_colour(row, colour)
except AttributeError:
pass
def play_selected(self):
if self.playlist.selectionModel().hasSelection():
@ -287,26 +340,18 @@ class Window(QMainWindow, Ui_MainWindow):
def set_next_track(self):
"""
Set the next track. In order of priority:
- the highlighted track so long as it's not the current track
- if the current track is highlighted, the next track if there
is one
- none, in which case disable play next controls
Sets the selected track as the next track.
"""
track_id = None
if self.playlist.selectionModel().hasSelection():
row = self.playlist.currentRow()
self.set_next_track_row(self.playlist.currentRow())
def set_next_track_row(self, row):
if self.playlist.item(row, 0):
track_id = int(self.playlist.item(row, 0).text())
if track_id == self.music.get_current_track_id():
# Current track highlighted: get next if it exists
try:
track_id = int(self.playlist.item(row + 1, 0).text())
except AttributeError:
# There is no next track
track_id = None
if track_id:
# Don't set current track as next track
return
DEBUG(f"set_next_track: track_id={track_id}")
if self.music.set_next_track(track_id) != track_id:
ERROR("Can't set next track")
@ -315,9 +360,11 @@ class Window(QMainWindow, Ui_MainWindow):
f"{self.music.get_next_artist()}"
)
self.enable_play_next_controls()
else:
self.next_track.setText("")
self.disable_play_next_controls()
self.playlist.clearSelection()
self.set_playlist_colours()
return True
return False
def tick(self):
# self.current_time.setText(now.strftime("%H:%M:%S"))

114
app/promoted_classes.py Normal file
View File

@ -0,0 +1,114 @@
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QDropEvent
from PyQt5.QtWidgets import (
QTableWidget,
QAbstractItemView,
QTableWidgetItem,
QWidget,
QHBoxLayout,
QApplication
)
from log import DEBUG
class Playlist(QTableWidget):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setDragEnabled(True)
self.setAcceptDrops(True)
self.viewport().setAcceptDrops(True)
self.setDragDropOverwriteMode(False)
self.setDropIndicatorShown(True)
self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dropEvent(self, event: QDropEvent):
if not event.isAccepted() and event.source() == self:
drop_row = self.drop_on(event)
rows = sorted(set(item.row() for item in self.selectedItems()))
rows_to_move = [
[QTableWidgetItem(self.item(row_index, column_index)) for
column_index in range(self.columnCount())]
for row_index in rows
]
for row_index in reversed(rows):
self.removeRow(row_index)
if row_index < drop_row:
drop_row -= 1
for row_index, data in enumerate(rows_to_move):
row_index += drop_row
self.insertRow(row_index)
for column_index, column_data in enumerate(data):
self.setItem(row_index, column_index, column_data)
event.accept()
for row_index in range(len(rows_to_move)):
for column_index in range(self.columnCount()):
self.item(drop_row + row_index,
column_index).setSelected(True)
super().dropEvent(event)
DEBUG(f"Moved row(s) {rows} to become row {drop_row}")
def drop_on(self, event):
index = self.indexAt(event.pos())
if not index.isValid():
return self.rowCount()
return (index.row() + 1 if self.is_below(event.pos(), index)
else index.row())
def is_below(self, pos, index):
rect = self.visualRect(index)
margin = 2
if pos.y() - rect.top() < margin:
return False
elif rect.bottom() - pos.y() < margin:
return True
# noinspection PyTypeChecker
return (
rect.contains(pos, True) and not
(int(self.model().flags(index)) & Qt.ItemIsDropEnabled)
and pos.y() >= rect.center().y() # noqa W503
)
def set_row_colour(self, row, colour):
for j in range(self.columnCount()):
if self.item(row, j):
self.item(row, j).setBackground(colour)
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
layout = QHBoxLayout()
self.setLayout(layout)
self.table_widget = Playlist()
layout.addWidget(self.table_widget)
# setup table widget
self.table_widget.setColumnCount(2)
self.table_widget.setHorizontalHeaderLabels(['Type', 'Name'])
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle'),
('Silver', 'Chevy'), ('Black', 'BMW')]
self.table_widget.setRowCount(len(items))
for i, (color, model) in enumerate(items):
self.table_widget.setItem(i, 0, QTableWidgetItem(color))
self.table_widget.setItem(i, 1, QTableWidgetItem(model))
self.resize(400, 400)
self.show()
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())

View File

@ -267,7 +267,7 @@ border: 1px solid rgb(85, 87, 83);</string>
</layout>
</item>
<item row="1" column="0">
<widget class="QTableWidget" name="playlist">
<widget class="Playlist" name="playlist">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
@ -275,7 +275,7 @@ border: 1px solid rgb(85, 87, 83);</string>
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
@ -732,6 +732,7 @@ border: 1px solid rgb(85, 87, 83);</string>
<addaction name="separator"/>
<addaction name="actionFade"/>
<addaction name="actionS_top"/>
<addaction name="action_Clear_selection"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuPlaylist"/>
@ -801,7 +802,22 @@ border: 1px solid rgb(85, 87, 83);</string>
<string>S&amp;top</string>
</property>
</action>
<action name="action_Clear_selection">
<property name="text">
<string>&amp;Clear selection</string>
</property>
<property name="shortcut">
<string>Esc</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>Playlist</class>
<extends>QTableWidget</extends>
<header>promoted_classes</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>