Compare commits
4 Commits
fed4e9fbde
...
9a7d24b895
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a7d24b895 | ||
|
|
45a564729b | ||
|
|
cc2f3733b2 | ||
|
|
77716005c7 |
@ -45,11 +45,9 @@ def Session() -> Generator[scoped_session, None, None]:
|
||||
function = frame.function
|
||||
lineno = frame.lineno
|
||||
Session = scoped_session(sessionmaker(bind=engine, future=True))
|
||||
log.debug(
|
||||
f"Session acquired, {file=}, {function=}, "
|
||||
f"function{lineno=}, {Session=}"
|
||||
)
|
||||
log.debug(f"SqlA: session acquired [{hex(id(Session))}]")
|
||||
log.debug(f"Session acquisition: {function}:{lineno} [{hex(id(Session))}]")
|
||||
yield Session
|
||||
log.debug(" Session released")
|
||||
log.debug(f" SqlA: session released [{hex(id(Session))}]")
|
||||
Session.commit()
|
||||
Session.close()
|
||||
|
||||
@ -249,6 +249,17 @@ class ImportTrack(QObject):
|
||||
self.finished.emit(self.playlist)
|
||||
|
||||
|
||||
class MusicMusterSignals(QObject):
|
||||
"""
|
||||
Class for all MusicMuster signals. See:
|
||||
- https://zetcode.com/gui/pyqt5/eventssignals/
|
||||
- https://stackoverflow.com/questions/62654525/
|
||||
emit-a-signal-from-another-class-to-main-class
|
||||
"""
|
||||
|
||||
save_playlist_signal = pyqtSignal()
|
||||
|
||||
|
||||
class Window(QMainWindow, Ui_MainWindow):
|
||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -267,6 +278,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.previous_track_position: Optional[float] = None
|
||||
self.selected_plrs: Optional[List[PlaylistRows]] = None
|
||||
|
||||
self.signals = MusicMusterSignals()
|
||||
|
||||
# Set colours that will be used by playlist row stripes
|
||||
palette = QPalette()
|
||||
palette.setColor(QPalette.Base, QColor(Config.COLOUR_EVEN_PLAYLIST))
|
||||
@ -514,11 +527,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
"Can't close current track playlist", 5000)
|
||||
return False
|
||||
|
||||
# Don't close next track playlist
|
||||
# Attempt to close next track playlist
|
||||
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
|
||||
self.statusbar.showMessage(
|
||||
"Can't close next track playlist", 5000)
|
||||
return False
|
||||
self.next_track.playlist_tab.mark_unnext()
|
||||
|
||||
# Record playlist as closed and update remaining playlist tabs
|
||||
with Session() as session:
|
||||
@ -614,7 +625,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
assert playlist.id
|
||||
|
||||
playlist_tab = PlaylistTab(
|
||||
musicmuster=self, session=session, playlist_id=playlist.id)
|
||||
musicmuster=self, session=session, playlist_id=playlist.id,
|
||||
signals=self.signals)
|
||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||
self.tabPlaylist.setCurrentIndex(idx)
|
||||
|
||||
@ -634,6 +646,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
def debug(self):
|
||||
"""Invoke debugger"""
|
||||
|
||||
visible_playlist_id = self.visible_playlist_tab().playlist_id
|
||||
print(f"Active playlist id={visible_playlist_id}")
|
||||
import ipdb # type: ignore
|
||||
ipdb.set_trace()
|
||||
|
||||
|
||||
107
app/playlists.py
107
app/playlists.py
@ -8,7 +8,6 @@ from datetime import datetime, timedelta
|
||||
from typing import cast, List, Optional, TYPE_CHECKING, Union
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
pyqtSignal,
|
||||
QEvent,
|
||||
QModelIndex,
|
||||
QObject,
|
||||
@ -60,7 +59,7 @@ from models import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from musicmuster import Window
|
||||
from musicmuster import Window, MusicMusterSignals
|
||||
|
||||
start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
|
||||
HEADER_NOTES_COLUMN = 2
|
||||
@ -138,10 +137,11 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
def __init__(self, musicmuster: "Window",
|
||||
session: scoped_session,
|
||||
playlist_id: int, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
playlist_id: int, signals: "MusicMusterSignals") -> None:
|
||||
super().__init__()
|
||||
self.musicmuster: Window = musicmuster
|
||||
self.playlist_id = playlist_id
|
||||
self.signals = signals
|
||||
|
||||
# Set up widget
|
||||
self.menu: Optional[QMenu] = None
|
||||
@ -170,6 +170,7 @@ class PlaylistTab(QTableWidget):
|
||||
self.horizontalHeader().sectionResized.connect(
|
||||
self.resizeRowsToContents)
|
||||
|
||||
# Drag and drop setup
|
||||
self.setAcceptDrops(True)
|
||||
self.viewport().setAcceptDrops(True)
|
||||
self.setDragDropOverwriteMode(False)
|
||||
@ -193,6 +194,7 @@ class PlaylistTab(QTableWidget):
|
||||
self.selecting_in_progress = False
|
||||
# Connect signals
|
||||
self.horizontalHeader().sectionResized.connect(self._column_resize)
|
||||
self.signals.save_playlist_signal.connect(self._deferred_save)
|
||||
|
||||
# Load playlist rows
|
||||
self.populate_display(session, self.playlist_id)
|
||||
@ -290,7 +292,7 @@ class PlaylistTab(QTableWidget):
|
||||
act_unnext = self.menu.addAction(
|
||||
"Unmark as next track")
|
||||
act_unnext.triggered.connect(
|
||||
lambda: self._mark_unnext(row_number))
|
||||
lambda: self.mark_unnext())
|
||||
if sep:
|
||||
self.menu.addSeparator()
|
||||
|
||||
@ -440,7 +442,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
track_id = self._get_row_track_id(row)
|
||||
|
||||
# Determin cell type changed
|
||||
# Determine cell type changed
|
||||
with Session() as session:
|
||||
# Get playlistrow object
|
||||
plr_id = self._get_playlistrow_id(row)
|
||||
@ -460,8 +462,12 @@ class PlaylistTab(QTableWidget):
|
||||
self._set_row_start_time(row, start_time)
|
||||
else:
|
||||
self._set_row_start_time(row, None)
|
||||
# Update display including note colour
|
||||
# Update note display
|
||||
self._set_row_note(session, row, new_text)
|
||||
# If this is a header row, ecalcuate track times in case
|
||||
# note added a start time
|
||||
if not track_id:
|
||||
self._update_start_end_times()
|
||||
else:
|
||||
track = None
|
||||
if track_id:
|
||||
@ -593,14 +599,14 @@ class PlaylistTab(QTableWidget):
|
||||
return [self._get_playlistrow_id(a) for a in self._get_selected_rows()]
|
||||
|
||||
def get_selected_playlistrows(
|
||||
self, session: scoped_session) -> Optional[List[PlaylistRows]]:
|
||||
self, session: scoped_session) -> List[PlaylistRows]:
|
||||
"""
|
||||
Return a list of PlaylistRows of the selected rows
|
||||
"""
|
||||
|
||||
plr_ids = self.get_selected_playlistrow_ids()
|
||||
if not plr_ids:
|
||||
return None
|
||||
return []
|
||||
plrs = [session.get(PlaylistRows, a) for a in plr_ids]
|
||||
|
||||
return [plr for plr in plrs if plr is not None]
|
||||
@ -700,7 +706,8 @@ class PlaylistTab(QTableWidget):
|
||||
_ = self._set_row_userdata(row, self.ROW_TRACK_ID, 0)
|
||||
|
||||
if update_track_times:
|
||||
self._update_start_end_times()
|
||||
# Queue time updates so playlist updates first
|
||||
QTimer.singleShot(0, lambda: self._update_start_end_times())
|
||||
|
||||
def insert_track(self, session: scoped_session, track: Tracks,
|
||||
note: Optional[str] = None, repaint: bool = True) -> None:
|
||||
@ -743,6 +750,19 @@ class PlaylistTab(QTableWidget):
|
||||
# Let display update, then save playlist
|
||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
||||
|
||||
def mark_unnext(self) -> None:
|
||||
"""
|
||||
Unmark passed row as next track
|
||||
"""
|
||||
|
||||
row = self._get_next_track_row_number()
|
||||
if not row:
|
||||
return
|
||||
self.musicmuster.clear_next()
|
||||
self.clear_selection()
|
||||
self._set_row_colour(row, None)
|
||||
self.musicmuster.update_headers()
|
||||
|
||||
def play_started(self, session: scoped_session) -> None:
|
||||
"""
|
||||
Notification from musicmuster that track has started playing.
|
||||
@ -833,13 +853,6 @@ class PlaylistTab(QTableWidget):
|
||||
for row in sorted(row_numbers, reverse=True):
|
||||
self.removeRow(row)
|
||||
|
||||
def remove_selected_rows(self) -> None:
|
||||
"""Remove selected rows from display"""
|
||||
|
||||
self.remove_rows(self._get_selected_rows())
|
||||
# Reset drag mode
|
||||
self.setDragEnabled(False)
|
||||
|
||||
def reset_plr_row_colour(self, plr_id: int) -> None:
|
||||
"""Reset background of row pointed to by plr_id"""
|
||||
|
||||
@ -1292,6 +1305,15 @@ class PlaylistTab(QTableWidget):
|
||||
cb.clear(mode=cb.Clipboard)
|
||||
cb.setText(pathqs, mode=cb.Clipboard)
|
||||
|
||||
def _deferred_save(self) -> None:
|
||||
"""
|
||||
Create session and save playlist
|
||||
"""
|
||||
|
||||
print("_deferred_save() called")
|
||||
with Session() as session:
|
||||
self.save_playlist(session)
|
||||
|
||||
def _delete_rows(self) -> None:
|
||||
"""
|
||||
Delete mutliple rows
|
||||
@ -1301,22 +1323,33 @@ class PlaylistTab(QTableWidget):
|
||||
- Save the playlist
|
||||
"""
|
||||
|
||||
# Delete rows from database
|
||||
plr_ids = self.get_selected_playlistrow_ids()
|
||||
if not plr_ids:
|
||||
return
|
||||
|
||||
# Get confirmation
|
||||
row_count = len(plr_ids)
|
||||
plural = 's' if row_count > 1 else ''
|
||||
if not ask_yes_no("Delete rows",
|
||||
f"Really delete {row_count} row{plural}?"):
|
||||
return
|
||||
|
||||
self.remove_selected_rows()
|
||||
|
||||
with Session() as session:
|
||||
QTimer.singleShot(0, lambda: self.save_playlist(session))
|
||||
plrs = self.get_selected_playlistrows(session)
|
||||
row_count = len(plrs)
|
||||
if not row_count:
|
||||
return
|
||||
|
||||
# Get confirmation
|
||||
plural = 's' if row_count > 1 else ''
|
||||
if not ask_yes_no("Delete rows",
|
||||
f"Really delete {row_count} row{plural}?"):
|
||||
return
|
||||
|
||||
rows_to_delete = [a.row_number for a in plrs]
|
||||
|
||||
# Delete rows from database. Would be more efficient to
|
||||
# query then have a single delete.
|
||||
for plr in plrs:
|
||||
session.delete(plr)
|
||||
|
||||
# Remove from display
|
||||
self.remove_rows(rows_to_delete)
|
||||
|
||||
# Reset drag mode
|
||||
self.setDragEnabled(False)
|
||||
|
||||
# QTimer.singleShot(0, lambda: self._deferred_save())
|
||||
self.signals.save_playlist_signal.emit()
|
||||
|
||||
def _drop_on(self, event):
|
||||
"""
|
||||
@ -1618,16 +1651,6 @@ class PlaylistTab(QTableWidget):
|
||||
and pos.y() >= rect.center().y() # noqa W503
|
||||
)
|
||||
|
||||
def _mark_unnext(self, row: int) -> None:
|
||||
"""
|
||||
Unmark passed row as next track
|
||||
"""
|
||||
|
||||
self.musicmuster.clear_next()
|
||||
self.clear_selection()
|
||||
self._set_row_colour(row, None)
|
||||
self.musicmuster.update_headers()
|
||||
|
||||
def _mark_unplayed(self, plr: PlaylistRows) -> None:
|
||||
"""
|
||||
Mark passed row as unplayed in this playlist
|
||||
|
||||
110
tree.py
Executable file
110
tree.py
Executable file
@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
datas = {
|
||||
"Category 1": [
|
||||
("New Game 2", "Playnite", "", "", "Never", "Not Played", ""),
|
||||
("New Game 3", "Playnite", "", "", "Never", "Not Played", ""),
|
||||
],
|
||||
"No Category": [
|
||||
("New Game", "Playnite", "", "", "Never", "Not Plated", ""),
|
||||
]
|
||||
}
|
||||
|
||||
class GroupDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self, parent=None):
|
||||
super(GroupDelegate, self).__init__(parent)
|
||||
self._plus_icon = QtGui.QIcon("plus.png")
|
||||
self._minus_icon = QtGui.QIcon("minus.png")
|
||||
|
||||
def initStyleOption(self, option, index):
|
||||
super(GroupDelegate, self).initStyleOption(option, index)
|
||||
if not index.parent().isValid():
|
||||
is_open = bool(option.state & QtWidgets.QStyle.State_Open)
|
||||
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
|
||||
option.icon = self._minus_icon if is_open else self._plus_icon
|
||||
|
||||
class GroupView(QtWidgets.QTreeView):
|
||||
def __init__(self, model, parent=None):
|
||||
super(GroupView, self).__init__(parent)
|
||||
self.setIndentation(0)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.clicked.connect(self.on_clicked)
|
||||
delegate = GroupDelegate(self)
|
||||
self.setItemDelegateForColumn(0, delegate)
|
||||
self.setModel(model)
|
||||
self.header().setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||
self.setStyleSheet("background-color: #0D1225;")
|
||||
|
||||
@QtCore.pyqtSlot(QtCore.QModelIndex)
|
||||
def on_clicked(self, index):
|
||||
if not index.parent().isValid() and index.column() == 0:
|
||||
self.setExpanded(index, not self.isExpanded(index))
|
||||
|
||||
|
||||
class GroupModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, parent=None):
|
||||
super(GroupModel, self).__init__(parent)
|
||||
self.setColumnCount(8)
|
||||
self.setHorizontalHeaderLabels(["", "Name", "Library", "Release Date", "Genre(s)", "Last Played", "Time Played", ""])
|
||||
for i in range(self.columnCount()):
|
||||
it = self.horizontalHeaderItem(i)
|
||||
it.setForeground(QtGui.QColor("#F2F2F2"))
|
||||
|
||||
def add_group(self, group_name):
|
||||
item_root = QtGui.QStandardItem()
|
||||
item_root.setEditable(False)
|
||||
item = QtGui.QStandardItem(group_name)
|
||||
item.setEditable(False)
|
||||
ii = self.invisibleRootItem()
|
||||
i = ii.rowCount()
|
||||
for j, it in enumerate((item_root, item)):
|
||||
ii.setChild(i, j, it)
|
||||
ii.setEditable(False)
|
||||
for j in range(self.columnCount()):
|
||||
it = ii.child(i, j)
|
||||
if it is None:
|
||||
it = QtGui.QStandardItem()
|
||||
ii.setChild(i, j, it)
|
||||
it.setBackground(QtGui.QColor("#002842"))
|
||||
it.setForeground(QtGui.QColor("#F2F2F2"))
|
||||
return item_root
|
||||
|
||||
def append_element_to_group(self, group_item, texts):
|
||||
j = group_item.rowCount()
|
||||
item_icon = QtGui.QStandardItem()
|
||||
item_icon.setEditable(False)
|
||||
item_icon.setIcon(QtGui.QIcon("game.png"))
|
||||
item_icon.setBackground(QtGui.QColor("#0D1225"))
|
||||
group_item.setChild(j, 0, item_icon)
|
||||
for i, text in enumerate(texts):
|
||||
item = QtGui.QStandardItem(text)
|
||||
item.setEditable(False)
|
||||
item.setBackground(QtGui.QColor("#0D1225"))
|
||||
item.setForeground(QtGui.QColor("#F2F2F2"))
|
||||
group_item.setChild(j, i+1, item)
|
||||
|
||||
|
||||
class MainWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self, parent=None):
|
||||
super(MainWindow, self).__init__(parent)
|
||||
|
||||
model = GroupModel(self)
|
||||
tree_view = GroupView(model)
|
||||
self.setCentralWidget(tree_view)
|
||||
|
||||
for group, childrens in datas.items():
|
||||
group_item = model.add_group(group)
|
||||
for children in childrens:
|
||||
model.append_element_to_group(group_item, children)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
w = MainWindow()
|
||||
w.resize(720, 240)
|
||||
w.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user