# Copyright 2021 (c) Anna Schumaker. import lib from gi.repository import GObject from gi.repository import Gio from gi.repository import Gtk class PlaylistModel(GObject.GObject, Gio.ListModel): def __init__(self): GObject.GObject.__init__(self) self.playlist = None self.tracks = [ ] def do_get_item_type(self): return GObject.TYPE_PYOBJECT def do_get_n_items(self): return len(self.tracks) def do_get_item(self, n): return self.tracks[n] if n < len(self.tracks) else None def get_playlist(self): return self.playlist def set_playlist(self, playlist): if self.playlist: self.playlist.disconnect_by_func(self.refreshed) self.playlist.disconnect_by_func(self.track_added) self.playlist.disconnect_by_func(self.track_removed) self.playlist = playlist self.refreshed(playlist) self.playlist.connect("refreshed", self.refreshed) self.playlist.connect("track-added", self.track_added) self.playlist.connect("track-removed", self.track_removed) def track_added(self, plist, track): index = plist.get_track_index(track) self.tracks.insert(index, track) self.emit("items-changed", index, 0, 1) def track_removed(self, plist, track, adjusted_current): index = self.tracks.index(track) del self.tracks[index] self.emit("items-changed", index, 1, 0) def refreshed(self, plist): rm = len(self.tracks) self.tracks = plist.get_tracks() self.emit("items-changed", 0, rm, len(self.tracks)) class Filter(lib.filter.Regex): def do_match(self, track): fields = [ f"disc={track.disc.number}", f"track={track.number}", f"title={track.title}", f"artist={track.artist.name}", f"album={track.album.name}", f"subtitle={track.disc.subtitle}", f"year={track.year.year}" ] return self.search(' & '.join(fields)) PlaylistFilter = Filter() class FilterPlaylistModel(Gtk.FilterListModel): def __init__(self): Gtk.FilterListModel.__init__(self) self.set_model(PlaylistModel()) self.set_filter(PlaylistFilter) def get_playlist(self): return self.get_model().get_playlist() def set_playlist(self, plist): self.set_incremental(plist.get_n_tracks() > 1000) self.get_model().set_playlist(plist) def get_runtime(self): n = self.get_n_items() return sum([ int(self.get_item(i).length) for i in range(n) ]) class PlaylistSelection(Gtk.MultiSelection): def __init__(self): Gtk.MultiSelection.__init__(self) self.set_model(FilterPlaylistModel()) def get_filter_model(self): return self.get_model() def get_playlist(self): return self.get_model().get_playlist() def set_playlist(self, plist): return self.get_model().set_playlist(plist) class SortPlaylistModel(Gtk.StringList): def __init__(self): Gtk.StringList.__init__(self) self.playlist = None self.connect("items-changed", self.order_changed) def get_index(self, string): fields = [ self.get_string(i).split()[0] for i in range(self.get_n_items()) ] return fields.index(string) if string in fields else None def get_direction(self, string): return self.get_string(self.get_index(string)).split()[1] def set_playlist(self, plist): self.handler_block_by_func(self.order_changed) self.playlist = plist order = [ f for f in plist.sort if f.split()[0] not in ( "discs.number", "tracks.trackid") ] self.splice(0, self.get_n_items(), order) self.handler_unblock_by_func(self.order_changed) def append(self, field): super().append(f"{field} ASC") def remove(self, field): super().remove(self.get_index(field)) def move_up(self, field): if (index := self.get_index(field)) > 0: self.splice(index - 1, 2, [ self.get_string(index), self.get_string(index - 1) ]) def move_down(self, field): if (index := self.get_index(field)) < (self.get_n_items() - 1): self.splice(index, 2, [ self.get_string(index + 1), self.get_string(index) ]) def reverse(self, field): index = self.get_index(field) dir = 'DESC' if self.get_direction(field) == 'ASC' else 'ASC' self.splice(index, 1, [ f"{field} {dir}" ]) def order_changed(self, model, pos, rm, add): order = [ self.get_string(i) for i in range(self.get_n_items()) ] if i := self.get_index("tracks.number"): order.insert(i, f"discs.number {self.get_direction('tracks.number')}") self.playlist.sort = order class SortOptionsModel(Gtk.StringList): def __init__(self): Gtk.StringList.__init__(self) self.append("tracks.number ASC") self.append("tracks.title ASC"), self.append("tracks.length ASC"), self.append("artists.sort ASC"), self.append("albums.sort ASC"), self.append("discs.subtitle ASC"), self.append("albums.release ASC"), self.append("tracks.playcount ASC"), self.append("tracks.lastplayed ASC") class DisabledOptionsFilter(Gtk.Filter): def __init__(self): Gtk.Filter.__init__(self) self.playlist = None def changed(self): super().changed(Gtk.FilterChange.DIFFERENT) def do_match(self, item): if self.playlist == None: return True field = item.get_string().split()[0] return field not in [ f.split()[0] for f in self.playlist.sort ] def set_playlist(self, plist): self.playlist = plist self.changed() class DisabledOptionsModel(Gtk.FilterListModel): def __init__(self): Gtk.FilterListModel.__init__(self) self.set_model(SortOptionsModel()) self.set_filter(DisabledOptionsFilter()) def set_playlist(self, plist): self.get_filter().set_playlist(plist) class SortModelsModel(GObject.GObject, Gio.ListModel): def __init__(self): GObject.GObject.__init__(self) self.models = [ SortPlaylistModel(), DisabledOptionsModel() ] def do_get_item_type(self): return Gio.ListModel def do_get_n_items(self): return len(self.models) def do_get_item(self, n): return self.models[n] def set_playlist(self, plist): for model in self.models: model.set_playlist(plist) class FlatSortModel(Gtk.FlattenListModel): def __init__(self): Gtk.FlattenListModel.__init__(self) self.set_model(SortModelsModel()) def set_playlist(self, plist): self.get_model().set_playlist(plist) def get_n_enabled(self): return self.get_enabled_model().get_n_items() def get_enabled_model(self): return self.get_model().get_item(0) def enable(self, field): self.get_model().get_item(0).append(field) self.get_model().get_item(1).get_filter().changed() def disable(self, field): self.get_model().get_item(0).remove(field) self.get_model().get_item(1).get_filter().changed()