############################################################################### # sgbackup - The SaveGame Backup tool # # Copyright (C) 2024,2025 Christian Moser # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation, either version 3 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program. If not, see . # ############################################################################### from gi.repository import Gtk,GLib,Gio from gi.repository.GObject import GObject,Property,Signal,SignalFlags from ..i18n import gettext as _,gettext_noop as N_ from ..game import GameManager,Game,EpicGameData,EpicWindowsData from ..epic import Epic,EpicGameInfo,EpicIgnoredApp from ._gamedialog import GameSearchDialog from ..utility import PLATFORM_WINDOWS from ._gamedialog import GameDialog class EpicNoNewAppsDialog(Gtk.MessageDialog): def __init__(self,parent:Gtk.Window|None): Gtk.MessageDialog.__init__(self, transient_for=parent, buttons=Gtk.ButtonsType.OK, text=_("There were no new Epic-Games apps found!"), use_markup=False, modal=True) def do_response(self,response): self.hide() self.destroy() class EpicNoIgnoredAppsDialog(Gtk.MessageDialog): def __init__(self,parent:Gtk.Window|None): Gtk.MessageDialog.__init__(self, transient_for=parent, buttons=Gtk.ButtonsType.OK, text=_("There are no ignored Epic-Games apps!"), use_markup=False, modal=True) def do_response(self,response): self.hide() self.destroy() ### EpicLookupGamesDialog ##################################################### class EpicLookupGamesDialog(GameSearchDialog): def __init__(self,parent:Gtk.Window|None=None,info:EpicGameInfo|None=None): GameSearchDialog.__init__(self,parent,info.name if info else None) self.__gameinfo = info @Property def gameinfo(self)->EpicGameInfo|None: return self.__gameinfo @gameinfo.setter def gameinfo(self,info:EpicGameInfo|None): if info: self.search_name = info.name else: self.search_name = None self.__gameinfo = info def do_prepare_game(self,game:Game): game = super().do_prepare_game(game) if self.gameinfo: if game.epic: game.epic.catalog_item_id = self.gameinfo.catalog_item_id else: game.epic = EpicGameData(self.gameinfo.catalog_item_id) if PLATFORM_WINDOWS: if not game.epic.windows: game.epic.windows = EpicWindowsData("","",installdir=self.gameinfo.installdir) else: game.epic.windows.installdir = self.gameinfo.installdir else: if not game.epic: game.epic = EpicGameData() if PLATFORM_WINDOWS and not game.epic.windows: game.epic.windows = EpicWindowsData("","") return game ### EpicNewGamesDialog ######################################################## class EpicNewAppsDialogSorter(Gtk.Sorter): def do_compare(self,item1:EpicGameInfo,item2:EpicGameInfo): name1 = item1.name.lower() name2 = item2.name.lower() if name1 > name2: return Gtk.Ordering.LARGER elif name1 < name2: return Gtk.Ordering.SMALLER return Gtk.Ordering.EQUAL class EpicNewAppsDialog(Gtk.Dialog): def __init__(self,parent:Gtk.Window|None=None): Gtk.Dialog.__init__(self, transient_for=parent, title=_("SGBackup: Manage new Epic-Games apps"), modal=False) self.set_default_size(800,600) scrolled = Gtk.ScrolledWindow() epic = Epic() self.__liststore = Gio.ListStore.new(EpicGameInfo) for info in epic.find_new_apps(): self.__liststore.append(info) sort_model = Gtk.SortListModel(model=self.__liststore, sorter=EpicNewAppsDialogSorter()) selection = Gtk.SingleSelection(model=sort_model, autoselect=False, can_unselect=True) factory = Gtk.SignalListItemFactory() factory.connect('setup',self._on_listview_setup) factory.connect('bind',self._on_listview_bind) self.__listview = Gtk.ListView(model=selection,factory=factory,hexpand=True,vexpand=True) scrolled.set_child(self.__listview) self.get_content_area().append(scrolled) self.__close_button = self.add_button(_("Close"),Gtk.ResponseType.OK) def _on_listview_setup(self,factory,item): child = Gtk.Grid(column_spacing=4,row_spacing=2) child.name_label = Gtk.Label(xalign=0.0,hexpand=True) child.attach(child.name_label,1,0,1,1) label = Gtk.Label(label=_("CatalogItemId:"), use_markup=False, xalign=0.0) child.catalogitemid_label = Gtk.Label(xalign=0.0, use_markup=False, hexpand=True) child.attach(label,0,1,1,1) child.attach(child.catalogitemid_label,1,1,1,1) label = Gtk.Label(label=_("Installation Directory:"), use_markup=False, xalign=0.0) child.installdir_label = Gtk.Label(xalign=0.0, use_markup=False, hexpand=True) child.attach(label,0,2,1,1) child.attach(child.installdir_label,1,2,1,1) actiongrid = Gtk.Grid(row_spacing=2,column_spacing=2) icon = Gtk.Image.new_from_icon_name('list-add-symbolic') icon.set_pixel_size(16) child.new_button = Gtk.Button() child.new_button.set_child(icon) child.new_button.set_tooltip_text(_("Add Epic-Games-app as a new game.")) actiongrid.attach(child.new_button,0,0,1,1) icon = Gtk.Image.new_from_icon_name('edit-delete-symbolic') icon.set_pixel_size(16) child.ignore_button = Gtk.Button() child.ignore_button.set_child(icon) child.ignore_button.set_tooltip_text(_("Add Epic-Games-app to the ignore list.")) actiongrid.attach(child.ignore_button,1,0,1,1) icon = Gtk.Image.new_from_icon_name('edit-find-symbolic') icon.set_pixel_size(16) child.lookup_button = Gtk.Button() child.lookup_button.set_child(icon) child.lookup_button.set_tooltip_text(_("Lookup Epic-Games-app for already registered game.")) actiongrid.attach(child.lookup_button,0,1,1,1) icon = Gtk.Image.new_from_icon_name('folder-download-symbolic') icon.set_pixel_size(16) child.online_button = Gtk.Button() child.online_button.set_child(icon) child.online_button.set_tooltip_text(_("Lookup Epic-Games-app online.")) actiongrid.attach(child.online_button,1,1,1,1) child.attach(actiongrid,2,0,1,3) item.set_child(child) def _on_listview_bind(self,factory,item): child = item.get_child() data = item.get_item() child.name_label.set_markup("{}".format( GLib.markup_escape_text(data.name))) child.catalogitemid_label.set_text(data.catalog_item_id) child.installdir_label.set_text(data.installdir) if hasattr(child.new_button,'_signal_clicked_connector'): child.new_button.disconnect(child.new_button._signal_clicked_connector) child.new_button._signal_clicked_connector = child.new_button.connect('clicked', self._on_listview_new_button_clicked, data) if hasattr(child.ignore_button,'_signal_clicked_connector'): child.ignore_button.disconnect(child.ignore_button._signal_clicked_connector) child.ignore_button._signal_clicked_connector = child.ignore_button.connect('clicked', self._on_listview_ignore_button_clicked, data) if hasattr(child.lookup_button,'_signal_clicked_connector'): child.lookup_button.disconnect(child.lookup_button._signal_clicked_connector) child.lookup_button._signal_clicked_connector = child.lookup_button.connect('clicked', self._on_listview_lookup_button_clicked, data) if hasattr(child.online_button,'_signal_clicked_connector'): child.online_button.disconnect(child.online_button._signal_clicked_connector) child.online_button._signal_clicked_connector = child.online_button.connect('clicked', self._on_listview_online_button_clicked, data) child.online_button.set_sensitive(False) def _on_game_dialog_response(self,dialog,response,info:EpicGameInfo): if response == Gtk.ResponseType.APPLY: for i in range(self.__liststore.get_n_items()): item = self.__liststore.get_item(i) if item.catalog_item_id == info.catalog_item_id: self.__liststore.remove(i) break def _on_listview_new_button_clicked(self,button:Gtk.Button,info:EpicGameInfo): game = Game("",info.name,"") if PLATFORM_WINDOWS: windows = EpicWindowsData("","",installdir=info.installdir) else: windows = None game.epic = EpicGameData(catalog_item_id=info.catalog_item_id, windows=windows) dialog = GameDialog(parent=self,game=game) dialog.connect_after('response',self._on_dialog_response,info) dialog.present() def _on_ignore_dialog_response(self,dialog,response,info:EpicGameInfo): if response == Gtk.ResponseType.YES: epic = Epic() epic.add_ignored_app(EpicIgnoredApp(info.catalog_item_id, info.name, dialog.reason_entry.get_text())) for i in range(self.__liststore.get_n_items()): item = self.__liststore.get_item(i) if item.catalog_item_id == info.catalog_item_id: self.__liststore.remove(i) break dialog.hide() dialog.destroy() def _on_listview_ignore_button_clicked(self,button:Gtk.Button,info:EpicGameInfo): dialog = Gtk.MessageDialog(transient_for=self, text=_("Do you really won to add the game {game} to the ignore list?").format( game=GLib.markup_escape_text(info.name)), use_markup=True, secondary_text=_("Please enter a reason below."), secondary_use_markup=True, buttons=Gtk.ButtonsType.YES_NO, modal=False) dialog.reason_entry = Gtk.Entry() dialog.reason_entry.set_hexpand(True) dialog.get_content_area().append(dialog.reason_entry) dialog.connect('response',self._on_ignore_dialog_response,info) dialog.present() def _on_listview_lookup_button_clicked(self,button:Gtk.Button,info:EpicGameInfo): dialog = EpicLookupGamesDialog(parent=self,info=info) dialog.present() def _on_listview_online_button_clicked(self,button:Gtk.Button,info:EpicGameInfo): pass def do_response(self,response): self.hide() self.destroy() def refresh(self): epic = Epic() self.__liststore.remove_all() for gameinfo in epic.find_new_apps(): self.__liststore.append(gameinfo) #### EpicIgnoreAppsDialog ########################################################## class EpicIgnoredAppsDialogSorter(Gtk.Sorter): def do_compare(self,item1:EpicIgnoredApp,item2:EpicIgnoredApp): name1=item1.name.lower() name2=item2.name.lower() if name1 < name2: return Gtk.Ordering.SMALLER elif name1 > name2: return Gtk.Ordering.LARGER return Gtk.Ordering.EQUAL class EpicIgnoredAppsDialog(Gtk.Dialog): def __init__(self,parent:Gtk.Window|None=None): Gtk.Dialog.__init__(self,transient_for=parent) self.set_default_size(640,480) epic = Epic() self.__liststore = Gio.ListStore.new(EpicIgnoredApp) for ignored in epic.ignored_apps.values(): self.__liststore.append(ignored) sort_model = Gtk.SortListModel(model=self.__liststore,sorter=EpicIgnoredAppsDialogSorter()) selection = Gtk.SingleSelection(model=sort_model) factory = Gtk.SignalListItemFactory() factory.connect('setup',self._on_listview_item_setup) factory.connect('bind',self._on_listview_item_bind) self.__listview = Gtk.ListView(model=selection, factory=factory, hexpand=True, vexpand=True) scrolled = Gtk.ScrolledWindow(hexpand=True, vexpand=True) scrolled.set_child(self.__listview) self.get_content_area().append(scrolled) self.add_button("Close",Gtk.ResponseType.OK) def _on_listview_item_setup(self,factory,item): child = Gtk.Grid(hexpand=True,column_spacing=4,row_spacing=2) child.name_label = Gtk.Label(use_markup=True,xalign=0.0,hexpand=True) child.attach(child.name_label,1,0,1,1) label = Gtk.Label(label=_("CatalogItemId:"),use_markup=False,xalign=0.0,hexpand=False) child.catalogitemid_label = Gtk.Label(use_markup=False,xalign=0.0,hexpand=True) child.attach(label,0,1,1,1) child.attach(child.catalogitemid_label,1,1,1,1) label = Gtk.Label(label=_("Reason:"),use_markup=False,xalign=0.0,hexpand=False) child.reason_label = Gtk.Label(use_markup=True,xalign=0.0,hexpand=True) child.attach(label,0,2,1,1) child.attach(child.reason_label,1,2,1,1) action_grid = Gtk.Grid(column_spacing=2,row_spacing=2) icon = Gtk.Image.new_from_icon_name("document-new-symbolic") icon.set_pixel_size(16) child.new_game_button = Gtk.Button() child.new_game_button.set_child(icon) child.new_game_button.set_tooltip_text("Add ignored SteamApp as a new game.") action_grid.attach(child.new_game_button,0,0,1,1) icon = Gtk.Image.new_from_icon_name("list-remove-symbolic") icon.set_pixel_size(16) child.remove_button = Gtk.Button() child.remove_button.set_child(icon) child.remove_button.set_tooltip_text("Remove ignored SteamApp from the list.") action_grid.attach(child.remove_button,0,1,1,1) child.attach(action_grid,2,0,1,3) item.set_child(child) def _on_listview_item_bind(self,factory,item): child = item.get_child() data = item.get_item() child.name_label.set_markup("{}".format( GLib.markup_escape_text(data.name))) child.catalogitemid_label.set_text(data.catalog_item_id) child.reason_label.set_markup("{}".format(GLib.markup_escape_text(data.reason))) if hasattr(child.new_game_button,"_signal_clicked_connector"): child.new_game_button.disconnect(child.new_game_button._signal_clicked_connector) child.new_game_button._signal_clicked_connector = child.new_game_button.connect('clicked', self._on_listitem_new_game_button_clicked, data) if hasattr(child.remove_button,'_signal_clicked_connector'): child.remove_button.disconnect(child.remove_button._signal_clicked_connector) child.remove_button._signal_clicked_connector = child.remove_button.connect('clicked', self._on_listitem_remove_button_clicked, data) def __remove_ignored_app(self,data:EpicIgnoredApp): epic = Epic() epic.remove_ignored_app(data) for i in range(self.__liststore.get_n_items()): item = self.__liststore.get_item(i) if item.catalog_item_id == data.catalog_item_id: self.__liststore.remove(i) break def _on_listitem_new_game_button_clicked(self,button,data:EpicIgnoredApp): def on_dialog_response(self,dialog,response,data): if response == Gtk.ResponseType.APPLY: self.__remove_item(data) game = Game("",data.name,"") epic = Epic() game_info = epic.find_apps()[data.catalog_item_id] if PLATFORM_WINDOWS: windows = EpicWindowsData("","",installdir=game_info.installdir) else: windows = None game.epic = EpicGameData(data.catalog_item_id,windows) parent = self.get_transient_for() dialog=GameDialog(parent=parent,game=game) dialog.connect_after('response',on_dialog_response,data) self.hide() dialog.present() def _on_listitem_remove_button_clicked(self,button,data:EpicIgnoredApp): epic = Epic() self.__remove_ignored_app(data) def do_response(self,response): self.hide() self.destroy()