diff --git a/emmental/tracklist/__init__.py b/emmental/tracklist/__init__.py index 561172c..3552f8f 100644 --- a/emmental/tracklist/__init__.py +++ b/emmental/tracklist/__init__.py @@ -7,6 +7,7 @@ from gi.repository import Gtk from .. import db from .. import entry from .. import playlist +from . import buttons from . import trackview @@ -20,14 +21,20 @@ class Card(Gtk.Box): """Set up the Tracklist widget.""" super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL, spacing=6, **kwargs) + self._top_left = Gtk.Box() self._top_box = Gtk.CenterBox(margin_top=6, margin_start=6, margin_end=6) self._filter = entry.Filter("tracks", hexpand=True, margin_start=100, margin_end=100) self._trackview = trackview.TrackView(sql, margin_start=6, margin_end=6) + self._visible_cols = buttons.VisibleColumns(self._trackview.columns) + self._top_left.append(self._visible_cols) + + self._top_box.set_start_widget(self._top_left) self._top_box.set_center_widget(self._filter) + self.append(self._top_box) self.append(self._trackview) diff --git a/emmental/tracklist/buttons.py b/emmental/tracklist/buttons.py new file mode 100644 index 0000000..1f44d0a --- /dev/null +++ b/emmental/tracklist/buttons.py @@ -0,0 +1,44 @@ +# Copyright 2022 (c) Anna Schumaker. +"""Extra buttons for the TrackList.""" +from gi.repository import GObject +from gi.repository import Gio +from gi.repository import Gtk +from .. import buttons +from .. import factory + + +class VisibleSwitch(factory.ListRow): + """A list row containing a Gtk.Switch.""" + + def __init__(self, listitem: Gtk.ListItem): + """Initialize a VisibleSwitch ListRow.""" + super().__init__(listitem=listitem, child=Gtk.Switch()) + + def do_bind(self) -> None: + """Bind the visible property to the switch active property.""" + self.bind_and_set_property("visible", "active", bidirectional=True) + + +class VisibleColumns(buttons.PopoverButton): + """Shows a Popover Menu to select which columns are visible.""" + + columns = GObject.Property(type=Gio.ListModel) + + def __init__(self, columns: Gio.ListModel, **kwargs): + """Initialize the VisibleColumns button.""" + super().__init__(columns=columns, icon_name="columns-symbolic", + has_frame=False, **kwargs) + self._selection = Gtk.NoSelection(model=self.columns) + self.popover_child = Gtk.ColumnView(model=self._selection, + show_row_separators=True) + self.__append_column(factory.InscriptionFactory("title"), + "Column", width=125) + self.__append_column(factory.Factory(row_type=VisibleSwitch), + "Visible") + self.popover_child.add_css_class("data-table") + + def __append_column(self, factory: factory.Factory, + title: str, *, width: int = -1) -> None: + column = Gtk.ColumnViewColumn(factory=factory, title=title, + fixed_width=width) + self.popover_child.append_column(column) diff --git a/icons/scalable/actions/columns-symbolic.svg b/icons/scalable/actions/columns-symbolic.svg new file mode 100644 index 0000000..cf1f833 --- /dev/null +++ b/icons/scalable/actions/columns-symbolic.svg @@ -0,0 +1,2 @@ + + diff --git a/tests/tracklist/test_buttons.py b/tests/tracklist/test_buttons.py new file mode 100644 index 0000000..67e0438 --- /dev/null +++ b/tests/tracklist/test_buttons.py @@ -0,0 +1,71 @@ +# Copyright 2022 (c) Anna Schumaker +"""Tests extra buttons for the TrackList.""" +import unittest +import unittest.mock +import emmental.tracklist.buttons +from gi.repository import Gio +from gi.repository import Gtk + + +class TestVisibleColumns(unittest.TestCase): + """Test the Visible Columns button.""" + + def setUp(self): + """Set up common variables.""" + self.columns = Gio.ListStore() + self.button = emmental.tracklist.buttons.VisibleColumns(self.columns) + + def test_init(self): + """Test that the Visible Columns button is set up properly.""" + self.assertIsInstance(self.button, emmental.buttons.PopoverButton) + self.assertFalse(self.button.get_has_frame()) + self.assertEqual(self.button.get_icon_name(), "columns-symbolic") + self.assertEqual(self.button.columns, self.columns) + + def test_popover_child(self): + """Test that the popover_child was set up properly.""" + self.assertIsInstance(self.button.popover_child, Gtk.ColumnView) + self.assertIsInstance(self.button._selection, Gtk.NoSelection) + self.assertTrue(self.button.popover_child.get_show_row_separators()) + self.assertTrue(self.button.popover_child.has_css_class("data-table")) + + self.assertEqual(self.button.popover_child.get_model(), + self.button._selection) + self.assertEqual(self.button._selection.get_model(), + self.button.columns) + + def test_columns(self): + """Test the popover_child columns.""" + columns = self.button.popover_child.get_columns() + self.assertEqual(len(columns), 2) + + self.assertIsInstance(columns[0].get_factory(), + emmental.factory.InscriptionFactory) + self.assertEqual(columns[0].get_title(), "Column") + self.assertEqual(columns[0].get_fixed_width(), 125) + + self.assertIsInstance(columns[1].get_factory(), + emmental.factory.Factory) + self.assertEqual(columns[1].get_factory().row_type, + emmental.tracklist.buttons.VisibleSwitch) + self.assertEqual(columns[1].get_title(), "Visible") + self.assertEqual(columns[1].get_fixed_width(), -1) + + def test_visible_switch(self): + """Test the visible switch widget.""" + item = Gtk.Label() + listitem = Gtk.ListItem() + listitem.get_item = unittest.mock.Mock(return_value=item) + switch = emmental.tracklist.buttons.VisibleSwitch(listitem) + + self.assertIsInstance(switch, emmental.factory.ListRow) + self.assertIsInstance(switch.child, Gtk.Switch) + + switch.bind() + self.assertEqual(len(switch.bindings), 1) + self.assertTrue(switch.child.get_active()) + + item.set_visible(False) + self.assertFalse(switch.child.get_active()) + switch.child.set_active(True) + item.set_visible(True) diff --git a/tests/tracklist/test_tracklist.py b/tests/tracklist/test_tracklist.py index 66a265f..8df7418 100644 --- a/tests/tracklist/test_tracklist.py +++ b/tests/tracklist/test_tracklist.py @@ -22,6 +22,7 @@ class TestTracklist(tests.util.TestCase): """Test that the Tracklist has been set up correctly.""" self.assertIsInstance(self.tracklist, Gtk.Box) self.assertIsInstance(self.tracklist._top_box, Gtk.CenterBox) + self.assertIsInstance(self.tracklist._top_left, Gtk.Box) self.assertEqual(self.tracklist.sql, self.sql) self.assertEqual(self.tracklist.get_spacing(), 6) @@ -36,9 +37,21 @@ class TestTracklist(tests.util.TestCase): self.assertEqual(self.tracklist.get_first_child(), self.tracklist._top_box) + self.assertEqual(self.tracklist._top_box.get_start_widget(), + self.tracklist._top_left) self.assertTrue(self.tracklist.has_css_class("card")) + def test_visible_columns(self): + """Test the visible columns button.""" + self.assertIsInstance(self.tracklist._visible_cols, + emmental.tracklist.buttons.VisibleColumns) + self.assertEqual(self.tracklist._visible_cols.columns, + self.tracklist._trackview.columns) + + self.assertEqual(self.tracklist._top_left.get_first_child(), + self.tracklist._visible_cols) + def test_filter(self): """Test the Tracklist Filter entry.""" self.assertIsInstance(self.tracklist._filter, emmental.entry.Filter)