Anna Schumaker
1b9458c278
Playlists use a tracks.TrackidSet to manage a set of trackids representing the Tracks in this Playlist. I have two functions for loading tracks: load_tracks() and reload_tracks(). Calling load_tracks() checks if the tracks have been loaded first before doing any work, but calling reload_tracks() will force the Playlist to go to the database to load the latest tracks. Finally, I add a have-next-track property to the main database connection. This is set to True whenever the active playlist has one or more tracks. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
# Copyright 2022 (c) Anna Schumaker
|
|
"""A customized Gio.ListStore for tracking Playlist GObjects."""
|
|
from gi.repository import GObject
|
|
from gi.repository import Gio
|
|
from gi.repository import Gtk
|
|
from .tracks import Track, TrackidSet
|
|
from .. import format
|
|
from . import table
|
|
|
|
|
|
class Playlist(table.Row):
|
|
"""Our shared Playlist Row object."""
|
|
|
|
propertyid = GObject.Property(type=int)
|
|
|
|
name = GObject.Property(type=str)
|
|
active = GObject.Property(type=bool, default=False)
|
|
|
|
tracks = GObject.Property(type=TrackidSet)
|
|
n_tracks = GObject.Property(type=int)
|
|
tracks_loaded = GObject.Property(type=bool, default=False)
|
|
tracks_movable = GObject.Property(type=bool, default=False)
|
|
|
|
children = GObject.Property(type=Gtk.FilterListModel)
|
|
|
|
def __init__(self, table: Gio.ListModel, propertyid: int,
|
|
name: str, **kwargs):
|
|
"""Initialize a Playlist object."""
|
|
super().__init__(table=table, propertyid=propertyid, name=name,
|
|
tracks=TrackidSet(), **kwargs)
|
|
self.tracks.bind_property("n-trackids", self, "n-tracks")
|
|
|
|
def __add_track(self, track: Track) -> bool:
|
|
self.tracks.add_track(track)
|
|
return True
|
|
|
|
def __remove_track(self, track: Track) -> bool:
|
|
self.tracks.remove_track(track)
|
|
self.table.remove_track(self, track)
|
|
return True
|
|
|
|
def add_children(self, child_table: table.Table,
|
|
child_filter: Gtk.Filter) -> None:
|
|
"""Create a FilterListModel for this playlist's children."""
|
|
self.children = Gtk.FilterListModel.new(child_table, child_filter)
|
|
self.children.set_incremental(True)
|
|
|
|
def do_update(self, column: str) -> bool:
|
|
"""Update a Playlist object."""
|
|
match column:
|
|
case "propertyid" | "name" | "n-tracks" | "children" | \
|
|
"tracks-loaded" | "tracks-movable": pass
|
|
case _: return super().do_update(column)
|
|
return True
|
|
|
|
def add_track(self, track: Track, *, idle: bool = False) -> None:
|
|
"""Add a Track to this Playlist."""
|
|
if self.table.add_track(self, track):
|
|
self.table.queue.push(self.__add_track, track, now=not idle)
|
|
|
|
def has_track(self, track: Track) -> bool:
|
|
"""Check if a Track is on this Playlist."""
|
|
return track in self.tracks
|
|
|
|
def load_tracks(self) -> bool:
|
|
"""Load this Playlist's Tracks (if they haven't been loaded yet)."""
|
|
if not self.tracks_loaded:
|
|
self.tracks.trackids = self.table.get_trackids(self)
|
|
self.tracks_loaded = True
|
|
return True
|
|
|
|
def move_track_down(self, track: Track) -> bool:
|
|
"""Move a track down in the sort order."""
|
|
return self.table.move_track_down(self, track)
|
|
|
|
def move_track_up(self, track: Track) -> bool:
|
|
"""Move a track up in the sort order."""
|
|
return self.table.move_track_up(self, track)
|
|
|
|
def reload_tracks(self, *, idle: bool = False) -> None:
|
|
"""Load this Playlist's Tracks."""
|
|
self.tracks_loaded = False
|
|
self.table.queue.push(self.load_tracks, now=not idle)
|
|
|
|
def remove_track(self, track: table.Row, *, idle: bool = False) -> None:
|
|
"""Remove a Track from this Playlist."""
|
|
self.table.queue.push(self.__remove_track, track, now=not idle)
|
|
|
|
def rename(self, new_name: str) -> bool:
|
|
"""Rename this playlist."""
|
|
return self.table.rename(self, new_name)
|
|
|
|
@GObject.Property(type=table.Row)
|
|
def parent(self) -> table.Row | None:
|
|
"""Get this playlist's parent playlist."""
|
|
return None
|
|
|
|
|
|
class Table(table.Table):
|
|
"""A table.Table with extra functionality for Playlists."""
|
|
|
|
active_playlist = GObject.Property(type=Playlist)
|
|
treemodel = GObject.Property(type=Gtk.TreeListModel)
|
|
|
|
def __init__(self, sql: GObject.TYPE_PYOBJECT, **kwargs):
|
|
"""Initialize a Playlist Table."""
|
|
super().__init__(sql=sql, **kwargs)
|
|
self.treemodel = Gtk.TreeListModel.new(root=self,
|
|
passthrough=False,
|
|
autoexpand=False,
|
|
create_func=self.__create_tree)
|
|
|
|
def __create_tree(self, plist: Playlist) -> Gtk.FilterListModel | None:
|
|
return plist.children
|
|
|
|
def do_get_sort_key(self, playlist: Playlist) -> tuple[str]:
|
|
"""Get a sort key for the requested Playlist."""
|
|
return format.sort_key(playlist.name)
|
|
|
|
def clear(self) -> None:
|
|
"""Clear the Table."""
|
|
self.active_playlist = None
|
|
super().clear()
|
|
|
|
def construct(self, propertyid: int, name: str, **kwargs) -> Playlist:
|
|
"""Construct a new Playlist object."""
|
|
res = super().construct(propertyid=propertyid, name=name, **kwargs)
|
|
if res.active:
|
|
self.sql.set_active_playlist(res)
|
|
return res
|
|
|
|
def delete(self, playlist: Playlist) -> bool:
|
|
"""Delete a playlist from the database."""
|
|
if playlist.active:
|
|
self.sql.set_active_playlist(None)
|
|
return super().delete(playlist)
|
|
|
|
def update(self, playlist: Playlist, column: str, newval) -> bool:
|
|
"""Update a Playlist in the Database."""
|
|
match column:
|
|
case "active":
|
|
return self.update_playlist_property(playlist, column, newval)
|
|
case _:
|
|
return super().update(playlist, column, newval)
|
|
|
|
def update_playlist_property(self, playlist: Playlist,
|
|
column: str, newval) -> bool:
|
|
"""Update the playlists_common table."""
|
|
match column:
|
|
case "active":
|
|
self.active_playlist = playlist if playlist.active else None
|
|
|
|
return self.sql(f"""UPDATE playlist_properties
|
|
SET {column}=? WHERE propertyid=?""",
|
|
newval, playlist.propertyid) is not None
|