sidebar: Create a Section for Artists

This section shows a tree of Artist and Album playlists. I use the
library-artists icon from the gnome icon-library as the section header,
and the music-artist / music-artist2 icons for the "show all" button to
indicate state..

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-08-22 15:59:45 -04:00
parent 711c04cb29
commit 2f239bd94d
5 changed files with 151 additions and 0 deletions

View File

@ -0,0 +1,56 @@
# Copyright 2022 (c) Anna Schumaker.
"""Displays our artist and album tree."""
from gi.repository import GObject
from gi.repository import Gtk
from ..buttons import ImageToggle
from .. import db
from . import row
from . import section
class ArtistRow(row.TreeRow):
"""A factory for setting Album covers properly."""
def __init__(self, *args, **kwargs):
"""Initialize an ArtistRow."""
super().__init__(*args, **kwargs)
self.child = row.Row()
def do_bind(self) -> None:
"""Bind the album cover property."""
super().do_bind()
if isinstance(self.item, db.albums.Album):
self.bind_and_set_property("cover", "image")
class Section(section.Section):
"""A sidebar Section for the artist and album playlist tree."""
album_table = GObject.Property(type=db.albums.Table)
show_all = GObject.Property(type=bool, default=False)
def __init__(self, artist_table: db.artists.Table,
album_table: db.albums.Table):
"""Initialize our artist & album section."""
super().__init__(artist_table, ArtistRow, title="Artists & Albums",
subtitle="0 artists, 0 albums",
icon_name="library-artists", album_table=album_table)
self.extra_widget = ImageToggle("music-artist", "music-artist2",
icon_size=Gtk.IconSize.NORMAL,
has_frame=False)
self.album_table.connect("items-changed", self.__update_subtitle)
self.bind_property("show-all", self.extra_widget, "active",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("show-all", artist_table, "show-all")
def __update_subtitle(self, table: db.albums.Table, position: int,
removed: int, added: int) -> None:
self.subtitle = self.do_get_subtitle(0)
def do_get_subtitle(self, n_items: int) -> str:
"""Return a subtitle for this section."""
n_artists = len(self.table)
s_artists = "s" if n_artists != 1 else ""
n_albums = len(self.album_table)
s_albums = "s" if n_albums != 1 else ""
return f"{n_artists} artist{s_artists}, {n_albums} album{s_albums}"

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 5 0 c -1.089844 0 -2 0.910156 -2 2 v 9 c 0 1.089844 0.910156 2 2 2 h 9 c 1.089844 0 2 -0.910156 2 -2 v -9 c 0 -1.089844 -0.910156 -2 -2 -2 z m 4.5 2 c 1.105469 0 2 0.894531 2 2 s -0.894531 2 -2 2 s -2 -0.894531 -2 -2 s 0.894531 -2 2 -2 z m -0.5 5 h 1 c 1.660156 0 3 1.339844 3 3 v 0.332031 c 0 0.667969 -0.667969 0.667969 -0.667969 0.667969 h -5.664062 s -0.667969 0 -0.667969 -0.667969 v -0.332031 c 0 -1.660156 1.339844 -3 3 -3 z m 0 0"/><path d="m 2 3 c -1.089844 0 -2 0.910156 -2 2 v 9 c 0 1.089844 0.910156 2 2 2 h 9 c 1.089844 0 2 -0.910156 2 -2 h -11 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 2 c -1.65625 0 -3 1.34375 -3 3 s 1.34375 3 3 3 s 3 -1.34375 3 -3 s -1.34375 -3 -3 -3 z m -1.5 7 c -2.492188 0 -4.5 2.007812 -4.5 4.5 v 0.5 c 0 1 1 1 1 1 h 6.1875 c -0.113281 -0.316406 -0.1875 -0.648438 -0.1875 -1 c 0 -1.644531 1.355469 -3 3 -3 v -1.242188 c -0.714844 -0.480468 -1.574219 -0.757812 -2.5 -0.757812 z m 5.5 4 c -0.566406 0 -1 0.4375 -1 1 s 0.4375 1 1 1 s 1 -0.4375 1 -1 v -0.574219 l -0.5 -0.289062 c -0.152344 -0.089844 -0.328125 -0.136719 -0.5 -0.136719 z m 0 0"/><path d="m 13 9 v 3.269531 c -0.304688 -0.175781 -0.648438 -0.269531 -1 -0.269531 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 s 2 -0.894531 2 -2 v -3 h 2 v -2 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 821 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><g fill="#222222"><path d="m 8 2 c -1.65625 0 -3 1.34375 -3 3 s 1.34375 3 3 3 s 3 -1.34375 3 -3 s -1.34375 -3 -3 -3 z m 0 2 c 0.550781 0 1 0.449219 1 1 s -0.449219 1 -1 1 s -1 -0.449219 -1 -1 s 0.449219 -1 1 -1 z m 0 0"/><path d="m 13 9 v 3.269531 c -0.304688 -0.175781 -0.648438 -0.269531 -1 -0.269531 c -1.105469 0 -2 0.894531 -2 2 s 0.894531 2 2 2 s 2 -0.894531 2 -2 v -3 h 2 v -2 z m 0 0"/><path d="m 6 9 s -0.957031 -0.015625 -1.949219 0.480469 c -0.988281 0.496093 -2.050781 1.6875 -2.050781 3.519531 v 1 c -0.003906 0.359375 0.183594 0.695312 0.496094 0.878906 c 0.3125 0.179688 0.695312 0.179688 1.007812 0 c 0.3125 -0.183594 0.5 -0.519531 0.496094 -0.878906 v -1 c 0 -1.167969 0.4375 -1.476562 0.949219 -1.730469 c 0.507812 -0.253906 1.050781 -0.269531 1.050781 -0.269531 h 5 c 0.359375 0.003906 0.695312 -0.183594 0.878906 -0.496094 c 0.179688 -0.3125 0.179688 -0.695312 0 -1.007812 c -0.183594 -0.3125 -0.519531 -0.5 -0.878906 -0.496094 z m 0 0"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,89 @@
# Copyright 2022 (c) Anna Schumaker.
"""Tests our artist / album / disc section and tree."""
import emmental.sidebar.artist
import emmental.sidebar.row
import tests.util
import unittest.mock
from gi.repository import Gtk
class TestArtist(tests.util.TestCase):
"""Test our Artist section."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.artists = emmental.sidebar.artist.Section(self.sql.artists,
self.sql.albums)
def test_init(self):
"""Test that the artists section is set up correctly."""
self.assertIsInstance(self.artists, emmental.sidebar.section.Section)
self.assertEqual(self.artists._factory.row_type,
emmental.sidebar.artist.ArtistRow)
self.assertEqual(self.artists.table, self.sql.artists)
self.assertEqual(self.artists.album_table, self.sql.albums)
self.assertEqual(self.artists.icon_name, "library-artists")
self.assertEqual(self.artists.title, "Artists & Albums")
def test_extra_widget(self):
"""Test the artist section extra widget."""
self.assertIsInstance(self.artists.extra_widget,
emmental.buttons.ImageToggle)
self.assertEqual(self.artists.extra_widget.active_icon_name,
"music-artist")
self.assertEqual(self.artists.extra_widget.inactive_icon_name,
"music-artist2")
self.assertEqual(self.artists.extra_widget.icon_size,
Gtk.IconSize.NORMAL)
self.assertFalse(self.artists.extra_widget.get_has_frame())
def test_subtitle(self):
"""Test that the subtitle property is set properly."""
self.artists.show_all = True
self.assertEqual(self.artists.subtitle, "0 artists, 0 albums")
artist = self.sql.artists.create("Artist 1")
self.assertEqual(self.artists.subtitle, "1 artist, 0 albums")
artist.add_album(self.sql.albums.create("Album 1", "Artist 1", "2022"))
self.assertEqual(self.artists.subtitle, "1 artist, 1 album")
artist.add_album(self.sql.albums.create("Album 2", "Artist 1", "2023"))
self.assertEqual(self.artists.subtitle, "1 artist, 2 albums")
self.sql.artists.create("Artist 2")
self.assertEqual(self.artists.subtitle, "2 artists, 2 albums")
def test_show_all(self):
"""Test the show all property."""
self.assertFalse(self.artists.show_all)
self.artists.show_all = True
self.assertTrue(self.artists.extra_widget.active)
self.assertTrue(self.sql.artists.show_all)
self.artists.extra_widget.active = False
self.assertFalse(self.artists.show_all)
self.assertFalse(self.sql.artists.show_all)
def test_artist_row(self):
"""Test setting an album cover to the row icon."""
artist = self.sql.artists.create("Test Artist")
album = self.sql.albums.create("Test Album", "Test Artist", "2022")
artist.add_album(album)
treeitem = Gtk.TreeListRow()
treeitem.get_item = unittest.mock.Mock(return_value=artist)
listitem = Gtk.ListItem()
listitem.get_item = unittest.mock.Mock(return_value=treeitem)
row = emmental.sidebar.artist.ArtistRow(listitem)
self.assertIsInstance(row.child, emmental.sidebar.row.Row)
self.assertTrue(row.indented)
row.bind()
self.assertEqual(row.child.name, "Test Artist")
row.unbind()
treeitem.get_item.return_value = album
row.bind()
self.assertEqual(row.child.name, "Test Album")
self.assertIsNone(row.child.image)
album.cover = tests.util.COVER_JPG
self.assertEqual(row.child.image, tests.util.COVER_JPG)