emmental/emmental/tracklist/selection.py

188 lines
7.2 KiB
Python

# Copyright 2023 (c) Anna Schumaker.
"""An OSD to show when tracks are selected."""
from gi.repository import GObject
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import Adw
from ..buttons import PopoverButton
from .. import db
from .. import factory
from .. import playlist
class PlaylistRowWidget(Gtk.Box):
"""A row widget for Playlists."""
name = GObject.Property(type=str)
image = GObject.Property(type=GObject.TYPE_PYOBJECT)
def __init__(self):
"""Initialize a PlaylistRowWidget."""
super().__init__()
self._icon = Adw.Avatar(size=32)
self._label = Gtk.Label(xalign=0.0)
self.bind_property("name", self._label, "label")
self.bind_property("name", self._icon, "text")
self.connect("notify::name", self.__name_changed)
self.connect("notify::image", self.__image_changed)
self.append(self._icon)
self.append(self._label)
def __name_changed(self, row: Gtk.Box, param) -> None:
match self.name:
case "Favorite Tracks": icon = "heart-filled-symbolic"
case "Queued Tracks": icon = "music-queue-symbolic"
case _: icon = "playlist2-symbolic"
self._icon.set_icon_name(icon)
def __image_changed(self, row: Gtk.Box, param) -> None:
if self.image is not None and self.image.is_file():
texture = Gdk.Texture.new_from_filename(str(self.image))
else:
texture = None
self._icon.set_custom_image(texture)
class PlaylistRow(factory.ListRow):
"""A list row for displaying Playlists."""
def __init__(self, listitem: Gtk.ListItem):
"""Initialize a PlaylistRow."""
super().__init__(listitem)
self.child = PlaylistRowWidget()
def do_bind(self):
"""Bind a Playlist to this Row."""
self.bind_and_set_property("name", "name")
self.bind_and_set_property("image", "image")
class UserTracksFilter(Gtk.Filter):
"""Filters for tracks with user-tracks set to True."""
playlist = GObject.Property(type=db.playlist.Playlist)
def __init__(self):
"""Initialize the UserTracksFilter."""
super().__init__()
self.connect("notify::playlist", self.__playlist_changed)
def __playlist_changed(self, filter: Gtk.Filter, param) -> None:
self.changed(Gtk.FilterChange.DIFFERENT)
def do_match(self, playlist: db.playlist.Playlist) -> bool:
"""Check if a specific playlist has user-tracks set to True."""
return playlist.user_tracks and playlist != self.playlist
class PlaylistView(Gtk.ListView):
"""A ListView for selecting Playlists."""
playlist = GObject.Property(type=db.playlist.Playlist)
def __init__(self, sql: db.Connection):
"""Initialize the PlaylistView."""
super().__init__(show_separators=True, single_click_activate=True)
self._filtered = Gtk.FilterListModel(model=sql.playlists,
filter=UserTracksFilter())
self._selection = Gtk.NoSelection(model=self._filtered)
self._factory = factory.Factory(PlaylistRow)
self.connect("activate", self.__playlist_activated)
self.bind_property("playlist", self._filtered.get_filter(), "playlist")
self.add_css_class("rich-list")
self.set_model(self._selection)
self.set_factory(self._factory)
def __playlist_activated(self, view: Gtk.ListView, position: int) -> None:
self.emit("playlist-selected", self._selection[position])
@GObject.Signal(arg_types=(db.playlists.Playlist,))
def playlist_selected(self, playlist: db.playlists.Playlist) -> None:
"""Signal that the user has selected a Playlist."""
class OSD(Gtk.Overlay):
"""An Overlay with extra controls for the Tracklist."""
playlist = GObject.Property(type=playlist.playlist.Playlist)
selection = GObject.Property(type=Gtk.SelectionModel)
have_selected = GObject.Property(type=bool, default=False)
n_selected = GObject.Property(type=int)
def __init__(self, sql: db.Connection,
selection: Gtk.SelectionModel, **kwargs):
"""Initialize an OSD."""
super().__init__(selection=selection, **kwargs)
self._add = PopoverButton(child=Adw.ButtonContent(label="Add",
icon_name="list-add-symbolic"),
halign=Gtk.Align.START, valign=Gtk.Align.END,
margin_start=16, margin_bottom=16,
direction=Gtk.ArrowType.UP, visible=False,
popover_child=PlaylistView(sql))
self._remove = Gtk.Button(child=Adw.ButtonContent(label="Remove",
icon_name="list-remove-symbolic"),
halign=Gtk.Align.END, valign=Gtk.Align.END,
margin_end=16, margin_bottom=16,
visible=False)
self._sizegroup = Gtk.SizeGroup.new(Gtk.SizeGroupMode.HORIZONTAL)
self._add.add_css_class("suggested-action")
self._add.add_css_class("pill")
self._remove.add_css_class("destructive-action")
self._remove.add_css_class("pill")
self.selection.connect("selection-changed", self.__selection_changed)
self._add.popover_child.connect("playlist-selected", self.__add_tracks)
self._remove.connect("clicked", self.__remove_clicked)
self._sizegroup.add_widget(self._add)
self._sizegroup.add_widget(self._remove)
self.add_overlay(self._add)
self.add_overlay(self._remove)
def __get_selected_tracks(self) -> list:
selection = self.selection.get_selection()
return [self.selection.get_item(selection.get_nth(n))
for n in range(selection.get_size())]
def __add_tracks(self, view: PlaylistView,
playlist: db.playlists.Playlist) -> None:
for track in self.__get_selected_tracks():
playlist.add_track(track)
self.clear_selection()
def __remove_clicked(self, button: Gtk.Button) -> None:
if self.playlist is not None:
for track in self.__get_selected_tracks():
self.playlist.remove_track(track)
self.clear_selection()
def __selection_changed(self, selection: Gtk.SelectionModel,
position: int, n_items: int) -> None:
self.n_selected = selection.get_selection().get_size()
self.have_selected = self.n_selected > 0
self.__update_visibility()
def __update_visibility(self) -> None:
db_plist = None if self.playlist is None else self.playlist.playlist
user = False if db_plist is None else db_plist.user_tracks
self._add.set_visible(db_plist is not None and self.have_selected)
self._remove.set_visible(user and self.have_selected)
def clear_selection(self, *args) -> None:
"""Clear the current selection."""
self.selection.unselect_all()
def reset(self) -> None:
"""Reset the OSD."""
self.selection.unselect_all()
self.__selection_changed(self.selection, 0, 0)
if self.playlist is not None:
self._add.popover_child.playlist = self.playlist.playlist