2025.01.02 10:13:39
15
.gitattributes
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
* text=auto
|
||||
|
||||
.githooks/pre-commit text eol=lf
|
||||
|
||||
*.yml text eol=lf
|
||||
*.sh text eol=lf
|
||||
*.py text
|
||||
*.txt text
|
||||
*.ps1 text eol=crlf
|
||||
*.ps1.in text eol=crlf
|
||||
*.bat text eol=crlf
|
||||
*.bat.in text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.cmd.in text eol=crlf
|
||||
|
||||
@ -7,6 +7,7 @@ PROJECT_ROOT="$(dirname "$(dirname "$SELF")")" ; export PROJECT_ROOT
|
||||
|
||||
pre_commit_d="${GITHOOKS_DIR}/pre-commit-d"
|
||||
|
||||
# run scripts from the pre-commit.d directory
|
||||
for i in $(ls "$pre_commit_d"); do
|
||||
script="${pre_commit_d}/$i"
|
||||
if [ -x "$script" ]; then
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self,id:str,name:str,description:str):
|
||||
self.__id = id
|
||||
|
||||
110
sgbackup/game.py
@ -107,6 +107,8 @@ class GameFileType(StrEnum):
|
||||
raise ValueError("Unknown GameFileType \"{}\"!".fomrat(typestring))
|
||||
|
||||
class GameFileMatcher(GObject):
|
||||
__gtype_name__ = "GameFileMatcher"
|
||||
|
||||
def __init__(self,match_type:GameFileType,match_file:str):
|
||||
GObject.__init__(self)
|
||||
self.match_type = type
|
||||
@ -165,6 +167,8 @@ class GameFileMatcher(GObject):
|
||||
return False
|
||||
|
||||
class GameData(GObject):
|
||||
__gtype_name__ = 'GameData'
|
||||
|
||||
"""
|
||||
:class: GameData
|
||||
:brief: Base class for platform specific data.
|
||||
@ -226,17 +230,41 @@ class GameData(GObject):
|
||||
self.__savegame_dir = sgdir
|
||||
|
||||
@Property
|
||||
def variables(self):
|
||||
def variables(self)->dict:
|
||||
return self.__variables
|
||||
@variables.setter
|
||||
def variables(self,vars:dict|None):
|
||||
if not vars:
|
||||
self.__variables = {}
|
||||
else:
|
||||
self.__variables = dict(vars)
|
||||
|
||||
@Property
|
||||
def file_match(self):
|
||||
return self.__filematch
|
||||
@file_match.setter
|
||||
def file_match(self,fm:list[GameFileMatcher]|None):
|
||||
if not fm:
|
||||
self.__filematch = []
|
||||
else:
|
||||
for matcher in fm:
|
||||
if not isinstance(matcher,GameFileMatcher):
|
||||
raise TypeError("\"file_match\" needs to be \"None\" or a list of \"GameFileMatcher\" instances!")
|
||||
self.__filematch = list(fm)
|
||||
|
||||
@Property
|
||||
def ignore_match(self):
|
||||
return self.__ignorematch
|
||||
|
||||
@file_match.setter
|
||||
def file_match(self,im:list[GameFileMatcher]|None):
|
||||
if not im:
|
||||
self.__ignorematch = []
|
||||
else:
|
||||
for matcher in im:
|
||||
if not isinstance(matcher,GameFileMatcher):
|
||||
raise TypeError("\"ignore_match\" needs to be \"None\" or a list of \"GameFileMatcher\" instances!")
|
||||
self.__ignorematch = list(im)
|
||||
|
||||
def has_variable(self,name:str)->bool:
|
||||
return (name in self.__variables)
|
||||
|
||||
@ -535,18 +563,25 @@ class SteamGame(GameData):
|
||||
|
||||
GameData.__init__(self,
|
||||
sgtype,
|
||||
appid,
|
||||
savegame_root,
|
||||
savegame_dir,
|
||||
variables,
|
||||
file_match,
|
||||
ignore_match)
|
||||
self.__installdir = installdir
|
||||
self.appid = int(appid)
|
||||
self.installdir = installdir
|
||||
|
||||
def get_variables(self):
|
||||
vars = super().get_variables()
|
||||
vars["INSTALLDIR"] = self.installdir if self.installdir else ""
|
||||
|
||||
@Property(type=int)
|
||||
def appid(self):
|
||||
return self.__appid
|
||||
@appid.setter
|
||||
def appid(self,appid):
|
||||
self.__appid = appid
|
||||
|
||||
@Property
|
||||
def installdir(self):
|
||||
return self.__installdir
|
||||
@ -622,6 +657,8 @@ class SteamMacOSGame(SteamGame):
|
||||
|
||||
|
||||
class Game(GObject):
|
||||
__gtype_name__ = "Game"
|
||||
|
||||
@staticmethod
|
||||
def new_from_dict(config:str):
|
||||
logger = logger.getChild("Game.new_from_dict()")
|
||||
@ -740,15 +777,17 @@ class Game(GObject):
|
||||
with open(filename,'rt',encoding="UTF-8") as ifile:
|
||||
return Game.new_from_dict(json.loads(ifile.read()))
|
||||
|
||||
def __init__(self,id:str,name:str,savegame_name:str):
|
||||
def __init__(self,key:str,name:str,savegame_name:str):
|
||||
GObject.__init__(self)
|
||||
self.__id = id
|
||||
self.__dbid = None
|
||||
self.__key = key
|
||||
self.__name = name
|
||||
self.__filename = None
|
||||
self.__savegame_name = savegame_name
|
||||
self.__savegame_type = SavegameType.UNSET
|
||||
self.__active = False
|
||||
self.__live = True
|
||||
self.__variables = dict()
|
||||
|
||||
self.__windows = None
|
||||
self.__linux = None
|
||||
@ -762,12 +801,25 @@ class Game(GObject):
|
||||
self.__epic_linux = None
|
||||
|
||||
@Property(type=str)
|
||||
def id(self)->str:
|
||||
def dbid(self)->str:
|
||||
return self.__id
|
||||
@id.setter
|
||||
@dbid.setter
|
||||
def id(self,id:str):
|
||||
self.__id = id
|
||||
|
||||
@Property(type=str)
|
||||
def key(self)->str:
|
||||
return self.__key
|
||||
@key.setter
|
||||
def key(self,key:str):
|
||||
set_game = False
|
||||
if self.__key in GAMES:
|
||||
del GAMES[self.__key]
|
||||
set_game = True
|
||||
|
||||
self.__key = key
|
||||
if set_game:
|
||||
GAMES[self.__key] = self
|
||||
|
||||
@Property(type=str)
|
||||
def name(self)->str:
|
||||
@ -820,6 +872,16 @@ class Game(GObject):
|
||||
else:
|
||||
self.__filename = fn
|
||||
|
||||
@Property
|
||||
def variables(self):
|
||||
return self.__variables
|
||||
@variables.setter
|
||||
def variables(self,vars:dict|None):
|
||||
if not vars:
|
||||
self.__variables = {}
|
||||
else:
|
||||
self.__variables = dict(vars)
|
||||
|
||||
@Property
|
||||
def game_data(self):
|
||||
sgtype = self.savegame_type
|
||||
@ -917,6 +979,22 @@ class Game(GObject):
|
||||
raise TypeError("SteamWindowsGame")
|
||||
self.__steam_macos = data
|
||||
|
||||
def add_variable(self,name:str,value:str):
|
||||
self.__variables[str(name)] = str(value)
|
||||
|
||||
def delete_variable(self,name):
|
||||
if name in self.__variables:
|
||||
del self.__variables[name]
|
||||
|
||||
def get_variable(self,name):
|
||||
vars = dict(os.environ)
|
||||
#vars.update(settings.variables)
|
||||
vars.update(self.__variables)
|
||||
game_data = self.game_data
|
||||
if (game_data is not None):
|
||||
vars.update(game_data.variables)
|
||||
|
||||
|
||||
def serialize(self)->dict:
|
||||
ret = {
|
||||
'id': self.id,
|
||||
@ -949,9 +1027,8 @@ class Game(GObject):
|
||||
# ret['epic_linux'] = self.epic_linux.serialize()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def save(self):
|
||||
data = self.serialize()
|
||||
old_path = pathlib.Path(self.filename).resolve()
|
||||
new_path = pathlib.Path(settings.gameconf_dir / '.'.join(self.id,'gameconf')).resolve()
|
||||
if (str(old_path) != str(new_path)) and old_path.is_file():
|
||||
@ -959,8 +1036,10 @@ class Game(GObject):
|
||||
if not new_path.parent.is_dir():
|
||||
os.makedirs(new_path.parent)
|
||||
|
||||
with open(new_path,'wt',encoding='UTF-8') as ofile:
|
||||
with open(new_path,'wt',encoding='utf-8') as ofile:
|
||||
ofile.write(json.dumps(self.serialize(),ensure_ascii=False,indent=4))
|
||||
|
||||
|
||||
|
||||
GAMES={}
|
||||
STEAM_GAMES={}
|
||||
@ -973,7 +1052,7 @@ def __init_games():
|
||||
if not os.path.isdir(gameconf_dir):
|
||||
return
|
||||
|
||||
for gcf in (os.path.join(gameconf_dir,i) for i in os.path.listdir(gameconf_dir)):
|
||||
for gcf in (os.path.join(gameconf_dir,i) for i in os.listdir(gameconf_dir)):
|
||||
if not os.path.isfile(gcf) or not gcf.endswith('.gameconf'):
|
||||
continue
|
||||
|
||||
@ -984,7 +1063,7 @@ def __init_games():
|
||||
except:
|
||||
continue
|
||||
|
||||
GAMES[game.id] = game
|
||||
GAMES[game.key] = game
|
||||
if (game.steam_windows):
|
||||
if not game.steam_windows.appid in STEAM_GAMES:
|
||||
STEAM_GAMES[game.steam_windows.appid] = game
|
||||
@ -1000,7 +1079,7 @@ def __init_games():
|
||||
__init_games()
|
||||
|
||||
def add_game(game:Game):
|
||||
GAMES[game.id] = game
|
||||
GAMES[game.key] = game
|
||||
if game.steam_windows:
|
||||
if not game.steam_windows.appid in STEAM_GAMES:
|
||||
STEAM_GAMES[game.steam_windows.appid] = game
|
||||
@ -1012,4 +1091,5 @@ def add_game(game:Game):
|
||||
if (game.steam_macos):
|
||||
if not game.steam_macos.appid in STEAM_GAMES:
|
||||
STEAM_GAMES[game.steam_macos.appid] = game
|
||||
STEAM_MACOS_GAMES[game.steam_macos.appid] = game
|
||||
STEAM_MACOS_GAMES[game.steam_macos.appid] = game
|
||||
|
||||
@ -15,4 +15,9 @@
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
from .application import Application
|
||||
|
||||
from ._app import Application,AppWindow
|
||||
from ._settingsdialog import SettingsDialog
|
||||
from ._gamedialog import GameDialog
|
||||
|
||||
app = None
|
||||
|
||||
448
sgbackup/gui/_app.py
Normal file
@ -0,0 +1,448 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,GObject,Gio,Gdk
|
||||
|
||||
import logging; logger=logging.getLogger(__name__)
|
||||
|
||||
import os
|
||||
from datetime import datetime as DateTime
|
||||
from pathlib import Path
|
||||
|
||||
from .. import game
|
||||
from ..settings import settings
|
||||
from ._settingsdialog import SettingsDialog
|
||||
from ._gamedialog import GameDialog
|
||||
|
||||
class GameView(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = "sgbackup-gui-GameView"
|
||||
|
||||
def __init__(self):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
self.__liststore = Gio.ListStore.new(game.Game)
|
||||
for g in game.GAMES.values():
|
||||
pass
|
||||
self.__liststore.append(g)
|
||||
|
||||
factory_key = Gtk.SignalListItemFactory.new()
|
||||
factory_key.connect('setup',self._on_key_column_setup)
|
||||
factory_key.connect('bind',self._on_key_column_bind)
|
||||
column_key = Gtk.ColumnViewColumn.new("Key",factory_key)
|
||||
|
||||
factory_name = Gtk.SignalListItemFactory.new()
|
||||
factory_name.connect('setup',self._on_name_column_setup)
|
||||
factory_name.connect('bind',self._on_name_column_bind)
|
||||
column_name = Gtk.ColumnViewColumn.new("Name",factory_name)
|
||||
column_name.set_expand(True)
|
||||
|
||||
factory_active = Gtk.SignalListItemFactory.new()
|
||||
factory_active.connect('setup',self._on_active_column_setup)
|
||||
factory_active.connect('bind',self._on_active_column_bind)
|
||||
factory_active.connect('unbind',self._on_active_column_unbind)
|
||||
column_active = Gtk.ColumnViewColumn.new("Active",factory_active)
|
||||
|
||||
factory_live = Gtk.SignalListItemFactory.new()
|
||||
factory_live.connect('setup',self._on_live_column_setup)
|
||||
factory_live.connect('bind',self._on_live_column_bind)
|
||||
factory_live.connect('unbind',self._on_live_column_unbind)
|
||||
column_live = Gtk.ColumnViewColumn.new("Live",factory_live)
|
||||
|
||||
selection = Gtk.SingleSelection.new(self._liststore)
|
||||
self.__columnview = Gtk.ColumnView.new(selection)
|
||||
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.set_single_click_activate(True)
|
||||
|
||||
self.set_child(self._columnview)
|
||||
|
||||
@property
|
||||
def _liststore(self):
|
||||
return self.__liststore
|
||||
|
||||
@property
|
||||
def _columnview(self):
|
||||
return self.__columnview
|
||||
|
||||
def _on_key_column_setup(self,factory,item):
|
||||
item.set_child(Gtk.Label())
|
||||
|
||||
def _on_key_column_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
game = item.get_item()
|
||||
label.bind_property(game,'key','label',GObject.BindingFlags.DEFAULT)
|
||||
|
||||
def _on_name_column_setup(self,factory,item):
|
||||
item.set_child(Gtk.Label())
|
||||
|
||||
def _on_name_column_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
game = item.get_item()
|
||||
label.bind_proprety(game,'name','label',GObject.BindingFlags.DEFAULT)
|
||||
|
||||
def _on_active_column_setup(self,factory,item):
|
||||
item.set_child(Gtk.Switch())
|
||||
|
||||
def _on_active_column_bind(self,factory,item):
|
||||
switch = item.get_child()
|
||||
game = item.get_data()
|
||||
switch.set_active(game.is_active)
|
||||
item._signal_active_state_set = switch.connect('state-set',self._on_active_state_set,game)
|
||||
|
||||
def _on_active_column_unbind(self,factory,item):
|
||||
if hasattr(item,'_signal_active_state_set'):
|
||||
item.get_child().disconnect(item._signal_active_state_set)
|
||||
del item._signal_active_state_set
|
||||
|
||||
def _on_active_state_set(self,switch,state,game):
|
||||
game.is_active = state
|
||||
game.save()
|
||||
|
||||
def _on_live_column_setup(self,factory,item):
|
||||
item.set_child(Gtk.Switch())
|
||||
|
||||
def _on_live_column_bind(self,factory,item):
|
||||
switch = item.get_child()
|
||||
game = item.get_data()
|
||||
switch.set_active(game.is_active)
|
||||
item._signal_live_state_set = switch.connect('state-set',self._on_live_state_set,game)
|
||||
|
||||
def _on_live_column_unbind(self,factory,item):
|
||||
if hasattr(item,'_signal_live_state_set'):
|
||||
item.get_child().disconnect(item._signal_live_state_set)
|
||||
del item._signal_live_state_set
|
||||
|
||||
def _on_live_state_set(self,switch,state,game):
|
||||
def on_dialog_response(dialog,response):
|
||||
if response == Gtk.ResponseType.YES:
|
||||
pass
|
||||
#archiver.backup(game)
|
||||
dialog.hide()
|
||||
dialog.destroy()
|
||||
|
||||
game.is_live = state
|
||||
game.save()
|
||||
if not state:
|
||||
dialog = Gtk.MessageDialog()
|
||||
dialog.set_transient_for(self.get_toplevel())
|
||||
dialog.props.buttons = Gtk.ButtonsType.YES_NO
|
||||
dialog.props.text = "Do you want to create a new savegame for <i>{game}</i>?".format(game=game.name)
|
||||
dialog.props.use_markup = True
|
||||
dialog.props.secondary_text = "The new savegame is added to the finsihed savegames for the game."
|
||||
dialog.props.secondary_use_markup = False
|
||||
dialog.connect('response',on_dialog_response)
|
||||
dialog.present()
|
||||
# GameView class
|
||||
|
||||
class BackupViewData(GObject.GObject):
|
||||
def __init__(self,_game:game.Game,filename:str):
|
||||
GObject.GObject.__init__(self)
|
||||
self.__game = _game
|
||||
self.__filename = filename
|
||||
|
||||
basename = os.path.basename(filename)
|
||||
self.__is_live = (os.path.basename(os.path.dirname(filename)) == 'live')
|
||||
parts = filename.split('.')
|
||||
self.__savegame_name = parts[0]
|
||||
self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S")
|
||||
|
||||
self.__extension = '.' + parts[3:]
|
||||
|
||||
@property
|
||||
def game(self)->game.Game:
|
||||
return self.__game
|
||||
|
||||
@GObject.Property
|
||||
def savegame_name(self):
|
||||
return self.__savegame_name
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def filename(self)->str:
|
||||
return self.__filename
|
||||
|
||||
@GObject.Property(type=bool,default=False)
|
||||
def is_live(self)->bool:
|
||||
pass
|
||||
|
||||
@GObject.Property
|
||||
def extension(self):
|
||||
return self.__extension
|
||||
|
||||
@GObject.Property
|
||||
def timestamp(self):
|
||||
return self.__timestamp
|
||||
|
||||
class BackupView(Gtk.ScrolledWindow):
|
||||
__gtype_name__ = "BackupView"
|
||||
def __init__(self,gameview:GameView):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.__gameview = gameview
|
||||
|
||||
self.__liststore = Gio.ListStore()
|
||||
selection = Gtk.SingleSelection.new(self.__liststore)
|
||||
|
||||
live_factory = Gtk.SignalListItemFactory()
|
||||
live_factory.connect('setup',self._on_live_column_setup)
|
||||
live_factory.connect('bind',self._on_live_column_bind)
|
||||
live_column = Gtk.ColumnViewColumn.new("Live",live_factory)
|
||||
|
||||
sgname_factory = Gtk.SignalListItemFactory()
|
||||
sgname_factory.connect('setup',self._on_savegamename_column_setup)
|
||||
sgname_factory.connect('bind',self._on_savegamename_column_bind)
|
||||
sgname_column = Gtk.ColumnViewColumn.new("Savegame name",sgname_factory)
|
||||
sgname_column.set_expand(True)
|
||||
|
||||
timestamp_factory = Gtk.SignalListItemFactory()
|
||||
timestamp_factory.connect('setup',self._on_timestamp_column_setup)
|
||||
timestamp_factory.connect('bind',self._on_timestamp_column_bind)
|
||||
timestamp_column = Gtk.ColumnViewColumn.new("Timestamp",timestamp_factory)
|
||||
|
||||
self.__columnview = Gtk.ColumnView.new(selection)
|
||||
self.__columnview.append_column(live_column)
|
||||
self.__columnview.append_column(sgname_column)
|
||||
self.__columnview.append_column(timestamp_column)
|
||||
|
||||
self._on_gameview_selection_changed(selection)
|
||||
self.gameview._columnview.get_model().connect('selection-changed',self._on_gameview_selection_changed)
|
||||
|
||||
self.set_child(self.__columnview)
|
||||
|
||||
@property
|
||||
def gameview(self)->GameView:
|
||||
return self.__gameview
|
||||
|
||||
def _on_live_column_setup(self,factory,item):
|
||||
checkbutton = Gtk.CheckButton()
|
||||
checkbutton.set_sensitive(False)
|
||||
|
||||
def _on_live_column_bind(self,factory,item):
|
||||
checkbutton = item.get_child()
|
||||
data = item.get_item()
|
||||
checkbutton.set_active(data.is_live)
|
||||
|
||||
|
||||
def _on_savegamename_column_setup(self,factory,item):
|
||||
label = Gtk.Label()
|
||||
self.set_child(label)
|
||||
|
||||
def _on_savegamename_column_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
label.set_text(data.savegame_name)
|
||||
|
||||
def _on_timestamp_column_setup(self,factory,item):
|
||||
label = Gtk.Label()
|
||||
item.set_child(label)
|
||||
|
||||
def _on_timestamp_column_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
label.set_text(data.timestamp.strftime("%d.%m.%Y %H:%M:%S"))
|
||||
|
||||
def _on_size_column_setup(self,factory,item):
|
||||
label = Gtk.Label()
|
||||
item.set_child(label)
|
||||
|
||||
def _on_size_column_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
file = Path(data.filename).resolve()
|
||||
|
||||
if not file.is_file():
|
||||
label.set_text("0 B")
|
||||
return
|
||||
|
||||
size = file.stat().st_size
|
||||
if (size > 1073741824):
|
||||
display_size = ".".join((str(int(size / 1073741824)),str(int(((size * 10) / 1073741824) % 10)))) + " GiB"
|
||||
elif (size > 1048576):
|
||||
display_size = ".".join((str(int(size / 1048576)), str(int(((size * 10) / 1048576) % 10)))) + " MiB"
|
||||
elif (size > 1024):
|
||||
display_size = ".".join((str(int(size / 1024)), str(int(((size * 10) / 1024) % 10)))) + " KiB"
|
||||
else:
|
||||
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
|
||||
|
||||
|
||||
class AppWindow(Gtk.ApplicationWindow):
|
||||
__gtype_name__ = "AppWindow"
|
||||
def __init__(self,application=None,**kwargs):
|
||||
kwargs['title'] = "SGBackup"
|
||||
|
||||
if (application is not None):
|
||||
kwargs['application']=application
|
||||
if (hasattr(application,'builder')):
|
||||
builder = application.builder
|
||||
else:
|
||||
builder = Gtk.Builder.new()
|
||||
|
||||
Gtk.ApplicationWindow.__init__(self,**kwargs)
|
||||
self.set_default_size(800,600)
|
||||
self.set_icon_name('sgbackup')
|
||||
|
||||
self.__builder = builder
|
||||
self.builder.add_from_file(os.path.join(os.path.dirname(__file__),'appmenu.ui'))
|
||||
gmenu = self.builder.get_object('appmenu')
|
||||
appmenu_popover = Gtk.PopoverMenu.new_from_model(gmenu)
|
||||
image = Gtk.Image.new_from_icon_name('open-menu-symbolic')
|
||||
menubutton = Gtk.MenuButton.new()
|
||||
menubutton.set_popover(appmenu_popover)
|
||||
menubutton.set_child(image)
|
||||
headerbar = Gtk.HeaderBar.new()
|
||||
headerbar.pack_start(menubutton)
|
||||
self.set_titlebar(headerbar)
|
||||
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.__vpaned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.__vpaned.set_hexpand(True)
|
||||
self.__vpaned.set_vexpand(True)
|
||||
self.__vpaned.set_wide_handle(True)
|
||||
self.__gameview = GameView()
|
||||
self.__vpaned.set_start_child(self.gameview)
|
||||
self.__backupview = BackupView(self.gameview)
|
||||
self.__vpaned.set_end_child(self.backupview)
|
||||
self.__vpaned.set_resize_start_child(True)
|
||||
self.__vpaned.set_resize_end_child(True)
|
||||
|
||||
vbox.append(self.__vpaned)
|
||||
|
||||
statusbar = Gtk.Statusbar()
|
||||
statusbar.set_hexpand(True)
|
||||
statusbar.set_vexpand(False)
|
||||
statusbar.push(0,'Running ...')
|
||||
vbox.append(statusbar)
|
||||
|
||||
self.set_child(vbox)
|
||||
|
||||
@GObject.Property
|
||||
def builder(self):
|
||||
return self.__builder
|
||||
|
||||
@GObject.Property
|
||||
def backupview(self):
|
||||
return self.__backupview
|
||||
|
||||
@GObject.Property
|
||||
def gameview(self):
|
||||
return self.__gameview
|
||||
|
||||
|
||||
class Application(Gtk.Application):
|
||||
__gtype_name__ = "Application"
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
AppFlags = Gio.ApplicationFlags
|
||||
kwargs['application_id'] = 'org.sgbackup.sgbackup'
|
||||
kwargs['flags'] = AppFlags.FLAGS_NONE
|
||||
Gtk.Application.__init__(self,*args,**kwargs)
|
||||
|
||||
self.__logger = logger.getChild('Application')
|
||||
self.__builder = None
|
||||
self.__appwindow = None
|
||||
|
||||
@property
|
||||
def _logger(self):
|
||||
return self.__logger
|
||||
|
||||
@GObject.Property
|
||||
def appwindow(self):
|
||||
return self.__appwindow
|
||||
|
||||
def do_startup(self):
|
||||
self._logger.debug('do_startup()')
|
||||
if not self.__builder:
|
||||
self.__builder = Gtk.Builder.new()
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
pkg_path = Path(__file__).resolve()
|
||||
pkg_path = pkg_path.parent.parent
|
||||
icons_path = pkg_path / "icons"
|
||||
|
||||
theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
|
||||
theme.add_resource_path("/org/sgbackup/sgbackup/icons")
|
||||
theme.add_search_path(str(icons_path))
|
||||
|
||||
action_about = Gio.SimpleAction.new('about',None)
|
||||
action_about.connect('activate',self.on_action_about)
|
||||
self.add_action(action_about)
|
||||
|
||||
action_new_game = Gio.SimpleAction.new('new-game',None)
|
||||
action_new_game.connect('activate',self.on_action_new_game)
|
||||
self.add_action(action_new_game)
|
||||
|
||||
action_quit = Gio.SimpleAction.new('quit',None)
|
||||
action_quit.connect('activate',self.on_action_quit)
|
||||
self.add_action(action_quit)
|
||||
|
||||
action_settings = Gio.SimpleAction.new('settings',None)
|
||||
action_settings.connect('activate',self.on_action_settings)
|
||||
self.add_action(action_settings)
|
||||
|
||||
# add accels
|
||||
self.set_accels_for_action('app.quit',["<Primary>q"])
|
||||
|
||||
@GObject.Property
|
||||
def builder(self):
|
||||
return self.__builder
|
||||
|
||||
def do_activate(self):
|
||||
self._logger.debug('do_activate()')
|
||||
if not (self.__appwindow):
|
||||
self.__appwindow = AppWindow(application=self)
|
||||
|
||||
|
||||
self.appwindow.present()
|
||||
|
||||
def on_action_about(self,action,param):
|
||||
pass
|
||||
|
||||
def on_action_settings(self,action,param):
|
||||
dialog = self.new_settings_dialog()
|
||||
dialog.present()
|
||||
|
||||
def on_action_quit(self,action,param):
|
||||
self.quit()
|
||||
|
||||
def on_action_new_game(self,action,param):
|
||||
def on_reponse(dialog,response):
|
||||
dialog.destroy()
|
||||
|
||||
dialog = GameDialog(self.appwindow)
|
||||
dialog.connect('response',on_reponse)
|
||||
dialog.present()
|
||||
|
||||
def new_settings_dialog(self):
|
||||
dialog = SettingsDialog(self.appwindow)
|
||||
self.emit('settings-dialog-init',dialog)
|
||||
return dialog
|
||||
|
||||
@GObject.Signal(name='settings-dialog-init',
|
||||
flags=GObject.SignalFlags.RUN_LAST,
|
||||
return_type=None,
|
||||
arg_types=(SettingsDialog,))
|
||||
def settings_dialog_init(self,dialog):
|
||||
pass
|
||||
|
||||
675
sgbackup/gui/_gamedialog.py
Normal file
@ -0,0 +1,675 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import GObject,Gio,GLib,Gtk,Pango
|
||||
from ..game import Game,GameFileMatcher,GameFileType
|
||||
|
||||
class GameVariableData(GObject.GObject):
|
||||
def __init__(self,name:str,value:str):
|
||||
GObject.GObject.__init__(self)
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def name(self)->str:
|
||||
return self.__name
|
||||
@name.setter
|
||||
def name(self,name):
|
||||
self.__name = name
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def value(self)->str:
|
||||
return self.__value
|
||||
@value.setter
|
||||
def value(self,value:str):
|
||||
self.__value = value
|
||||
|
||||
class RegistryKeyData(GObject.GObject):
|
||||
def __init__(self,regkey=None):
|
||||
GObject.GObject.__init__(self)
|
||||
if not regkey:
|
||||
self.__regkey = ""
|
||||
|
||||
@GObject.Property(type=str)
|
||||
def regkey(self):
|
||||
return self.__regkey
|
||||
@regkey.setter
|
||||
def regkey(self,key:str):
|
||||
self.__regkey = key
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self.__regkey)
|
||||
|
||||
class GameVariableDialog(Gtk.Dialog):
|
||||
def __init__(self,parent:Gtk.Window,columnview:Gtk.ColumnView,variable:GameVariableData|None=None):
|
||||
Gtk.Dialog.__init__(self)
|
||||
self.set_transient_for(parent)
|
||||
self.set_default_size(600,-1)
|
||||
|
||||
self.__columnview = columnview
|
||||
|
||||
if variable:
|
||||
self.__variable = variable
|
||||
else:
|
||||
self.__variable = None
|
||||
|
||||
grid = Gtk.Grid()
|
||||
label = Gtk.Label.new("Name:")
|
||||
self.__name_entry = Gtk.Entry()
|
||||
self.__name_entry.set_hexpand(True)
|
||||
self.__name_entry.connect("changed",self._on_name_entry_changed)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(self.__name_entry,1,0,1,1)
|
||||
|
||||
label = Gtk.Label.new("Value")
|
||||
self.__value_entry = Gtk.Entry()
|
||||
self.__value_entry.set_hexpand(True)
|
||||
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(self.__value_entry,1,1,1,1)
|
||||
|
||||
self.get_content_area().append(grid)
|
||||
|
||||
if self.__variable:
|
||||
self.__name_entry.set_text(self.__variable.name)
|
||||
self.__value_entry.set_text(self.__variable.value)
|
||||
|
||||
self.__apply_button = self.add_button("Apply",Gtk.ResponseType.APPLY)
|
||||
self.__apply_button.set_sensitive(bool(self))
|
||||
|
||||
self.add_button("Cancel",Gtk.ResponseType.CANCEL)
|
||||
|
||||
def __bool__(self):
|
||||
name = self.__name_entry.get_text()
|
||||
if name:
|
||||
if self.__variable and self.__variable.name == name:
|
||||
return True
|
||||
model = self.__columnview.get_model().get_model()
|
||||
for i in range(model.get_n_items()):
|
||||
if name == model.get_item(i).name:
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_name_entry_changed(self,entry):
|
||||
self.__apply_button.set_sensitive(bool(self))
|
||||
|
||||
def do_response(self,response):
|
||||
if response == Gtk.ResponseType.APPLY:
|
||||
if not bool(self):
|
||||
return
|
||||
if self.__variable:
|
||||
self.__variable.name = self.__name_entry.get_text()
|
||||
self.__variable.value = self.__value_entry.get_text()
|
||||
else:
|
||||
model = self.__columnview.get_model().get_model()
|
||||
model.append(GameVariableData(self.__name_entry.get_text(),self.__value_entry.get_text()))
|
||||
self.hide()
|
||||
self.destroy()
|
||||
|
||||
class GameDialog(Gtk.Dialog):
|
||||
def __init__(self,
|
||||
parent:Gtk.Window|None=None,
|
||||
game:Game|None=Game):
|
||||
|
||||
Gtk.Dialog.__init__(self)
|
||||
if (parent):
|
||||
self.set_transient_for(parent)
|
||||
|
||||
if isinstance(game,Game):
|
||||
self.__game = game
|
||||
else:
|
||||
self.__game = None
|
||||
|
||||
self.set_default_size(800,600)
|
||||
|
||||
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
paned.set_position(200)
|
||||
|
||||
self.__stack = Gtk.Stack.new()
|
||||
paned.set_end_child(self.__stack)
|
||||
paned.set_hexpand(True)
|
||||
paned.set_vexpand(True)
|
||||
|
||||
self.__stack_sidebar = Gtk.ListBox()
|
||||
self.__stack_sidebar.set_activate_on_single_click(False)
|
||||
self.__stack_sidebar.set_selection_mode(Gtk.SelectionMode.SINGLE)
|
||||
self.__stack_sidebar.connect('selected_rows_changed',self._on_stack_sidebar_selected_rows_changed)
|
||||
|
||||
sidebar_scrolled = Gtk.ScrolledWindow()
|
||||
sidebar_scrolled.set_child(self.__stack_sidebar)
|
||||
paned.set_start_child(sidebar_scrolled)
|
||||
|
||||
self.__variable_name_factory = Gtk.SignalListItemFactory()
|
||||
self.__variable_name_factory.connect('setup',self._on_variable_name_setup)
|
||||
self.__variable_name_factory.connect('bind',self._on_variable_name_bind)
|
||||
|
||||
self.__variable_value_factory = Gtk.SignalListItemFactory()
|
||||
self.__variable_value_factory.connect('setup',self._on_variable_value_setup)
|
||||
self.__variable_value_factory.connect('bind',self._on_variable_value_bind)
|
||||
|
||||
self.__add_game_page()
|
||||
self.__windows = self.__create_windows_page()
|
||||
self.__linux = self.__create_linux_page()
|
||||
self.__macos = self.__create_macos_page()
|
||||
self.__steam_windows = self.__create_steam_page('steam-windows','Steam Windows')
|
||||
self.__steam_linux = self.__create_steam_page('steam-linux','Steam Linux')
|
||||
self.__steam_macos = self.__create_steam_page('steam-macos','Steam MacOS')
|
||||
|
||||
|
||||
for stack_page in self.__stack.get_pages():
|
||||
hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,4)
|
||||
label = Gtk.Label.new(stack_page.props.title)
|
||||
attrs = Pango.AttrList.new()
|
||||
size_attr = Pango.AttrSize.new(14 * Pango.SCALE)
|
||||
attrs.insert(size_attr)
|
||||
label.set_attributes(attrs)
|
||||
icon = Gtk.Image.new_from_icon_name(stack_page.props.icon_name)
|
||||
icon.set_pixel_size(20)
|
||||
hbox.append(icon)
|
||||
hbox.append(label)
|
||||
hbox.page_name = stack_page.props.name
|
||||
|
||||
self.__stack_sidebar.append(hbox)
|
||||
|
||||
self.reset()
|
||||
self.__stack_sidebar.select_row(self.__stack_sidebar.get_row_at_index(0))
|
||||
|
||||
self.get_content_area().append(paned)
|
||||
self.add_button("Apply",Gtk.ResponseType.APPLY)
|
||||
self.add_button("Cancel",Gtk.ResponseType.CANCEL)
|
||||
|
||||
|
||||
def __add_game_page(self):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
self.__set_widget_margin(vbox,5)
|
||||
|
||||
grid = Gtk.Grid.new()
|
||||
|
||||
label = Gtk.Label.new("Is active?")
|
||||
self.__active_switch = Gtk.Switch()
|
||||
entry_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,5)
|
||||
entry_hbox.append(self.__active_switch)
|
||||
entry_hbox.append(label)
|
||||
vbox.append(entry_hbox)
|
||||
|
||||
label = Gtk.Label.new("Is live?")
|
||||
self.__live_switch = Gtk.Switch()
|
||||
entry_hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,5)
|
||||
|
||||
entry_hbox.append(self.__live_switch)
|
||||
entry_hbox.append(label)
|
||||
vbox.append(entry_hbox)
|
||||
|
||||
label = Gtk.Label.new("ID:")
|
||||
self.__id_label = Gtk.Label()
|
||||
self.__id_label.set_hexpand(True)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(self.__id_label,1,0,1,1)
|
||||
|
||||
label = Gtk.Label.new("Key:")
|
||||
self.__key_entry = Gtk.Entry()
|
||||
self.__key_entry.set_hexpand(True)
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(self.__key_entry,1,1,1,1)
|
||||
|
||||
label = Gtk.Label.new("Game name:")
|
||||
self.__name_entry = Gtk.Entry()
|
||||
self.__name_entry.set_hexpand(True)
|
||||
grid.attach(label,0,2,1,1)
|
||||
grid.attach(self.__name_entry,1,2,1,1)
|
||||
|
||||
label = Gtk.Label.new("Savegame name:")
|
||||
self.__sgname_entry = Gtk.Entry()
|
||||
self.__sgname_entry.set_hexpand(True)
|
||||
grid.attach(label,0,3,1,1)
|
||||
grid.attach(self.__sgname_entry,1,3,1,1)
|
||||
vbox.append(grid)
|
||||
|
||||
self.__game_variables = self.__create_variables_widget()
|
||||
|
||||
vbox.append(self.__game_variables)
|
||||
|
||||
page.set_child(vbox)
|
||||
self.__stack.add_titled(page,"main","Game")
|
||||
stack_page = self.__stack.get_page(page)
|
||||
stack_page.set_icon_name('sgbackup')
|
||||
|
||||
|
||||
def __create_windows_page(self):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
self.__set_widget_margin(vbox,5)
|
||||
|
||||
grid = Gtk.Grid()
|
||||
|
||||
label = Gtk.Label.new("Root directory:")
|
||||
page.sgroot_entry = Gtk.Entry()
|
||||
page.sgroot_entry.set_hexpand(True)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(page.sgroot_entry,1,0,1,1)
|
||||
|
||||
label = Gtk.Label.new("Backup directory:")
|
||||
page.sgdir_entry = Gtk.Entry()
|
||||
page.sgdir_entry.set_hexpand(True)
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(page.sgdir_entry,1,1,1,1)
|
||||
|
||||
vbox.append(grid)
|
||||
|
||||
page.lookup_regkeys = self.__create_registry_key_widget("Lookup Registry keys")
|
||||
vbox.append(page.lookup_regkeys)
|
||||
|
||||
page.installdir_regkeys = self.__create_registry_key_widget("Installations directory Registry keys")
|
||||
vbox.append(page.installdir_regkeys)
|
||||
|
||||
page.variables = self.__create_variables_widget()
|
||||
vbox.append(page.variables)
|
||||
|
||||
page.set_child(vbox)
|
||||
self.__stack.add_titled(page,"windows","Windows")
|
||||
stack_page = self.__stack.get_page(page)
|
||||
stack_page.set_icon_name("windows-svgrepo-com")
|
||||
|
||||
return page
|
||||
|
||||
|
||||
def __create_linux_page(self):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
self.__set_widget_margin(vbox,5)
|
||||
|
||||
grid = Gtk.Grid()
|
||||
label = Gtk.Label.new("Root directory:")
|
||||
page.sgroot_entry = Gtk.Entry()
|
||||
page.sgroot_entry.set_hexpand(True)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(page.sgroot_entry,1,0,1,1)
|
||||
|
||||
label = Gtk.Label.new("Backup directory:")
|
||||
page.sgdir_entry = Gtk.Entry()
|
||||
page.sgdir_entry.set_hexpand(True)
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(page.sgdir_entry,1,1,1,1)
|
||||
|
||||
label = Gtk.Label.new("Executable")
|
||||
page.binary_entry = Gtk.Entry()
|
||||
page.binary_entry.set_hexpand(True)
|
||||
grid.attach(label,0,2,1,1)
|
||||
grid.attach(page.binary_entry,1,2,1,1)
|
||||
vbox.append(grid)
|
||||
|
||||
page.variables = self.__create_variables_widget()
|
||||
vbox.append(page.variables)
|
||||
|
||||
page.set_child(vbox)
|
||||
self.__stack.add_titled(page,"linux","Linux")
|
||||
stack_page = self.__stack.get_page(page)
|
||||
stack_page.set_icon_name("linux-svgrepo-com")
|
||||
|
||||
return page
|
||||
|
||||
def __create_macos_page(self):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
self.__set_widget_margin(vbox,5)
|
||||
|
||||
grid = Gtk.Grid()
|
||||
label = Gtk.Label.new("Root directory:")
|
||||
page.sgroot_entry = Gtk.Entry()
|
||||
page.sgroot_entry.set_hexpand(True)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(page.sgroot_entry,1,0,1,1)
|
||||
|
||||
label = Gtk.Label.new("Backup directory:")
|
||||
page.sgdir_entry = Gtk.Entry()
|
||||
page.sgdir_entry.set_hexpand(True)
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(page.sgdir_entry,1,1,1,1)
|
||||
|
||||
label = Gtk.Label.new("Executable")
|
||||
page.binary_entry = Gtk.Entry()
|
||||
page.binary_entry.set_hexpand(True)
|
||||
grid.attach(label,0,2,1,1)
|
||||
grid.attach(page.binary_entry,1,2,1,1)
|
||||
vbox.append(grid)
|
||||
|
||||
page.variables = self.__create_variables_widget()
|
||||
vbox.append(page.variables)
|
||||
|
||||
page.set_child(vbox)
|
||||
self.__stack.add_titled(page,"macos","MacOS")
|
||||
stack_page = self.__stack.get_page(page)
|
||||
stack_page.set_icon_name("apple-svgrepo-com")
|
||||
|
||||
return page
|
||||
|
||||
def __create_steam_page(self,name,title):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
self.__set_widget_margin(vbox,5)
|
||||
|
||||
grid = Gtk.Grid()
|
||||
|
||||
label = Gtk.Label.new("App ID:")
|
||||
page.appid_entry = Gtk.Entry()
|
||||
page.appid_entry.set_hexpand(True)
|
||||
grid.attach(label,0,0,1,1)
|
||||
grid.attach(page.appid_entry,1,0,1,1)
|
||||
vbox.append(grid)
|
||||
|
||||
|
||||
label = Gtk.Label.new("Root directory:")
|
||||
page.sgroot_entry = Gtk.Entry()
|
||||
page.sgroot_entry.set_hexpand(True)
|
||||
grid.attach(label,0,1,1,1)
|
||||
grid.attach(page.sgroot_entry,1,1,1,1)
|
||||
|
||||
label = Gtk.Label.new("Backup directory:")
|
||||
page.sgdir_entry = Gtk.Entry()
|
||||
page.sgdir_entry.set_hexpand(True)
|
||||
grid.attach(label,0,2,1,1)
|
||||
grid.attach(page.sgdir_entry,1,2,1,1)
|
||||
|
||||
page.variables = self.__create_variables_widget()
|
||||
vbox.append(page.variables)
|
||||
|
||||
page.set_child(vbox)
|
||||
self.__stack.add_titled(page,name,title)
|
||||
stack_page = self.__stack.get_page(page)
|
||||
stack_page.set_icon_name("steam-svgrepo-com")
|
||||
|
||||
return page
|
||||
def __set_widget_margin(self,widget,margin):
|
||||
widget.set_margin_start(margin)
|
||||
widget.set_margin_end(margin)
|
||||
widget.set_margin_top(margin)
|
||||
widget.set_margin_bottom(margin)
|
||||
|
||||
def __create_variables_widget(self):
|
||||
widget = Gtk.Frame.new("Variables")
|
||||
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,0)
|
||||
|
||||
model = Gio.ListStore.new(GameVariableData)
|
||||
selection = Gtk.SingleSelection.new(model)
|
||||
selection.set_autoselect(False)
|
||||
selection.set_can_unselect(True)
|
||||
|
||||
widget.columnview = Gtk.ColumnView.new(selection)
|
||||
widget.columnview.set_vexpand(True)
|
||||
|
||||
widget.actions = Gtk.ActionBar()
|
||||
icon = Gtk.Image.new_from_icon_name("list-add-symbolic")
|
||||
widget.add_button = Gtk.Button()
|
||||
widget.add_button.set_child(icon)
|
||||
widget.add_button.connect('clicked',
|
||||
self._on_variables_add_button_clicked,
|
||||
widget.columnview)
|
||||
widget.actions.pack_start(widget.add_button)
|
||||
|
||||
icon = Gtk.Image.new_from_icon_name("document-edit-symbolic")
|
||||
widget.edit_button = Gtk.Button()
|
||||
widget.edit_button.set_child(icon)
|
||||
widget.edit_button.set_sensitive(False)
|
||||
widget.edit_button.connect('clicked',
|
||||
self._on_variables_edit_buton_clicked,
|
||||
widget.columnview)
|
||||
widget.actions.pack_start(widget.edit_button)
|
||||
|
||||
icon = Gtk.Image.new_from_icon_name("list-remove-symbolic")
|
||||
widget.remove_button = Gtk.Button()
|
||||
widget.remove_button.set_child(icon)
|
||||
widget.remove_button.set_sensitive(False)
|
||||
widget.remove_button.connect('clicked',
|
||||
self._on_variables_remove_button_clicked,
|
||||
widget.columnview)
|
||||
widget.actions.pack_start(widget.remove_button)
|
||||
|
||||
name_column = Gtk.ColumnViewColumn.new("Name",self.__variable_name_factory)
|
||||
name_column.set_expand(True)
|
||||
widget.columnview.append_column(name_column)
|
||||
|
||||
value_column = Gtk.ColumnViewColumn.new("Value",self.__variable_value_factory)
|
||||
value_column.set_expand(True)
|
||||
widget.columnview.append_column(value_column)
|
||||
|
||||
selection.connect('selection-changed',self._on_variable_selection_changed,widget)
|
||||
|
||||
vbox.append(widget.actions)
|
||||
vbox.append(widget.columnview)
|
||||
|
||||
widget.set_child(vbox)
|
||||
return widget
|
||||
|
||||
def __create_registry_key_widget(self,title):
|
||||
widget = Gtk.Frame.new(title)
|
||||
vbox=Gtk.Box.new(Gtk.Orientation.VERTICAL,2)
|
||||
|
||||
widget.actions = Gtk.ActionBar()
|
||||
icon = Gtk.Image.new_from_icon_name("list-add-symbolic")
|
||||
button = Gtk.Button.new()
|
||||
button.set_child(icon)
|
||||
button.connect('clicked',self._on_windows_regkey_add_button_clicked,widget)
|
||||
widget.actions.pack_start(button)
|
||||
|
||||
model = Gio.ListStore.new(RegistryKeyData)
|
||||
selection = Gtk.SingleSelection.new(model)
|
||||
selection.set_autoselect(False)
|
||||
selection.set_can_unselect(True)
|
||||
|
||||
factory = Gtk.SignalListItemFactory()
|
||||
factory.connect('setup',self._on_windows_regkey_setup)
|
||||
factory.connect('bind',self._on_windows_regkey_bind,widget)
|
||||
|
||||
widget.listview = Gtk.ListView.new(selection,factory)
|
||||
|
||||
vbox.append(widget.actions)
|
||||
vbox.append(widget.listview)
|
||||
widget.set_child(vbox)
|
||||
|
||||
return widget
|
||||
|
||||
def reset(self):
|
||||
self.__active_switch.set_active(True)
|
||||
self.__live_switch.set_active(True)
|
||||
self.__name_entry.set_text("")
|
||||
self.__sgname_entry.set_text("")
|
||||
self.__game_variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
#windows
|
||||
self.__windows.sgroot_entry.set_text("")
|
||||
self.__windows.sgdir_entry.set_text("")
|
||||
self.__windows.variables.columnview.get_model().get_model().remove_all()
|
||||
self.__windows.lookup_regkeys.listview.get_model().get_model().remove_all()
|
||||
self.__windows.installdir_regkeys.listview.get_model().get_model().remove_all()
|
||||
|
||||
#linux
|
||||
self.__linux.sgroot_entry.set_text("")
|
||||
self.__linux.sgdir_entry.set_text("")
|
||||
self.__linux.binary_entry.set_text("")
|
||||
self.__linux.variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
#linux
|
||||
self.__macos.sgroot_entry.set_text("")
|
||||
self.__macos.sgdir_entry.set_text("")
|
||||
self.__macos.binary_entry.set_text("")
|
||||
self.__macos.variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
#steam windows
|
||||
self.__steam_windows.sgroot_entry.set_text("")
|
||||
self.__steam_windows.sgdir_entry.set_text("")
|
||||
self.__steam_windows.appid_entry.set_text("")
|
||||
self.__steam_windows.variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
#steam linux
|
||||
self.__steam_linux.sgroot_entry.set_text("")
|
||||
self.__steam_linux.sgdir_entry.set_text("")
|
||||
self.__steam_linux.appid_entry.set_text("")
|
||||
self.__steam_linux.variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
#steam macos
|
||||
self.__steam_macos.sgroot_entry.set_text("")
|
||||
self.__steam_macos.sgdir_entry.set_text("")
|
||||
self.__steam_macos.appid_entry.set_text("")
|
||||
self.__steam_macos.variables.columnview.get_model().get_model().remove_all()
|
||||
|
||||
if self.__game:
|
||||
self.__active_switch.set_active(self.__game.is_active)
|
||||
self.__live_switch.set_active(self.__game.is_live)
|
||||
self.__name_entry.set_text(self.__game.name)
|
||||
self.__sgname_entry.set_text(self.__game.savegame_name)
|
||||
for name,value in self.__game.variables.items():
|
||||
self.__game_variables.get_model().get_model().append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.windows:
|
||||
self.__windows.sgroot_entry.set_text(self.__game.windows.savegame_root)
|
||||
self.__windows.sgdir_entry.set_text(self.__game.windows.savegame_dir)
|
||||
|
||||
# set lookup regkeys
|
||||
var_model = self.__windows.variables.columnview.get_model().get_model()
|
||||
grk_model = self.__windows.lookup_regkeys.listview.get_model().get_model()
|
||||
irk_model = self.__windows.installdir_regkeys.listview.get_model().get_model()
|
||||
for rk in self.__game.windows.game_registry_keys:
|
||||
grk_model.append(RegistryKeyData(rk))
|
||||
|
||||
#set installdir regkeys
|
||||
for rk in self.__game.windows.installdir_registry_keys:
|
||||
irk_model.append(RegistryKeyData(rk))
|
||||
|
||||
#set variables
|
||||
for name,value in self.__game.windows.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.linux:
|
||||
self.__linux.sgroot_entry.set_text(self.__game.linux.savegame_root)
|
||||
self.__linux.sgdir_entry.set_text(self.__game.linux.savegame_dir)
|
||||
self.__linux.binary_entry.set_text(self.__game.linux.binary)
|
||||
var_model = self.__linux.variables.columnview.get_model().get_model()
|
||||
for name,value in self.__game.linux.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.macos:
|
||||
self.__macos.sgroot_entry.set_text(self.__game.linux.savegame_root)
|
||||
self.__macos.sgdir_entry.set_text(self.__game.linux.savegame_dir)
|
||||
self.__macos.binary_entry.set_text(self.__game.linux.binary)
|
||||
var_model = self.__macos.variables.columnview.get_model().get_model()
|
||||
for name,value in self.__game.linux.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.steam_windows:
|
||||
self.__steam_windows.sgroot_entry.set_text(self.__game.steam_windows.savegame_root)
|
||||
self.__steam_windows.sgdir_entry.set_text(self.__game.steam_windows.savegame_dir)
|
||||
self.__steam_windows.appid_entry.set_text(self.__game.steam_windows.appid)
|
||||
var_model = self.__steam_windows.variables.columnview.get_model().get_model()
|
||||
for name,value in self.__game.steam_windows.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.steam_linux:
|
||||
self.__steam_linux.sgroot_entry.set_text(self.__game.steam_linux.savegame_root)
|
||||
self.__steam_linux.sgdir_entry.set_text(self.__game.steam_linux.savegame_dir)
|
||||
self.__steam_linux.appid_entry.set_text(self.__game.steam_linux.appid)
|
||||
var_model = self.__steam_linux.variables.columnview.get_model().get_model()
|
||||
for name,value in self.__game.steam_linux.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
|
||||
if self.__game.steam_macos:
|
||||
self.__steam_macos.sgroot_entry.set_text(self.__game.steam_macos.savegame_root)
|
||||
self.__steam_macos.sgdir_entry.set_text(self.__game.steam_macos.savegame_dir)
|
||||
self.__steam_macos.appid_entry.set_text(self.__game.steam_macos.appid)
|
||||
var_model = self.__steam_macos.variables.columnview.get_model().get_model()
|
||||
for name,value in self.__game.steam_macos.variables.items():
|
||||
var_model.append(GameVariableData(name,value))
|
||||
# reset()
|
||||
|
||||
def _on_variable_name_setup(self,factory,item):
|
||||
label = Gtk.Label()
|
||||
item.set_child(label)
|
||||
|
||||
def _on_variable_name_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
data.bind_property('name',label,'label',GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
def _on_variable_value_setup(self,factory,item):
|
||||
label = Gtk.Label()
|
||||
item.set_child(label)
|
||||
|
||||
def _on_variable_value_bind(self,factory,item):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
data.bind_property('value',label,'label',GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
def _on_windows_regkey_setup(self,factory,item):
|
||||
label = Gtk.EditableLabel()
|
||||
item.set_child(label)
|
||||
|
||||
def _on_windows_regkey_bind(self,factory,item,widget):
|
||||
label = item.get_child()
|
||||
data = item.get_item()
|
||||
label.set_text(data.regkey)
|
||||
label.bind_property('text',data,'regkey',GObject.BindingFlags.DEFAULT)
|
||||
label.connect('changed',self._on_windows_regkey_label_changed,widget)
|
||||
if not label.get_text():
|
||||
label.start_editing()
|
||||
label.grab_focus()
|
||||
|
||||
def _on_stack_sidebar_selected_rows_changed(self,sidebar):
|
||||
row = sidebar.get_selected_row()
|
||||
self.__stack.set_visible_child_name(row.get_child().page_name)
|
||||
|
||||
def _on_variables_add_button_clicked(self,button,columnview):
|
||||
dialog = GameVariableDialog(self,columnview)
|
||||
dialog.present()
|
||||
|
||||
def _on_variables_remove_button_clicked(self,button,columnview):
|
||||
selection = columnview.get_model()
|
||||
model = selection.get_model()
|
||||
selected = selection.get_selected()
|
||||
if selected == Gtk.INVALID_LIST_POSITION:
|
||||
return
|
||||
model.remove(selected)
|
||||
|
||||
def _on_variables_edit_buton_clicked(self,button,columnview):
|
||||
data = columnview.get_model().get_selected()
|
||||
if data:
|
||||
dialog = GameVariableDialog(self,columnview,data)
|
||||
dialog.present()
|
||||
|
||||
def _on_variable_selection_changed(self,selection,position,n_items,var_widget):
|
||||
if (selection.get_model().get_n_items() == 0) or (selection.get_selected() == Gtk.INVALID_LIST_POSITION):
|
||||
var_widget.edit_button.set_sensitive(False)
|
||||
var_widget.remove_button.set_sensitive(False)
|
||||
else:
|
||||
var_widget.edit_button.set_sensitive(True)
|
||||
var_widget.remove_button.set_sensitive(True)
|
||||
|
||||
def _on_windows_regkey_add_button_clicked(self,button,widget):
|
||||
widget.listview.get_model().get_model().append(RegistryKeyData())
|
||||
|
||||
def _on_windows_regkey_label_changed(self,label,widget):
|
||||
if not label.get_text():
|
||||
model = widget.listview.get_model().get_model()
|
||||
i = 0
|
||||
while i < model.get_n_items():
|
||||
item = model.get_item(i)
|
||||
if not item.regkey:
|
||||
model.remove(i)
|
||||
continue
|
||||
i += 1
|
||||
|
||||
97
sgbackup/gui/_settingsdialog.py
Normal file
@ -0,0 +1,97 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,GLib,Gio
|
||||
from gi.repository.GObject import GObject,Signal,Property
|
||||
|
||||
from ..settings import settings
|
||||
|
||||
class SettingsDialog(Gtk.Dialog):
|
||||
def __init__(self,parent=None):
|
||||
Gtk.Dialog.__init__(self)
|
||||
if parent:
|
||||
self.set_transient_for(parent)
|
||||
self.set_default_size(800,600)
|
||||
vbox = self.get_content_area()
|
||||
paned = Gtk.Paned(orientation=Gtk.Orientation.HORIZONTAL)
|
||||
paned.set_position(250)
|
||||
|
||||
self.__stack = Gtk.Stack()
|
||||
self.__stack_sidebar = Gtk.StackSidebar.new()
|
||||
self.__add_general_settings_page()
|
||||
|
||||
paned.set_start_child(self.__stack_sidebar)
|
||||
paned.set_end_child(self.__stack)
|
||||
paned.set_vexpand(True)
|
||||
self.__stack_sidebar.set_stack(self.__stack)
|
||||
|
||||
vbox.append(paned)
|
||||
|
||||
self.add_button("Apply",Gtk.ResponseType.APPLY)
|
||||
self.add_button("Cancel",Gtk.ResponseType.CANCEL)
|
||||
|
||||
def __add_general_settings_page(self):
|
||||
page = Gtk.ScrolledWindow()
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
grid = Gtk.Grid()
|
||||
|
||||
label = Gtk.Label.new('Backup directory: ')
|
||||
grid.attach(label,0,0,1,1)
|
||||
self.__backupdir_label = Gtk.Label.new(settings.backup_dir)
|
||||
self.__backupdir_label.set_hexpand(True)
|
||||
grid.attach(self.__backupdir_label,1,0,1,1)
|
||||
img = Gtk.Image.new_from_icon_name('document-open-symbolic')
|
||||
img.set_pixel_size(16)
|
||||
backupdir_button = Gtk.Button()
|
||||
backupdir_button.set_child(img)
|
||||
backupdir_button.connect('clicked',self._on_backupdir_button_clicked)
|
||||
grid.attach(backupdir_button,2,0,1,1)
|
||||
|
||||
vbox.append(grid)
|
||||
page.set_child(vbox)
|
||||
|
||||
self.add_page(page,"general","Generic settings")
|
||||
|
||||
def _on_backupdir_dialog_select_folder(self,dialog,result,*data):
|
||||
try:
|
||||
dir = dialog.select_folder_finish(result)
|
||||
if dir is not None:
|
||||
self.__backupdir_label.set_text(dir.get_path())
|
||||
except:
|
||||
pass
|
||||
|
||||
def _on_backupdir_button_clicked(self,button):
|
||||
dialog = Gtk.FileDialog.new()
|
||||
dialog.set_title("sgbackup: Choose backup folder")
|
||||
dialog.select_folder(self,None,self._on_backupdir_dialog_select_folder)
|
||||
|
||||
|
||||
|
||||
def add_page(self,page,name,title):
|
||||
self.__stack.add_titled(page,name,title)
|
||||
|
||||
def do_response(self,response):
|
||||
if response == Gtk.ResponseType.APPLY:
|
||||
self.emit('save')
|
||||
settings.save()
|
||||
self.destroy()
|
||||
|
||||
@Signal(name='save')
|
||||
def do_save(self):
|
||||
settings.backup_dir = self.__backupdir_label.get_text()
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,GObject,Gio
|
||||
from .appwindow import AppWindow
|
||||
|
||||
import logging; logger=logging.getLogger(__name__)
|
||||
|
||||
class Application(Gtk.Application):
|
||||
def __init__(self,*args,**kwargs):
|
||||
AppFlags = Gio.ApplicationFlags
|
||||
kwargs['application_id'] = 'org.sgbackup.sgbackup'
|
||||
kwargs['flags'] = AppFlags.FLAGS_NONE
|
||||
Gtk.Application.__init__(self,*args,**kwargs)
|
||||
|
||||
self.__logger = logger.getChild('Application')
|
||||
self.__builder = None
|
||||
self.__appwindow = None
|
||||
|
||||
@property
|
||||
def _logger(self):
|
||||
return self.__logger
|
||||
|
||||
@GObject.Property
|
||||
def appwindow(self):
|
||||
return self.__appwindow
|
||||
|
||||
def do_startup(self):
|
||||
self._logger.debug('do_startup()')
|
||||
if not self.__builder:
|
||||
self.__builder = Gtk.Builder.new()
|
||||
|
||||
Gtk.Application.do_startup(self)
|
||||
|
||||
action_about = Gio.SimpleAction.new('about',None)
|
||||
action_about.connect('activate',self.on_action_about)
|
||||
self.add_action(action_about)
|
||||
|
||||
action_quit = Gio.SimpleAction.new('quit',None)
|
||||
action_quit.connect('activate',self.on_action_quit)
|
||||
self.add_action(action_quit)
|
||||
|
||||
action_settings = Gio.SimpleAction.new('settings',None)
|
||||
action_settings.connect('activate',self.on_action_settings)
|
||||
self.add_action(action_settings)
|
||||
|
||||
# add accels
|
||||
self.set_accels_for_action('app.quit',["<Primary>q"])
|
||||
|
||||
@GObject.Property
|
||||
def builder(self):
|
||||
return self.__builder
|
||||
|
||||
def do_activate(self):
|
||||
self._logger.debug('do_activate()')
|
||||
if not (self.__appwindow):
|
||||
self.__appwindow = AppWindow(application=self)
|
||||
|
||||
self.appwindow.present()
|
||||
|
||||
def on_action_about(self,action,param):
|
||||
pass
|
||||
|
||||
def on_action_settings(self,action,param):
|
||||
pass
|
||||
|
||||
def on_action_quit(self,action,param):
|
||||
self.quit()
|
||||
@ -1,6 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<interface>
|
||||
<menu id='appmenu'>
|
||||
<section>
|
||||
<submenu>
|
||||
<attribute name='label' translatable='true'>_Game</attribute>
|
||||
|
||||
<item>
|
||||
<attribute name='label' translatable='true'>_Add Game</attribute>
|
||||
<attribute name='action'>app.new-game</attribute>
|
||||
</item>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name='label' translatable='true'>_Steam</attribute>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name='label' translatable='true'>_Epic</attribute>
|
||||
</submenu>
|
||||
<submenu>
|
||||
<attribute name='label' translatable='true'>_GoG</attribute>
|
||||
</submenu>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name='label' translatable='true'>_Settings</attribute>
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,Gio,GObject
|
||||
|
||||
import os
|
||||
from .gameview import GameView
|
||||
from .backupview import BackupView
|
||||
|
||||
class AppWindow(Gtk.ApplicationWindow):
|
||||
def __init__(self,application=None,**kwargs):
|
||||
kwargs['title'] = "SGBackup"
|
||||
|
||||
if (application is not None):
|
||||
kwargs['application']=application
|
||||
if (hasattr(application,'builder')):
|
||||
builder = application.builder
|
||||
else:
|
||||
builder = Gtk.Builder.new()
|
||||
|
||||
Gtk.ApplicationWindow.__init__(self,**kwargs)
|
||||
self.set_default_size(800,600)
|
||||
|
||||
self.__builder = builder
|
||||
self.builder.add_from_file(os.path.join(os.path.dirname(__file__),'appmenu.ui'))
|
||||
gmenu = self.builder.get_object('appmenu')
|
||||
appmenu_popover = Gtk.PopoverMenu.new_from_model(gmenu)
|
||||
image = Gtk.Image.new_from_icon_name('open-menu-symbolic')
|
||||
menubutton = Gtk.MenuButton.new()
|
||||
menubutton.set_popover(appmenu_popover)
|
||||
menubutton.set_child(image)
|
||||
headerbar = Gtk.HeaderBar.new()
|
||||
headerbar.pack_start(menubutton)
|
||||
self.set_titlebar(headerbar)
|
||||
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||
|
||||
self.__vpaned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
|
||||
self.__vpaned.set_hexpand(True)
|
||||
self.__vpaned.set_vexpand(True)
|
||||
self.__vpaned.set_wide_handle(True)
|
||||
self.__gameview = GameView()
|
||||
self.__vpaned.set_start_child(self.gameview)
|
||||
self.__backupview = BackupView(self.gameview)
|
||||
self.__vpaned.set_end_child(self.backupview)
|
||||
self.__vpaned.set_resize_start_child(True)
|
||||
self.__vpaned.set_resize_end_child(True)
|
||||
|
||||
vbox.append(self.__vpaned)
|
||||
|
||||
statusbar = Gtk.Statusbar()
|
||||
statusbar.set_hexpand(True)
|
||||
statusbar.set_vexpand(False)
|
||||
statusbar.push(0,'Running ...')
|
||||
vbox.append(statusbar)
|
||||
|
||||
self.set_child(vbox)
|
||||
|
||||
@GObject.Property
|
||||
def builder(self):
|
||||
return self.__builder
|
||||
|
||||
@GObject.Property
|
||||
def backupview(self):
|
||||
return self.__backupview
|
||||
|
||||
@GObject.Property
|
||||
def gameview(self):
|
||||
return self.__gameview
|
||||
|
||||
|
||||
@ -1,30 +0,0 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,Gio,GObject
|
||||
|
||||
from .gameview import GameView
|
||||
|
||||
class BackupView(Gtk.ScrolledWindow):
|
||||
def __init__(self,gameview:GameView):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
self.__gameview = GameView
|
||||
|
||||
@GObject.Property
|
||||
def gameview(self):
|
||||
return self.__gameview
|
||||
@ -1,24 +0,0 @@
|
||||
###############################################################################
|
||||
# sgbackup - The SaveGame Backup tool #
|
||||
# Copyright (C) 2024 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 <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
from gi.repository import Gtk,Gio,GObject
|
||||
|
||||
class GameView(Gtk.ScrolledWindow):
|
||||
def __init__(self):
|
||||
Gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
BIN
sgbackup/icons/hicolor/128x128/apps/sgbackup.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
sgbackup/icons/hicolor/256x256/apps/sgbackup.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
sgbackup/icons/hicolor/32x32/apps/sgbackup.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
sgbackup/icons/hicolor/32x32/apps/windows.png
Normal file
|
After Width: | Height: | Size: 782 B |
BIN
sgbackup/icons/hicolor/512x512/apps/sgbackup.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
sgbackup/icons/hicolor/64x64/apps/sgbackup.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
21
sgbackup/icons/hicolor/org.sgabackup.sgbackup.gresource.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/org/sgbackup/sgbackup/icons/32x32/apps">
|
||||
<file>sgbackup.png</file>
|
||||
</gresource>
|
||||
<gresource prefix="/org/sgbackup/sgbacukp/icons/64x64/apps">
|
||||
<file>sgbackup.png</file>
|
||||
</gresource>
|
||||
<gresource prefix="/org/sgbackup/sgbackup/icons/128x128/apps">
|
||||
<file>sgbackup.png</file>
|
||||
</gresource>
|
||||
<gresource prefix="/org/sgbackup/sgbackup/icons/256x256/apps">
|
||||
<file>sgbackup.png</file>
|
||||
</gresource>
|
||||
<gresource prefix="/org/sgbackup/sgbackup/icons/512x512/apps">
|
||||
<file>sgbackup.png</file>
|
||||
</gresource>
|
||||
<gresource prefix="/org/sgbackup/sgbackup/icons/scalable/apps">
|
||||
<file>icons8-windows-10.svg</file>
|
||||
</gresource>
|
||||
</gresources>
|
||||
19
sgbackup/icons/hicolor/scalable/apps/apple-svgrepo-com.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 305 305" xml:space="preserve">
|
||||
<g id="XMLID_228_">
|
||||
<path id="XMLID_229_" d="M40.738,112.119c-25.785,44.745-9.393,112.648,19.121,153.82C74.092,286.523,88.502,305,108.239,305
|
||||
c0.372,0,0.745-0.007,1.127-0.022c9.273-0.37,15.974-3.225,22.453-5.984c7.274-3.1,14.797-6.305,26.597-6.305
|
||||
c11.226,0,18.39,3.101,25.318,6.099c6.828,2.954,13.861,6.01,24.253,5.815c22.232-0.414,35.882-20.352,47.925-37.941
|
||||
c12.567-18.365,18.871-36.196,20.998-43.01l0.086-0.271c0.405-1.211-0.167-2.533-1.328-3.066c-0.032-0.015-0.15-0.064-0.183-0.078
|
||||
c-3.915-1.601-38.257-16.836-38.618-58.36c-0.335-33.736,25.763-51.601,30.997-54.839l0.244-0.152
|
||||
c0.567-0.365,0.962-0.944,1.096-1.606c0.134-0.661-0.006-1.349-0.386-1.905c-18.014-26.362-45.624-30.335-56.74-30.813
|
||||
c-1.613-0.161-3.278-0.242-4.95-0.242c-13.056,0-25.563,4.931-35.611,8.893c-6.936,2.735-12.927,5.097-17.059,5.097
|
||||
c-4.643,0-10.668-2.391-17.645-5.159c-9.33-3.703-19.905-7.899-31.1-7.899c-0.267,0-0.53,0.003-0.789,0.008
|
||||
C78.894,73.643,54.298,88.535,40.738,112.119z"/>
|
||||
<path id="XMLID_230_" d="M212.101,0.002c-15.763,0.642-34.672,10.345-45.974,23.583c-9.605,11.127-18.988,29.679-16.516,48.379
|
||||
c0.155,1.17,1.107,2.073,2.284,2.164c1.064,0.083,2.15,0.125,3.232,0.126c15.413,0,32.04-8.527,43.395-22.257
|
||||
c11.951-14.498,17.994-33.104,16.166-49.77C214.544,0.921,213.395-0.049,212.101,0.002z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
49
sgbackup/icons/hicolor/scalable/apps/linux-svgrepo-com.svg
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 304.998 304.998" xml:space="preserve">
|
||||
<g id="XMLID_91_">
|
||||
<path id="XMLID_92_" d="M274.659,244.888c-8.944-3.663-12.77-8.524-12.4-15.777c0.381-8.466-4.422-14.667-6.703-17.117
|
||||
c1.378-5.264,5.405-23.474,0.004-39.291c-5.804-16.93-23.524-42.787-41.808-68.204c-7.485-10.438-7.839-21.784-8.248-34.922
|
||||
c-0.392-12.531-0.834-26.735-7.822-42.525C190.084,9.859,174.838,0,155.851,0c-11.295,0-22.889,3.53-31.811,9.684
|
||||
c-18.27,12.609-15.855,40.1-14.257,58.291c0.219,2.491,0.425,4.844,0.545,6.853c1.064,17.816,0.096,27.206-1.17,30.06
|
||||
c-0.819,1.865-4.851,7.173-9.118,12.793c-4.413,5.812-9.416,12.4-13.517,18.539c-4.893,7.387-8.843,18.678-12.663,29.597
|
||||
c-2.795,7.99-5.435,15.537-8.005,20.047c-4.871,8.676-3.659,16.766-2.647,20.505c-1.844,1.281-4.508,3.803-6.757,8.557
|
||||
c-2.718,5.8-8.233,8.917-19.701,11.122c-5.27,1.078-8.904,3.294-10.804,6.586c-2.765,4.791-1.259,10.811,0.115,14.925
|
||||
c2.03,6.048,0.765,9.876-1.535,16.826c-0.53,1.604-1.131,3.42-1.74,5.423c-0.959,3.161-0.613,6.035,1.026,8.542
|
||||
c4.331,6.621,16.969,8.956,29.979,10.492c7.768,0.922,16.27,4.029,24.493,7.035c8.057,2.944,16.388,5.989,23.961,6.913
|
||||
c1.151,0.145,2.291,0.218,3.39,0.218c11.434,0,16.6-7.587,18.238-10.704c4.107-0.838,18.272-3.522,32.871-3.882
|
||||
c14.576-0.416,28.679,2.462,32.674,3.357c1.256,2.404,4.567,7.895,9.845,10.724c2.901,1.586,6.938,2.495,11.073,2.495
|
||||
c0.001,0,0,0,0.001,0c4.416,0,12.817-1.044,19.466-8.039c6.632-7.028,23.202-16,35.302-22.551c2.7-1.462,5.226-2.83,7.441-4.065
|
||||
c6.797-3.768,10.506-9.152,10.175-14.771C282.445,250.905,279.356,246.811,274.659,244.888z M124.189,243.535
|
||||
c-0.846-5.96-8.513-11.871-17.392-18.715c-7.26-5.597-15.489-11.94-17.756-17.312c-4.685-11.082-0.992-30.568,5.447-40.602
|
||||
c3.182-5.024,5.781-12.643,8.295-20.011c2.714-7.956,5.521-16.182,8.66-19.783c4.971-5.622,9.565-16.561,10.379-25.182
|
||||
c4.655,4.444,11.876,10.083,18.547,10.083c1.027,0,2.024-0.134,2.977-0.403c4.564-1.318,11.277-5.197,17.769-8.947
|
||||
c5.597-3.234,12.499-7.222,15.096-7.585c4.453,6.394,30.328,63.655,32.972,82.044c2.092,14.55-0.118,26.578-1.229,31.289
|
||||
c-0.894-0.122-1.96-0.221-3.08-0.221c-7.207,0-9.115,3.934-9.612,6.283c-1.278,6.103-1.413,25.618-1.427,30.003
|
||||
c-2.606,3.311-15.785,18.903-34.706,21.706c-7.707,1.12-14.904,1.688-21.39,1.688c-5.544,0-9.082-0.428-10.551-0.651l-9.508-10.879
|
||||
C121.429,254.489,125.177,250.583,124.189,243.535z M136.254,64.149c-0.297,0.128-0.589,0.265-0.876,0.411
|
||||
c-0.029-0.644-0.096-1.297-0.199-1.952c-1.038-5.975-5-10.312-9.419-10.312c-0.327,0-0.656,0.025-1.017,0.08
|
||||
c-2.629,0.438-4.691,2.413-5.821,5.213c0.991-6.144,4.472-10.693,8.602-10.693c4.85,0,8.947,6.536,8.947,14.272
|
||||
C136.471,62.143,136.4,63.113,136.254,64.149z M173.94,68.756c0.444-1.414,0.684-2.944,0.684-4.532
|
||||
c0-7.014-4.45-12.509-10.131-12.509c-5.552,0-10.069,5.611-10.069,12.509c0,0.47,0.023,0.941,0.067,1.411
|
||||
c-0.294-0.113-0.581-0.223-0.861-0.329c-0.639-1.935-0.962-3.954-0.962-6.015c0-8.387,5.36-15.211,11.95-15.211
|
||||
c6.589,0,11.95,6.824,11.95,15.211C176.568,62.78,175.605,66.11,173.94,68.756z M169.081,85.08
|
||||
c-0.095,0.424-0.297,0.612-2.531,1.774c-1.128,0.587-2.532,1.318-4.289,2.388l-1.174,0.711c-4.718,2.86-15.765,9.559-18.764,9.952
|
||||
c-2.037,0.274-3.297-0.516-6.13-2.441c-0.639-0.435-1.319-0.897-2.044-1.362c-5.107-3.351-8.392-7.042-8.763-8.485
|
||||
c1.665-1.287,5.792-4.508,7.905-6.415c4.289-3.988,8.605-6.668,10.741-6.668c0.113,0,0.215,0.008,0.321,0.028
|
||||
c2.51,0.443,8.701,2.914,13.223,4.718c2.09,0.834,3.895,1.554,5.165,2.01C166.742,82.664,168.828,84.422,169.081,85.08z
|
||||
M205.028,271.45c2.257-10.181,4.857-24.031,4.436-32.196c-0.097-1.855-0.261-3.874-0.42-5.826
|
||||
c-0.297-3.65-0.738-9.075-0.283-10.684c0.09-0.042,0.19-0.078,0.301-0.109c0.019,4.668,1.033,13.979,8.479,17.226
|
||||
c2.219,0.968,4.755,1.458,7.537,1.458c7.459,0,15.735-3.659,19.125-7.049c1.996-1.996,3.675-4.438,4.851-6.372
|
||||
c0.257,0.753,0.415,1.737,0.332,3.005c-0.443,6.885,2.903,16.019,9.271,19.385l0.927,0.487c2.268,1.19,8.292,4.353,8.389,5.853
|
||||
c-0.001,0.001-0.051,0.177-0.387,0.489c-1.509,1.379-6.82,4.091-11.956,6.714c-9.111,4.652-19.438,9.925-24.076,14.803
|
||||
c-6.53,6.872-13.916,11.488-18.376,11.488c-0.537,0-1.026-0.068-1.461-0.206C206.873,288.406,202.886,281.417,205.028,271.45z
|
||||
M39.917,245.477c-0.494-2.312-0.884-4.137-0.465-5.905c0.304-1.31,6.771-2.714,9.533-3.313c3.883-0.843,7.899-1.714,10.525-3.308
|
||||
c3.551-2.151,5.474-6.118,7.17-9.618c1.228-2.531,2.496-5.148,4.005-6.007c0.085-0.05,0.215-0.108,0.463-0.108
|
||||
c2.827,0,8.759,5.943,12.177,11.262c0.867,1.341,2.473,4.028,4.331,7.139c5.557,9.298,13.166,22.033,17.14,26.301
|
||||
c3.581,3.837,9.378,11.214,7.952,17.541c-1.044,4.909-6.602,8.901-7.913,9.784c-0.476,0.108-1.065,0.163-1.758,0.163
|
||||
c-7.606,0-22.662-6.328-30.751-9.728l-1.197-0.503c-4.517-1.894-11.891-3.087-19.022-4.241c-5.674-0.919-13.444-2.176-14.732-3.312
|
||||
c-1.044-1.171,0.167-4.978,1.235-8.337c0.769-2.414,1.563-4.91,1.998-7.523C41.225,251.596,40.499,248.203,39.917,245.477z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>steam</title>
|
||||
<path d="M18.102 12.129c0-0 0-0 0-0.001 0-1.564 1.268-2.831 2.831-2.831s2.831 1.268 2.831 2.831c0 1.564-1.267 2.831-2.831 2.831-0 0-0 0-0.001 0h0c-0 0-0 0-0.001 0-1.563 0-2.83-1.267-2.83-2.83 0-0 0-0 0-0.001v0zM24.691 12.135c0-2.081-1.687-3.768-3.768-3.768s-3.768 1.687-3.768 3.768c0 2.081 1.687 3.768 3.768 3.768v0c2.080-0.003 3.765-1.688 3.768-3.767v-0zM10.427 23.76l-1.841-0.762c0.524 1.078 1.611 1.808 2.868 1.808 1.317 0 2.448-0.801 2.93-1.943l0.008-0.021c0.155-0.362 0.246-0.784 0.246-1.226 0-1.757-1.424-3.181-3.181-3.181-0.405 0-0.792 0.076-1.148 0.213l0.022-0.007 1.903 0.787c0.852 0.364 1.439 1.196 1.439 2.164 0 1.296-1.051 2.347-2.347 2.347-0.324 0-0.632-0.066-0.913-0.184l0.015 0.006zM15.974 1.004c-7.857 0.001-14.301 6.046-14.938 13.738l-0.004 0.054 8.038 3.322c0.668-0.462 1.495-0.737 2.387-0.737 0.001 0 0.002 0 0.002 0h-0c0.079 0 0.156 0.005 0.235 0.008l3.575-5.176v-0.074c0.003-3.12 2.533-5.648 5.653-5.648 3.122 0 5.653 2.531 5.653 5.653s-2.531 5.653-5.653 5.653h-0.131l-5.094 3.638c0 0.065 0.005 0.131 0.005 0.199 0 0.001 0 0.002 0 0.003 0 2.342-1.899 4.241-4.241 4.241-2.047 0-3.756-1.451-4.153-3.38l-0.005-0.027-5.755-2.383c1.841 6.345 7.601 10.905 14.425 10.905 8.281 0 14.994-6.713 14.994-14.994s-6.713-14.994-14.994-14.994c-0 0-0.001 0-0.001 0h0z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
19
sgbackup/icons/hicolor/scalable/apps/windows-svgrepo-com.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 305 305" xml:space="preserve">
|
||||
<g id="XMLID_108_">
|
||||
<path id="XMLID_109_" d="M139.999,25.775v116.724c0,1.381,1.119,2.5,2.5,2.5H302.46c1.381,0,2.5-1.119,2.5-2.5V2.5
|
||||
c0-0.726-0.315-1.416-0.864-1.891c-0.548-0.475-1.275-0.687-1.996-0.583L142.139,23.301
|
||||
C140.91,23.48,139.999,24.534,139.999,25.775z"/>
|
||||
<path id="XMLID_110_" d="M122.501,279.948c0.601,0,1.186-0.216,1.644-0.616c0.544-0.475,0.856-1.162,0.856-1.884V162.5
|
||||
c0-1.381-1.119-2.5-2.5-2.5H2.592c-0.663,0-1.299,0.263-1.768,0.732c-0.469,0.469-0.732,1.105-0.732,1.768l0.006,98.515
|
||||
c0,1.25,0.923,2.307,2.16,2.477l119.903,16.434C122.274,279.94,122.388,279.948,122.501,279.948z"/>
|
||||
<path id="XMLID_138_" d="M2.609,144.999h119.892c1.381,0,2.5-1.119,2.5-2.5V28.681c0-0.722-0.312-1.408-0.855-1.883
|
||||
c-0.543-0.475-1.261-0.693-1.981-0.594L2.164,42.5C0.923,42.669-0.001,43.728,0,44.98l0.109,97.521
|
||||
C0.111,143.881,1.23,144.999,2.609,144.999z"/>
|
||||
<path id="XMLID_169_" d="M302.46,305c0.599,0,1.182-0.215,1.64-0.613c0.546-0.475,0.86-1.163,0.86-1.887l0.04-140
|
||||
c0-0.663-0.263-1.299-0.732-1.768c-0.469-0.469-1.105-0.732-1.768-0.732H142.499c-1.381,0-2.5,1.119-2.5,2.5v117.496
|
||||
c0,1.246,0.918,2.302,2.151,2.476l159.961,22.504C302.228,304.992,302.344,305,302.46,305z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@ -19,9 +19,9 @@
|
||||
|
||||
import logging
|
||||
from . import gui
|
||||
from .gui.application import Application
|
||||
from .gui import Application
|
||||
from .steam import SteamLibrary
|
||||
import sys
|
||||
|
||||
|
||||
logger=logging.getLogger(__name__)
|
||||
|
||||
@ -37,6 +37,6 @@ def curses_main():
|
||||
|
||||
def gui_main():
|
||||
logger.debug("Running gui_main()")
|
||||
gui.app = Application()
|
||||
gui.app.run()
|
||||
gui._app = Application()
|
||||
gui._app.run()
|
||||
return 0
|
||||
@ -23,6 +23,8 @@ import sys
|
||||
from gi.repository import GLib,GObject
|
||||
|
||||
class Settings(GObject.GObject):
|
||||
__gtype_name__ = "Settings"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@ -35,6 +37,13 @@ class Settings(GObject.GObject):
|
||||
if (os.path.isfile(self.__config_file)):
|
||||
with open(self.__config_file,'r') as conf:
|
||||
self.__configparser.read_file(conf)
|
||||
|
||||
if not os.path.isdir(self.config_dir):
|
||||
os.makedirs(self.config_dir)
|
||||
|
||||
if not os.path.isdir(self.gameconf_dir):
|
||||
os.makedirs(self.gameconf_dir)
|
||||
|
||||
|
||||
@GObject.Property(nick="parser")
|
||||
def parser(self)->ConfigParser:
|
||||
@ -61,11 +70,12 @@ class Settings(GObject.GObject):
|
||||
def backup_dir(self)->str:
|
||||
if self.parser.has_option('sgbackup','backupDirectory'):
|
||||
return self.parser.get('sgbackup','backupDirectory')
|
||||
return GLib.build_filename(GLib.build_filename(GLib.get_home_dir(),'SavagameBackups'))
|
||||
return os.path.join(GLib.get_home_dir(),'SavagameBackups')
|
||||
@backup_dir.setter
|
||||
def backup_dir(self,directory:str):
|
||||
if not os.path.isabs(directory):
|
||||
raise ValueError("\"backup_dir\" needs to be an absolute path!")
|
||||
self.ensure_section('sgbackup')
|
||||
return self.parser.set('sgbackup','backupDirectory',directory)
|
||||
|
||||
@GObject.Property(type=str)
|
||||
@ -77,6 +87,10 @@ class Settings(GObject.GObject):
|
||||
def save(self):
|
||||
self.emit('save')
|
||||
|
||||
def ensure_section(self,section:str):
|
||||
if not self.parser.has_section(section):
|
||||
self.parser.add_section(section)
|
||||
|
||||
@GObject.Signal(name='save',flags=GObject.SIGNAL_RUN_LAST,return_type=None,arg_types=())
|
||||
def do_save(self):
|
||||
with open(self.config_file,'w') as ofile:
|
||||
|
||||
@ -23,8 +23,11 @@ import sys
|
||||
import json
|
||||
|
||||
from .settings import settings
|
||||
from .game import STEAM_GAMES,STEAM_WINDOWS_GAMES,STEAM_LINUX_GAMES,STEAM_MACOS_GAMES
|
||||
|
||||
PLATFORM_WINDOWS = (sys.platform.lower() == 'win32')
|
||||
PLATFORM_LINUX = (sys.platform.lower() in ('linux','freebsd','netbsd','openbsd','dragonfly'))
|
||||
PLATFORM_MACOS = (sys.platform.lower() == 'macos')
|
||||
|
||||
|
||||
from gi.repository.GObject import GObject,Property,Signal
|
||||
@ -69,8 +72,6 @@ class AcfFileParser(object):
|
||||
|
||||
|
||||
return line_count,ret
|
||||
|
||||
|
||||
|
||||
def parse_file(self,acf_file)->dict:
|
||||
if not os.path.isfile(acf_file):
|
||||
@ -86,6 +87,14 @@ class AcfFileParser(object):
|
||||
raise RuntimeError("Not a acf file!")
|
||||
|
||||
class IgnoreSteamApp(GObject):
|
||||
__gtype_name__ = "sgbackup-steam-IgnoreSteamApp"
|
||||
|
||||
def __init__(self,appid:int,name:str,reason:str):
|
||||
GObject.__init__(self)
|
||||
self.__appid = int(appid)
|
||||
self.__name = name
|
||||
self.__reason = reason
|
||||
|
||||
@staticmethod
|
||||
def new_from_dict(conf:dict):
|
||||
if ('appid' in conf and 'name' in conf):
|
||||
@ -95,12 +104,6 @@ class IgnoreSteamApp(GObject):
|
||||
return SteamIgnoreApp(appid,name,reason)
|
||||
|
||||
return None
|
||||
|
||||
def __init__(self,appid:int,name:str,reason:str):
|
||||
GObject.__init__(self)
|
||||
self.__appid = int(appid)
|
||||
self.__name = name
|
||||
self.__reason = reason
|
||||
|
||||
@Property(type=int)
|
||||
def appid(self)->str:
|
||||
@ -120,7 +123,6 @@ class IgnoreSteamApp(GObject):
|
||||
def reason(self,reason:str):
|
||||
self.__reason = reason
|
||||
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'appid': self.appid,
|
||||
@ -128,7 +130,10 @@ class IgnoreSteamApp(GObject):
|
||||
'reason': self.reason,
|
||||
}
|
||||
|
||||
|
||||
class SteamApp(GObject):
|
||||
__gtype_name__ = "sgbackup-steam-SteamApp"
|
||||
|
||||
def __init__(self,appid:int,name:str,installdir:str):
|
||||
GObject.__init__(self)
|
||||
self.__appid = int(appid)
|
||||
@ -159,8 +164,11 @@ class SteamApp(GObject):
|
||||
def __eq__(self,other):
|
||||
return self.appid == other.appid
|
||||
|
||||
|
||||
|
||||
class SteamLibrary(GObject):
|
||||
__gtype_name__ = "sgbackup-steam-SteamLibrary"
|
||||
|
||||
def __init__(self,library_path:str):
|
||||
GObject.__init__(self)
|
||||
self.directory = library_path
|
||||
@ -183,7 +191,7 @@ class SteamLibrary(GObject):
|
||||
return Path(self.directory).resolve()
|
||||
|
||||
@Property
|
||||
def steam_apps(self)->list:
|
||||
def steam_apps(self)->list[SteamApp]:
|
||||
parser = AcfFileParser()
|
||||
appdir = self.path / "steamapps"
|
||||
commondir = appdir / "common"
|
||||
@ -202,10 +210,12 @@ class SteamLibrary(GObject):
|
||||
return sorted(ret)
|
||||
|
||||
class Steam(GObject):
|
||||
__gtype_name__ = "sgbackup-steam-Steam"
|
||||
|
||||
def __init__(self):
|
||||
GObject.__init__(self)
|
||||
self.__libraries = []
|
||||
self.__ignore_apps = []
|
||||
self.__ignore_apps = {}
|
||||
|
||||
if not self.steamlib_list_file.is_file():
|
||||
if (PLATFORM_WINDOWS):
|
||||
@ -229,7 +239,6 @@ class Steam(GObject):
|
||||
except:
|
||||
pass
|
||||
|
||||
ignore_apps = []
|
||||
if self.ignore_apps_file.is_file():
|
||||
with open(str(self.ignore_apps_file),'r',encoding="utf-8") as ifile:
|
||||
ignore_list = json.loads(ifile.read())
|
||||
@ -239,8 +248,8 @@ class Steam(GObject):
|
||||
except:
|
||||
continue
|
||||
if ignore_app:
|
||||
self.__ignore_apps.append(ignore_app)
|
||||
self.__ignore_apps = sorted(ignore_apps)
|
||||
self.__ignore_apps[ignore_app.appid] = ignore_app
|
||||
|
||||
#__init__()
|
||||
|
||||
@Property
|
||||
@ -252,17 +261,21 @@ class Steam(GObject):
|
||||
return Path(settings.config_dir).resolve / 'ignore_steamapps.json'
|
||||
|
||||
@Property
|
||||
def libraries(self):
|
||||
def libraries(self)->list[SteamLibrary]:
|
||||
return self.__libraries
|
||||
|
||||
@Property
|
||||
def ignore_apps(self):
|
||||
def ignore_apps(self)->dict[int:IgnoreSteamApp]:
|
||||
return self.__ignore_apps
|
||||
|
||||
def __write_steamlib_list_file(self):
|
||||
with open(self.steamlib_list_file,'w',encoding='utf-8') as ofile:
|
||||
ofile.write('\n'.join(str(sl.directory) for sl in self.libraries))
|
||||
|
||||
def __write_ignore_steamapps_file(self):
|
||||
with open(self.ignore_apps_file,'w',encoding='utf-8') as ofile:
|
||||
ofile.write(json.dumps([i.serialize() for i in self.ignore_apps.values()]))
|
||||
|
||||
def add_library(self,steamlib:SteamLibrary|str):
|
||||
if isinstance(steamlib,SteamLibrary):
|
||||
lib = steamlib
|
||||
@ -294,3 +307,46 @@ class Steam(GObject):
|
||||
for i in sorted(delete_libs,reverse=True):
|
||||
del self.__libraries[i]
|
||||
self.__write_steamlib_list_file()
|
||||
|
||||
def add_ignore_app(self,app:IgnoreSteamApp):
|
||||
self.__ignore_apps[app.appid] = app
|
||||
self.__write_ignore_steamapps_file()
|
||||
|
||||
def remove_ignore_app(self,app:IgnoreSteamApp|int):
|
||||
if isinstance(app,IgnoreSteamApp):
|
||||
appid = app.appid
|
||||
else:
|
||||
appid = int(app)
|
||||
if appid in self.__ignore_apps:
|
||||
del self.__ignore_apps[appid]
|
||||
self.__write_ignore_steamapps_file()
|
||||
|
||||
def find_new_steamapps(self)->list[SteamApp]:
|
||||
new_apps = []
|
||||
for lib in self.libraries:
|
||||
for app in lib.steam_apps:
|
||||
if not app.appid in STEAM_GAMES and not app.appid in self.ignore_apps:
|
||||
new_apps.append(app)
|
||||
return sorted(new_apps)
|
||||
|
||||
def update_steam_apps(self):
|
||||
for lib in self.libraries():
|
||||
for app in lib.steam_apps:
|
||||
if PLATFORM_WINDOWS:
|
||||
if ((app.appid in STEAM_WINDOWS_GAMES)
|
||||
and (STEAM_WINDOWS_GAMES[app.appid].installdir != app.installdir)):
|
||||
game = STEAM_WINDOWS_GAMES[app.appid]
|
||||
game.installdir = app.installdir
|
||||
game.save()
|
||||
elif PLATFORM_LINUX:
|
||||
if ((app.appid in STEAM_LINUX_GAMES)
|
||||
and (STEAM_LINUX_GAMES[app.appid].installdir != app.installdir)):
|
||||
game = STEAM_LINUX_GAMES[app.appid]
|
||||
game.installdir = app.installdir
|
||||
game.save()
|
||||
elif PLATFORM_MACOS:
|
||||
if ((app.appid in STEAM_MACOS_GAMES)
|
||||
and (STEAM_MACOS_GAMES[app.appid].installdir != app.installdir)):
|
||||
game = STEAM_MACOS_GAMES[app.appid]
|
||||
game.installdir = app.installdir
|
||||
game.save()
|
||||
|
||||