diff --git a/emmental/tracklist/row.py b/emmental/tracklist/row.py index 62dc413..6187a85 100644 --- a/emmental/tracklist/row.py +++ b/emmental/tracklist/row.py @@ -5,6 +5,7 @@ import dateutil.tz import pathlib from gi.repository import GObject from gi.repository import Gtk +from .. import buttons from .. import factory @@ -157,3 +158,20 @@ class PathString(InscriptionRow): def path(self, newval: pathlib.Path) -> None: self.__path = newval self.child.set_text(str(newval)) + + +class FavoriteButton(TrackRow): + """A TrackRow with an toggle to set Track favorite status.""" + + def __init__(self, listitem: Gtk.ListItem, property: str) -> None: + """Initialize a Favorite Button.""" + super().__init__(listitem, property=property) + self.child = buttons.ImageToggle("heart-filled", "heart-outline-thick", + icon_size=Gtk.IconSize.NORMAL, + valign=Gtk.Align.CENTER, + has_frame=False) + + def do_bind(self): + """Bind a track property to the Toggle Button.""" + super().do_bind() + self.bind_and_set_property(self.property, "active", bidirectional=True) diff --git a/emmental/tracklist/trackview.py b/emmental/tracklist/trackview.py index 088678d..e28727e 100644 --- a/emmental/tracklist/trackview.py +++ b/emmental/tracklist/trackview.py @@ -28,6 +28,8 @@ class TrackView(Gtk.Frame): model=self._selection) self._scrollwin = Gtk.ScrolledWindow(child=self._columnview) + self.__append_column("Fav", "favorite", row.FavoriteButton, + resizable=False) self.__append_column("Title", "title", row.TrackString, width=300) self.__append_column("Length", "length", row.LengthString, xalign=1.0, numeric=True) @@ -51,10 +53,10 @@ class TrackView(Gtk.Frame): def __append_column(self, title: str, property: str, row_type: type, *, width: int = -1, visible: bool = True, - **kwargs) -> None: + resizable: bool = True, **kwargs) -> None: fctry = factory.Factory(row_type=row_type, property=property, **kwargs) col = Gtk.ColumnViewColumn(title=title, factory=fctry, visible=visible, - resizable=True, fixed_width=width) + resizable=resizable, fixed_width=width) self._columnview.append_column(col) def __runtime_changed(self, selection: Gtk.MultiSelection, diff --git a/tests/test_settings.py b/tests/test_settings.py index f168567..b4dc3fe 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -121,6 +121,7 @@ class TestSettings(unittest.TestCase): def test_save_tracklist_column_width(self, mock_stdout: io.StringIO): """Test saving tracklist column widths.""" + self.assertEqual(self.settings["tracklist.fav.size"], -1) self.assertEqual(self.settings["tracklist.title.size"], 300) self.assertEqual(self.settings["tracklist.length.size"], -1) self.assertEqual(self.settings["tracklist.artist.size"], 250) @@ -132,6 +133,7 @@ class TestSettings(unittest.TestCase): for column in self.win.tracklist.columns: column.set_fixed_width(123) + self.assertEqual(self.settings["tracklist.fav.size"], 123) self.assertEqual(self.settings["tracklist.title.size"], 123) self.assertEqual(self.settings["tracklist.length.size"], 123) self.assertEqual(self.settings["tracklist.artist.size"], 123) @@ -142,6 +144,7 @@ class TestSettings(unittest.TestCase): def test_save_tracklist_column_visible(self, mock_stdout: io.StringIO): """Test saving tracklist column visibility.""" + self.assertTrue(self.settings["tracklist.fav.visible"]) self.assertTrue(self.settings["tracklist.title.visible"]) self.assertTrue(self.settings["tracklist.length.visible"]) self.assertTrue(self.settings["tracklist.artist.visible"]) @@ -153,6 +156,7 @@ class TestSettings(unittest.TestCase): for column in self.win.tracklist.columns: column.set_visible(not column.get_visible()) + self.assertFalse(self.settings["tracklist.fav.visible"]) self.assertFalse(self.settings["tracklist.title.visible"]) self.assertFalse(self.settings["tracklist.length.visible"]) self.assertFalse(self.settings["tracklist.artist.visible"]) diff --git a/tests/tracklist/test_row.py b/tests/tracklist/test_row.py index 8545f29..77a9eaf 100644 --- a/tests/tracklist/test_row.py +++ b/tests/tracklist/test_row.py @@ -17,7 +17,7 @@ class TestTrackRowWidgets(tests.util.TestCase): def setUp(self): """Set up common variables.""" super().setUp() - self.sql.playlists.load() + self.sql.playlists.load(now=True) self.library = self.sql.libraries.create(pathlib.Path("/a/b")) self.album = self.sql.albums.create("Test Album", "Artist", "2023") self.medium = self.sql.media.create(self.album, "", number=1) @@ -102,23 +102,6 @@ class TestTrackRowWidgets(tests.util.TestCase): self.track.length = 123.84 self.assertEqual(row.child.get_text(), "2:04") - def test_playcount_string(self): - """Test the PlayCountString widget.""" - row = emmental.tracklist.row.PlayCountString(self.listitem, - "playcount") - self.assertIsInstance(row, emmental.tracklist.row.InscriptionRow) - self.assertEqual(row.property, "playcount") - self.assertEqual(row.playcount, 0) - - row.bind() - self.assertEqual(row.child.get_text(), "Unplayed") - self.track.playcount = 1 - self.assertEqual(row.child.get_text(), "Played 1 time") - self.track.playcount = 2 - self.assertEqual(row.child.get_text(), "Played 2 times") - self.track.playcount = 3 - self.assertEqual(row.child.get_text(), "Played 3 times") - def test_timestamp_string(self): """Test the TimestampString widget.""" row = emmental.tracklist.row.TimestampString(self.listitem, @@ -145,3 +128,25 @@ class TestTrackRowWidgets(tests.util.TestCase): row.bind() self.assertEqual(row.child.get_text(), str(pathlib.Path("/a/b/1.ogg"))) + + def test_favorite_button(self): + """Test the Favorite Button widget.""" + row = emmental.tracklist.row.FavoriteButton(self.listitem, "favorite") + self.assertIsInstance(row, emmental.tracklist.row.TrackRow) + self.assertIsInstance(row.child, emmental.buttons.ImageToggle) + + self.assertEqual(row.property, "favorite") + self.assertEqual(row.child.active_icon_name, "heart-filled") + self.assertEqual(row.child.inactive_icon_name, "heart-outline-thick") + self.assertEqual(row.child.icon_size, Gtk.IconSize.NORMAL) + self.assertEqual(row.child.get_valign(), Gtk.Align.CENTER) + self.assertFalse(row.child.get_has_frame()) + + self.track.favorite = True + row.bind() + self.assertTrue(row.child.active) + + self.track.favorite = False + self.assertFalse(row.child.active) + row.child.active = True + self.assertTrue(self.track.favorite) diff --git a/tests/tracklist/test_trackview.py b/tests/tracklist/test_trackview.py index bd0f188..6955ea1 100644 --- a/tests/tracklist/test_trackview.py +++ b/tests/tracklist/test_trackview.py @@ -93,7 +93,22 @@ class TestTrackViewColumns(tests.util.TestCase): self.columns = self.trackview.columns self.listitem = Gtk.ListItem() - def test_title_column(self, i: int = 0): + def test_favorite_column(self, i: int = 0): + """Test the favorite column.""" + self.assertEqual(self.columns[i].get_title(), "Fav") + self.assertEqual(self.columns[i].get_fixed_width(), -1) + self.assertFalse(self.columns[i].get_resizable()) + self.assertTrue(self.columns[i].get_visible()) + + factory = self.columns[i].get_factory() + self.assertIsInstance(factory, emmental.factory.Factory) + self.assertEqual(factory.row_type, + emmental.tracklist.row.FavoriteButton) + + factory.emit("setup", self.listitem) + self.assertEqual(self.listitem.listrow.property, "favorite") + + def test_title_column(self, i: int = 1): """Test the title column.""" self.assertEqual(self.columns[i].get_title(), "Title") self.assertEqual(self.columns[i].get_fixed_width(), 300) @@ -109,7 +124,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0) self.assertFalse(self.listitem.listrow.child.has_css_class("numeric")) - def test_length_column(self, i: int = 1): + def test_length_column(self, i: int = 2): """Test the length column.""" self.assertEqual(self.columns[i].get_title(), "Length") self.assertEqual(self.columns[i].get_fixed_width(), -1) @@ -125,7 +140,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 1.0) self.assertTrue(self.listitem.listrow.child.has_css_class("numeric")) - def test_artist_column(self, i: int = 2): + def test_artist_column(self, i: int = 3): """Test the artist column.""" self.assertEqual(self.columns[i].get_title(), "Artist") self.assertEqual(self.columns[i].get_fixed_width(), 250) @@ -141,7 +156,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0) self.assertFalse(self.listitem.listrow.child.has_css_class("numeric")) - def test_playcount_column(self, i: int = 3): + def test_playcount_column(self, i: int = 4): """Test the play count column.""" self.assertEqual(self.columns[i].get_title(), "Play Count") self.assertEqual(self.columns[i].get_fixed_width(), 135) @@ -158,7 +173,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0) self.assertTrue(self.listitem.listrow.child.has_css_class("numeric")) - def test_last_started_column(self, i: int = 4): + def test_last_started_column(self, i: int = 5): """Test the last started column.""" self.assertEqual(self.columns[i].get_title(), "Last Started") self.assertEqual(self.columns[i].get_fixed_width(), 250) @@ -175,7 +190,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0) self.assertTrue(self.listitem.listrow.child.has_css_class("numeric")) - def test_last_played_column(self, i: int = 5): + def test_last_played_column(self, i: int = 6): """Test the last played column.""" self.assertEqual(self.columns[i].get_title(), "Last Played") self.assertEqual(self.columns[i].get_fixed_width(), 250) @@ -192,7 +207,7 @@ class TestTrackViewColumns(tests.util.TestCase): self.assertEqual(self.listitem.listrow.child.get_xalign(), 0.0) self.assertTrue(self.listitem.listrow.child.has_css_class("numeric")) - def test_filepath_column(self, i: int = 6): + def test_filepath_column(self, i: int = 7): """Test the last played column.""" self.assertEqual(self.columns[i].get_title(), "Filepath") self.assertEqual(self.columns[i].get_fixed_width(), -1)