2025.03.11 23:40:14 (desktop)

This commit is contained in:
Christian Moser 2025-03-11 23:40:14 +01:00
parent 5a36b4f358
commit f3ef3df537
Failed to extract signature
8 changed files with 726 additions and 92 deletions

View File

@ -7,7 +7,9 @@ sgbackup/commands/help.py
sgbackup/commands/__init__.py
sgbackup/curses/__init__.py
sgbackup/curses/__main__.py
sgbackup/epic.py
sgbackup/game.py
sgbackup/gog.py
sgbackup/gui/_app.py
sgbackup/gui/_backupdialog.py
sgbackup/gui/_dialogs.py

328
PO/de.po
View File

@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-05 10:15+0100\n"
"PO-Revision-Date: 2025-03-05 10:19+0100\n"
"POT-Creation-Date: 2025-03-11 02:01+0100\n"
"PO-Revision-Date: 2025-03-11 15:31+0100\n"
"Last-Translator: Christian Moser <christian@cmoser.eu>\n"
"Language-Team: \n"
"Language: de\n"
@ -18,54 +18,59 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.5\n"
#: sgbackup/gui/_app.py:224
#: sgbackup/epic.py:162
#, python-brace-format
msgid "Unable to load Epic manifest \"{manifest}\"! ({error})"
msgstr "Kann Epic Manifest \"{manifest}\" nicht laden! ({error})"
#: sgbackup/gui/_app.py:228
msgid "Add a new game."
msgstr "Neues Spiel hinzufügen."
#: sgbackup/gui/_app.py:232
#: sgbackup/gui/_app.py:236
msgid "Manage new Steam-Apps."
msgstr "Neue Steam-Apps verwalten."
#: sgbackup/gui/_app.py:242
#: sgbackup/gui/_app.py:246
msgid "Backup all <i>active</i> and <i>live</i> Games."
msgstr "Alle <i>aktiven</i> und <i>live</i> Spiele sichern."
#: sgbackup/gui/_app.py:276
#: sgbackup/gui/_app.py:280
msgid "Key"
msgstr "Schlüssel"
#: sgbackup/gui/_app.py:282
#: sgbackup/gui/_app.py:286
msgid "Name"
msgstr "Name"
#: sgbackup/gui/_app.py:290
#: sgbackup/gui/_app.py:294
msgid "Active"
msgstr "Aktiv"
#: sgbackup/gui/_app.py:296 sgbackup/gui/_app.py:864
#: sgbackup/gui/_app.py:300 sgbackup/gui/_app.py:868
msgid "Live"
msgstr "Live"
#: sgbackup/gui/_app.py:455
#: sgbackup/gui/_app.py:459
msgid "No icon-name for sgtype {}"
msgstr "Kein Icon-Name für sgtype {}"
#: sgbackup/gui/_app.py:543
#: sgbackup/gui/_app.py:547
#, python-brace-format
msgid "Do you want to create a new savegame for <i>{game}</i>?"
msgstr "Willst du ein neues SaveGame für <i>{game}</i> erstellen?"
#: sgbackup/gui/_app.py:545
#: sgbackup/gui/_app.py:549
msgid "The new savegame is added to the finsihed savegames for the game."
msgstr ""
"Das Neue SaveGame wird zu den beendeten Speicherständen für dieses Spiel "
"hinzugefügt."
#: sgbackup/gui/_app.py:557
#: sgbackup/gui/_app.py:561
msgid "Backup the SaveGames for this game."
msgstr "Sichere das SaveGame für dieses Spiel."
#: sgbackup/gui/_app.py:569
#: sgbackup/gui/_app.py:573
msgid ""
"Remove this game.\n"
"<span weight='ultrabold'>This also deletes the game configuration file!!!</"
@ -75,125 +80,354 @@ msgstr ""
"<span weight='ultrabold'>Dies wird auch die Konfigurationsdatei des Spiels "
"löschen!!!</span>"
#: sgbackup/gui/_app.py:663
#: sgbackup/gui/_app.py:667
#, python-brace-format
msgid ""
"Do you really want to remove the game <span weight='bold'>{game}</span>?"
msgstr "Wills du wirklich das Spiel <span weight='bold'>{game}</span> löschen?"
#: sgbackup/gui/_app.py:666
#: sgbackup/gui/_app.py:670
msgid "Removing games cannot be undone!!!"
msgstr "Das Löschen von Spielen kann nicht Rückgängig gemacht werden!!!"
#: sgbackup/gui/_app.py:859
#: sgbackup/gui/_app.py:863
msgid "OS"
msgstr "OS"
#: sgbackup/gui/_app.py:869
#: sgbackup/gui/_app.py:873
msgid "Savegame name"
msgstr "Speicherstandname"
#: sgbackup/gui/_app.py:875
#: sgbackup/gui/_app.py:879
msgid "Timestamp"
msgstr "Zeitstempel"
#: sgbackup/gui/_app.py:880
#: sgbackup/gui/_app.py:884
msgid "Size"
msgstr "Größe"
#: sgbackup/gui/_app.py:1066
#: sgbackup/gui/_app.py:1072
msgid "%m.%d.%Y %H:%M:%S"
msgstr "%d.%m.%Y %H:%M:%S"
#: sgbackup/gui/_app.py:1099
#: sgbackup/gui/_app.py:1105
msgid "Restore the SaveGameBackup."
msgstr "Stelle das Speicherstandbackup wieder her."
#: sgbackup/gui/_app.py:1104
#: sgbackup/gui/_app.py:1110
msgid "Convert to another SaveGameBackup."
msgstr "Konvertiere zu anderen Speicherstandbackup"
msgstr "Konvertiere zu anderen Speicherstandbackup."
#: sgbackup/gui/_app.py:1113
#: sgbackup/gui/_app.py:1119
msgid "Delete this SaveGameBackup."
msgstr "Lösche dieses Speicherstandbackup."
#: sgbackup/gui/_app.py:1170
#: sgbackup/gui/_app.py:1176
msgid "SGBackup"
msgstr "SGBackup"
#: sgbackup/gui/_app.py:1232 sgbackup/gui/_app.py:1300
#: sgbackup/gui/_app.py:1238 sgbackup/gui/_app.py:1307
#, python-brace-format
msgid ""
"{games} Games -- {active} Games active -- {live} Games live -- {finished} "
"Games finished"
msgstr ""
"{games} Spile -- {active} Spiele aktiv -- {live} Spiele live -- {finished} "
"{games} Spiele -- {active} Spiele aktiv -- {live} Spiele live -- {finished} "
"Spiele beendet"
#: sgbackup/gui/appmenu.ui:6
msgid "_Game"
msgstr "_Spiel"
#: sgbackup/gui/_dialogs.py:38
msgid "There are no games to backup!"
msgstr "Es gibt keine Spiele zu sichern!"
#: sgbackup/gui/appmenu.ui:9
#: sgbackup/gui/_dialogs.py:53
msgid "There were no games to backup found!"
msgstr "Es wurden keine Spiele zum Sichern gefunden!"
#: sgbackup/gui/_gamedialog.py:248
msgid "Name:"
msgstr "Name:"
#: sgbackup/gui/_gamedialog.py:256
msgid "Value:"
msgstr "Wert:"
#: sgbackup/gui/_gamedialog.py:332
msgid "Filename"
msgstr "Dateiname"
#: sgbackup/gui/_gamedialog.py:333
msgid "Glob"
msgstr "Glob"
#: sgbackup/gui/_gamedialog.py:334
msgid "Regular expression"
msgstr "Regulärer Ausdruck"
#: sgbackup/gui/_gamedialog.py:403 sgbackup/gui/_gamedialog.py:531
#: sgbackup/gui/_gamedialog.py:688 sgbackup/gui/_gamedialog.py:767
msgid "Windows"
msgstr "Windows"
#: sgbackup/gui/_gamedialog.py:404
msgid "Steam Windows"
msgstr "Steam Windows"
#: sgbackup/gui/_gamedialog.py:405
msgid "Epic Windows"
msgstr "Epic Windows"
#. (SavegameType.GOG_WINDOWS,"GoG Windows","object-select-symbolic"),
#: sgbackup/gui/_gamedialog.py:408
msgid "Linux native"
msgstr "Linux"
#: sgbackup/gui/_gamedialog.py:409
msgid "Steam Linux"
msgstr "Steam Linux"
#. (SavegameType.EPIC_LINUX,_("Epic Linux"),"epic-games-svgrepo-com-symbolic"),
#. (SavegameType.GOG_LINUX,"GoG Linux","object-select-symbolic"),
#: sgbackup/gui/_gamedialog.py:413 sgbackup/gui/_gamedialog.py:618
msgid "MacOS"
msgstr "Mac OS"
#: sgbackup/gui/_gamedialog.py:414
msgid "Steam MacOS"
msgstr "Steam Mac OS"
#: sgbackup/gui/_gamedialog.py:422
msgid "Is active?"
msgstr "Ist Aktiv?"
#: sgbackup/gui/_gamedialog.py:429
msgid "Is live?"
msgstr "Ist live?"
#: sgbackup/gui/_gamedialog.py:437
msgid "ID:"
msgstr "ID:"
#: sgbackup/gui/_gamedialog.py:443
msgid "Key:"
msgstr "Schlüssel:"
#: sgbackup/gui/_gamedialog.py:458
msgid "Savegame Type:"
msgstr "Speicherstandtyp:"
#: sgbackup/gui/_gamedialog.py:462
msgid "Game name:"
msgstr "Spielname:"
#: sgbackup/gui/_gamedialog.py:469
msgid "Savegame name:"
msgstr "Speicherstandname:"
#: sgbackup/gui/_gamedialog.py:482
msgid "Game"
msgstr "Spiel"
#: sgbackup/gui/_gamedialog.py:494 sgbackup/gui/_gamedialog.py:544
#: sgbackup/gui/_gamedialog.py:587 sgbackup/gui/_gamedialog.py:638
#: sgbackup/gui/_gamedialog.py:718
msgid "Root directory:"
msgstr "Wurzelverzeichnis:"
#: sgbackup/gui/_gamedialog.py:501 sgbackup/gui/_gamedialog.py:551
#: sgbackup/gui/_gamedialog.py:594 sgbackup/gui/_gamedialog.py:645
#: sgbackup/gui/_gamedialog.py:725
msgid "Backup directory:"
msgstr "Speicherverzeichnis:"
#: sgbackup/gui/_gamedialog.py:515
msgid "Match Files"
msgstr "Dateiübereinstimmung"
#: sgbackup/gui/_gamedialog.py:518
msgid "Ignore Files"
msgstr "Ignoriere Dateien"
#: sgbackup/gui/_gamedialog.py:521
msgid "Lookup Registry keys"
msgstr "Registrierungsschlüssel nachschlagen"
#: sgbackup/gui/_gamedialog.py:524
msgid "Installations directory Registry keys"
msgstr "Registrierungsschlüssel für Installationsverzeichnis"
#: sgbackup/gui/_gamedialog.py:558
msgid "Executable:"
msgstr "Ausführbare Datei:"
#: sgbackup/gui/_gamedialog.py:575 sgbackup/gui/_gamedialog.py:691
msgid "Linux"
msgstr "Linux"
#: sgbackup/gui/_gamedialog.py:601
msgid "Executable"
msgstr "Ausführbare Datei"
#: sgbackup/gui/_gamedialog.py:652 sgbackup/gui/_gamedialog.py:732
msgid "Installation directory:"
msgstr "Installationsverzeichnis:"
#: sgbackup/gui/_gamedialog.py:694
msgid "Mac OS"
msgstr "Mac OS"
#: sgbackup/gui/_gamedialog.py:756
msgid "AppName:"
msgstr "AppName:"
#: sgbackup/gui/_steam.py:60
msgid "Steam libraries"
msgstr "Steam Bibliotheken"
#: sgbackup/gui/_steam.py:82
msgid "Apply"
msgstr "Anwenden"
#: sgbackup/gui/_steam.py:83
msgid "Cancel"
msgstr "Abbrechen"
#: sgbackup/gui/_steam.py:101
msgid "SGBackup: Select Steam library path"
msgstr "SGBackup: Wähle Steam Bibliothekspfad"
#: sgbackup/gui/_steam.py:130
msgid "SGBackup: Change Steam library path"
msgstr "SGBackup: Ändere Steam Bibliothekspfad"
#: sgbackup/gui/_steam.py:216
msgid "SGBackup: New Steam apps"
msgstr "Neue Steam-Apps"
#: sgbackup/gui/_steam.py:286
msgid "Add SteamApp as new Game."
msgstr "Füge Steam-App als neues Spiel hinzu."
#: sgbackup/gui/_steam.py:293
msgid "Add SteamApp to ignore list."
msgstr "Füge Steam-App zur Ignorieren-Liste hinzu."
#: sgbackup/gui/_steam.py:300
msgid "Lookup SteamApp for already registered game."
msgstr "Schlage Steam-App unter den bereits registrierten Spielen nach."
#: sgbackup/gui/_steam.py:307
msgid "Lookup SteamApp online."
msgstr "Suche Steam-App online."
#: sgbackup/gui/_steam.py:366
msgid "SGBackup: Add Steam Game"
msgstr "SGBackup: Füge Steam-Spiel hinzu"
#: sgbackup/gui/_steam.py:386
#, python-brace-format
msgid ""
"Do you want to put <span weight=\"bold\">\"{steamapp}\"</span> on the ignore "
"list?"
msgstr ""
"Wills du wirklich das Spiel <span weight='bold'>{steamapp}</span> auf die "
"Ignorieren-Liste setzen?"
#: sgbackup/gui/_steam.py:390
msgid "Please enter the reason for ignoring this app."
msgstr "Bitte gib eine Begründing für das Ignorieren dieser App ein."
#: sgbackup/gui/_steam.py:413
msgid "There were no new Steam-Apps found!"
msgstr "Es wurden keine Steam-Apps gefunden!"
#: sgbackup/gui/_steam.py:426
msgid "There are no Steam-Apps that are ignored!"
msgstr "Es gibt keine Steam-Apps die ignoriert werden!"
#: sgbackup/gui/_steam.py:449
msgid "SGBackup: manage ignored SteamApps"
msgstr "SGBackup: Verwalte ignorierte Steam-Apps"
#: sgbackup/gui/_steam.py:480
msgid "Close"
msgstr "Schießen"
#: sgbackup/gui/_steam.py:489
msgid "Reason:"
msgstr "Begründung:"
#: sgbackup/gui/appmenu.ui:6
msgid "_Add Game"
msgstr "Spiel _hinzufügen"
#: sgbackup/gui/appmenu.ui:14
#: sgbackup/gui/appmenu.ui:12
msgid "_Backup active & live games"
msgstr "Aktive- & Live-Spiele sichern"
#: sgbackup/gui/appmenu.ui:16
msgid "Backup _all games"
msgstr "_Alle Spiele sichern"
#: sgbackup/gui/appmenu.ui:22
msgid "_Steam"
msgstr "_Steam"
#: sgbackup/gui/appmenu.ui:17
#: sgbackup/gui/appmenu.ui:25
msgid "New Steam Apps"
msgstr "Neue Steam-App"
#: sgbackup/gui/appmenu.ui:21
#: sgbackup/gui/appmenu.ui:29
msgid "Manage Steam Libraries"
msgstr "Steam Bibliotheken verwalten"
#: sgbackup/gui/appmenu.ui:27
#: sgbackup/gui/appmenu.ui:33
msgid "Manage Ignored Apps"
msgstr "Ignorierte Steam-Apps verwalten"
#: sgbackup/gui/appmenu.ui:39
msgid "_Epic"
msgstr "_Epic"
#: sgbackup/gui/appmenu.ui:30
#: sgbackup/gui/appmenu.ui:42
msgid "_GoG"
msgstr "_GoG"
#: sgbackup/gui/appmenu.ui:35
#: sgbackup/gui/appmenu.ui:47
msgid "_Settings"
msgstr "_Einstellungen"
#: sgbackup/gui/appmenu.ui:42
#: sgbackup/gui/appmenu.ui:54
msgid "_Help"
msgstr "_Hilfe"
#: sgbackup/gui/appmenu.ui:47
#: sgbackup/gui/appmenu.ui:59
msgid "_About SGBackup"
msgstr "_Über SGBackup"
#: sgbackup/gui/appmenu.ui:56
#: sgbackup/gui/appmenu.ui:68
msgid "_Quit"
msgstr "_Beenden"
#: sgbackup/gui/appmenu.ui:65
#: sgbackup/gui/appmenu.ui:77
msgid "Convert to _Windows"
msgstr "Nach _Windows konvertieren"
#: sgbackup/gui/appmenu.ui:69
#: sgbackup/gui/appmenu.ui:81
msgid "Convert to _Linux"
msgstr "Nach _Linux konvertieren"
#: sgbackup/gui/appmenu.ui:73
#: sgbackup/gui/appmenu.ui:85
msgid "Convert to _Mac OS"
msgstr "Nach _MacOS konvertieren"
#: sgbackup/gui/appmenu.ui:77
#: sgbackup/gui/appmenu.ui:89
msgid "Convert to _Steam Windows"
msgstr "Nach _Steam-Windows konvertieren"
#: sgbackup/gui/appmenu.ui:81
#: sgbackup/gui/appmenu.ui:93
msgid "Convert to Steam Linux"
msgstr "Nach _Steam-Linux konvertieren"
#: sgbackup/gui/appmenu.ui:85
#: sgbackup/gui/appmenu.ui:97
msgid "Convert to Steam Mac OS"
msgstr "Nach _Steam-MacOS konvertieren"

