Playlist drag and drop; track colours
This commit is contained in:
parent
d3b739dc36
commit
cf52eb3c9c
@ -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
|
||||
|
||||
@ -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:
|
||||
Sets the selected track as the next track.
|
||||
"""
|
||||
|
||||
- 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
|
||||
"""
|
||||
|
||||
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
114
app/promoted_classes.py
Normal 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_())
|
||||
@ -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&top</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Clear_selection">
|
||||
<property name="text">
|
||||
<string>&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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user