sidebar: Create a basic Sidebar widget

It only contains a FilterEntry for filtering future playlists. The
application will save its size when resized.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-07-25 11:18:11 -04:00
parent 5545cb106d
commit 40c463da81
7 changed files with 129 additions and 3 deletions

View File

@ -9,6 +9,7 @@ from . import header
from . import mpris2
from . import nowplaying
from . import options
from . import sidebar
from . import window
from gi.repository import GObject
from gi.repository import GLib
@ -92,18 +93,23 @@ class Application(Adw.Application):
playing.connect("play", self.player.play)
playing.connect("pause", self.player.pause)
playing.connect("seek", self.__on_seek)
return playing
def build_sidebar(self) -> sidebar.Card:
"""Build a new sidebar card."""
return sidebar.Card(sql=self.db)
def build_window(self) -> window.Window:
"""Build a new window instance."""
win = window.Window(VERSION_STRING,
header=self.build_header(),
now_playing=self.build_now_playing())
now_playing=self.build_now_playing(),
sidebar=self.build_sidebar())
for (setting, property) in [("window.width", "default-width"),
("window.height", "default-height"),
("now-playing.size", "now-playing-size")]:
("now-playing.size", "now-playing-size"),
("sidebar.size", "sidebar-size")]:
self.db.settings.bind_setting(setting, win, property)
return win

View File

@ -0,0 +1,34 @@
# Copyright 2022 (c) Anna Schumaker.
"""A card for displaying the list of playlists."""
from gi.repository import GObject
from gi.repository import Gtk
from .. import db
from .. import entry
class Card(Gtk.Box):
"""Our playlist Sidebar."""
sql = GObject.Property(type=db.Connection)
def __init__(self, sql: db.Connection, **kwargs):
"""Set up the Sidebar widget."""
super().__init__(sql=sql, orientation=Gtk.Orientation.VERTICAL,
sensitive=False, **kwargs)
self._filter = entry.Filter("playlists")
self.append(self._filter)
self._filter.connect("search-changed", self.__search_changed)
self.sql.connect("table-loaded", self.__table_loaded)
self.add_css_class("background")
self.add_css_class("linked")
self.add_css_class("card")
def __search_changed(self, entry: entry.Filter) -> None:
self.sql.filter(entry.get_query())
def __table_loaded(self, sql: db.Connection, table: db.table.Table):
loaded = {tbl.loaded for tbl in sql.playlist_tables()}
self.set_sensitive(False not in loaded)

View File

