diff --git a/emmental/tracklist/__init__.py b/emmental/tracklist/__init__.py index 5126790..40159b5 100644 --- a/emmental/tracklist/__init__.py +++ b/emmental/tracklist/__init__.py @@ -34,12 +34,14 @@ class Card(Gtk.Box): has_frame=False, sensitive=False) self._loop = buttons.LoopButton() self._shuffle = buttons.ShuffleButton() + self._sort = buttons.SortButton() self._top_left.append(self._visible_cols) self._top_left.append(self._unselect) self._top_right.append(self._loop) self._top_right.append(self._shuffle) + self._top_right.append(self._sort) self._top_box.set_start_widget(self._top_left) self._top_box.set_center_widget(self._filter) @@ -55,6 +57,7 @@ class Card(Gtk.Box): self._unselect.connect("clicked", self.__clear_selection) self._loop.connect("notify::state", self.__update_loop_state) self._shuffle.connect("notify::active", self.__update_shuffle_state) + self._sort.connect("notify::sort-order", self.__update_sort_order) self.add_css_class("card") @@ -69,12 +72,15 @@ class Card(Gtk.Box): self.__set_button_state() case "shuffle": self._shuffle.active = playlist.shuffle + case "sort-order": + self._sort.sort_order = playlist.sort_order def __set_button_state(self) -> None: can_disable = self.playlist.playlist != self.sql.playlists.collection self._loop.can_disable = can_disable self._loop.state = self.playlist.loop self._shuffle.active = self.playlist.shuffle + self._sort.set_sort_order(self.playlist.sort_order) def __update_loop_state(self, loop: buttons.LoopButton, param) -> None: if self.playlist.loop != loop.state: @@ -85,6 +91,10 @@ class Card(Gtk.Box): if self.playlist.shuffle != shuffle.active: self.playlist.shuffle = shuffle.active + def __update_sort_order(self, sort: buttons.SortButton, param) -> None: + if self.playlist.sort_order != sort.sort_order: + self.playlist.sort_order = sort.sort_order + def __search_changed(self, filter: entry.Filter) -> None: self.sql.tracks.filter(filter.get_query()) diff --git a/emmental/tracklist/buttons.py b/emmental/tracklist/buttons.py index e3d49e1..1c1ab27 100644 --- a/emmental/tracklist/buttons.py +++ b/emmental/tracklist/buttons.py @@ -162,3 +162,41 @@ class SortFieldWidget(Gtk.Box): self._name.set_text(field.name if field is not None else "") self._enabled.set_active(field is not None and field.enabled) self._reverse.active = field is not None and field.reversed + + +class SortRow(factory.ListRow): + """A row for managing Sort Order.""" + + def __init__(self, listitem: Gtk.ListItem): + """Initialize a Sort Row.""" + super().__init__(listitem=listitem, child=SortFieldWidget()) + + def do_bind(self) -> None: + """Bind Sort Field properties to the Widget.""" + self.child.set_sort_field(self.item) + + def do_unbind(self) -> None: + """Unbind properties from the widget.""" + self.child.set_sort_field(None) + + +class SortButton(buttons.PopoverButton): + """Shows a Popover Menu to sort playlists.""" + + model = GObject.Property(type=sorter.SortOrderModel) + sort_order = GObject.Property(type=str) + + def __init__(self, **kwargs): + """Initialize the Sort button.""" + super().__init__(has_frame=False, model=sorter.SortOrderModel(), + icon_name="view-list-ordered-symbolic", **kwargs) + self._selection = Gtk.NoSelection(model=self.model) + self._factory = factory.Factory(row_type=SortRow) + self.popover_child = Gtk.ListView(model=self._selection, + factory=self._factory, + show_separators=True) + self.model.bind_property("sort-order", self, "sort-order") + + def set_sort_order(self, newval: str) -> None: + """Directly set the sort order.""" + self.model.set_sort_order(newval) diff --git a/tests/tracklist/test_buttons.py b/tests/tracklist/test_buttons.py index 4ad47b1..4ac6a65 100644 --- a/tests/tracklist/test_buttons.py +++ b/tests/tracklist/test_buttons.py @@ -275,3 +275,63 @@ class TestSortFieldWidget(unittest.TestCase): self.assertFalse(self.model[0].reversed) self.sort._reverse.emit("clicked") self.assertTrue(self.model[0].reversed) + + +class TestSortButton(unittest.TestCase): + """Test the Sort button.""" + + def setUp(self): + """Set up common variables.""" + self.sort = emmental.tracklist.buttons.SortButton() + + def test_init(self): + """Test that the Sort button is configured correctly.""" + self.assertIsInstance(self.sort, emmental.buttons.PopoverButton) + + self.assertEqual(self.sort.get_icon_name(), + "view-list-ordered-symbolic") + self.assertFalse(self.sort.get_has_frame()) + + def test_popover_child(self): + """Test that the popover_child is configured correctly.""" + self.assertIsInstance(self.sort.popover_child, Gtk.ListView) + self.assertIsInstance(self.sort.model, + emmental.tracklist.sorter.SortOrderModel) + self.assertIsInstance(self.sort._selection, Gtk.NoSelection) + self.assertIsInstance(self.sort._factory, emmental.factory.Factory) + + self.assertTrue(self.sort.popover_child.get_show_separators()) + + self.assertEqual(self.sort.popover_child.get_model(), + self.sort._selection) + self.assertEqual(self.sort._selection.get_model(), self.sort.model) + self.assertEqual(self.sort.popover_child.get_factory(), + self.sort._factory) + self.assertEqual(self.sort._factory.row_type, + emmental.tracklist.buttons.SortRow) + + def test_sort_row(self): + """Test the Sort Row object.""" + (field := self.sort.model[0]).enable() + listitem = Gtk.ListItem() + listitem.get_item = lambda: field + self.sort.model[1].enable() + + row = emmental.tracklist.buttons.SortRow(listitem) + self.assertIsInstance(row, emmental.factory.ListRow) + self.assertIsInstance(row.child, + emmental.tracklist.buttons.SortFieldWidget) + + row.bind() + self.assertEqual(row.child.sort_field, field) + row.unbind() + self.assertIsNone(row.child.sort_field) + + def test_sort_order(self): + """Test the sort-order property.""" + self.assertEqual(self.sort.sort_order, "") + self.sort.model[0].enable() + self.assertEqual(self.sort.sort_order, self.sort.model.sort_order) + + self.sort.set_sort_order("artist") + self.assertEqual(self.sort.model.sort_order, "artist") diff --git a/tests/tracklist/test_tracklist.py b/tests/tracklist/test_tracklist.py index f8273cf..2e140b4 100644 --- a/tests/tracklist/test_tracklist.py +++ b/tests/tracklist/test_tracklist.py @@ -136,6 +136,27 @@ class TestTracklist(tests.util.TestCase): self.tracklist._shuffle.active = False self.assertFalse(self.playlist.shuffle) + def test_sort_button(self): + """Test the sort button.""" + self.assertIsInstance(self.tracklist._sort, + emmental.tracklist.buttons.SortButton) + self.assertEqual(self.tracklist._shuffle.get_next_sibling(), + self.tracklist._sort) + + self.tracklist.playlist = self.playlist + self.assertEqual(self.tracklist._sort.sort_order, + self.playlist.sort_order) + + self.playlist.playlist = self.sql.playlists.collection + self.assertEqual(self.tracklist._sort.sort_order, + self.sql.playlists.collection.sort_order) + + self.playlist.sort_order = "album" + self.assertEqual(self.tracklist._sort.sort_order, "album") + + self.tracklist._sort.sort_order = "artist" + self.assertEqual(self.playlist.sort_order, "artist") + def test_trackview(self): """Test the Trackview widget.""" self.assertIsInstance(self.tracklist._trackview,