emmental/emmental/sidebar/section.py

149 lines
5.9 KiB
Python

# 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)