From 0abb7db5c3199880e9d38e2c73aac677364b4124 Mon Sep 17 00:00:00 2001 From: Christian Moser Date: Mon, 17 Feb 2025 02:21:41 +0100 Subject: [PATCH] 2025.02.17 02:21:41 (desktop) --- pyproject.toml | 24 ------------ requirements.txt | 2 + setup.py | 38 +++++++++++++++++++ sgbackup/__init__.py | 4 +- sgbackup/gui/_app.py | 75 +++++++++++++++++-------------------- sgbackup/gui/_gamedialog.py | 32 ++++++++-------- sgbackup/gui/_steam.py | 39 ++++++++++++++----- sgbackup/help/__init__.py | 0 sgbackup/main.py | 6 +-- 9 files changed, 126 insertions(+), 94 deletions(-) delete mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 sgbackup/help/__init__.py diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 02bf8f5..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,24 +0,0 @@ -[build-system] -builbackend = 'setuptools.build_meta' -requires = ['setuptools >= 61.0'] - -[project] -dynamic = ["version"] -name = 'sgbackup' -version = '0.0.0' -requires_python = '>= 3.11' -description = 'Savegame Backup Tool' -readme = 'README.md' -license = {file = 'LICENSE'} -authors = [ - {name = 'Christian Moser', email = 'christian@mydevel.at'}, -] -dependencies = ['gi','yaml'] - -[project.scripts] -sgbackup = 'sgbackup:cli_main' -csgbackup = 'sgbackup:curses_main' - -[project.gui-scripts] -gsgbackup = 'sgbackup:gui_main' - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8cf4d3c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +rapidfuzz + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0a801c0 --- /dev/null +++ b/setup.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +import os +import sys +from setuptools import setup +import subprocess +import bz2 + +PACKAGE_ROOT=os.path.dirname(__file__) + +sys.path.insert(0,PACKAGE_ROOT) +import sgbackup + +setup( + name='sgbackup', + version=sgbackup.__version__ + description='A backup tool for savegames.', + author="Christian Moser", + author_email="christian@cmoser.eu", + packages=[ + 'sgbackup', + 'sgbackup.archivers', + 'sgbackup.commands', + 'sgbackup.curses', + 'sgbackup.help', + 'sgbackup.gui', + ], + package_data={ + 'sgbackup':[ + 'icons/sgbackup.ico', + 'icons/hicolor/symbolic/*/*.svg' + ], + 'sgbackup.gui': [ + '*.ui' + ], + }, + platforms=['win32','linux'] +) diff --git a/sgbackup/__init__.py b/sgbackup/__init__.py index df1c589..d66822d 100644 --- a/sgbackup/__init__.py +++ b/sgbackup/__init__.py @@ -21,7 +21,7 @@ from . import _import_gtk __version__ = "0.0.1" from .settings import settings from . import _logging -from .main import cli_main,curses_main,gui_main +from .main import cli_main,gui_main from . import game from .command import Command from . import commands @@ -31,7 +31,7 @@ __ALL__ = [ "settings" "cli_main", "gui_main", - "curses_main", + #"curses_main", 'game', "Command", "commands", diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index 3c26736..079f421 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -164,9 +164,6 @@ class GameView(Gtk.Box): pass self.__liststore.append(g) self.__sort_model = Gtk.SortListModel.new(self._liststore,self.__name_sorter) - self.__sort_model - self.__action_dialog = None - self.__backup_dialog = None factory_icon = Gtk.SignalListItemFactory.new() factory_icon.connect('setup',self._on_icon_column_setup) @@ -319,12 +316,14 @@ class GameView(Gtk.Box): def _on_key_column_setup(self,factory,item): label = Gtk.Label() label.set_xalign(0.0) + label.set_use_markup(True) item.set_child(label) def _on_key_column_bind(self,factory,item): label = item.get_child() game = item.get_item() - game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE) + game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE, + lambda _binding,s: '{}'.format(GLib.markup_escape_text(s))) def _on_name_column_setup(self,factory,item): label = Gtk.Label() @@ -424,8 +423,13 @@ class GameView(Gtk.Box): game = item.get_item() archiver_manager = ArchiverManager.get_global() - if not hasattr(child.backup_button,'_signal_clicked_connection'): - child.backup_button._signal_clicked_connection = child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item) + # check if we are already connected. + # if we dont check we might have more than one dialog open or execute backups more than once + # due to Gtk4 reusing the widgets. When selecting a row in the columnview this method is called. + if hasattr(child.backup_button,'_signal_clicked_connection'): + child.backup_button.disconnect(child.backup_button._signal_clicked_connection) + child.backup_button._signal_clicked_connection = child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item) + if not hasattr(child.backup_button,'_property_backup_in_progress_binding'): child.backup_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress', child.backup_button, @@ -433,8 +437,9 @@ class GameView(Gtk.Box): BindingFlags.SYNC_CREATE, lambda binding,x: False if x else True) - if not hasattr(child.edit_button,'_signal_clicked_connection'): - child.edit_button._signal_clicked_connection = child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item) + if hasattr(child.edit_button,'_signal_clicked_connection'): + child.edit_button.disconnect(child.edit_button._signal_clicked_connection) + child.edit_button._signal_clicked_connection = child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item) if not hasattr(child.edit_button,'_property_backup_in_progress_binding'): child.edit_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress', @@ -443,8 +448,10 @@ class GameView(Gtk.Box): BindingFlags.SYNC_CREATE, lambda binding,x: False if x else True) - if not hasattr(child.remove_button,'_signal_clicked_connection'): - child.remove_button._signal_clicked_connection = child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item) + if hasattr(child.remove_button,'_signal_clicked_connection'): + child.remove_button.disconnect(child.remove_button._signal_clicked_connection) + child.remove_button._signal_clicked_connection = child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item) + if not hasattr(child.remove_button,'_property_backup_in_progress_binding'): child.remove_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress', child.remove_button,'sensitive', @@ -458,30 +465,22 @@ class GameView(Gtk.Box): child.backup_button.set_sensitive(False) def _on_columnview_backup_button_clicked(self,button,item): - def on_dialog_response(dialog,response): - self.__backup_dialog = None - - if self.__backup_dialog is None: - game = item.get_item() - self.__backup_dialog = BackupSingleDialog(self.get_root(),game) - self.__backup_dialog.connect('response',on_dialog_response) - self.__backup_dialog.run() + game = item.get_item() + dialog = BackupSingleDialog(self.get_root(),game) + dialog.connect('response',on_dialog_response) + dialog.run() def _on_columnview_edit_button_clicked(self,button,item): def on_dialog_response(dialog,response): if response == Gtk.ResponseType.APPLY: self.refresh() - self.__action_dialog = None - if self.__action_dialog is None: - game = item.get_item() + game = item.get_item() - self.__action_dialog = GameDialog(self.get_root(),game) - self.__action_dialog.set_modal(False) - self.__action_dialog.connect('response',on_dialog_response) - self.__action_dialog.present() - else: - self.__action_dialog.present() + dialog = GameDialog(self.get_root(),game) + dialog.set_modal(False) + dialog.connect('response',on_dialog_response) + dialog.present() def _on_columnview_remove_button_clicked(self,button,item): @@ -497,21 +496,17 @@ class GameView(Gtk.Box): dialog.hide() dialog.destroy() - self.__action_dialog = None game = item.get_item() - if self.__action_dialog is None: - self.__action_dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO, - text="Do you really want to remove the game {game}?".format( - game=game.name), - use_markup=True, - secondary_text="Removing games cannot be undone!!!") - self.__action_dialog.set_transient_for(self.get_root()) - self.__action_dialog.set_modal(False) - self.__action_dialog.connect('response',on_dialog_response,game) - self.__action_dialog.present() - else: - self.__action_dialog.present() + dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO, + text="Do you really want to remove the game {game}?".format( + game=game.name), + use_markup=True, + secondary_text="Removing games cannot be undone!!!") + dialog.set_transient_for(self.get_root()) + dialog.set_modal(False) + dialog.connect('response',on_dialog_response,game) + dialog.present() # GameView class diff --git a/sgbackup/gui/_gamedialog.py b/sgbackup/gui/_gamedialog.py index e0889be..876cf75 100644 --- a/sgbackup/gui/_gamedialog.py +++ b/sgbackup/gui/_gamedialog.py @@ -100,7 +100,10 @@ class RegistryKeyData(GObject): """ GObject.__init__(self) if not regkey: - self.__regkey = "" + self.regkey = "" + else: + self.regkey = regkey + @Property(type=str) def regkey(self)->str: @@ -972,12 +975,12 @@ class GameDialog(Gtk.Dialog): irk = [] for i in range(grk_model.get_n_items()): - item = grk.model.get_item(i) + item = grk_model.get_item(i) if item.regkey: grk.append(item.regkey) for i in range(irk_model.get_n_items()): - item = irk.model.get_item(i) + item = irk_model.get_item(i) if item.regkey: irk.append(item.regkey) @@ -1255,8 +1258,8 @@ class GameDialog(Gtk.Dialog): 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) + label.bind_property('text',data,'regkey',BindingFlags.DEFAULT) + label.connect('notify::editing',self._on_windows_regkey_label_notify_editing,widget) if not label.get_text(): label.start_editing() label.grab_focus() @@ -1316,16 +1319,15 @@ class GameDialog(Gtk.Dialog): 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 + def _on_windows_regkey_label_notify_editing(self,label,state,widget): + if not label.get_editing(): + if not label.get_text(): + model = widget.listview.get_model().get_model() + i = 0 + for i in reversed(range(model.get_n_items())): + item = model.get_item(i) + if not item.regkey: + model.remove(i) def do_response(self,response): if (response == Gtk.ResponseType.APPLY): diff --git a/sgbackup/gui/_steam.py b/sgbackup/gui/_steam.py index 8493284..a4ff467 100644 --- a/sgbackup/gui/_steam.py +++ b/sgbackup/gui/_steam.py @@ -172,8 +172,17 @@ class SteamLibrariesDialog(Gtk.Dialog): lib = item.get_item() child.label.set_text(lib.directory) child.label.bind_property('text',lib,'directory',BindingFlags.DEFAULT) - child.chooser_button.connect('clicked',self._on_list_chooser_button_clicked,child.label) - child.remove_button.connect('clicked',self._on_list_remove_button_clicked,child.label) + if hasattr(child.chooser_button,'_signal_clicked_connector'): + child.chooser_button.disconnect(child.chooser_button._signal_clicked_connector) + child.chooser_button._signal_clicked_connector = child.chooser_button.connect('clicked', + self._on_list_chooser_button_clicked, + child.label) + + if hasattr(child.remove_button,'_signal_clicked_connector'): + child.remove_button.disconnect(child.remove_button._signal_clicked_connector) + child.remove_button._signal_clicked_connector = child.remove_button.connect('clicked', + self._on_list_remove_button_clicked, + child.label) def do_response(self,response): if response == Gtk.ResponseType.APPLY: @@ -219,6 +228,7 @@ class NewSteamAppsDialog(Gtk.Dialog): factory = Gtk.SignalListItemFactory() factory.connect('setup',self._on_listitem_setup) factory.connect('bind',self._on_listitem_bind) + factory.connect('unbind',self._on_listitem_unbind) self.__listview = Gtk.ListView.new(selection,factory) self.__listview.set_vexpand(True) @@ -287,23 +297,32 @@ class NewSteamAppsDialog(Gtk.Dialog): child.name_label.set_markup("{}".format(GLib.markup_escape_text(data.name))) child.appid_label.set_text(str(data.appid)) child.installdir_label.set_text(data.installdir) - if not hasattr(child.add_app_button,'_signal_clicked_connector'): - child.add_app_button._signal_clicked_connector = child.add_app_button.connect('clicked',self._on_add_steamapp_button_clicked,data) - if not hasattr(child.ignore_app_button,'_signal_clicked_connector'): - child.ignore_app_button._signal_clicked_connector = child.ignore_app_button.connect('clicked',self._on_ignore_steamapp_button_clicked,data) + + # Check if we are already connected. + # if we dont check we might have more than one dialog open + # due to Gtk4 reusing the widgets. + # When selecting a row in the columnview this method is called so we + # need to ensure that the last binding is used to work as expected. + if hasattr(child.add_app_button,'_signal_clicked_connector'): + child.add_app_button.disconnect(child.add_app_button._signal_clicked_connector) + child.add_app_button._signal_clicked_connector = child.add_app_button.connect('clicked',self._on_add_steamapp_button_clicked,data) + + if hasattr(child.ignore_app_button,'_signal_clicked_connector'): + child.ignore_app_button.disconnect(child.ignore_app_button._signal_clicked_connector) + child.ignore_app_button._signal_clicked_connector = child.ignore_app_button.connect('clicked',self._on_ignore_steamapp_button_clicked,data) + + def _on_listitem_unbind(self,factory,item): + child = item.get_child() + data = item.get_item() def _on_add_steamapp_button_clicked(self,button,data:SteamApp,*args): def on_dialog_response(dialog,response): - self.__gamedialog = None if response == Gtk.ResponseType.APPLY: for i in range(self.__listmodel.get_n_items()): if data.appid == self.__listmodel.get_item(i).appid: self.__listmodel.remove(i) break - if self.__gamedialog is not None: - return - game = Game("Enter key",data.name,"") if PLATFORM_WINDOWS: game.steam_windows = SteamWindowsGame(data.appid,"","",installdir=data.installdir) diff --git a/sgbackup/help/__init__.py b/sgbackup/help/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sgbackup/main.py b/sgbackup/main.py index ddc1389..d238dcc 100644 --- a/sgbackup/main.py +++ b/sgbackup/main.py @@ -29,9 +29,9 @@ def cli_main(): logger.debug("Running cli_main()") return 0 -def curses_main(): - logger.debug("Running curses_main()") - return 0 +#def curses_main(): +# logger.debug("Running curses_main()") +# return 0 def gui_main(): logger.debug("Running gui_main()")