155 lines
5.4 KiB
Python
155 lines
5.4 KiB
Python
# Copyright 2022 (c) Anna Schumaker.
|
|
"""A Sorter designed for sorting Playlists."""
|
|
from gi.repository import GObject
|
|
from gi.repository import Gio
|
|
|
|
SORT_ORDER_FLAGS = GObject.ParamFlags.READWRITE | \
|
|
GObject.ParamFlags.EXPLICIT_NOTIFY
|
|
SORT_FIELDS = {"Album": ["album"], "Artist": ["artist"],
|
|
"Album Artist": ["albumartist"],
|
|
"Filepath": ["filepath"], "Length": ["length"],
|
|
"Last Played Date": ["lastplayed"],
|
|
"Last Started Date": ["laststarted"],
|
|
"Play Count": ["playcount"], "Release Date": ["release"],
|
|
"Title": ["title"], "Track Number": ["mediumno", "number"]}
|
|
REVERSE_LOOKUP = {v: k for k, l in SORT_FIELDS.items() for v in l}
|
|
|
|
|
|
class SortField(GObject.GObject):
|
|
"""Used to represent a single field in the sort order."""
|
|
|
|
name = GObject.Property(type=str)
|
|
model = GObject.Property(type=Gio.ListModel)
|
|
|
|
enabled = GObject.Property(type=bool, default=False)
|
|
reversed = GObject.Property(type=bool, default=False)
|
|
|
|
def __init__(self, model: Gio.ListStore, name: str):
|
|
"""Initialize a Sort Field."""
|
|
if name not in SORT_FIELDS:
|
|
raise KeyError
|
|
super().__init__(model=model, name=name)
|
|
|
|
def __str__(self) -> str:
|
|
"""Convert this Field into a string."""
|
|
columns = SORT_FIELDS[self.name]
|
|
if self.reversed:
|
|
columns = [f"{col} DESC" for col in columns]
|
|
return ", ".join(columns)
|
|
|
|
def disable(self) -> bool:
|
|
"""Disable this Sort Field."""
|
|
return self.model.disable(self)
|
|
|
|
def enable(self) -> bool:
|
|
"""Enable this Sort Field."""
|
|
return self.model.enable(self)
|
|
|
|
def move_down(self) -> bool:
|
|
"""Move this Sort Field down in the list."""
|
|
return self.model.move_down(self)
|
|
|
|
def move_up(self) -> bool:
|
|
"""Move this Sort Field up in the list."""
|
|
return self.model.move_up(self)
|
|
|
|
def reverse(self) -> None:
|
|
"""Reverse the direction of this Sort Field."""
|
|
self.model.reverse(self)
|
|
|
|
|
|
class SortOrderModel(Gio.ListStore):
|
|
"""A ListModel for managing Sort Order."""
|
|
|
|
n_enabled = GObject.Property(type=int, default=0)
|
|
sort_order = GObject.Property(type=str)
|
|
|
|
def __init__(self):
|
|
"""Initialize the Sort Order Model."""
|
|
super().__init__(item_type=SortField)
|
|
for name in SORT_FIELDS.keys():
|
|
self.append(SortField(model=self, name=name))
|
|
|
|
def __move_field(self, field: SortField, position: int,
|
|
*, update: bool) -> None:
|
|
self.remove(self.index(field))
|
|
self.insert(position, field)
|
|
if update:
|
|
self.__update_sort_order()
|
|
|
|
def __update_sort_order(self) -> None:
|
|
enabled = list(filter(lambda f: f.enabled, self))
|
|
self.n_enabled = len(enabled)
|
|
self.sort_order = ", ".join([str(f) for f in enabled])
|
|
|
|
def disable(self, field: SortField) -> bool:
|
|
"""Disable a SortField."""
|
|
if field.enabled:
|
|
field.enabled = False
|
|
field.reversed = False
|
|
self.__move_field(field, self.n_enabled - 1, update=True)
|
|
return True
|
|
return False
|
|
|
|
def enable(self, field: SortField) -> bool:
|
|
"""Enable a SortField."""
|
|
if not field.enabled:
|
|
field.enabled = True
|
|
self.__move_field(field, self.n_enabled, update=True)
|
|
return True
|
|
return False
|
|
|
|
def index(self, field: SortField) -> int | None:
|
|
"""Find the index of a specific field."""
|
|
(found, position) = self.find(field)
|
|
return position if found else None
|
|
|
|
def lookup(self, name: str) -> SortField | None:
|
|
"""Find a SortField by name."""
|
|
for field in self:
|
|
if field.name == name:
|
|
return field
|
|
|
|
def move_down(self, field: SortField) -> bool:
|
|
"""Move a SortField one position closer to the end of the list."""
|
|
if (index := self.index(field)) < self.n_enabled - 1:
|
|
self.__move_field(field, index + 1, update=True)
|
|
return True
|
|
return False
|
|
|
|
def move_up(self, field: SortField) -> bool:
|
|
"""Move a SortField one position closer to the start of the list."""
|
|
if 0 < (index := self.index(field)) < self.n_enabled:
|
|
self.__move_field(field, index - 1, update=True)
|
|
return True
|
|
return False
|
|
|
|
def reverse(self, field: SortField) -> None:
|
|
"""Reverse the ordering of a Sort Field."""
|
|
field.reversed = not field.reversed
|
|
self.__update_sort_order()
|
|
|
|
def set_sort_order(self, new_order: str | None) -> None:
|
|
"""Manually set the sort order."""
|
|
if self.sort_order == new_order or new_order is None:
|
|
return
|
|
|
|
for field in self:
|
|
field.enabled = False
|
|
field.reversed = False
|
|
|
|
if new_order == "user" or new_order == "":
|
|
self.n_enabled = 0
|
|
self.sort_order = new_order
|
|
else:
|
|
position = 0
|
|
for desc in new_order.split(", "):
|
|
col, order = (desc.split(" ") + [None])[:2]
|
|
if field := self.lookup(REVERSE_LOOKUP[col]):
|
|
if not field.enabled:
|
|
field.enabled = True
|
|
field.reversed = order == "DESC"
|
|
self.__move_field(field, position, update=False)
|
|
position += 1
|
|
self.__update_sort_order()
|