diff --git a/emmental/sidebar/artist.py b/emmental/sidebar/artist.py new file mode 100644 index 0000000..1c7d286 --- /dev/null +++ b/emmental/sidebar/artist.py @@ -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}" diff --git a/icons/scalable/actions/library-artists-symbolic.svg b/icons/scalable/actions/library-artists-symbolic.svg new file mode 100644 index 0000000..ec6ecf9 --- /dev/null +++ b/icons/scalable/actions/library-artists-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/icons/scalable/actions/music-artist-symbolic.svg b/icons/scalable/actions/music-artist-symbolic.svg new file mode 100644 index 0000000..228e9a8 --- /dev/null +++ b/icons/scalable/actions/music-artist-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/icons/scalable/actions/music-artist2-symbolic.svg b/icons/scalable/actions/music-artist2-symbolic.svg new file mode 100644 index 0000000..e834152 --- /dev/null +++ b/icons/scalable/actions/music-artist2-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/tests/sidebar/test_artist.py b/tests/sidebar/test_artist.py new file mode 100644 index 0000000..1dd76ad --- /dev/null +++ b/tests/sidebar/test_artist.py @@ -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)