tracklist: Add a Footer widget

This widget displays the number of visible tracks, number of selected
tracks, and runtime of the current playlist. I wire it up to these
properties from the trackview.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-10-20 10:53:41 -04:00
parent ee1152bcc4
commit 911aeb84a1
4 changed files with 165 additions and 0 deletions

View File

@ -9,6 +9,7 @@ from ..playlist.previous import Previous
from .. import db
from .. import entry
from . import buttons
from . import footer
from . import trackview
@ -35,6 +36,8 @@ class Card(Gtk.Box):
self._loop = buttons.LoopButton()
self._shuffle = buttons.ShuffleButton()
self._sort = buttons.SortButton()
self._footer = footer.Footer(margin_start=6, margin_end=6,
margin_bottom=6)
self._top_left.append(self._visible_cols)
self._top_left.append(self._unselect)
@ -49,6 +52,11 @@ class Card(Gtk.Box):
self.append(self._top_box)
self.append(self._trackview)
self.append(self._footer)
self._trackview.bind_property("n-tracks", self._footer, "count")
self._trackview.bind_property("n-selected", self._footer, "selected")
self._trackview.bind_property("runtime", self._footer, "runtime")
self._trackview.bind_property("have-selected", self._unselect,
"sensitive")

View File

@ -0,0 +1,58 @@
# Copyright 2022 (c) Anna Schumaker.
"""A Footer widget to display below the TrackView."""
from gi.repository import GObject
from gi.repository import Gtk
class Footer(Gtk.CenterBox):
"""Footer labels for the Tracklist card."""
count = GObject.Property(type=int)
selected = GObject.Property(type=int)
runtime = GObject.Property(type=float)
def __init__(self, **kwargs):
"""Initialize a Footer widget."""
super().__init__(**kwargs)
self._count = Gtk.Label(label="Showing 0 tracks", xalign=0.0)
self._selected = Gtk.Label()
self._runtime = Gtk.Label(label="0 seconds", xalign=1.0)
self.set_start_widget(self._count)
self.set_center_widget(self._selected)
self.set_end_widget(self._runtime)
self.connect("notify", self.__update_labels)
self.add_css_class("dim-label")
def __update_labels(self, footer: Gtk.CenterBox, param) -> None:
match param.name:
case "count":
s_count = "s" if self.count != 1 else ""
self._count.set_text(f"Showing {self.count} track{s_count}")
case "selected":
match self.selected:
case 0: text = ""
case _:
s_count = "s" if self.selected != 1 else ""
text = f"{self.selected} track{s_count} selected"
self._selected.set_text(text)
case "runtime":
res = []
(min, sec) = divmod(round(self.runtime), 60)
(hour, min) = divmod(min, 60)
(day, hour) = divmod(hour, 24)
(week, day) = divmod(day, 7)
if week > 0:
res.append(f"{week} week{'s' if week != 1 else ''}")
if day > 0:
res.append(f"{day} day{'s' if day != 1 else ''}")
if hour > 0:
res.append(f"{hour} hour{'s' if hour != 1 else ''}")
if min > 0:
res.append(f"{min} minute{'s' if min != 1 else ''}")
if sec > 0:
res.append(f"{sec} second{'s' if sec != 1 else ''}")
self._runtime.set_text(", ".join(res))

View File

@ -0,0 +1,81 @@
# Copyright 2022 (c) Anna Schumaker.
"""Tests our Tracklist Footer."""
import unittest
import emmental.tracklist.footer
from gi.repository import Gtk
class TestFooter(unittest.TestCase):
"""Test the Tracklist Footer."""
def setUp(self):
"""Set up common variables."""
self.footer = emmental.tracklist.footer.Footer()
def test_init(self):
"""Test that the footer is initialized properly."""
self.assertIsInstance(self.footer, Gtk.CenterBox)
self.assertTrue(self.footer.has_css_class("dim-label"))
def test_count(self):
"""Test the count property."""
self.assertIsInstance(self.footer._count, Gtk.Label)
self.assertEqual(self.footer._count.get_xalign(), 0.0)
self.assertEqual(self.footer.get_start_widget(), self.footer._count)
self.assertEqual(self.footer.count, 0)
self.assertEqual(self.footer._count.get_text(), "Showing 0 tracks")
self.footer.count = 1
self.assertEqual(self.footer._count.get_text(), "Showing 1 track")
self.footer.count = 2
self.assertEqual(self.footer._count.get_text(), "Showing 2 tracks")
def test_selected(self):
"""Test the selected property."""
self.assertIsInstance(self.footer._selected, Gtk.Label)
self.assertEqual(self.footer._selected.get_xalign(), 0.5)
self.assertEqual(self.footer.get_center_widget(),
self.footer._selected)
self.assertEqual(self.footer.selected, 0)
self.assertEqual(self.footer._selected.get_text(), "")
self.footer.selected = 1
self.assertEqual(self.footer._selected.get_text(), "1 track selected")
self.footer.selected = 2
self.assertEqual(self.footer._selected.get_text(), "2 tracks selected")
self.footer.selected = 0
self.assertEqual(self.footer._selected.get_text(), "")
def test_runtime(self):
"""Test the runtime property."""
self.assertIsInstance(self.footer._runtime, Gtk.Label)
self.assertEqual(self.footer._runtime.get_xalign(), 1.0)
self.assertEqual(self.footer.get_end_widget(), self.footer._runtime)
self.assertEqual(self.footer.runtime, 0.0)
self.assertEqual(self.footer._runtime.get_text(),
"0 seconds")
self.footer.runtime = 1.2
self.assertEqual(self.footer._runtime.get_text(),
"1 second")
self.footer.runtime = 65
self.assertEqual(self.footer._runtime.get_text(),
"1 minute, 5 seconds")
self.footer.runtime = 3720
self.assertEqual(self.footer._runtime.get_text(),
"1 hour, 2 minutes")
self.footer.runtime = 93600
self.assertEqual(self.footer._runtime.get_text(),
"1 day, 2 hours")
self.footer.runtime = 777600
self.assertEqual(self.footer._runtime.get_text(),
"1 week, 2 days")
self.footer.runtime = 1209600
self.assertEqual(self.footer._runtime.get_text(),
"2 weeks")

View File

@ -169,6 +169,24 @@ class TestTracklist(tests.util.TestCase):
self.assertEqual(self.tracklist.columns,
self.tracklist._trackview.columns)
def test_footer(self):
"""Test that the footer is wired up properly."""
self.assertIsInstance(self.tracklist._footer,
emmental.tracklist.footer.Footer)
self.assertEqual(self.tracklist._footer.get_margin_start(), 6)
self.assertEqual(self.tracklist._footer.get_margin_end(), 6)
self.assertEqual(self.tracklist._footer.get_margin_bottom(), 6)
self.assertEqual(self.tracklist._trackview.get_next_sibling(),
self.tracklist._footer)
self.tracklist._trackview.n_tracks = 5
self.tracklist._trackview.n_selected = 3
self.tracklist._trackview.runtime = 12345
self.assertEqual(self.tracklist._footer.count, 5)
self.assertEqual(self.tracklist._footer.selected, 3)
self.assertEqual(self.tracklist._footer.runtime, 12345)
def test_playlist(self):
"""Test the playlist property."""
self.assertIsNone(self.tracklist.playlist)