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()")