@ -28,6 +28,7 @@ class Window(Adw.Window):
header = GObject.Property(type=Gtk.Widget)
sidebar = GObject.Property(type=Gtk.Widget)
sidebar_size = GObject.Property(type=int, default=300)
now_playing = GObject.Property(type=Gtk.Widget)
now_playing_size = GObject.Property(type=int, default=250)
tracklist = GObject.Property(type=Gtk.Widget)
@ -43,6 +44,7 @@ class Window(Adw.Window):
start_child=self.now_playing,
end_child=self.tracklist)
self._outer_pane = _make_pane(Gtk.Orientation.HORIZONTAL,
position=self.sidebar_size,
start_child=self.sidebar,
end_child=self._inner_pane)
self._toast = Adw.ToastOverlay(child=self._outer_pane)
@ -53,6 +55,8 @@ class Window(Adw.Window):
self.bind_property("header", self._header, "child")
self.bind_property("sidebar", self._outer_pane, "start-child")
self.bind_property("sidebar-size", self._outer_pane, "position",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("now-playing", self._inner_pane, "start-child")
self.bind_property("now-playing-size", self._inner_pane, "position",
GObject.BindingFlags.BIDIRECTIONAL)

View File

@ -0,0 +1,49 @@
# Copyright 2022 (c) Anna Schumaker.
"""Tests our playlist Sidebar card."""
import emmental.sidebar
import tests.util
import unittest.mock
from gi.repository import Gtk
class TestSidebar(tests.util.TestCase):
"""Tests the Sidebar card."""
def setUp(self):
"""Set up common variables."""
super().setUp()
self.sidebar = emmental.sidebar.Card(sql=self.sql)
def test_init(self):
"""Test that the Sidebar has been set up correctly."""
self.assertIsInstance(self.sidebar, Gtk.Box)
self.assertEqual(self.sidebar.sql, self.sql)
self.assertEqual(self.sidebar.get_orientation(),
Gtk.Orientation.VERTICAL)
self.assertTrue(self.sidebar.has_css_class("background"))
self.assertTrue(self.sidebar.has_css_class("linked"))
self.assertTrue(self.sidebar.has_css_class("card"))
def test_filter(self):
"""Test the Sidebar filter entry."""
self.assertIsInstance(self.sidebar._filter, emmental.entry.Filter)
self.assertEqual(self.sidebar.get_first_child(), self.sidebar._filter)
self.assertEqual(self.sidebar._filter.get_placeholder_text(),
"type to filter playlists")
with unittest.mock.patch.object(self.sql, "filter") as mock_filter:
self.sidebar._filter.set_text("test text")
self.sidebar._filter.emit("search-changed")
mock_filter.assert_called_with("*test text*")
def test_sensitivity(self):
"""Test setting the sidebar sensitivity when all tables have loaded."""
tables = [t for t in self.sql.playlist_tables()]
self.assertFalse(self.sidebar.get_sensitive())
for table in tables:
self.sql.emit("table-loaded", table)
self.assertEqual(self.sidebar.get_sensitive(), table == tables[-1])

View File

@ -103,6 +103,7 @@ class TestEmmental(unittest.TestCase):
self.assertIsInstance(win, emmental.window.Window)
self.assertIsInstance(win.header, emmental.header.Header)
self.assertIsInstance(win.now_playing, emmental.nowplaying.Card)
self.assertIsInstance(win.sidebar, emmental.sidebar.Card)
self.assertEqual(win.header.title, emmental.VERSION_STRING)
@ -141,6 +142,15 @@ class TestEmmental(unittest.TestCase):
self.application.autopause = 2
self.assertEqual(win.now_playing.autopause, 2)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_sidebar(self, mock_stdout: io.StringIO):
"""Check that the sidebar widget is wired up properly."""
self.application.db = emmental.db.Connection()
self.application.player = emmental.audio.Player()
win = self.application.build_window()
self.assertEqual(win.sidebar.sql, self.application.db)
@unittest.mock.patch("sys.stdout", new_callable=io.StringIO)
def test_replaygain(self, mock_stdout: io.StringIO):
"""Test setting replaygain modes."""

View File

@ -98,3 +98,13 @@ class TestSettings(unittest.TestCase):
self.assertFalse(self.settings["now-playing.prefer-artist"])
self.assertFalse(self.app.build_window().now_playing.prefer_artist)
def test_save_sidebar_size(self, mock_stdout: io.StringIO):
"""Check saving and loading the sidebar widget size."""
self.assertEqual(self.win.sidebar_size, 300)
self.assertEqual(self.settings["sidebar.size"], 300)
self.win.sidebar_size = 400
self.assertEqual(self.settings["sidebar.size"], 400)
self.assertEqual(self.app.build_window().sidebar_size, 400)

View File

@ -80,6 +80,19 @@ class TestWindow(unittest.TestCase):
self.assertEqual(window2._outer_pane.get_start_child(),
window2.sidebar)
def test_sidebar_size(self):
"""Check setting the size of the sidebar area."""
self.assertEqual(self.window.sidebar_size, 300)
self.assertEqual(self.window._outer_pane.get_position(), 300)
self.window.sidebar_size = 100
self.assertEqual(self.window.sidebar_size, 100)
self.assertEqual(self.window._outer_pane.get_position(), 100)
self.window._outer_pane.set_position(200)
self.assertEqual(self.window.sidebar_size, 200)
self.assertEqual(self.window._outer_pane.get_position(), 200)
def test_now_playing(self):
"""Check setting a widget to the now_playing area."""
self.assertIsNone(self.window.now_playing)