rind: Implement the PlaylistModel
This is a special tree model that translates our Playlist object into a format that GTK understands. Signed-off-by: Anna Schumaker <Anna@NoWheyCreamery.com>
This commit is contained in:
parent
94ef044b59
commit
31d7ae26fa
112
emmental.ui
112
emmental.ui
|
@ -11,10 +11,116 @@
|
|||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">label</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="playlist_treeview">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscroll_policy">natural</property>
|
||||
<property name="vscroll_policy">natural</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="title" translatable="yes">#</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">250</property>
|
||||
<property name="title" translatable="yes">Title</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="title" translatable="yes">Length</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">250</property>
|
||||
<property name="title" translatable="yes">Artist</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">250</property>
|
||||
<property name="title" translatable="yes">Album</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">4</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">autosize</property>
|
||||
<property name="title" translatable="yes">Year</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">5</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">250</property>
|
||||
<property name="title" translatable="yes">Genre</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">6</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import gtk
|
||||
from . import playlist
|
||||
|
||||
Application = gtk.Application
|
||||
PlistModel = playlist.PlistModel
|
||||
Treeview = playlist.Treeview
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import gtk
|
||||
import curds
|
||||
from gi.repository import GObject, Gtk
|
||||
|
||||
cols = [ "tracknumber", "title", "length", "artist", "album", "date", "genre" ]
|
||||
|
||||
class PlaylistModel(GObject.GObject, Gtk.TreeModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
GObject.GObject.__init__(self)
|
||||
self.playlist = curds.PlaylistManager["Collection"]
|
||||
curds.Notify.notify_me("add-track", self.on_add_track)
|
||||
|
||||
def do_get_column_type(self, col):
|
||||
return str
|
||||
|
||||
def do_get_flags(self):
|
||||
return Gtk.TreeModelFlags.LIST_ONLY
|
||||
|
||||
def do_get_iter(self, path):
|
||||
return self.do_iter_nth_child(None, path.get_indices()[0])
|
||||
|
||||
def do_get_n_columns(self):
|
||||
return 7
|
||||
|
||||
def do_get_path(self, iter):
|
||||
return Gtk.TreePath((iter.user_data))
|
||||
|
||||
def do_get_value(self, iter, column):
|
||||
track = self.playlist[iter.user_data]
|
||||
if column == 0:
|
||||
return f"{track['tracknumber']:02}"
|
||||
elif column == 2:
|
||||
(m, s) = divmod(int(track["length"]), 60)
|
||||
return f"{m}:{s:02}"
|
||||
return str(track[cols[column]])
|
||||
|
||||
def do_iter_has_child(self, iter):
|
||||
return False
|
||||
|
||||
def do_iter_n_children(self, iter):
|
||||
return len(self.playlist)
|
||||
|
||||
def do_iter_nth_child(self, iter, n):
|
||||
if n < len(self.playlist):
|
||||
iter = Gtk.TreeIter()
|
||||
iter.user_data = n
|
||||
return (True, iter)
|
||||
return (False, None)
|
||||
|
||||
def do_iter_next(self, iter):
|
||||
iter.user_data += 1
|
||||
if iter.user_data >= len(self.playlist):
|
||||
return (False, None)
|
||||
return (True, iter)
|
||||
|
||||
def on_add_track(self, plist, track):
|
||||
if plist == self.playlist:
|
||||
path = Gtk.TreePath.new_from_indices([0])
|
||||
iter = self.get_iter(path)
|
||||
self.row_inserted(path, iter)
|
||||
|
||||
def set_playlist(self, plist):
|
||||
self.playlist = plist
|
||||
|
||||
|
||||
PlistModel = PlaylistModel()
|
||||
Treeview = gtk.Builder.get_object("playlist_treeview")
|
||||
Treeview.set_model(PlistModel)
|
|
@ -0,0 +1,73 @@
|
|||
# Copyright 2019 (c) Anna Schumaker.
|
||||
from . import gtk
|
||||
from . import playlist
|
||||
import curds
|
||||
import os
|
||||
import unittest
|
||||
from gi.repository import Gtk, GObject
|
||||
|
||||
test_album = os.path.abspath("./trier/Test Library/Test Artist 01/Test Album 1")
|
||||
|
||||
class TestPlaylist(unittest.TestCase):
|
||||
def test_init(self):
|
||||
self.assertIsInstance(gtk.Builder.get_object("playlist_treeview"), Gtk.TreeView)
|
||||
self.assertIsInstance(playlist.PlistModel, Gtk.TreeModel)
|
||||
self.assertEqual(playlist.Treeview, gtk.Builder.get_object("playlist_treeview"))
|
||||
self.assertEqual(playlist.Treeview.get_model(), playlist.PlistModel)
|
||||
|
||||
def test_model_init(self):
|
||||
model = playlist.PlaylistModel()
|
||||
self.assertEqual(model.playlist, curds.PlaylistManager["Collection"])
|
||||
self.assertEqual(model.get_n_columns(), 7)
|
||||
self.assertEqual(model.get_flags(), Gtk.TreeModelFlags.LIST_ONLY)
|
||||
for col in range(model.get_n_columns()):
|
||||
self.assertEqual(model.get_column_type(col), GObject.GType(str))
|
||||
|
||||
def test_model_empty(self):
|
||||
plist = curds.Playlist("Test Playlist")
|
||||
model = playlist.PlistModel
|
||||
path = Gtk.TreePath(0)
|
||||
|
||||
model.set_playlist(plist)
|
||||
self.assertEqual(model.playlist, plist)
|
||||
self.assertEqual(len(model), 0)
|
||||
self.assertRaises(ValueError, model.get_iter, path)
|
||||
self.assertEqual(model.iter_n_children(None), 0)
|
||||
self.assertIsNone(model.iter_nth_child(None, 1))
|
||||
|
||||
def test_model(self):
|
||||
plist = curds.Playlist("Test Playlist")
|
||||
model = playlist.PlaylistModel()
|
||||
path = Gtk.TreePath(1)
|
||||
tracks = [ ]
|
||||
|
||||
model.set_playlist(plist)
|
||||
self.assertEqual(model.playlist, plist)
|
||||
|
||||
for i in range(1, 11):
|
||||
track = curds.Track.lookup(os.path.join(test_album, f"{i:02} - Test Track {i:02}.ogg"))
|
||||
tracks.append(track)
|
||||
plist.add(track)
|
||||
self.assertEqual(len(model), i)
|
||||
|
||||
iter = model.get_iter(path)
|
||||
self.assertEqual(iter.user_data, 1)
|
||||
self.assertFalse(model.iter_has_child(iter))
|
||||
for i in range(2, 10):
|
||||
iter = model.iter_next(iter)
|
||||
path = model.get_path(iter)
|
||||
self.assertEqual(iter.user_data, i)
|
||||
self.assertEqual(path.get_indices()[0], i)
|
||||
self.assertIsNone(model.iter_next(iter))
|
||||
|
||||
iter = model.iter_nth_child(None, 4)
|
||||
self.assertEqual(iter.user_data, 4)
|
||||
self.assertIsNone(model.iter_nth_child(iter, 999))
|
||||
|
||||
self.assertEqual(model.get_value(iter, 0), "05")
|
||||
self.assertEqual(model.get_value(iter, 1), "Test Track 05")
|
||||
self.assertEqual(model.get_value(iter, 2), "0:05")
|
||||
self.assertEqual(model.get_value(iter, 3), "Test Artist 01")
|
||||
self.assertEqual(model.get_value(iter, 4), "Test Album 1")
|
||||
self.assertEqual(model.get_value(iter, 5), "1973")
|
||||
self.assertEqual(model.get_value(iter, 6), "Test Genre 1")
|
|
@ -16,3 +16,5 @@ class TestEmmental(unittest.TestCase):
|
|||
|
||||
def test_import_rind(self):
|
||||
self.assertEqual(rind.Application, rind.gtk.Application)
|
||||
self.assertEqual(rind.PlistModel, rind.playlist.PlistModel)
|
||||
self.assertEqual(rind.Treeview, rind.playlist.Treeview)
|
||||
|
|
Loading…
Reference in New Issue