diff --git a/msys-install.sh b/msys-install.sh index 2324044..34c6605 100755 --- a/msys-install.sh +++ b/msys-install.sh @@ -4,7 +4,7 @@ SELF="$( realpath "$0" )" PROJECT_DIR="$( dirname "$SELF")" -PACKAGES="gtk4 gobject-introspection python-gobject python-rapidfuzz" +PACKAGES="gtk4 gobject-introspection python-pip python-gobject python-rapidfuzz" _install_pkg="base-devel" for i in $PACKAGES; do @@ -15,7 +15,7 @@ pacman -Sy pacman -S --noconfirm $_install_pkg cd $PROJECT_DIR -pip install --user . +pip install --break-system-packages --verbose --user . bindir=$( realpath ~/bin ) wbindir=$( cygpath -w "$bindir" ) diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index fdc0593..17dd66e 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -22,7 +22,7 @@ import rapidfuzz import logging; logger=logging.getLogger(__name__) -import os +import os,sys from datetime import datetime as DateTime from pathlib import Path @@ -641,7 +641,6 @@ class GameView(Gtk.Box): dialog.set_modal(False) dialog.connect('response',on_dialog_response,game) dialog.present() - # GameView class class BackupViewData(GObject): @@ -709,15 +708,28 @@ class BackupViewData(GObject): def savegame_type_icon_name(self)->str: return SAVEGAME_TYPE_ICONS[self.__sgtype] + @Property(type=str) + def savegame_os(self)->str: + return self.__sgos + + @Property(type=bool,default=False) + def savegame_os_is_host_os(self): + platform = "unknown" + if (sys.platform.lower().startswith('win')): + platform = 'windows' + elif (sys.platform.lower() == 'macos'): + paltform = 'macos' + elif (sys.platform.lower() in ('linux','freebsd','netbsd','openbsd','dragonfly')): + platform = 'linux' + + return (self.savegame_os == platform) + @Property(type=str) def savegame_os_icon_name(self)->str: if self.__sgos: return SAVEGAME_TYPE_ICONS[self.__sgos] return "" - @Property(type=str) - def ostype_icon_name(self): - pass @Property(type=str) def savegame_name(self)->str: @@ -786,6 +798,10 @@ class BackupView(Gtk.Box): scrolled = Gtk.ScrolledWindow() self.__gameview = gameview + self.__action_group = Gio.SimpleActionGroup.new() + self.__create_actions() + self.insert_action_group("backupview",self.action_group) + self.__liststore = Gio.ListStore() selection = Gtk.SingleSelection.new(self.__liststore) @@ -836,6 +852,18 @@ class BackupView(Gtk.Box): self.append(self._title_label) self.append(scrolled) + gesture = Gtk.GestureClick.new() + gesture.set_button(3) + self.__columnview.add_controller(gesture) + gesture.connect('pressed',self._on_gesture_button3_press) + + builder = Gtk.Builder() + builder.add_from_file(os.path.join(os.path.dirname(__file__),'appmenu.ui')) + self.__contextmenu_model = builder.get_object('backupview-contextmenu') + self.__contextmenu_popover = Gtk.PopoverMenu.new_from_model(self.__contextmenu_model) + self.__contextmenu_popover.set_parent(self.__columnview) + self.__contextmenu_popover.set_has_arrow(False) + @property def gameview(self)->GameView: """ @@ -844,7 +872,112 @@ class BackupView(Gtk.Box): :type: GameView """ return self.__gameview + + @property + def action_group(self)->Gio.SimpleActionGroup: + return self.__action_group + def __create_actions(self): + self.__restore_action = Gio.SimpleAction.new("restore",None) + self.__restore_action.connect('activate',self._on_action_restore) + self.action_group.add_action(self.__restore_action) + + self.__convert_to_windows_action = Gio.SimpleAction.new("convert-to-windows",None) + self.__convert_to_windows_action.connect('activate',self._on_action_convert_to_windows) + self.action_group.add_action(self.__convert_to_windows_action) + + self.__convert_to_linux_action = Gio.SimpleAction.new("convert-to-linux",None) + self.__convert_to_linux_action.connect('activate',self._on_action_convert_to_linux) + self.action_group.add_action(self.__convert_to_linux_action) + + self.__convert_to_macos_action = Gio.SimpleAction.new("convert-to-macos",None) + self.__convert_to_macos_action.connect('activate',self._on_action_convert_to_macos) + self.action_group.add_action(self.__convert_to_macos_action) + + self.__convert_to_epic_linux_action = Gio.SimpleAction.new("convert-to-epic-linux",None) + self.__convert_to_epic_linux_action.connect('activate',self._on_action_convert_to_epic_linux) + self.action_group.add_action(self.__convert_to_epic_linux_action) + + self.__convert_to_epic_windows_action = Gio.SimpleAction.new("convert-to-epic-windows",None) + self.__convert_to_epic_windows_action.connect('activate',self._on_action_convert_to_epic_windows) + self.action_group.add_action(self.__convert_to_epic_windows_action) + + self.__convert_to_gog_linux_action = Gio.SimpleAction.new("convert-to-gog-linux",None) + self.__convert_to_gog_linux_action.connect('activate',self._on_action_convert_to_gog_linux) + self.action_group.add_action(self.__convert_to_gog_linux_action) + + self.__convert_to_gog_windows_action = Gio.SimpleAction.new("convert-to-gog-windows",None) + self.__convert_to_gog_windows_action.connect('activate',self._on_action_convert_to_gog_windows) + self.action_group.add_action(self.__convert_to_gog_windows_action) + + self.__convert_to_steam_linux_action = Gio.SimpleAction.new("convert-to-steam-linux",None) + self.__convert_to_steam_linux_action.connect('activate',self._on_action_convert_to_steam_linux) + self.action_group.add_action(self.__convert_to_steam_linux_action) + + self.__convert_to_steam_macos_action = Gio.SimpleAction.new("convert-to-steam-macos",None) + self.__convert_to_steam_macos_action.connect('activate',self._on_action_convert_to_steam_macos) + self.action_group.add_action(self.__convert_to_steam_macos_action) + + self.__convert_to_steam_windows_action = Gio.SimpleAction.new("convert-to-steam-windows",None) + self.__convert_to_steam_windows_action.connect('activate',self._on_action_convert_to_steam_windows) + self.action_group.add_action(self.__convert_to_steam_windows_action) + + def _on_action_restore(self,action,param): + pass + + def _on_action_convert_to_windows(self,action,param): + pass + + def _on_action_convert_to_linux(self,action,param): + pass + + def _on_action_convert_to_macos(self,action,param): + pass + + def _on_action_convert_to_steam_windows(self,action,param): + pass + + def _on_action_convert_to_steam_linux(self,action,param): + pass + + def _on_action_convert_to_steam_macos(self,action,param): + pass + + def _on_action_convert_to_epic_windows(self,action,param): + pass + + def _on_action_convert_to_epic_linux(self,action,param): + pass + + def _on_action_convert_to_gog_windows(self,action_param): + pass + + def _on_action_convert_to_gog_linux(self,action,param): + pass + + + def _on_gesture_button3_press(self,gesture,n_press,x,y): + item = self.__columnview.get_model().get_selected_item() + self.__restore_action.set_enabled(item.savegame_os_is_host_os) + + ####################################################################### + #TODO: implement converter + self.__convert_to_linux_action.set_enabled(False) + self.__convert_to_macos_action.set_enabled(False) + self.__convert_to_windows_action.set_enabled(False) + self.__convert_to_epic_linux_action.set_enabled(False) + self.__convert_to_epic_windows_action.set_enabled(False) + self.__convert_to_gog_linux_action.set_enabled(False) + self.__convert_to_gog_windows_action.set_enabled(False) + self.__convert_to_steam_linux_action.set_enabled(False) + self.__convert_to_steam_macos_action.set_enabled(False) + self.__convert_to_steam_windows_action.set_enabled(False) + ####################################################################### + + self.__contextmenu_popover.popup() + + + def _on_sgtype_column_setup(self,factory,item): icon = Gtk.Image() icon.set_pixel_size(16) @@ -875,7 +1008,6 @@ class BackupView(Gtk.Box): 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() diff --git a/sgbackup/gui/_backupdialog.py b/sgbackup/gui/_backupdialog.py index 76f94dd..7c66eae 100644 --- a/sgbackup/gui/_backupdialog.py +++ b/sgbackup/gui/_backupdialog.py @@ -19,8 +19,10 @@ from gi.repository import Gtk,GLib,GObject,Gio from ..game import GameManager,Game from ..archiver import ArchiverManager +from ..settings import settings from threading import Thread,ThreadError + import logging logger = logging.getLogger(__name__) @@ -73,8 +75,8 @@ class BackupSingleDialog(Gtk.Dialog): am.disconnect(self.__am_signal_progress) del self.__am_signal_progress - #if settings.backup_dialog_close_when_finished: - # self.response(Gtk.ResponseType.OK) + if settings.gui_autoclose_backup_dialog: + self.response(Gtk.ResponseType.OK) return False @@ -173,8 +175,8 @@ class BackupManyDialog(Gtk.Dialog): self.set_transient_for(parent) self.set_decorated(False) self.set_modal(True) - self.set_default_size(640,480) - + self.set_default_size(640,480) + self.__scrolled = Gtk.ScrolledWindow() self.__games_liststore = Gio.ListStore.new(BackupGameData) self.__games_progress_sorter = BackupGameDataSorter() @@ -230,7 +232,7 @@ class BackupManyDialog(Gtk.Dialog): #self.__ok_button.set_sensitive(False) #else: #self.__ok_button.set_sensitive(True) - + def do_response(self,response): self.hide() self.destroy() @@ -253,11 +255,13 @@ class BackupManyDialog(Gtk.Dialog): GLib.idle_add(self._on_backup_finished) if not self.games: - print("no games to backup!") - self.desotroy() + logger.warning("No games to backup!") + self.hide() + self.destroy() self.response(Gtk.Response.OK) return + self.__ok_button.set_sensitive(False) am = ArchiverManager.get_global() @@ -274,12 +278,11 @@ class BackupManyDialog(Gtk.Dialog): self.__signal_backup_game_finished = am.connect('backup-game-finished', on_am_backup_game_finished) - print ("Starting thread") - print(self.games) thread = Thread(target=thread_func,args=(am,list(self.__games)),daemon=True) self.present() thread.start() - + + def _on_column_name_setup(self,factory,item): label = Gtk.Label() @@ -334,7 +337,7 @@ class BackupManyDialog(Gtk.Dialog): self.__games_progress_sorter.changed(Gtk.SorterChange.DIFFERENT) vadjustment = self.__scrolled.get_vadjustment() vadjustment.set_value(vadjustment.get_upper() - vadjustment.get_page_size()) - + return False def _on_backup_progress(self,progress:float): @@ -360,7 +363,10 @@ class BackupManyDialog(Gtk.Dialog): if hasattr(self,'__signal_backup_progress'): am.disconnect(self.__signal_backup_progress) del self.__signal_backup_progress - + + if settings.gui_autoclose_backup_dialog: + self.response(Gtk.ResponseType.OK) + return False diff --git a/sgbackup/gui/_settingsdialog.py b/sgbackup/gui/_settingsdialog.py index b62f8f6..15f7dfe 100644 --- a/sgbackup/gui/_settingsdialog.py +++ b/sgbackup/gui/_settingsdialog.py @@ -128,6 +128,31 @@ class SettingsDialog(Gtk.Dialog): self.add_button("Apply",Gtk.ResponseType.APPLY) self.add_button("Cancel",Gtk.ResponseType.CANCEL) + def create_frame(self,label_text:str): + label = Gtk.Label() + label.set_markup("{}".format(GLib.markup_escape_text(label_text))) + frame = Gtk.Frame() + frame.set_label_widget(label) + frame.set_margin_start(5) + frame.set_margin_end(5) + frame.set_margin_top(5) + frame.set_margin_bottom(5) + return frame + + def create_grid(self): + grid = Gtk.Grid() + grid.set_margin_start(5) + grid.set_margin_end(5) + grid.set_margin_top(5) + grid.set_margin_bottom(5) + grid.set_column_spacing(3) + return grid + + def create_label(self,label_text:str): + label = Gtk.Label.new(label_text) + label.set_xalign(0.0) + return label + @property def general_page(self): return self.__general_page @@ -136,14 +161,18 @@ class SettingsDialog(Gtk.Dialog): def archiver_page(self): return self.__archiver_page + @property + def variables_page(self): + return self.__variables_page + def __add_general_settings_page(self): page = Gtk.ScrolledWindow() vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,8) - backup_frame = Gtk.Frame.new('Backup Settings') + backup_frame = self.create_frame('Backup Settings') - grid = Gtk.Grid() + grid = self.create_grid() - label = Gtk.Label.new('Backup directory:') + label = self.create_label('Backup directory:') grid.attach(label,0,0,1,1) page.backupdir_label = Gtk.Label.new(settings.backup_dir) page.backupdir_label.set_hexpand(True) @@ -155,21 +184,21 @@ class SettingsDialog(Gtk.Dialog): backupdir_button.connect('clicked',self._on_backupdir_button_clicked) grid.attach(backupdir_button,2,0,1,1) - label = Gtk.Label.new('Backup versions:') + label = self.create_label('Backup versions:') grid.attach(label,0,1,1,1) page.backup_versions_spinbutton = Gtk.SpinButton.new_with_range(0,1000,1) page.backup_versions_spinbutton.set_hexpand(True) page.backup_versions_spinbutton.set_value(settings.backup_versions) grid.attach(page.backup_versions_spinbutton,1,1,2,1) - label = Gtk.Label.new('Max. Backup Threads:') + label = self.create_label('Max. Backup Threads:') page.backup_threads_spinbutton = Gtk.SpinButton.new_with_range(1,256,1) page.backup_threads_spinbutton.set_hexpand(True) page.backup_threads_spinbutton.set_value(settings.backup_threads) grid.attach(label,0,2,1,1) grid.attach(page.backup_threads_spinbutton,1,2,2,1) - label = Gtk.Label.new("Archiver:") + label = self.create_label("Archiver:") archiver_model = Gio.ListStore.new(Archiver) for archiver in ArchiverManager.get_global().archivers.values(): archiver_model.append(archiver) @@ -192,17 +221,18 @@ class SettingsDialog(Gtk.Dialog): backup_frame.set_child(grid) vbox.append(backup_frame) - search_frame = Gtk.Frame.new('Search Settings') - search_grid = Gtk.Grid() + ### Search Settings + search_frame = self.create_frame('Search Settings') + search_grid = self.create_grid() - label = Gtk.Label.new("Minimum Characters:") + label = self.create_label("Minimum Characters:") page.search_minchars_spinbutton = Gtk.SpinButton.new_with_range(1,32,1) page.search_minchars_spinbutton.set_value(settings.search_min_chars) page.search_minchars_spinbutton.set_hexpand(True) search_grid.attach(label,0,0,1,1) search_grid.attach(page.search_minchars_spinbutton,1,0,1,1) - label = Gtk.Label.new("Maximum Results:") + label = self.create_label("Maximum Results:") page.search_maxresults_spinbutton = Gtk.SpinButton.new_with_range(1,100,1) page.search_maxresults_spinbutton.set_value(settings.search_max_results) page.search_maxresults_spinbutton.set_hexpand(True) @@ -212,6 +242,31 @@ class SettingsDialog(Gtk.Dialog): search_frame.set_child(search_grid) vbox.append(search_frame) + ### GUI settings + gui_frame = self.create_frame('GUI') + gui_grid = self.create_grid() + + label = self.create_label("Automatic close Backup Dialogs?") + page.gui_autoclose_backup_dialog_switch = Gtk.Switch() + page.gui_autoclose_backup_dialog_switch.set_active(settings.gui_autoclose_backup_dialog) + filler = Gtk.Label() + filler.set_hexpand(True) + gui_grid.attach(label,0,0,1,1) + gui_grid.attach(filler,1,0,1,1) + gui_grid.attach(page.gui_autoclose_backup_dialog_switch,2,0,1,1) + + label = self.create_label("Automatic close Restore Dialogs?") + page.gui_autoclose_restore_dialog_switch = Gtk.Switch() + page.gui_autoclose_restore_dialog_switch.set_active(settings.gui_autoclose_restore_dialog) + filler = Gtk.Label() + filler.set_hexpand(True) + gui_grid.attach(label,0,1,1,1) + gui_grid.attach(filler,1,1,1,1) + gui_grid.attach(page.gui_autoclose_restore_dialog_switch,2,1,1,1) + + gui_frame.set_child(gui_grid) + vbox.append(gui_frame) + page.set_child(vbox) self.add_page(page,"general","Generic settings") return page @@ -219,10 +274,8 @@ class SettingsDialog(Gtk.Dialog): def __add_archiver_settings_page(self): page = Gtk.ScrolledWindow() page.vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,4) - self._widget_set_margin(page.vbox,4) - grid = Gtk.Grid() - self._widget_set_margin(grid,4) + grid = self.create_grid() zf_compressors = [ (zipfile.ZIP_STORED,"Stored",True), @@ -231,8 +284,8 @@ class SettingsDialog(Gtk.Dialog): (zipfile.ZIP_LZMA,"LZMA",False), ] - zipfile_frame = Gtk.Frame.new("ZipFile Archiver") - label = Gtk.Label.new("Compressor:") + zipfile_frame = self.create_frame("ZipFile Archiver") + label = self.create_label("Compressor:") zf_compressor_model = Gio.ListStore.new(ZipfileCompressorData) for i in zf_compressors: zf_compressor_model.append(ZipfileCompressorData(*i)) @@ -250,7 +303,7 @@ class SettingsDialog(Gtk.Dialog): grid.attach(label,0,0,1,1) grid.attach(page.zf_compressor_dropdown,1,0,1,1) - label = Gtk.Label.new("Compression Level:") + label = self.create_label("Compression Level:") page.zf_compresslevel_spinbutton = Gtk.SpinButton.new_with_range(0.0,9.0,1.0) page.zf_compresslevel_spinbutton.set_value(settings.zipfile_compresslevel) page.zf_compresslevel_spinbutton.set_hexpand(True) @@ -419,12 +472,13 @@ class SettingsDialog(Gtk.Dialog): settings.backup_versions = self.general_page.backup_versions_spinbutton.get_value_as_int() settings.backup_threads = self.general_page.backup_threads_spinbutton.get_value_as_int() settings.archiver = self.general_page.archiver_dropdown.get_selected_item().key + settings.gui_autoclose_backup_dialog = self.general_page.gui_autoclose_backup_dialog_switch.get_active() + settings.gui_autoclose_restore_dialog = self.general_page.gui_autoclose_restore_dialog_switch.get_active() settings.search_min_chars = self.general_page.search_minchars_spinbutton.get_value_as_int() settings.search_max_results = self.general_page.search_maxresults_spinbutton.get_value_as_int() settings.zipfile_compression = self.archiver_page.zf_compressor_dropdown.get_selected_item().compressor settings.zipfile_compresslevel = self.archiver_page.zf_compresslevel_spinbutton.get_value_as_int() - variables = {} variable_model = self.__variables_page.variable_columnview.get_model() while hasattr(variable_model,'get_model'): diff --git a/sgbackup/gui/appmenu.ui b/sgbackup/gui/appmenu.ui index d630803..e53463b 100644 --- a/sgbackup/gui/appmenu.ui +++ b/sgbackup/gui/appmenu.ui @@ -58,4 +58,39 @@ + +
\ No newline at end of file diff --git a/sgbackup/settings.py b/sgbackup/settings.py index fe75794..ed612c0 100644 --- a/sgbackup/settings.py +++ b/sgbackup/settings.py @@ -356,6 +356,22 @@ class Settings(GObject.GObject): def search_min_chars(self,min_chars:int): self.set_integer('search','minChars',min_chars) + @GObject.Property(type=bool,default=False) + def gui_autoclose_backup_dialog(self)->bool: + return self.get_boolean('gui','autocloseBackupDialog',False) + + @gui_autoclose_backup_dialog.setter + def gui_autoclose_backup_dialog(self,autoclose:bool): + self.set_boolean('gui','autocloseBackupDialog',autoclose) + + @GObject.Property(type=bool,default=False) + def gui_autoclose_restore_dialog(self)->bool: + return self.get_boolean('gui','autocloseRestoreDialog',False) + + @gui_autoclose_restore_dialog.setter + def gui_autoclose_restore_dialog(self,autoclose:bool): + self.set_boolean('gui','autocloseRestoreDialog',autoclose) + @GObject.Property def variables(self)->dict[str:str]: ret = {}