sidebar: Create a PlaylistRow

This row is configured for showing user and system playlists. This means
we use a SettableIcon, and EditableTitle, and an extra button for
deleting playlists.  I use the big-x-symbolic icon from the gnome icon
library for deleting playlists.

Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
Anna Schumaker 2022-09-12 14:03:00 -04:00
parent 0adb0d472b
commit dcd63015b8
4 changed files with 138 additions and 0 deletions

View File

@ -56,3 +56,7 @@ image.emmental-sidebar-arrow:checked {
transform: rotate(-180deg);
color: @accent_color;
}
button.emmental-delete>image {
color: @destructive_color;
}

View File

@ -3,7 +3,9 @@
from gi.repository import GObject
from gi.repository import Gtk
from .title import PlaylistTitle
from .title import EditableTitle
from .icon import Icon
from .icon import Settable
from .. import db
@ -45,3 +47,49 @@ class Row(BaseRow):
digits = str(year)
self._year = year
self._icon.text = f"{digits[-2]} {digits[-1]}"
class PlaylistRow(BaseRow):
"""An advanced playlist row with extra actions for users."""
icon_name = GObject.Property(type=str)
filepath = GObject.Property(type=GObject.TYPE_PYOBJECT)
modifiable = GObject.Property(type=bool, default=False)
def __init__(self, **kwargs):
"""Initialize a PlaylistRow."""
super().__init__(**kwargs)
self._icon = Settable()
self._title = EditableTitle(margin_start=12, margin_end=12)
self._delete = Gtk.Button(icon_name="big-x-symbolic",
valign=Gtk.Align.CENTER,
has_frame=False, visible=False)
self.bind_property("name", self._icon, "text")
self.bind_property("icon-name", self._icon, "icon-name")
self.bind_property("filepath", self._icon, "filepath",
GObject.BindingFlags.BIDIRECTIONAL)
self.bind_property("modifiable", self._icon, "settable")
self.bind_property("name", self._title, "title")
self.bind_property("count", self._title, "count")
self.bind_property("modifiable", self._title, "editable")
self.bind_property("modifiable", self._delete, "visible")
self._title.connect("request-rename", self.__on_rename)
self._delete.connect("clicked", self.__on_delete)
self._delete.add_css_class("emmental-delete")
self.append(self._icon)
self.append(self._title)
self.append(self._delete)
def __on_rename(self, title: EditableTitle, new_name: str) -> None:
if self.playlist is not None:
self.playlist.rename(new_name)
def __on_delete(self, button: Gtk.Button) -> None:
if self.playlist is not None:
self.playlist.delete()

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 2 2 h 1 h 0.03125 c 0.253906 0.011719 0.511719 0.128906 0.6875 0.3125 l 4.28125 4.28125 l 4.3125 -4.28125 c 0.265625 -0.230469 0.445312 -0.304688 0.6875 -0.3125 h 1 v 1 c 0 0.285156 -0.035156 0.550781 -0.25 0.75 l -4.28125 4.28125 l 4.25 4.25 c 0.1875 0.1875 0.28125 0.453125 0.28125 0.71875 v 1 h -1 c -0.265625 0 -0.53125 -0.09375 -0.71875 -0.28125 l -4.28125 -4.28125 l -4.28125 4.28125 c -0.1875 0.1875 -0.453125 0.28125 -0.71875 0.28125 h -1 v -1 c 0 -0.265625 0.09375 -0.53125 0.28125 -0.71875 l 4.28125 -4.25 l -4.28125 -4.28125 c -0.210938 -0.195312 -0.304688 -0.46875 -0.28125 -0.75 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@ -93,3 +93,87 @@ class TestRow(unittest.TestCase):
self.assertEqual(self.row._icon.text, "8 8")
self.row.year = 2000
self.assertEqual(self.row._icon.text, "0 0")
class TestPlaylistRow(unittest.TestCase):
"""Test our playlist row with extra buttons."""
def setUp(self):
"""Set up common variables."""
self.row = emmental.sidebar.row.PlaylistRow()
self.playlist = emmental.db.playlists.Playlist(table=Gio.ListStore(),
propertyid=12345,
playlistid=67890,
name="Test Playlist")
def test_init(self):
"""Test that the playlist row is configured properly."""
self.assertIsInstance(self.row, emmental.sidebar.row.BaseRow)
def test_icon(self):
"""Test the settable icon widget and properties."""
self.assertIsInstance(self.row._icon, emmental.sidebar.icon.Settable)
self.assertEqual(self.row.get_first_child(), self.row._icon)
self.row.name = "Test Playlist"
self.row.icon_name = "image-missing"
self.row.filepath = tests.util.COVER_JPG
self.assertEqual(self.row._icon.text, "Test Playlist")
self.assertEqual(self.row._icon.icon_name, "image-missing")
self.assertEqual(self.row._icon.filepath, tests.util.COVER_JPG)
self.row._icon.filepath = None
self.assertIsNone(self.row.filepath)
def test_title(self):
"""Test the editable title widget and properties."""
self.assertIsInstance(self.row._title,
emmental.sidebar.title.EditableTitle)
self.assertEqual(self.row._icon.get_next_sibling(), self.row._title)
self.assertEqual(self.row._title.get_margin_start(), 12)
self.assertEqual(self.row._title.get_margin_end(), 12)
self.row.name = "Test Playlist"
self.row.count = 42
self.assertEqual(self.row._title.title, "Test Playlist")
self.assertEqual(self.row._title.count, 42)
def test_delete(self):
"""Test the delete button."""
self.assertIsInstance(self.row._delete, Gtk.Button)
self.assertEqual(self.row._title.get_next_sibling(), self.row._delete)
self.assertEqual(self.row._delete.get_icon_name(), "big-x-symbolic")
self.assertEqual(self.row._delete.get_valign(), Gtk.Align.CENTER)
self.assertFalse(self.row._delete.get_has_frame())
self.assertTrue(self.row._delete.has_css_class("emmental-delete"))
self.row._delete.emit("clicked")
self.playlist.delete = unittest.mock.Mock()
self.row.playlist = self.playlist
self.row._delete.emit("clicked")
self.playlist.delete.assert_called()
def test_modifiable(self):
"""Test the modifiable property."""
self.assertFalse(self.row.modifiable)
self.assertFalse(self.row._icon.settable)
self.assertFalse(self.row._title.editable)
self.assertFalse(self.row._delete.get_visible())
self.row.modifiable = True
self.assertTrue(self.row._icon.settable)
self.assertTrue(self.row._title.editable)
self.assertTrue(self.row._delete.get_visible())
def test_rename(self):
"""Test responding to the request-rename signal."""
self.row._title.emit("request-rename", "No Error Expected")
self.playlist.rename = unittest.mock.Mock()
self.row.playlist = self.playlist
self.row._title.emit("request-rename", "New Name")
self.playlist.rename.assert_called_with("New Name")