collection-details: Support editing and removing
This commit is contained in:
@@ -2,14 +2,19 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||
|
||||
from collections.abc import Generator, Iterable
|
||||
from typing import Any, cast
|
||||
from gettext import gettext as _
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from gi.repository import Gio, GLib, GObject
|
||||
|
||||
from cartridges import SETTINGS
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .application import Application
|
||||
from .ui.window import Window
|
||||
|
||||
class Collection(GObject.Object):
|
||||
|
||||
class Collection(Gio.SimpleActionGroup):
|
||||
"""Collection data class."""
|
||||
|
||||
__gtype_name__ = __qualname__
|
||||
@@ -38,6 +43,27 @@ class Collection(GObject.Object):
|
||||
lambda _, name: f"{name}-symbolic",
|
||||
)
|
||||
|
||||
self.add_action(remove := Gio.SimpleAction.new("remove"))
|
||||
remove.connect("activate", lambda *_: self._remove())
|
||||
self.bind_property(
|
||||
"in-model",
|
||||
remove,
|
||||
"enabled",
|
||||
GObject.BindingFlags.SYNC_CREATE,
|
||||
)
|
||||
|
||||
def _remove(self):
|
||||
self.removed = True
|
||||
save()
|
||||
|
||||
app = cast("Application", Gio.Application.get_default())
|
||||
window = cast("Window", app.props.active_window)
|
||||
window.send_toast(_("{} removed").format(self.name), undo=self._undo_remove)
|
||||
|
||||
def _undo_remove(self):
|
||||
self.removed = False
|
||||
save()
|
||||
|
||||
|
||||
def _get_collections() -> Generator[Collection]:
|
||||
for data in SETTINGS.get_value("collections").unpack():
|
||||
|
||||
@@ -2,11 +2,18 @@ using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $CollectionDetails: Adw.Dialog {
|
||||
title: _("New Collection");
|
||||
title: bind $_or(template.collection as <$Collection>.name, _("New Collection")) as <string>;
|
||||
content-width: 360;
|
||||
default-widget: apply_button;
|
||||
focus-widget: name_entry;
|
||||
|
||||
ShortcutController {
|
||||
Shortcut {
|
||||
trigger: "Delete|KP_Delete";
|
||||
action: "action(collection.remove)";
|
||||
}
|
||||
}
|
||||
|
||||
child: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
@@ -24,7 +31,7 @@ template $CollectionDetails: Adw.Dialog {
|
||||
Button apply_button {
|
||||
action-name: "details.apply";
|
||||
icon-name: "apply-symbolic";
|
||||
tooltip-text: _("Add");
|
||||
tooltip-text: bind $_if_else(template.collection as <$Collection>.in-model, _("Apply"), _("Add")) as <string>;
|
||||
|
||||
styles [
|
||||
"suggested-action",
|
||||
@@ -36,6 +43,7 @@ template $CollectionDetails: Adw.Dialog {
|
||||
Adw.PreferencesGroup {
|
||||
Adw.EntryRow name_entry {
|
||||
title: _("Name");
|
||||
text: bind template.collection as <$Collection>.name;
|
||||
activates-default: true;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +61,19 @@ template $CollectionDetails: Adw.Dialog {
|
||||
"card",
|
||||
]
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
visible: bind remove_row.sensitive;
|
||||
|
||||
Adw.ButtonRow remove_row {
|
||||
title: _("Remove");
|
||||
action-name: "collection.remove";
|
||||
|
||||
styles [
|
||||
"destructive-action",
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||
|
||||
from typing import Any, cast
|
||||
from typing import Any, TypeVar, cast
|
||||
|
||||
from gi.repository import Adw, Gio, GObject, Gtk
|
||||
|
||||
@@ -33,6 +33,8 @@ ICONS = (
|
||||
"fist",
|
||||
)
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
|
||||
@Gtk.Template.from_resource(f"{PREFIX}/collection-details.ui")
|
||||
class CollectionDetails(Adw.Dialog):
|
||||
@@ -43,7 +45,19 @@ class CollectionDetails(Adw.Dialog):
|
||||
name_entry: Adw.EntryRow = Gtk.Template.Child()
|
||||
icons_box: Gtk.FlowBox = Gtk.Template.Child()
|
||||
|
||||
collection = GObject.Property(type=Collection)
|
||||
sort_changed = GObject.Signal()
|
||||
|
||||
@GObject.Property(type=Collection)
|
||||
def collection(self) -> Collection:
|
||||
"""The collection that `self` represents."""
|
||||
return self._collection
|
||||
|
||||
@collection.setter
|
||||
def collection(self, collection: Collection):
|
||||
self._collection = collection
|
||||
self.insert_action_group("collection", collection)
|
||||
remove_action = cast(Gio.SimpleAction, collection.lookup_action("remove"))
|
||||
remove_action.connect("activate", lambda *_: self.force_close())
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
@@ -77,7 +91,12 @@ class CollectionDetails(Adw.Dialog):
|
||||
)
|
||||
|
||||
def _apply(self):
|
||||
self.collection.name = self.name_entry.props.text
|
||||
name = self.name_entry.props.text
|
||||
if self.collection.name != name:
|
||||
self.collection.name = name
|
||||
if self.collection.in_model:
|
||||
self.emit("sort-changed")
|
||||
|
||||
self.collection.icon = ICONS[
|
||||
self.icons_box.get_selected_children()[0].get_index()
|
||||
]
|
||||
@@ -88,3 +107,11 @@ class CollectionDetails(Adw.Dialog):
|
||||
|
||||
collections.save()
|
||||
self.close()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _or(self, _obj, first: _T, second: _T) -> _T:
|
||||
return first or second
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _if_else(self, _obj, condition: object, first: _T, second: _T) -> _T:
|
||||
return first if condition else second
|
||||
|
||||
@@ -90,6 +90,8 @@ template $Window: Adw.ApplicationWindow {
|
||||
|
||||
Adw.SidebarSection collections {
|
||||
title: _("Collections");
|
||||
|
||||
menu-model: menu collection_menu {};
|
||||
}
|
||||
|
||||
Adw.SidebarSection {
|
||||
|
||||
@@ -48,8 +48,10 @@ class Window(Adw.ApplicationWindow):
|
||||
__gtype_name__ = __qualname__
|
||||
|
||||
split_view: Adw.OverlaySplitView = Gtk.Template.Child()
|
||||
sidebar: Adw.Sidebar = Gtk.Template.Child() # pyright: ignore[reportAttributeAccessIssue]
|
||||
collections: Adw.SidebarSection = Gtk.Template.Child() # pyright: ignore[reportAttributeAccessIssue]
|
||||
new_collection_item: Adw.SidebarItem = Gtk.Template.Child() # pyright: ignore[reportAttributeAccessIssue]
|
||||
collection_menu: Gio.Menu = Gtk.Template.Child()
|
||||
navigation_view: Adw.NavigationView = Gtk.Template.Child()
|
||||
header_bar: Adw.HeaderBar = Gtk.Template.Child()
|
||||
title_box: Gtk.CenterBox = Gtk.Template.Child()
|
||||
@@ -63,10 +65,11 @@ class Window(Adw.ApplicationWindow):
|
||||
|
||||
search_text = GObject.Property(type=str)
|
||||
show_hidden = GObject.Property(type=bool, default=False)
|
||||
collection = GObject.Property(type=Collection)
|
||||
|
||||
settings = GObject.Property(type=Gtk.Settings)
|
||||
|
||||
_collection: Collection | None = None
|
||||
_collection_removed_signal: int | None = None
|
||||
_selected_sidebar_item = 0
|
||||
|
||||
@GObject.Property(type=Gio.ListStore)
|
||||
@@ -74,6 +77,27 @@ class Window(Adw.ApplicationWindow):
|
||||
"""Model of the user's games."""
|
||||
return games.model
|
||||
|
||||
@GObject.Property(type=Collection)
|
||||
def collection(self) -> Collection | None:
|
||||
"""The currently selected collection."""
|
||||
return self._collection
|
||||
|
||||
@collection.setter
|
||||
def collection(self, collection: Collection | None):
|
||||
if self._collection and self._collection_removed_signal:
|
||||
self._collection.disconnect(self._collection_removed_signal)
|
||||
|
||||
self._collection = collection
|
||||
self._collection_removed_signal = (
|
||||
collection.connect("notify::removed", lambda *_: self._collection_removed())
|
||||
if collection
|
||||
else None
|
||||
)
|
||||
|
||||
def _collection_removed(self):
|
||||
self.collection = None
|
||||
self.sidebar.props.selected = 0
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -106,6 +130,16 @@ class Window(Adw.ApplicationWindow):
|
||||
"u",
|
||||
),
|
||||
("add", lambda *_: self._add()),
|
||||
(
|
||||
"edit-collection",
|
||||
lambda _action, param, *_: self._edit_collection(param.get_uint32()),
|
||||
"u",
|
||||
),
|
||||
(
|
||||
"remove-collection",
|
||||
lambda _action, param, *_: self._remove_collection(param.get_uint32()),
|
||||
"u",
|
||||
),
|
||||
("undo", lambda *_: self._undo()),
|
||||
))
|
||||
|
||||
@@ -152,6 +186,20 @@ class Window(Adw.ApplicationWindow):
|
||||
sidebar.props.selected = self._selected_sidebar_item
|
||||
self._selected_sidebar_item = sidebar.props.selected
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _setup_sidebar_menu(self, _sidebar, item: Adw.SidebarItem): # pyright: ignore[reportAttributeAccessIssue]
|
||||
if isinstance(item, CollectionSidebarItem):
|
||||
menu = self.collection_menu
|
||||
menu.remove_all()
|
||||
menu.append(
|
||||
_("Edit"),
|
||||
f"win.edit-collection(uint32 {item.get_section_index()})",
|
||||
)
|
||||
menu.append(
|
||||
_("Remove"),
|
||||
f"win.remove-collection(uint32 {item.get_section_index()})",
|
||||
)
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _setup_gamepad_monitor(self, *_args):
|
||||
if sys.platform.startswith("linux"):
|
||||
@@ -210,6 +258,19 @@ class Window(Adw.ApplicationWindow):
|
||||
details = CollectionDetails(collection=Collection())
|
||||
details.present(self)
|
||||
|
||||
def _edit_collection(self, pos: int):
|
||||
collection = self.collections.get_item(pos).collection
|
||||
details = CollectionDetails(collection=collection)
|
||||
details.connect(
|
||||
"sort-changed",
|
||||
lambda *_: collections.sorter.changed(Gtk.SorterChange.DIFFERENT),
|
||||
)
|
||||
details.present(self)
|
||||
|
||||
def _remove_collection(self, pos: int):
|
||||
collection = self.collections.get_item(pos).collection
|
||||
collection.activate_action("remove")
|
||||
|
||||
def _undo(self, toast: Adw.Toast | None = None):
|
||||
if toast:
|
||||
self._history.pop(toast)()
|
||||
|
||||
Reference in New Issue
Block a user