games: Add sorter

This commit is contained in:
Jamie Gravendeel
2025-12-04 18:10:13 +01:00
committed by Laura Kramolis
parent 9d4932f22e
commit c1e3c987c1
3 changed files with 52 additions and 48 deletions

View File

@@ -5,6 +5,7 @@
import itertools
import json
import locale
import os
import subprocess
from collections.abc import Generator, Iterable
@@ -12,11 +13,11 @@ from json import JSONDecodeError
from pathlib import Path
from shlex import quote
from types import UnionType
from typing import Any, NamedTuple, Self, cast
from typing import Any, NamedTuple, Self, cast, override
from gi.repository import Gdk, Gio, GLib, GObject, Gtk
from cartridges import DATA_DIR
from cartridges import DATA_DIR, state_settings
class _GameProp(NamedTuple):
@@ -45,6 +46,13 @@ _COVERS_DIR = DATA_DIR / "covers"
_SPEC_VERSION = 2.0
_MANUALLY_ADDED_ID = "imported"
_SORT_MODES = {
"last_played": ("last-played", True),
"a-z": ("name", False),
"z-a": ("name", True),
"newest": ("added", True),
"oldest": ("added", False),
}
class Game(Gio.SimpleActionGroup):
@@ -143,6 +151,39 @@ class Game(Gio.SimpleActionGroup):
json.dump(properties, f, indent=4)
class GameSorter(Gtk.Sorter):
"""A sorter for game objects.
Automatically updates if the "sort-mode" GSetting changes.
"""
__gtype_name__ = __qualname__
def __init__(self, **kwargs: Any):
super().__init__(**kwargs)
state_settings.connect(
"changed::sort-mode", lambda *_: self.changed(Gtk.SorterChange.DIFFERENT)
)
@override
def do_compare(self, game1: Game, game2: Game) -> Gtk.Ordering: # pyright: ignore[reportIncompatibleMethodOverride]
prop, invert = _SORT_MODES[state_settings.get_string("sort-mode")]
a = (game2 if invert else game1).get_property(prop)
b = (game1 if invert else game2).get_property(prop)
return Gtk.Ordering(
self._name_cmp(a, b)
if isinstance(a, str)
else ((a > b) - (a < b)) or self._name_cmp(game1.name, game2.name)
)
@staticmethod
def _name_cmp(a: str, b: str) -> int:
a, b = (name.lower().removeprefix("the ") for name in (a, b))
return max(-1, min(locale.strcoll(a, b), 1))
def _increment_manually_added_id() -> int:
numbers = {
game.game_id.split("_")[1]

View File

@@ -63,31 +63,31 @@ template $Window: Adw.ApplicationWindow {
item {
label: _("Last Played");
action: "win.sort";
action: "win.sort-mode";
target: "last_played";
}
item {
label: _("A-Z");
action: "win.sort";
action: "win.sort-mode";
target: "a-z";
}
item {
label: _("Z-A");
action: "win.sort";
action: "win.sort-mode";
target: "z-a";
}
item {
label: _("Newest");
action: "win.sort";
action: "win.sort-mode";
target: "newest";
}
item {
label: _("Oldest");
action: "win.sort";
action: "win.sort-mode";
target: "oldest";
}
}
@@ -137,7 +137,7 @@ template $Window: Adw.ApplicationWindow {
model: NoSelection {
model: SortListModel {
sorter: CustomSorter sorter {};
sorter: $GameSorter sorter {};
model: FilterListModel {
watch-items: true;

View File

@@ -3,15 +3,13 @@
# SPDX-FileCopyrightText: Copyright 2022-2025 kramo
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
import locale
from collections.abc import Generator
from typing import Any, TypeVar, cast
from gi.repository import Adw, Gio, GLib, GObject, Gtk
from cartridges import games, state_settings
from cartridges.config import PREFIX, PROFILE
from cartridges.games import Game
from cartridges.games import Game, GameSorter
from .game_details import GameDetails
from .game_item import GameItem # noqa: F401
@@ -36,7 +34,7 @@ class Window(Adw.ApplicationWindow):
navigation_view: Adw.NavigationView = Gtk.Template.Child()
search_entry: Gtk.SearchEntry = Gtk.Template.Child()
grid: Gtk.GridView = Gtk.Template.Child()
sorter: Gtk.CustomSorter = Gtk.Template.Child()
sorter: GameSorter = Gtk.Template.Child()
details: GameDetails = Gtk.Template.Child()
search_text = GObject.Property(type=str)
@@ -60,17 +58,11 @@ class Window(Adw.ApplicationWindow):
# https://gitlab.gnome.org/GNOME/gtk/-/issues/7901
self.search_entry.set_key_capture_widget(self)
self.sorter.set_sort_func(self._sort_func)
self.add_action(state_settings.create_action("sort-mode"))
self.add_action(Gio.PropertyAction.new("show-hidden", self, "show-hidden"))
self.add_action_entries((
("search", lambda *_: self.search_entry.grab_focus()),
(
"sort",
self._sort,
"s",
state_settings.get_value("sort-mode").print_(False),
),
("edit", lambda _action, param, *_: self._edit(param.get_uint32()), "u"),
("add", lambda *_: self._add()),
))
@@ -102,35 +94,6 @@ class Window(Adw.ApplicationWindow):
entry.props.text = ""
self.grid.grab_focus()
def _sort(self, action: Gio.SimpleAction, parameter: GLib.Variant, *_args):
action.change_state(parameter)
sort_mode = parameter.get_string()
prop, invert = SORT_MODES[sort_mode]
prev_prop, prev_invert = SORT_MODES[state_settings.get_string("sort-mode")]
opposite = (prev_prop == prop) and (prev_invert != invert)
state_settings.set_string("sort-mode", sort_mode)
self.sorter.changed(
Gtk.SorterChange.INVERTED if opposite else Gtk.SorterChange.DIFFERENT
)
def _sort_func(self, game1: Game, game2: Game, _) -> int:
prop, invert = SORT_MODES[state_settings.get_string("sort-mode")]
a = (game2 if invert else game1).get_property(prop)
b = (game1 if invert else game2).get_property(prop)
return (
locale.strcoll(*self._sortable(a, b))
if isinstance(a, str)
else (a > b) - (a < b)
or locale.strcoll(*self._sortable(game1.name, game2.name))
)
@staticmethod
def _sortable(*strings: str) -> Generator[str]:
for string in strings:
yield string.lower().removeprefix("the ")
@Gtk.Template.Callback()
def _sort_changed(self, *_args):
self.sorter.changed(Gtk.SorterChange.DIFFERENT)