emmental/emmental/tracklist/row.py

208 lines
7.1 KiB
Python

# Copyright 2023 (c) Anna Schumaker.
"""Widgets for displaying Track information in the TrackView."""
import datetime
import dateutil.tz
import pathlib
from gi.repository import GObject
from gi.repository import Gtk
from .. import buttons
from .. import factory
class TrackRow(factory.ListRow):
"""Base class for Track Row widgets."""
property = GObject.Property(type=str)
mediumid = GObject.Property(type=int)
def __init__(self, listitem: Gtk.ListItem, property: str):
"""Initialize a TrackRow."""
super().__init__(listitem, property=property)
def do_bind(self) -> None:
"""Bind a Track to this Row."""
super().do_bind()
library = self.item.get_library()
self.bind_and_set(library, "online", self, "online")
self.bind_active("active")
def bind_to_self(self, item_prop: str, child_prop: str) -> None:
"""Bind an item property directly to a TrackRow property."""
self.bind_and_set(self.item, item_prop, self, child_prop)
@GObject.Property(type=bool, default=False)
def active(self) -> bool:
"""Get the active state of this Row."""
if parent := self.listitem.get_child().get_parent():
if parent := parent.get_parent():
return parent.get_state_flags() & Gtk.StateFlags.CHECKED
return False
@active.setter
def active(self, newval: bool) -> None:
if parent := self.listitem.get_child().get_parent():
if parent := parent.get_parent():
if newval:
parent.set_state_flags(Gtk.StateFlags.CHECKED, False)
else:
parent.unset_state_flags(Gtk.StateFlags.CHECKED)
@GObject.Property(type=bool, default=True)
def online(self) -> bool:
"""Get the online state of this Row."""
return self.listitem.get_activatable()
@online.setter
def online(self, newval: bool) -> None:
self.listitem.set_activatable(newval)
self.child.set_sensitive(newval)
class InscriptionRow(TrackRow):
"""Base class for Track Rows displaying a Gtk.Inscription."""
def __init__(self, listitem: Gtk.ListItem, property: str,
xalign: float = 0.0, numeric: bool = False):
"""Initialize a LabelRow."""
super().__init__(listitem, property)
self.child = Gtk.Inscription(xalign=xalign)
self.child.bind_property("text", self.child, "tooltip-text")
if numeric:
self.child.add_css_class("numeric")
class TrackString(InscriptionRow):
"""An InscriptionRow displaying a string Track property."""
def do_bind(self) -> None:
"""Bind a track string to the Track Inscription."""
super().do_bind()
self.bind_and_set_property(self.property, "text")
class LengthString(InscriptionRow):
"""An InscriptionRow displaying a length Track property."""
def do_bind(self) -> None:
"""Bind the track length to the length property."""
super().do_bind()
self.bind_and_set(self.item, self.property, self, "length")
@GObject.Property(type=float)
def length(self) -> float:
"""Get the current length."""
return getattr(self, "__length", 0.0)
@length.setter
def length(self, newval: float) -> None:
self.__length = newval
self.child.set_text("{}:{:02d}".format(*divmod(round(newval), 60)))
class PlayCountString(InscriptionRow):
"""An InscriptionRow displaying a playcount Track property."""
def do_bind(self) -> None:
"""Bind the track playcount to the playcount property."""
super().do_bind()
self.bind_and_set(self.item, self.property, self, "playcount")
@GObject.Property(type=int)
def playcount(self) -> int:
"""Get the current playcount."""
return getattr(self, "__playcount", 0)
@playcount.setter
def playcount(self, newval: int) -> None:
self.__playcount = newval
match newval:
case 0: self.child.set_text("Unplayed")
case 1: self.child.set_text("Played 1 time")
case _: self.child.set_text(f"Played {newval} times")
class TimestampString(InscriptionRow):
"""An InscriptionRow displaying a datetime Track property."""
TZ_UTC = dateutil.tz.tzutc()
def do_bind(self) -> None:
"""Bind the track datetime property to the timestamp property."""
super().do_bind()
self.bind_and_set(self.item, self.property, self, "timestamp")
@GObject.Property(type=GObject.TYPE_PYOBJECT)
def timestamp(self) -> datetime.datetime:
"""Get the current timestamp."""
return getattr(self, "__timestamp", None)
@timestamp.setter
def timestamp(self, newval: datetime.datetime) -> None:
self.__timestamp = newval
if newval is None:
self.child.set_text("Never")
else:
local = newval.replace(tzinfo=TimestampString.TZ_UTC).astimezone()
self.child.set_text(local.replace(tzinfo=None).strftime("%c"))
class PathString(InscriptionRow):
"""An InscriptionRow displaying a pathlib.Path Track property."""
def do_bind(self) -> None:
"""Bind the track path property to the path property."""
super().do_bind()
self.bind_and_set(self.item, self.property, self, "path")
@GObject.Property(type=GObject.TYPE_PYOBJECT)
def path(self) -> pathlib.Path:
"""Get the current path."""
return getattr(self, "__path", None)
@path.setter
def path(self, newval: pathlib.Path) -> None:
self.__path = newval
self.child.set_text(str(newval))
class TracknoString(InscriptionRow):
"""A FormatString that combines medium and track numbers."""
number = GObject.Property(type=int)
def __init__(self, *args, **kwargs):
"""Initialize a TracknoString."""
super().__init__(*args, **kwargs)
self.connect("notify", self.__update_trackno)
def __update_trackno(self, row: InscriptionRow, param) -> None:
"""Update the trackno string when properties change."""
match param.name:
case "mediumid": self.notify("number")
case "number":
mediumno = self.item.get_medium().number
self.child.set_text("{}-{:02d}".format(mediumno, self.number))
def do_bind(self) -> None:
"""Bind track and medium numbers to the Track Inscription."""
super().do_bind()
self.bind_to_self("mediumid", "mediumid")
self.bind_to_self("number", "number")
class FavoriteButton(TrackRow):
"""A TrackRow with an toggle to set Track favorite status."""
def __init__(self, listitem: Gtk.ListItem, property: str) -> None:
"""Initialize a Favorite Button."""
super().__init__(listitem, property=property)
self.child = buttons.ImageToggle("heart-filled", "heart-outline-thick",
icon_size=Gtk.IconSize.NORMAL,
valign=Gtk.Align.CENTER,
has_frame=False)
def do_bind(self):
"""Bind a track property to the Toggle Button."""
super().do_bind()
self.bind_and_set_property(self.property, "active", bidirectional=True)