mirror of
https://github.com/c9moser/sgbackup.git
synced 2026-01-19 19:40:13 +00:00
2025.01.16 23:22:14
This commit is contained in:
parent
391b42f3bb
commit
352b421479
@ -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)))
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
###############################################################################
|
||||
|
||||
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 <span weight='bold'>{game}</span>?".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("<span size='large' weight='bold'>{}</span>".format(GLib.markup_escape_text(game.name)))
|
||||
|
||||
|
||||
|
||||
class AppWindow(Gtk.ApplicationWindow):
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- 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"
|
||||
viewBox="0 0 305 305"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="windows-svgrepo-com.svg"
|
||||
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#ffffff"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:zoom="1.46625"
|
||||
inkscape:cx="400"
|
||||
inkscape:cy="400"
|
||||
inkscape:window-width="3440"
|
||||
inkscape:window-height="1369"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="XMLID_108_"
|
||||
style="fill-rule:evenodd;image-rendering:auto;fill:#2e3434;fill-opacity:1">
|
||||
<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"
|
||||
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
|
||||
<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"
|
||||
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
|
||||
<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"
|
||||
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
|
||||
<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"
|
||||
style="fill-rule:evenodd;fill:#2e3434;fill-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
Loading…
Reference in New Issue
Block a user