1
0
mirror of https://github.com/lutris/lutris synced 2024-07-05 16:38:42 +00:00

Support multiple games in the edit-game-categories dialog.

When a category is applied to some but not all selected games, an indeterminate checkbox shows. Click it and it becomes determinate (and checked).

On save, any remaining indeterminate categories are skipped, but we work out what categories are to be added or removed for each game in turn.

Resolves #5372
This commit is contained in:
Daniel Johnson 2024-03-22 18:55:52 -04:00 committed by Mathieu Comandon
parent ad5c35a65e
commit e5bff4e2af
2 changed files with 45 additions and 28 deletions

View File

@ -157,6 +157,10 @@ class GameActions:
dlg = application.show_window(UninstallDialog, parent=self.window)
dlg.add_games(game_ids)
def on_edit_game_categories(self, _widget):
"""Edit game categories"""
self.application.show_window(EditGameCategoriesDialog, games=self.get_games(), parent=self.window)
class MultiGameActions(GameActions):
"""This actions class handles actions on multiple games together, and only iof they
@ -174,6 +178,7 @@ class MultiGameActions(GameActions):
return [
("stop", _("Stop"), self.on_game_stop),
(None, "-", None),
("category", _("Categories"), self.on_edit_game_categories),
("favorite", _("Add to favorites"), self.on_add_favorite_game),
("deletefavorite", _("Remove from favorites"), self.on_delete_favorite_game),
("hide", _("Hide game from library"), self.on_hide_game),
@ -185,6 +190,7 @@ class MultiGameActions(GameActions):
def get_displayed_entries(self):
return {
"stop": self.is_game_running,
"category": True,
"favorite": any(g for g in self.games if not g.is_favorite),
"deletefavorite": any(g for g in self.games if g.is_favorite),
"hide": any(g for g in self.games if g.is_installed and not g.is_hidden),
@ -309,10 +315,6 @@ class SingleGameActions(GameActions):
"""Edit game preferences"""
self.application.show_window(EditGameConfigDialog, game=self.game, parent=self.window)
def on_edit_game_categories(self, _widget):
"""Edit game categories"""
self.application.show_window(EditGameCategoriesDialog, game=self.game, parent=self.window)
def on_browse_files(self, _widget):
"""Callback to open a game folder in the file browser"""
path = self.game.get_browse_dir()

View File

@ -11,12 +11,19 @@ from lutris.gui.dialogs import SavableModelessDialog
class EditGameCategoriesDialog(SavableModelessDialog):
"""Game category edit dialog."""
def __init__(self, parent, game):
super().__init__(_("Categories - %s") % game.name, parent=parent, border_width=10)
def __init__(self, parent, games):
name = games[0].name if len(games) == 1 else _("%s games") % len(games)
super().__init__(_("Categories - %s") % name, parent=parent, border_width=10)
self.game = game
self.game_id = game.id
self.game_categories = categories_db.get_categories_in_game(self.game_id)
self.games = games
self.game_ids = [game.id for game in games]
self.categories = sorted(
[c["name"] for c in categories_db.get_categories() if c["name"] != "favorite"], key=locale.strxfrm
)
self.game_categories = {game_id: categories_db.get_categories_in_game(game_id) for game_id in self.game_ids}
self.category_games = {
category: [t[0] for t in self.game_categories.items() if category in t[1]] for category in self.categories
}
self.grid = Gtk.Grid()
@ -34,15 +41,16 @@ class EditGameCategoriesDialog(SavableModelessDialog):
# frame.set_label("Categories") # probably too much redundancy
sw = Gtk.ScrolledWindow()
row = Gtk.VBox()
categories = sorted(
[c for c in categories_db.get_categories() if c["name"] != "favorite"],
key=lambda c: locale.strxfrm(c["name"]),
)
for category in categories:
label = category["name"]
for category in self.categories:
label = category
checkbutton_option = Gtk.CheckButton(label)
if label in self.game_categories:
games_included = len(self.category_games.get(category) or [])
if len(self.game_ids) == games_included:
checkbutton_option.set_active(True)
elif games_included > 0:
checkbutton_option.set_inconsistent(True)
checkbutton_option.connect("toggled", self.on_inconsistent_checkbutton_toggled)
self.grid.attach_next_to(checkbutton_option, None, Gtk.PositionType.BOTTOM, 3, 1)
row.pack_start(self.grid, True, True, 0)
@ -79,22 +87,29 @@ class EditGameCategoriesDialog(SavableModelessDialog):
return hbox
@staticmethod
def on_inconsistent_checkbutton_toggled(checkbutton):
checkbutton.set_inconsistent(False)
def on_save(self, _button):
"""Save game info and destroy widget."""
removed_categories = set()
added_categories = set()
for category_checkbox in self.grid.get_children():
label = category_checkbox.get_label()
for game in self.games:
for category_checkbox in self.grid.get_children():
removed_categories = set()
added_categories = set()
if label in self.game_categories:
if not category_checkbox.get_active():
removed_categories.add(label)
else:
if category_checkbox.get_active():
added_categories.add(label)
if not category_checkbox.get_inconsistent():
label = category_checkbox.get_label()
if added_categories or removed_categories:
self.game.update_game_categories(added_categories, removed_categories)
if label in self.game_categories[game.id]:
if not category_checkbox.get_active():
removed_categories.add(label)
else:
if category_checkbox.get_active():
added_categories.add(label)
if added_categories or removed_categories:
game.update_game_categories(added_categories, removed_categories)
self.destroy()