# Copyright 2022 (c) Anna Schumaker. """A sidebar Header attached to a hidden ListView for selecting playlists.""" import typing from gi.repository import GObject from gi.repository import Gtk from .. import db from .. import factory from . import header from . import row class Section(header.Header): """A widget for displaying a group of playlists with a header.""" table = GObject.Property(type=db.playlist.Table) def __init__(self, table: db.playlist.Table, row_type: typing.Type[row.TreeRow], **kwargs): """Set up our sidebar Section.""" super().__init__(table=table, pending=table.queue.running, **kwargs) self._selection = Gtk.SingleSelection(model=self.table.treemodel, autoselect=False) self._factory = factory.Factory(row_type=row_type) self._listview = Gtk.ListView.new(self._selection, self._factory) self.reveal_widget = Gtk.ScrolledWindow(child=self._listview) self.reveal_widget.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) table.queue.bind_property("running", self, "pending") table.queue.bind_property("progress", self, "progress") table.treemodel.connect("items-changed", self.__model_items_changed) self._selection.connect("selection-changed", self.__selection_changed) self._listview.connect("activate", self.__activated) self._listview.add_css_class("navigation-sidebar") self.add_css_class("emmental-sidebar-section") def __activated(self, view: Gtk.ListView, position: int) -> None: if playlist := self.__get_playlist(position): self.emit("playlist-activated", playlist) def __get_playlist(self, index: int) -> db.playlist.Playlist | None: if item := self._selection.get_item(index): return item.get_item() def __model_items_changed(self, model: Gtk.TreeModel, position: int, removed: int, added: int) -> None: self.subtitle = self.do_get_subtitle(len(model)) def __selection_changed(self, model: Gtk.SingleSelection, position: int, n_items: int) -> None: if (index := model.get_selected()) != Gtk.INVALID_LIST_POSITION: self.emit("playlist-selected", self.__get_playlist(index)) def do_get_subtitle(self, n_items: int) -> str: """Return a new subtitle for the section.""" raise NotImplementedError def clear_selection(self) -> None: """Clear the selected playlist.""" self._selection.set_selected(Gtk.INVALID_LIST_POSITION) def playlist_index(self, playlist: db.playlist.Playlist) -> int | None: """Find the index of a specific playlist in the tree.""" if playlist.parent is None: index = -1 depth = 0 else: index = self.playlist_index(playlist.parent) parent = self._selection.get_item(index) parent.set_expanded(True) depth = parent.get_depth() + 1 for index in range(index + 1, len(self._selection)): item = self._selection.get_item(index) if item.get_depth() > depth: continue if item.get_depth() < depth: break if item.get_item() == playlist: return index def select_playlist(self, playlist: db.playlist.Playlist) -> None: """Select the requested playlist.""" if (index := self.playlist_index(playlist)) is not None: self._listview.scroll_to(index, Gtk.ListScrollFlags.SELECT) @GObject.Signal(arg_types=(db.playlist.Playlist,)) def playlist_activated(self, playlist: db.playlist.Playlist): """Signal that a playlist has been activated.""" @GObject.Signal(arg_types=(db.playlist.Playlist,)) def playlist_selected(self, playlist: db.playlist.Playlist): """Signal that the selected playlist has changed.""" class View(Gtk.Box): """A widget for displaying a group of sections.""" sql = GObject.Property(type=db.Connection) current = GObject.Property(type=Section) selected_section = GObject.Property(type=Section) selected_playlist = GObject.Property(type=db.playlist.Playlist) def __init__(self, sql: db.Connection): """Initialize a Section View.""" super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL) self._sections = [] def __on_active(self, section: Section, param: GObject.ParamSpec) -> None: if section.active: new = self._sections.index(section) prev = len(self._sections) if self.current and self.current is not section: prev = self._sections.index(self.current) self.current.active = False self.current = section if new < prev: section.animation = Gtk.RevealerTransitionType.SLIDE_DOWN else: section.animation = Gtk.RevealerTransitionType.SLIDE_UP if section is self.selected_section: section.select_playlist(self.selected_playlist) def __playlist_activated(self, section: Section, playlist: db.playlist.Playlist) -> None: self.sql.set_active_playlist(playlist) def __playlist_selected(self, section: Section, playlist: db.playlist.Playlist) -> None: if self.selected_section and self.selected_section is not section: self.selected_section.clear_selection() self.selected_section = section self.selected_playlist = playlist def add(self, section: Section) -> None: """Add a section to the group.""" self._sections.append(section) self.append(section) section.connect("notify::active", self.__on_active) section.connect("playlist-activated", self.__playlist_activated) section.connect("playlist-selected", self.__playlist_selected)