View File

@ -159,7 +159,7 @@ class Epic(GObject):
with open(filename,'r',encoding="utf-8") as ifile:
data = json.loads(ifile.read())
except Exception as ex:
self._logger.error(_("Unable ot load Epic manifest \"{manifest}\"! ({error})").format(
self._logger.error(_("Unable to load Epic manifest \"{manifest}\"! ({error})").format(
manifest=filename,
error=str(ex)
))

View File

@ -152,7 +152,7 @@ VALID_SAVEGAME_TYPES = [
SavegameType.STEAM_MACOS,
SavegameType.STEAM_WINDOWS,
#SavegameType.EPIC_LINUX,
#SavegameType.EPIC_WINDOWS,
SavegameType.EPIC_WINDOWS,
#SavegameType.GOG_LINUX,
#SavegameType.GOG_WINDOWS,
]
@ -160,8 +160,8 @@ VALID_SAVEGAME_TYPES = [
SAVEGAME_TYPE_ICONS = {
SavegameType.UNSET : None,
SavegameType.WINDOWS: 'windows-svgrepo-com-symbolic',
SavegameType.LINUX: 'linux-svgrepo-com-symbolic.svg',
SavegameType.MACOS: 'apple-svgrepo-com-symbolic.svg',
SavegameType.LINUX: 'linux-svgrepo-com-symbolic',
SavegameType.MACOS: 'apple-svgrepo-com-symbolic',
SavegameType.STEAM_LINUX: 'steam-svgrepo-com-symbolic',
SavegameType.STEAM_MACOS: 'steam-svgrepo-com-symbolic',
SavegameType.STEAM_WINDOWS: 'steam-svgrepo-com-symbolic',
@ -171,6 +171,23 @@ SAVEGAME_TYPE_ICONS = {
SavegameType.GOG_WINDOWS: 'gog-com-svgrepo-com-symbolic',
}
class GameProvider(StrEnum):
WINDOWS = "windows"
LINUX = "linux"
MACOS = "macos"
STEAM = "steam"
EPIC_GAMES = "epic-games"
GOG = "gog"
GAME_PROVIDER_ICONS = {
GameProvider.WINDOWS: 'windows-svgrepo-com-symbolic',
GameProvider.LINUX: 'liux-svgrepo-com-symbolic',
GameProvider.MACOS: 'apple-svgrepo-com-symbolic',
GameProvider.STEAM: 'steam-svgrepo-com-symbolic',
GameProvider.EPIC_GAMES: 'epic-games-svgrepo-com-symbolic',
GameProvider.GOG: 'gog-com-svgrepo-com-symbolic',
}
class GameFileType(StrEnum):
"""
GameFileType The file matcher type for `GameFileMatcher`.
@ -1784,9 +1801,7 @@ class GameManager(GObject):
self.__games = {}
self.__steam_games = {}
self.__steam_linux_games = {}
self.__steam_windows_games = {}
self.__steam_macos_games = {}
self.__epic_games = {}
self.load()
@ -1798,18 +1813,6 @@ class GameManager(GObject):
def steam_games(self)->dict[int:Game]:
return self.__steam_games
@Property(type=object)
def steam_windows_games(self)->dict[int:Game]:
return self.__steam_windows_games
@Property(type=object)
def steam_linux_games(self)->dict[int:Game]:
return self.__steam_linux_games
@Property(type=object)
def steam_macos_games(self)->dict[int:Game]:
return self.__steam_macos_games
def load(self):
if self.__games:
self.__games = {}
@ -1838,13 +1841,30 @@ class GameManager(GObject):
def add_game(self,game:Game):
self.__games[game.key] = game
if (game.steam_macos):
self.__steam_games[game.steam_macos.appid] = game
self.__steam_macos_games[game.steam_macos.appid] = game
if (game.steam_linux):
self.__steam_games[game.steam_linux.appid] = game
self.__steam_linux_games[game.steam_linux.appid] = game
if (game.steam_windows):
self.__steam_games[game.steam_windows.appid] = game
self.__steam_windows_games[game.steam_windows.appid] = game
if game.steam:
self.__steam_games[game.steam.appid] = game
if game.epic:
self.__epic_games[game.epic.appname] = game
def remove_game(self,game:Game|str):
if isinstance(game,str):
if key not in self.__games:
return
key = game
game = self.__games[key]
elif isinstance(game,Game):
if game.key not in self.__games:
return
key = game.key
for appid,steam_game in list(self.__steam_games.items()):
if steam_game.key == game.key:
del self.__steam_games[appid]
for appname,epic_game in list(self.__epic_games.items()):
if epic_game.key == game.key:
del self.__epic_games[appname]
del self.__games[key]

View File

@ -31,7 +31,14 @@ from pathlib import Path
from ..settings import settings
from ._settingsdialog import SettingsDialog
from ._gamedialog import GameDialog
from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS,SavegameType
from ..game import (
Game,
GameManager,
SAVEGAME_TYPE_ICONS,
SavegameType,
GAME_PROVIDER_ICONS,
GameProvider
)
from ._steam import (
SteamLibrariesDialog,
NewSteamAppsDialog,
@ -229,7 +236,7 @@ class GameView(Gtk.Box):
add_game_button.connect('clicked',self._on_add_game_button_clicked)
self.actionbar.pack_start(add_game_button)
icon = Gtk.Image.new_from_icon_name('steam-svgrepo-com-symbolic')
icon = Gtk.Image.new_from_icon_name(GAME_PROVIDER_ICONS[GameProvider.STEAM])
icon.set_pixel_size(16)
new_steam_games_button = Gtk.Button()
new_steam_games_button.set_child(icon)
@ -237,6 +244,14 @@ class GameView(Gtk.Box):
new_steam_games_button.connect('clicked',self._on_new_steam_games_button_clicked)
self.actionbar.pack_start(new_steam_games_button)
icon = Gtk.Image.new_from_icon_name(GAME_PROVIDER_ICONS[GameProvider.EPIC_GAMES])
icon.set_pixel_size(16)
new_epic_games_button = Gtk.Button()
new_epic_games_button.set_child(icon)
new_epic_games_button.set_tooltip_text(_("Manage new Epic-Games apps"))
new_epic_games_button.connect('clicked',self._on_new_epic_games_button_clicked)
self.actionbar.pack_start(new_epic_games_button)
self.actionbar.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
icon = Gtk.Image.new_from_icon_name('document-save-symbolic')
@ -395,6 +410,10 @@ class GameView(Gtk.Box):
dialog = SteamNoNewAppsDialog(parent=self.get_root())
dialog.present()
def _on_new_epic_games_button_clicked(self,button):
# TODO
pass
def _on_backup_active_live_button_clicked(self,button):
backup_games = []
for i in range(self._liststore.get_n_items()):
@ -1279,10 +1298,11 @@ class AppWindow(Gtk.ApplicationWindow):
def statusbar(self):
return self.__statusbar
def refresh(self):
def refresh(self,reload_game_manager:bool=False):
"""
refresh Refresh the views of this window.
"""
if reload_game_manager:
GameManager.get_global().load()
self.gameview.refresh()
#self.backupview.refresh()
@ -1510,7 +1530,7 @@ class Application(Gtk.Application):
dialog.connect_after('response',on_dialog_response)
dialog.present()
else:
dialog = NoNewSteamAppsDialog(self.appwindow)
dialog = SteamNoNewAppsDialog(self.appwindow)
dialog.present()

View File

@ -17,8 +17,13 @@
###############################################################################
from gi.repository import Gtk,GLib,Gio
from gi.repository.GObject import GObject,Property
from ..version import VERSION
from ..i18n import gettext as _
class AboutDialog(Gtk.AboutDialog):
def __init__(self):
Gtk.AboutDialog.__init__(self)
@ -61,3 +66,4 @@ class NoGamesToBackupFoundDialog(Gtk.MessageDialog):
self.hide()
self.destroy()

View File

@ -20,7 +20,7 @@ from .. import _import_gtk
from gi.repository import Gio,GLib,Gtk,Pango
from gi.repository.GObject import Property,Signal,GObject,BindingFlags,SignalFlags
import rapidfuzz
from ..i18n import gettext as _, gettext_noop as N_
from ..game import (
@ -39,6 +39,8 @@ from ..game import (
EpicGameData,
EpicWindowsData,
GameManager,
GameProvider,
GAME_PROVIDER_ICONS,
)
@ -1080,6 +1082,9 @@ class GameDialog(Gtk.Dialog):
savegame_name = self.__sgname_entry.get_text()
savegame_type = self.__savegame_type_dropdown.get_selected_item().savegame_type
if self.has_game:
if self.__game.key != key:
GameManager.get_global().remove_game(self.__game)
self.__game.key = key
self.__game.name = name
self.__game.savegame_type = savegame_type
@ -1494,3 +1499,274 @@ class GameDialog(Gtk.Dialog):
@Signal(name='apply',flags=SignalFlags.RUN_FIRST,return_type=None,arg_types=())
def do_apply(self):
pass
### GameSearchDialog ##########################################################
class GameSearchDialogData(GObject):
def __init__(self,game:Game,fuzzy_match:float=0.0):
GObject.__init__(self)
self.__game = game
self.__fuzzy_match = fuzzy_match
@Property
def game(self)->Game:
return self.__game
@Property(type=float)
def fuzzy_match(self)->float:
return self.__fuzzy_match
@fuzzy_match.setter
def fuzzy_match(self,match:float):
self.__fuzzy_match = match
class GameSearchDialogDataSorter(Gtk.Sorter):
def do_compare(self,item1:GameSearchDialogData,item2:GameSearchDialogData):
if (item1.fuzzy_match > item2.fuzzy_match):
return Gtk.Ordering.SMALLER
elif (item1.fuzzy_match < item2.fuzzy_match):
return Gtk.Ordering.LARGER
name1 = item1.game.name.lower()
name2 = item2.game.name.lower()
if (name1 > name2):
return Gtk.Ordering.LARGER
elif (name1 < name2):
return Gtk.Ordering.SMALLER
return Gtk.Ordering.EQUAL
class GameSearchDialogDataNameSorter(Gtk.Sorter):
def do_compare(self,item1:GameSearchDialogData,item2:GameSearchDialogData):
name1 = item1.game.name.lower()
name2 = item2.game.name.lower()
if (name1 > name2):
return Gtk.Ordering.LARGER
elif (name1 < name2):
return Gtk.Ordering.SMALLER
return Gtk.Ordering.EQUAL
class GameSearchDialogDataFilter(Gtk.Filter):
def do_match(self,item:GameSearchDialogData):
return (item.fuzzy_match > 0.0)
class GameSearchDialog(Gtk.Dialog):
def __init__(self,
parent:Gtk.Window|None=None,
search_name:str|None=None,
title:str|None=None):
Gtk.Dialog.__init__(self)
self.set_title("SGBackup: {title}".format(
title=title if title else _("Search for games")
))
if parent:
self.set_transient_for(parent)
self.search_name = search_name
self.__actionbar = Gtk.ActionBar()
new_game_button = Gtk.Button()
icon = Gtk.Image.new_from_icon_name('list-add-symbolic')
icon.set_pixel_size(16)
new_game_button.set_child(icon)
new_game_button.connect('clicked',self._on_new_game_button_clicked)
self.__actionbar.pack_start(new_game_button)
self.__action_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2)
label = Gtk.Label.new(_("Enable Search?"))
self.__search_switch = Gtk.Switch()
self.__search_switch.set_active(bool(self.search_name))
self.__search_switch.set_sensitive(bool(self.search_name))
self.__action_hbox.append(label)
self.__action_hbox.append(self.__search_switch)
self.__actionbar.pack_end(self.__action_hbox)
self.get_content_area().append(self.__actionbar)
scrolled = Gtk.ScrolledWindow()
self.__liststore = Gio.ListStore()
for i in self.__get_search_games(search_name):
self.__liststore.append(GameSearchDialogData(**i))
self.__sort_model = Gtk.SortListModel(model=self.__liststore,sorter=GameSearchDialogDataSorter())
self.__filter_model = Gtk.FilterListModel(model=self.__sort_model,
filter=GameSearchDialogDataFilter() if search_name else None)
selection = Gtk.SingleSelection(model=self.__filter_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)
scrolled.set_child(self.__listview)
self.get_content_area().append(scrolled)
self.add_button("Close",Gtk.ResponseType.OK)
def __get_search_games(self,search_name:str|None)->list:
games=[]
game_names=[]
gm = GameManager.get_global()
for g in gm.games.values():
games.append({'game':g,'fuzzy_match':0.0})
game_names.append(g.name)
if search_name:
result = rapidfuzz.process.extract(
query=search_name,
choices=game_names,
limit=20,
scorer=rapidfuzz.fuzz.WRatio)
for choice,score,index in result:
games[index]['fuzzy_match']=score
return games
def _on_listview_setup(self,factory,item):
child = Gtk.Grid()
child.set_column_spacing(4)
child.set_row_spacing(2)
child.name_label=Gtk.Label()
child.name_label.set_xalign(0.0)
child.name_label.set_hexpand(True)
child.attach(child.name_label,1,0,1,1)
label = Gtk.Label.new(_("Key:"))
label.set_xalign(0.0)
child.key_label = Gtk.Label()
child.key_label.set_xalign(0.0)
child.key_label.set_hexpand(True)
child.attach(label,0,1,1,1)
child.attach(child.key_label,1,1,1,1)
label = Gtk.Label.new(_("Platform:"))
label.set_xalign(0.0)
child.platform_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,4)
child.platform_hbox.set_hexpand(True)
child.platform_hbox._is_bound = False
child.attach(label,0,2,1,1)
child.attach(child.platform_hbox,1,2,1,1)
action_grid = Gtk.Grid()
icon = Gtk.Image.new_from_icon_name('document-edit-symbolic')
icon.set_pixel_size(16)
child.edit_button = Gtk.Button()
child.edit_button.set_child(icon)
action_grid.attach(child.edit_button,0,1,1,1)
child.attach(action_grid,2,0,1,3)
item.set_child(child)
def _on_listview_bind(self,factory,item):
def add_platform_icon(child,provider:GameProvider):
icon = Gtk.Image.new_from_icon_name(GAME_PROVIDER_ICONS[provider])
icon.set_pixel_size(16)
child.platform_hbox.append(icon)
child = item.get_child()
data = item.get_item()
child.name_label.set_markup("<span weight='bold' size='large'>{}</span>".format(
GLib.markup_escape_text(data.game.name)))
child.key_label.set_text(data.game.key)
if not child.platform_hbox._is_bound:
child.platform_hbox._is_bound = True
if data.game.windows:
add_platform_icon(child,GameProvider.WINDOWS)
if data.game.linux:
add_platform_icon(child,GameProvider.LINUX)
if data.game.macos:
add_platform_icon(child,GameProvider.MACOS)
if data.game.steam:
add_platform_icon(child,GameProvider.STEAM)
if data.game.epic:
add_platform_icon(child,GameProvider.EPIC_GAMES)
#if data.game.gog:
# add_platform_icon(child,GameProvider.GOG)
if hasattr(child.edit_button,'_signal_clicked_connector'):
child.edit_button.disconnect(child.edit_button._signal_clicked_connector)
child.edit_button._signal_clicked_connector = child.edit_button.connect('clicked',
self._on_edit_button_clicked,
data.game)
def do_prepare_game(self,game:Game|None):
if game is None:
return Game("",self.serch_name,"")
else:
return game
def _on_new_game_button_clicked(self,button):
def on_response(dialog,response,parent):
if response == Gtk.ResponseType.APPLY:
parent.refresh()
game = self.do_prepare_game(None)
parent = self.get_Transient_for()
dialog = GameDialog(parent=parent,game=game)
if hasattr(parent,"refresh"):
dialog.connect('response',on_response)
dialog.present()
self.response(Gtk.ResponseType.OK)
def _on_edit_button_clicked(self,button:Gtk.Button,game:Game):
def on_response(dialog,response,parent):
if response == Gtk.ResponseType.APPLY:
parent.refresh()
try:
game = self.do_prepare_game()
except Exception as ex:
dialog = Gtk.MessageDialog(
message=_("Unable to edit game <i>{}</i>!".format(GLib.markup_escape_text(game.name))),
use_markup=True,
secondary_message=str(ex),
secondary_use_markup=False,
transient_for=self.get_transient_for(),
parent=self.get_transient_for(),
buttons=Gtk.Buttons.OK
)
dialog.connect('response',lambda dialog,response: dialog.destroy())
dialog.present()
return
parent = self.get_transient_for()
dialog = GameDialog(parent=parent,game=game)
if hasattr(parent,"refresh"):
dialog.connect('response',on_response,parent)
dialog.present()
self.response(Gtk.ResponseType.OK)
def _on_search_switch_state_set(self,switch:Gtk.Switch,state:bool):
if state:
self.__sort_model.set_sorter(GameSearchDialogDataSorter())
self.__filter_model.set_filter(GameSearchDialogDataFilter())
else:
self.__sort_model.set_sorter(GameSearchDialogDataNameSorter())
self.__filter_model.set_filter(None)
@Property(type=str)
def search_name(self)->str:
return self.__search_name if self.__search_name else ""
@search_name.setter
def search_name(self,name:str):
if not name:
self.__search_name = None
else:
self.__search_name = name
def do_response(self,response):
self.hide()
self.destroy()

View File

@ -22,9 +22,28 @@ from gi.repository.GObject import GObject,Property,Signal,BindingFlags
from ..i18n import gettext as _
import os
from ..steam import Steam,SteamLibrary,SteamApp,IgnoreSteamApp,PLATFORM_LINUX,PLATFORM_MACOS,PLATFORM_WINDOWS
from ..game import GameManager,Game,SteamGameData,SteamLinuxData,SteamMacOSData,SteamWindowsData,SavegameType
from ._gamedialog import GameDialog
from ..steam import (
Steam,
SteamLibrary,
SteamApp,
IgnoreSteamApp,
PLATFORM_LINUX,
PLATFORM_MACOS,
PLATFORM_WINDOWS
)
from ..game import (
GameManager,
Game,
SteamGameData,
SteamLinuxData,
SteamMacOSData,
SteamWindowsData,
SavegameType,
)
from ._gamedialog import GameDialog,GameSearchDialog
class SteamLibrariesDialog(Gtk.Dialog):
def __init__(self,parent:Gtk.Window|None=None):
@ -207,6 +226,61 @@ class NewSteamAppSorter(Gtk.Sorter):
else:
return Gtk.Ordering.EQUAL
class SteamGameLookupDialog(GameSearchDialog):
def __init__(self,parent,steam_app:SteamApp):
GameSearchDialog.__init__(self,parent,steam_app.name,_("Search Steam Apps"))
self.__steam_app = steam_app
@Property
def steam_app(self)->SteamApp:
return self.__steam_app
def do_prepare_game(self, game):
game = super().do_prepare_game(game)
if game.steam:
if not game.steam.appid != self.steam_app.appid:
raise ValueError("Steam appid error")
if PLATFORM_WINDOWS:
if not game.steam.windows:
game.steam.windows = SteamWindowsData("","",installdir=self.steam_app.installdir)
else:
game.steam.windows.installdir=self.steam_app.installdir
if PLATFORM_LINUX:
if not game.steam.linux:
game.steam.linux = SteamLinuxData("","",installdir=self.steam_app.installdir)
else:
game.steam.linux.installdir=self.steam_app.installdir
if PLATFORM_MACOS:
if not game.steam.macos:
game.steam.macos = SteamWindowsData("","",installdir=self.steam_app.installdir)
else:
game.steam.macos.installdir=self.steam_app.installdir
else:
if PLATFORM_WINDOWS:
windows = SteamWindowsData("","",installdir=self.steam_app.installdir)
else:
windows = None
if PLATFORM_LINUX:
linux = SteamLinuxData("","",installdir=self.steam_app.installdir)
else:
linux = None
if PLATFORM_MACOS:
macos = SteamMacOSData("","",installdir=self.steam_app.installdir)
else:
macos = None
game.steam = SteamGameData(appid=self.steam_app.appid,
windows=windows,
linux=linux,
macos=macos)
return game
class NewSteamAppsDialog(Gtk.Dialog):
def __init__(self,parent:Gtk.Window|None=None):
Gtk.Dialog.__init__(self)
@ -334,8 +408,7 @@ class NewSteamAppsDialog(Gtk.Dialog):
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_lookup_steamapp_button_clicked,data)
child.lookup_button.set_sensitive(False)
child.lookup_button._signal_clicked_connector = child.lookup_button.connect('clicked',self._on_lookup_steamapp_button_clicked,data)
if hasattr(child.search_online_button,'_signal_clicked_connector'):
child.search_online_button.disconnect(child.search_online_button._signal_clicked_connector)
@ -395,6 +468,10 @@ class NewSteamAppsDialog(Gtk.Dialog):
dialog.connect('response',on_dialog_response,data)
dialog.present()
def _on_lookup_steamapp_button_clicked(self,dialog,data:SteamApp):
dialog = SteamGameLookupDialog(parent=self,steam_app=data)
dialog.present()
def refresh(self):
self.__listmodel.remove_all()
for app in self.__steam.find_new_steamapps():
@ -530,7 +607,6 @@ class SteamIgnoreAppsDialog(Gtk.Dialog):
child.remove_button._signal_clicked_connector = child.remove_button.connect('clicked',self._on_remove_button_clicked,data)
def __remove_item(self,item:IgnoreSteamApp):
for i in range(self.__liststore.get_n_items()):
ignoreapp = self.__liststore.get_item(i)
if item.appid == ignoreapp.appid: