games: Add sorter
This commit is contained in:
committed by
Laura Kramolis
parent
9d4932f22e
commit
c1e3c987c1
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user