mirror of
https://github.com/lutris/lutris
synced 2024-09-30 04:54:18 +00:00
Merge branch 'next' of https://github.com/Riesi/lutris into Riesi-next
This commit is contained in:
commit
6129f9dedb
|
@ -8,6 +8,7 @@ from lutris.gui import dialogs
|
|||
from lutris.gui.widgets.utils import open_uri
|
||||
from lutris.gui.config.add_game import AddGameDialog
|
||||
from lutris.gui.config.edit_game import EditGameConfigDialog
|
||||
from lutris.gui.config.edit_game_categories import EditGameCategoriesDialog
|
||||
from lutris.gui.installerwindow import InstallerWindow
|
||||
from lutris.gui.dialogs.uninstall_game import UninstallGameDialog
|
||||
from lutris.gui.dialogs.log import LogWindow
|
||||
|
@ -72,6 +73,10 @@ class GameActions:
|
|||
"configure", "Configure",
|
||||
self.on_edit_game_configuration
|
||||
),
|
||||
(
|
||||
"category", "Categories",
|
||||
self.on_edit_game_categories
|
||||
),
|
||||
(
|
||||
"execute-script", "Execute script",
|
||||
self.on_execute_script_clicked
|
||||
|
@ -119,6 +124,7 @@ class GameActions:
|
|||
"stop": self.is_game_running,
|
||||
"show_logs": self.game.is_installed,
|
||||
"configure": bool(self.game.is_installed),
|
||||
"category": bool(self.game.is_installed),
|
||||
"install_more": self.game.is_installed and not self.game.is_search_result,
|
||||
"execute-script": bool(
|
||||
self.game.is_installed
|
||||
|
@ -200,6 +206,10 @@ class GameActions:
|
|||
"""Edit game preferences"""
|
||||
EditGameConfigDialog(self.window, self.game)
|
||||
|
||||
def on_edit_game_categories(self, _widget):
|
||||
"""Edit game categories"""
|
||||
EditGameCategoriesDialog(self.window, self.game)
|
||||
|
||||
def on_execute_script_clicked(self, _widget):
|
||||
"""Execute the game's associated script"""
|
||||
manual_command = self.game.runner.system_config.get("manual_command")
|
||||
|
|
115
lutris/gui/config/edit_game_categories.py
Normal file
115
lutris/gui/config/edit_game_categories.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import re
|
||||
|
||||
from gi.repository import Gtk, Pango
|
||||
|
||||
from lutris import pga
|
||||
from lutris.gui.dialogs import Dialog
|
||||
from lutris.gui.config.common import GameDialogCommon
|
||||
#from lutris.gui.config import DIALOG_WIDTH, DIALOG_HEIGHT
|
||||
|
||||
|
||||
class EditGameCategoriesDialog(Dialog, GameDialogCommon):
|
||||
"""Game category edit dialog."""
|
||||
|
||||
def __init__(self, parent, game):
|
||||
super().__init__("Categories - %s" % game.name, parent=parent)
|
||||
self.parent = parent
|
||||
|
||||
self.game = game
|
||||
self.game_id = game.id
|
||||
self.game_categories = pga.get_categories_in_game(self.game_id)
|
||||
self.grid = Gtk.Grid()
|
||||
|
||||
self.set_default_size(350, 250)
|
||||
self.set_border_width(10)
|
||||
|
||||
self.vbox.set_homogeneous(False)
|
||||
self.vbox.set_spacing(10)
|
||||
self.vbox.pack_start(self._create_category_checkboxes(), True, True, 0)
|
||||
self.vbox.pack_start(self._create_add_category(), False, False, 0)
|
||||
|
||||
self.build_action_area(self.on_save)
|
||||
self.show_all()
|
||||
|
||||
def _create_category_checkboxes(self):
|
||||
frame = Gtk.Frame()
|
||||
# frame.set_label("Categories") # probably too much redundancy
|
||||
sw = Gtk.ScrolledWindow()
|
||||
row = Gtk.VBox()
|
||||
for category in pga.get_categories():
|
||||
checkbutton_option = Gtk.CheckButton(category)
|
||||
if category in self.game_categories:
|
||||
checkbutton_option.set_active(True)
|
||||
self.grid.attach_next_to(checkbutton_option, None, Gtk.PositionType.BOTTOM, 3, 1)
|
||||
|
||||
row.pack_start(self.grid, True, True, 0)
|
||||
sw.add_with_viewport(row)
|
||||
frame.add(sw)
|
||||
return frame
|
||||
|
||||
def _create_add_category(self):
|
||||
def on_add_category(widget=None):
|
||||
category_text = category_entry.get_text().strip()
|
||||
if category_text != "":
|
||||
category_text = re.sub(' +', ' ', category_text) # Remove excessive whitespaces
|
||||
for category_checkbox in self.grid.get_children():
|
||||
if category_checkbox.get_label() == category_text:
|
||||
return
|
||||
category_entry.set_text("")
|
||||
checkbutton_option = Gtk.CheckButton(category_text)
|
||||
checkbutton_option.set_active(True)
|
||||
self.grid.attach_next_to(checkbutton_option, None, Gtk.PositionType.TOP, 3, 1)
|
||||
pga.add_category(category_text)
|
||||
self.vbox.show_all()
|
||||
|
||||
hbox = Gtk.HBox()
|
||||
hbox.set_spacing(10)
|
||||
|
||||
category_entry = Gtk.Entry()
|
||||
category_entry.set_text("")
|
||||
hbox.pack_start(category_entry, True, True, 0)
|
||||
|
||||
button = Gtk.Button.new_with_label("Add Category")
|
||||
button.connect("clicked", on_add_category)
|
||||
button.set_tooltip_text("Adds the category to the list.")
|
||||
hbox.pack_start(button, False, False, 0)
|
||||
|
||||
return hbox
|
||||
|
||||
# Override the save action box, because we don't need the advanced-checkbox
|
||||
def build_action_area(self, button_callback, callback2=None):
|
||||
self.action_area.set_layout(Gtk.ButtonBoxStyle.END)
|
||||
# Buttons
|
||||
hbox = Gtk.Box()
|
||||
cancel_button = Gtk.Button(label="Cancel")
|
||||
cancel_button.connect("clicked", self.on_cancel_clicked)
|
||||
hbox.pack_start(cancel_button, True, True, 10)
|
||||
|
||||
save_button = Gtk.Button(label="Save")
|
||||
if callback2:
|
||||
save_button.connect("clicked", button_callback, callback2)
|
||||
else:
|
||||
save_button.connect("clicked", button_callback)
|
||||
hbox.pack_start(save_button, True, True, 0)
|
||||
self.action_area.pack_start(hbox, True, True, 0)
|
||||
|
||||
def is_valid(self):
|
||||
return True
|
||||
|
||||
def on_save(self, _button):
|
||||
"""Save game info and destroy widget. Return True if success."""
|
||||
if not self.is_valid():
|
||||
return False
|
||||
|
||||
for category_checkbox in self.grid.get_children():
|
||||
label = category_checkbox.get_label()
|
||||
if label in self.game_categories:
|
||||
if not category_checkbox.get_active():
|
||||
pga.delete_game_by_id_from_category(self.game_id, label)
|
||||
else:
|
||||
if category_checkbox.get_active():
|
||||
pga.add_game_to_category(self.game_id, label)
|
||||
|
||||
self.parent.on_game_updated(self.game)
|
||||
|
||||
self.destroy()
|
|
@ -82,6 +82,7 @@ class LutrisWindow(Gtk.ApplicationWindow):
|
|||
self.threads_stoppers = []
|
||||
self.selected_runner = None
|
||||
self.selected_platform = None
|
||||
self.selected_category = None
|
||||
self.icon_type = None
|
||||
|
||||
# Load settings
|
||||
|
@ -453,7 +454,7 @@ class LutrisWindow(Gtk.ApplicationWindow):
|
|||
child = scrollwindow_children[0]
|
||||
child.destroy()
|
||||
self.games_scrollwindow.add(self.view)
|
||||
self.set_selected_filter(self.selected_runner, self.selected_platform)
|
||||
self.set_selected_filter(self.selected_runner, self.selected_platform, self.selected_category)
|
||||
self.set_show_installed_state(self.filter_installed)
|
||||
self.view.show_all()
|
||||
|
||||
|
@ -808,18 +809,22 @@ class LutrisWindow(Gtk.ApplicationWindow):
|
|||
def on_sidebar_changed(self, widget):
|
||||
row = widget.get_selected_row()
|
||||
if row is None:
|
||||
self.set_selected_filter(None, None)
|
||||
self.set_selected_filter(None, None, None)
|
||||
elif row.type == "runner":
|
||||
self.set_selected_filter(row.id, None)
|
||||
self.set_selected_filter(row.id, None, None)
|
||||
elif row.type == "categories":
|
||||
self.set_selected_filter(None, None, row.id)
|
||||
else:
|
||||
self.set_selected_filter(None, row.id)
|
||||
self.set_selected_filter(None, row.id, None)
|
||||
|
||||
def set_selected_filter(self, runner, platform):
|
||||
def set_selected_filter(self, runner, platform, category):
|
||||
"""Filter the view to a given runner and platform"""
|
||||
self.selected_runner = runner
|
||||
self.selected_platform = platform
|
||||
self.selected_category = category
|
||||
self.game_store.filter_runner = self.selected_runner
|
||||
self.game_store.filter_platform = self.selected_platform
|
||||
self.game_store.filter_category = self.selected_category
|
||||
self.invalidate_game_filter()
|
||||
|
||||
def show_invalid_credential_warning(self):
|
||||
|
|
|
@ -88,6 +88,7 @@ class GameStore(GObject.Object):
|
|||
self.filter_text = None
|
||||
self.filter_runner = None
|
||||
self.filter_platform = None
|
||||
self.filter_category = None
|
||||
self.store = Gtk.ListStore(
|
||||
int,
|
||||
str,
|
||||
|
@ -218,6 +219,10 @@ class GameStore(GObject.Object):
|
|||
platform = model.get_value(_iter, COL_PLATFORM)
|
||||
if platform != self.filter_platform:
|
||||
return False
|
||||
if self.filter_category:
|
||||
identifier = model.get_value(_iter, COL_ID)
|
||||
if (identifier is None) or identifier not in pga.get_games_in_categories(self.filter_category):
|
||||
return False
|
||||
return True
|
||||
|
||||
def sort_view(self, key="name", ascending=True):
|
||||
|
|
|
@ -137,6 +137,7 @@ class SidebarListBox(Gtk.ListBox):
|
|||
self.active_platforms = pga.get_used_platforms()
|
||||
self.runners = sorted(runners.__all__)
|
||||
self.platforms = sorted(platforms.__all__)
|
||||
self.sidebar_categories = dict() # We have to keep track on the elements somewhere
|
||||
|
||||
GObject.add_emission_hook(RunnersDialog, "runner-installed", self.update)
|
||||
GObject.add_emission_hook(RunnersDialog, "runner-removed", self.update)
|
||||
|
@ -177,6 +178,10 @@ class SidebarListBox(Gtk.ListBox):
|
|||
if row.id is None:
|
||||
return True # 'All'
|
||||
return row.id in self.installed_runners
|
||||
elif row.type == "categories":
|
||||
if len(self.sidebar_categories) < 1:
|
||||
return False # Hide useless filter
|
||||
return True
|
||||
else:
|
||||
if len(self.active_platforms) <= 1:
|
||||
return False # Hide useless filter
|
||||
|
@ -192,8 +197,28 @@ class SidebarListBox(Gtk.ListBox):
|
|||
row.set_header(SidebarHeader("Runners"))
|
||||
elif before.type == "runner" and row.type == "platform":
|
||||
row.set_header(SidebarHeader("Platforms"))
|
||||
elif before.type == "platform" and row.type == "categories":
|
||||
row.set_header(SidebarHeader("Categories"))
|
||||
|
||||
def add_category_entries(self):
|
||||
pga.delete_categories_without_games()
|
||||
categories = pga.get_categories()
|
||||
for category in pga.get_categories():
|
||||
if category not in self.sidebar_categories.keys():
|
||||
temp = SidebarRow(category, "categories", category, None)
|
||||
self.sidebar_categories[category] = temp
|
||||
self.add(temp)
|
||||
removalbe_categories = []
|
||||
for sidebar_category in self.sidebar_categories.keys():
|
||||
if sidebar_category not in categories:
|
||||
self.remove(self.sidebar_categories[sidebar_category])
|
||||
removalbe_categories.append(sidebar_category)
|
||||
for rem_category in removalbe_categories:
|
||||
del self.sidebar_categories[rem_category]
|
||||
self.show_all()
|
||||
|
||||
def update(self, *args):
|
||||
self.installed_runners = [runner.name for runner in runners.get_installed()]
|
||||
self.active_platforms = pga.get_used_platforms()
|
||||
self.add_category_entries()
|
||||
self.invalidate_filter()
|
||||
|
|
115
lutris/pga.py
115
lutris/pga.py
|
@ -49,6 +49,18 @@ DATABASE = {
|
|||
"sources": [
|
||||
{"name": "id", "type": "INTEGER", "indexed": True},
|
||||
{"name": "uri", "type": "TEXT UNIQUE"},
|
||||
],
|
||||
"categories": [
|
||||
{"name": "id", "type": "INTEGER", "indexed": True},
|
||||
{"name": "category", "type": "TEXT"},
|
||||
{"name": "category", "type": "UNIQUE"},
|
||||
],
|
||||
"games2categories": [
|
||||
{"name": "games", "type": "INTEGER", "indexed": False},
|
||||
{"name": "categories", "type": "INTEGER", "indexed": False},
|
||||
{"name": "games", "type": "REFERENCE", "indexed": False, "referenced": "id"},
|
||||
{"name": "categories", "type": "REFERENCE", "indexed": False, "referenced": "id"},
|
||||
#{"name": "games, categories", "type": "UNIQUE"},
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -79,13 +91,19 @@ def get_schema(tablename):
|
|||
|
||||
|
||||
def field_to_string(
|
||||
name="", type="", indexed=False
|
||||
name="", type="", indexed=False, referenced=None
|
||||
): # pylint: disable=redefined-builtin
|
||||
"""Converts a python based table definition to it's SQL statement"""
|
||||
field_query = "%s %s" % (name, type)
|
||||
if indexed:
|
||||
field_query += " PRIMARY KEY"
|
||||
return field_query
|
||||
if type == "UNIQUE":
|
||||
return "UNIQUE (%s)" % name
|
||||
|
||||
if referenced is None:
|
||||
field_query = "%s %s" % (name, type)
|
||||
if indexed:
|
||||
field_query += " PRIMARY KEY"
|
||||
return field_query
|
||||
else:
|
||||
return "FOREIGN KEY (%s) REFERENCES %s(%s)" % (name, name, referenced)
|
||||
|
||||
|
||||
def create_table(name, schema):
|
||||
|
@ -415,3 +433,90 @@ def get_used_platforms_game_count():
|
|||
rows = cursor.execute(query)
|
||||
results = rows.fetchall()
|
||||
return {result[0]: result[1] for result in results if result[0]}
|
||||
|
||||
|
||||
def get_categories(select="*"):
|
||||
"""Get the list of every category in database."""
|
||||
query = "select " + select + " from categories"
|
||||
params = []
|
||||
|
||||
query += " ORDER BY category"
|
||||
|
||||
return_categories = []
|
||||
for category in sql.db_query(PGA_DB, query, tuple(params)):
|
||||
return_categories.append(category["category"])
|
||||
|
||||
return return_categories
|
||||
|
||||
def get_games_in_categories(category="*"):
|
||||
"""Get the ids of games in database."""
|
||||
query = "select games.id from games " \
|
||||
"JOIN games2categories ON games.id = games2categories.games " \
|
||||
"JOIN categories ON categories.id = games2categories.categories " \
|
||||
"WHERE categories.category = \"" + category + "\""
|
||||
params = []
|
||||
return_ids = []
|
||||
for category in sql.db_query(PGA_DB, query, tuple(params)):
|
||||
return_ids.append(category["id"])
|
||||
|
||||
return return_ids
|
||||
|
||||
def get_categories_in_game(game_id=-1):
|
||||
"""Get the categories of a game in database."""
|
||||
if game_id < 0:
|
||||
return None
|
||||
query = "select categories.category from categories " \
|
||||
"JOIN games2categories ON categories.id = games2categories.categories " \
|
||||
"JOIN games ON games.id = games2categories.games " \
|
||||
"WHERE games.id = \"" + str(game_id) + "\""
|
||||
params = []
|
||||
return_ids = []
|
||||
for category in sql.db_query(PGA_DB, query, tuple(params)):
|
||||
return_ids.append(category["category"])
|
||||
|
||||
return return_ids
|
||||
|
||||
def get_games2categories(select="*"):
|
||||
"""Get the m2m table for Games2Categories in database."""
|
||||
query = "select " + select + " from games2categories"
|
||||
params = []
|
||||
|
||||
return_categories = []
|
||||
for category in sql.db_query(PGA_DB, query, tuple(params)):
|
||||
return_categories.append({category["categories"]: category["games"]})
|
||||
print("m2m-List Games2Categories")
|
||||
print(return_categories)
|
||||
return return_categories
|
||||
|
||||
def add_category(category_name=None):
|
||||
"""Add a category to the PGA database."""
|
||||
return sql.db_insert(PGA_DB, "categories", {"category": category_name})
|
||||
|
||||
def add_game_to_category(game_id=-1, category=None):
|
||||
"""Add a m2m reference from game2category to the PGA database."""
|
||||
query = "insert into games2categories (games, categories) " \
|
||||
"select " + str(game_id) + " as games, categories.id as categories from categories " \
|
||||
"where categories.category = \"" + category + "\""
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
sql.cursor_execute(cursor, query)
|
||||
|
||||
def delete_category(category):
|
||||
"""Delete a category from the PGA."""
|
||||
sql.db_delete(PGA_DB, "categories", "category", category)
|
||||
|
||||
def delete_categories_without_games():
|
||||
"""Deletes category that has no entry in the m2m table."""
|
||||
query = "delete from categories where id not in ( " \
|
||||
"select categories.id from games2categories " \
|
||||
"where games2categories.categories = categories.id )"
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
sql.cursor_execute(cursor, query)
|
||||
|
||||
def delete_game_by_id_from_category(game_id, category):
|
||||
"""Delete a game to category entry from the m2m table."""
|
||||
query = "DELETE FROM games2categories WHERE categories IN " \
|
||||
"( SELECT games2categories.categories from categories WHERE games2categories.categories = categories.id " \
|
||||
"AND categories.category = \"" + category + "\" ) AND games2categories.games = " + str(game_id)
|
||||
with sql.db_cursor(PGA_DB) as cursor:
|
||||
sql.cursor_execute(cursor, query)
|
||||
|
||||
|
|
Loading…
Reference in a new issue