diff --git a/sgbackup/game.py b/sgbackup/game.py
index 9aedabe..e75c1ff 100644
--- a/sgbackup/game.py
+++ b/sgbackup/game.py
@@ -143,6 +143,20 @@ class SavegameType(StrEnum):
return st.UNSET
+
+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.STEAM_LINUX: 'steam-svgrepo-com-symbolic',
+ SavegameType.STEAM_MACOS: 'steam-svgrepo-com-symbolic',
+ SavegameType.STEAM_WINDOWS: 'steam-svgrepo-com-symbolic',
+ SavegameType.EPIC_LINUX: 'epic-games-svgrepo-com-symbolic',
+ SavegameType.EPIC_WINDOWS: 'epic-games-svgrepo-com-symbolic',
+ SavegameType.GOG_LINUX: 'gog-com-svgrepo-com-symbolic',
+ SavegameType.GOG_WINDOWS: 'gog-com-svgrepo-com-symbolic',
+}
class GameFileType(StrEnum):
"""
@@ -619,10 +633,24 @@ class WindowsGame(GameData):
def game_registry_keys(self)->list:
return self.__game_registry_keys
+ @game_registry_keys.setter
+ def game_registry_keys(self,keys:list[str]|tuple[str]|None):
+ self.__game_registry_keys = []
+ if keys:
+ for rk in keys:
+ self.__game_registry_keys.append(str(rk))
+
@Property
def installdir_registry_keys(self)->list:
return self.__installdir_registry_keys
+ @installdir_registry_keys.setter
+ def installdir_registry_keys(self,keys:list[str]|tuple[str]|None):
+ self.__installdir_registry_keys = []
+ if keys:
+ for rk in keys:
+ self.__installdir_registry_keys.append(str(rk))
+
@Property
def is_installed(self)->bool|None:
if not PLATFORM_WIN32 or not self.game_registry_keys:
@@ -666,6 +694,7 @@ class WindowsGame(GameData):
def get_variables(self):
variables = super().get_variables()
variables["INSTALLDIR"] = self.installdir if self.installdir else ""
+ return variables
def serialize(self):
ret = super().serialize()
@@ -900,8 +929,8 @@ class Game(GObject):
_logger = logger.getChild("Game.new_from_dict()")
def get_file_match(conf:dict):
- conf_fm = conf['file_match'] if 'file_match' in conf else None
- conf_im = conf['ignore_match'] if 'ignore_match' in conf else None
+ conf_fm = conf['file_match'] if 'file_match' in conf else []
+ conf_im = conf['ignore_match'] if 'ignore_match' in conf else []
if (conf_fm):
file_match = []
@@ -933,11 +962,11 @@ class Game(GObject):
return (file_match,ignore_match)
def new_steam_game(conf,cls:SteamGame):
- appid = conf['appid'] if 'appid' in conf else None
- sgroot = conf['savegame_root'] if 'savegame_root' in conf else None
- sgdir = conf['savegame_dir'] if 'savegame_dir' in conf else None
- vars = conf['variables'] if 'variables' in conf else None
- installdir = conf['installdir'] if 'installdir' in conf else None
+ appid = conf['appid'] if 'appid' in conf else ""
+ sgroot = conf['savegame_root'] if 'savegame_root' in conf else ""
+ sgdir = conf['savegame_dir'] if 'savegame_dir' in conf else ""
+ vars = conf['variables'] if 'variables' in conf else {}
+ installdir = conf['installdir'] if 'installdir' in conf else ""
file_match,ignore_match = get_file_match(conf)
if appid is not None and sgroot and sgdir:
@@ -966,40 +995,37 @@ class Game(GObject):
winconf = config['windows']
sgroot = winconf['savegame_root'] if 'savegame_root' in winconf else None
sgdir = winconf['savegame_dir'] if 'savegame_dir' in winconf else None
- vars = winconf['variables'] if 'variables' in winconf else None
+ vars = winconf['variables'] if 'variables' in winconf else {}
installdir = winconf['installdir'] if 'installdir' in winconf else None
- game_regkeys = winconf['game_registry_keys'] if 'game_registry_keys' in winconf else None
- installdir_regkeys = winconf['installdir_registry_keys'] if 'installdir_registry_keys' in winconf else None
+ game_regkeys = winconf['game_registry_keys'] if 'game_registry_keys' in winconf else []
+ installdir_regkeys = winconf['installdir_registry_keys'] if 'installdir_registry_keys' in winconf else []
file_match,ignore_match = get_file_match(winconf)
- if (sgroot and sgdir):
- game.windows = WindowsGame(sgroot,
- sgdir,
- vars,
- installdir,
- game_regkeys,
- installdir_regkeys,
- file_match,
- ignore_match)
+ game.windows = WindowsGame(sgroot,
+ sgdir,
+ vars,
+ installdir,
+ game_regkeys,
+ installdir_regkeys,
+ file_match,
+ ignore_match)
if 'linux' in config:
linconf = config['linux']
sgroot = linconf['savegame_root'] if 'savegame_root' in linconf else None
sgdir = linconf['savegame_dir'] if 'savegame_dir' in linconf else None
- vars = linconf['variables'] if 'variables' in linconf else None
+ vars = linconf['variables'] if 'variables' in linconf else {}
binary = linconf['binary'] if 'binary' in linconf else None
file_match,ignore_match = get_file_match(linconf)
- if (sgroot and sgdir):
- game.linux = LinuxGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
+ game.linux = LinuxGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'macos' in config:
macconf = config['macos']
sgroot = macconf['savegame_root'] if 'savegame_root' in macconf else None
sgdir = macconf['savegame_dir'] if 'savegame_dir' in macconf else None
- vars = macconf['variables'] if 'variables' in macconf else None
+ vars = macconf['variables'] if 'variables' in macconf else {}
binary = macconf['binary'] if 'binary' in macconf else None
file_match,ignore_match = get_file_match(macconf)
- if (sgroot and sgdir):
- game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
+ game.macos = MacOSGame(sgroot,sgdir,vars,binary,file_match,ignore_match)
if 'steam_windows' in config:
game.steam_windows = new_steam_game(config['steam_windows'],SteamWindowsGame)
@@ -1099,9 +1125,9 @@ class Game(GObject):
@Property
def filename(self)->str|None:
if not self.__filename:
- if not self.__key:
+ if not self.key:
return None
- os.path.join(settings.gameconf_dir,'.'.join((self.key,'gameconf')))
+ return os.path.join(settings.gameconf_dir,'.'.join((self.key,'gameconf')))
return self.__filename
@@ -1428,9 +1454,8 @@ class GameManager(GObject):
if not game:
self.logger.warn("Not loaded game \"{game}\"!".format(
game=(game.name if game is not None else "UNKNOWN GAME")))
- print(game.serialize())
continue
- except Exception as ex:
+ except GLib.Error as ex: #Exception as ex:
self.logger.error("Unable to load gameconf {gameconf}! ({what})".format(
gameconf = os.path.basename(gcf),
what = str(ex)))
diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py
index 65cf38a..d6d1347 100644
--- a/sgbackup/gui/_app.py
+++ b/sgbackup/gui/_app.py
@@ -16,7 +16,7 @@
# along with this program. If not, see . #
###############################################################################
-from gi.repository import Gtk,Gio,Gdk
+from gi.repository import Gtk,Gio,Gdk,GLib
from gi.repository.GObject import GObject,Signal,Property,SignalFlags,BindingFlags
import logging; logger=logging.getLogger(__name__)
@@ -28,7 +28,7 @@ from pathlib import Path
from ..settings import settings
from ._settingsdialog import SettingsDialog
from ._gamedialog import GameDialog
-from ..game import Game,GameManager
+from ..game import Game,GameManager,SAVEGAME_TYPE_ICONS
from ._steam import SteamLibrariesDialog,NewSteamAppsDialog
__gtype_name__ = __name__
@@ -52,6 +52,11 @@ class GameView(Gtk.ScrolledWindow):
pass
self.__liststore.append(g)
+ factory_icon = Gtk.SignalListItemFactory.new()
+ factory_icon.connect('setup',self._on_icon_column_setup)
+ factory_icon.connect('bind',self._on_icon_column_bind)
+ column_icon = Gtk.ColumnViewColumn.new("",factory_icon)
+
factory_key = Gtk.SignalListItemFactory.new()
factory_key.connect('setup',self._on_key_column_setup)
factory_key.connect('bind',self._on_key_column_bind)
@@ -75,14 +80,22 @@ class GameView(Gtk.ScrolledWindow):
factory_live.connect('unbind',self._on_live_column_unbind)
column_live = Gtk.ColumnViewColumn.new("Live",factory_live)
+ factory_actions = Gtk.SignalListItemFactory.new()
+ factory_actions.connect('setup',self._on_actions_column_setup)
+ factory_actions.connect('bind',self._on_actions_column_bind)
+ column_actions = Gtk.ColumnViewColumn.new("",factory_actions)
+
selection = Gtk.SingleSelection.new(self._liststore)
self.__columnview = Gtk.ColumnView.new(selection)
+ self.columnview.append_column(column_icon)
self.columnview.append_column(column_key)
self.columnview.append_column(column_name)
self.columnview.append_column(column_active)
self.columnview.append_column(column_live)
+ self.columnview.append_column(column_actions)
self.columnview.set_single_click_activate(True)
+
self.set_child(self.columnview)
self.refresh()
@@ -115,6 +128,22 @@ class GameView(Gtk.ScrolledWindow):
for game in GameManager.get_global().games.values():
self.__liststore.append(game)
+ def _on_icon_column_setup(self,factory,item):
+ item.set_child(Gtk.Image())
+
+ def _on_icon_column_bind(self,factory,item):
+ def transform_to_icon_name(_bidning,sgtype):
+ icon_name = SAVEGAME_TYPE_ICONS[sgtype] if sgtype in SAVEGAME_TYPE_ICONS else None
+ if icon_name:
+ return icon_name
+ return ""
+ icon = item.get_child()
+ game = item.get_item()
+ game.bind_property('savegame_type',icon,'icon_name',BindingFlags.SYNC_CREATE,transform_to_icon_name)
+
+
+
+
def _on_key_column_setup(self,factory,item):
item.set_child(Gtk.Label())
@@ -171,9 +200,9 @@ class GameView(Gtk.ScrolledWindow):
dialog.hide()
dialog.destroy()
- game.is_live = state
+ game.is_live = switch.get_active()
game.save()
- if not state:
+ if not game.is_live:
dialog = Gtk.MessageDialog()
dialog.set_transient_for(self.get_root())
dialog.props.buttons = Gtk.ButtonsType.YES_NO
@@ -184,21 +213,61 @@ class GameView(Gtk.ScrolledWindow):
dialog.connect('response',on_dialog_response)
dialog.present()
- @property
- def current_game(self)->Game|None:
- """
- current_game Get the currently selected `Game`
+ def _on_actions_column_setup(self,action,item):
+ child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2)
+ icon = Gtk.Image.new_from_icon_name('document-edit-symbolic')
+ child.edit_button = Gtk.Button()
+ child.edit_button.set_child(icon)
+ child.append(child.edit_button)
- If no `Game` is selected this property resolves to `Null`
-
- :type: Game|None
- """
- selection = self._columnview.get_model()
- pos = selection.get_selected()
- if pos == Gtk.INVALID_LIST_POSITION:
- return None
- return selection.get_model().get_item(pos)
+ icon = Gtk.Image.new_from_icon_name('list-remove-symbolic')
+ child.remove_button = Gtk.Button()
+ child.remove_button.set_child(icon)
+ child.append(child.remove_button)
+
+ item.set_child(child)
+
+ def _on_actions_column_bind(self,action,item):
+ child = item.get_child()
+ game = item.get_item()
+
+ child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item)
+ child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item)
+ def _on_columnview_edit_button_clicked(self,button,item):
+ def on_dialog_response(dialog,response):
+ if response == Gtk.ResponseType.APPLY:
+ self.refresh()
+
+ game = item.get_item()
+ dialog = GameDialog(self.get_root(),game)
+ dialog.connect('response',on_dialog_response)
+ dialog.present()
+
+ def _on_columnview_remove_button_clicked(self,button,item):
+ def on_dialog_response(dialog,response,game:Game):
+ if response == Gtk.ResponseType.YES:
+ if os.path.isfile(game.filename):
+ os.unlink(game.filename)
+ for i in range(self._liststore.get_n_items()):
+ item = self._liststore.get_item(i)
+ if item.key == game.key:
+ self._liststore.remove_item(i)
+ return
+
+ dialog.hide()
+ dialog.destroy()
+
+ game = item.get_item()
+ dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO,
+ text="Do you really want to remove the game {game}?".format(
+ game=game.name),
+ use_markup=True,
+ secondary_text="Removing games cannot be undone!!!")
+ dialog.set_transient_for(self.get_root())
+ dialog.connect('response',on_dialog_response,game)
+ dialog.present()
+
# GameView class
class BackupViewData(GObject):
@@ -278,7 +347,7 @@ class BackupViewData(GObject):
def _on_selection_changed(self,selection):
pass
-class BackupView(Gtk.ScrolledWindow):
+class BackupView(Gtk.Box):
"""
BackupView This view displays the backup for the selected `Game`.
"""
@@ -290,7 +359,9 @@ class BackupView(Gtk.ScrolledWindow):
:param gameview: The `GameView` to connect this class to.
:type gameview: GameView
"""
- Gtk.ScrolledWindow.__init__(self)
+ Gtk.Box.__init__(self,orientation=Gtk.Orientation.VERTICAL)
+ self._title_label = Gtk.Label()
+ scrolled = Gtk.ScrolledWindow()
self.__gameview = gameview
self.__liststore = Gio.ListStore()
@@ -316,11 +387,14 @@ class BackupView(Gtk.ScrolledWindow):
self.__columnview.append_column(live_column)
self.__columnview.append_column(sgname_column)
self.__columnview.append_column(timestamp_column)
+ self.__columnview.set_vexpand(True)
- self._on_gameview_selection_changed(selection)
- self.gameview.columnview.get_model().connect('selection-changed',self._on_gameview_selection_changed)
+ self.gameview.columnview.connect('activate',self._on_gameview_columnview_activate)
- self.set_child(self.__columnview)
+ scrolled.set_child(self.__columnview)
+
+ self.append(self._title_label)
+ self.append(scrolled)
@property
def gameview(self)->GameView:
@@ -383,10 +457,12 @@ class BackupView(Gtk.ScrolledWindow):
display_size = str(size) + " B"
label.set_text(display_size)
- def _on_gameview_selection_changed(self,model):
- game = model.get_selected_item()
- if game is None:
- return
+ def _on_gameview_columnview_activate(self,columnview,position):
+ model = columnview.get_model().get_model()
+ game = model.get_item(position)
+
+ self._title_label.set_markup("{}".format(GLib.markup_escape_text(game.name)))
+
class AppWindow(Gtk.ApplicationWindow):
diff --git a/sgbackup/gui/_gamedialog.py b/sgbackup/gui/_gamedialog.py
index 66e0561..008d37d 100644
--- a/sgbackup/gui/_gamedialog.py
+++ b/sgbackup/gui/_gamedialog.py
@@ -831,6 +831,7 @@ class GameDialog(Gtk.Dialog):
self.__active_switch.set_active(self.__game.is_active if self.has_game else True)
self.__live_switch.set_active(self.__game.is_live if self.has_game else True)
self.__name_entry.set_text(self.__game.name if self.has_game else "")
+ self.__key_entry.set_text(self.__game.key if self.has_game else "")
self.__sgname_entry.set_text(self.__game.savegame_name if self.has_game else "")
set_variables(self.__game_variables,self.__game.variables if self.has_game else None)
diff --git a/sgbackup/icons/hicolor/symbolic/apps/$ b/sgbackup/icons/hicolor/symbolic/apps/$
deleted file mode 100644
index b764c16..0000000
--- a/sgbackup/icons/hicolor/symbolic/apps/$
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-