diff --git a/sgbackup/epic.py b/sgbackup/epic.py index 5a559f7..a207ae3 100644 --- a/sgbackup/epic.py +++ b/sgbackup/epic.py @@ -129,7 +129,7 @@ class Epic(GObject): self.__ignored_apps[app.catalog_item_id] = app self.__write_ignore_file() - def remove_ignored_apps(self,app:str|EpicIgnoredApp): + def remove_ignored_app(self,app:str|EpicIgnoredApp): if isinstance(app,str): item_id = app elif isinstance(app,EpicIgnoredApp): diff --git a/sgbackup/gui/_app.py b/sgbackup/gui/_app.py index c627cf0..ff0517a 100644 --- a/sgbackup/gui/_app.py +++ b/sgbackup/gui/_app.py @@ -50,7 +50,11 @@ from ..steam import Steam from ..epic import Epic from ._epic import ( EpicNewAppsDialog, + EpicNoIgnoredAppsDialog, + EpicNoNewAppsDialog, + EpicIgnoredAppsDialog, ) + from ._backupdialog import BackupSingleDialog,BackupManyDialog from ..archiver import ArchiverManager from ._dialogs import ( @@ -416,18 +420,11 @@ class GameView(Gtk.Box): def _on_new_epic_games_button_clicked(self,button): epic = Epic() if not epic.find_new_apps(): - dialog = Gtk.MessageDialog( - transient_for=self.get_root(), - message="No new Epic-Games applications found!", - buttons=Gtk.ButtonsType.OK, - modal=False - ) - dialog.connect('response',lambda d,r: d.destroy()) - dialog.present() - return - - dialog = EpicNewAppsDialog(self.get_root()) - dialog.connect_after('response',self._on_new_apps_dialog_response) + dialog = EpicNewAppsDialog(self.get_root()) + else: + dialog = EpicNewAppsDialog(self.get_root()) + dialog.connect_after('response',self._on_new_apps_dialog_response) + dialog.present() def _on_backup_active_live_button_clicked(self,button): @@ -449,10 +446,18 @@ class GameView(Gtk.Box): choices.append(item.name) item.fuzzy_match = 0.0 - result = rapidfuzz.process.extract(query=search_name, + if settings.search_case_sensitive: + processor=None + query=search_name + else: + processor=lambda s: s.lower() + query=search_name.lower() + + result = rapidfuzz.process.extract(query=query, choices=choices, limit=settings.search_max_results, - scorer=rapidfuzz.fuzz.WRatio) + scorer=rapidfuzz.fuzz.WRatio, + processor=processor) for name,match,pos in result: @@ -1572,18 +1577,24 @@ class Application(Gtk.Application): def _on_action_epic_new_apps(self,action,param): epic = Epic() if not epic.find_new_apps(): - ### TODO ##################################### - return + dialog = EpicNoNewAppsDialog(self.appwindow) else: dialog = EpicNewAppsDialog(self.appwindow) - dialog.connect_after("response",lambda d,r: self.appwindow.refresh()) + dialog.connect_after('response',lambda d,r: self.appwindow.refresh()) dialog.present() def _on_action_epic_manage_ignore(self,action,param): - ### TODO ########################################## - pass + epic = Epic() + if not epic.ignored_apps: + dialog = EpicNoIgnoredAppsDialog(self.appwindow) + else: + dialog = EpicIgnoredAppsDialog(self.appwindow) + dialog.connect_after('response',lambda d,r: self.appwindow.refresh()) + dialog.present() + + def new_settings_dialog(self)->SettingsDialog: """ new_settings_dialog Create a new `SettingsDialog`. diff --git a/sgbackup/gui/_epic.py b/sgbackup/gui/_epic.py index d2de8d6..7acec62 100644 --- a/sgbackup/gui/_epic.py +++ b/sgbackup/gui/_epic.py @@ -27,6 +27,35 @@ from ..utility import PLATFORM_WINDOWS from ._gamedialog import GameDialog +class EpicNoNewAppsDialog(Gtk.MessageDialog): + def __init__(self,parent:Gtk.Window|None): + Gtk.MessageDialog.__init__(self, + transient_for=parent, + buttons=Gtk.ButtonsType.OK, + text=_("There were no new Epic-Games apps found!"), + use_markup=False, + modal=True) + + def do_response(self,response): + self.hide() + self.destroy() + + +class EpicNoIgnoredAppsDialog(Gtk.MessageDialog): + def __init__(self,parent:Gtk.Window|None): + Gtk.MessageDialog.__init__(self, + transient_for=parent, + buttons=Gtk.ButtonsType.OK, + text=_("There are no ignored Epic-Games apps!"), + use_markup=False, + modal=True) + + def do_response(self,response): + self.hide() + self.destroy() + +### EpicLookupGamesDialog ##################################################### + class EpicLookupGamesDialog(GameSearchDialog): def __init__(self,parent:Gtk.Window|None=None,info:EpicGameInfo|None=None): GameSearchDialog.__init__(self,parent,info.name if info else None) @@ -272,3 +301,147 @@ class EpicNewAppsDialog(Gtk.Dialog): for gameinfo in epic.find_new_apps(): self.__liststore.append(gameinfo) +#### EpicIgnoreAppsDialog ########################################################## + +class EpicIgnoredAppsDialogSorter(Gtk.Sorter): + def do_compare(self,item1:EpicIgnoredApp,item2:EpicIgnoredApp): + name1=item1.name.lower() + name2=item2.name.lower() + + if name1 < name2: + return Gtk.Ordering.SMALLER + elif name1 > name2: + return Gtk.Ordering.LARGER + + return Gtk.Ordering.EQUAL + + +class EpicIgnoredAppsDialog(Gtk.Dialog): + def __init__(self,parent:Gtk.Window|None=None): + Gtk.Dialog.__init__(self,transient_for=parent) + self.set_default_size(640,480) + + epic = Epic() + self.__liststore = Gio.ListStore.new(EpicIgnoredApp) + for ignored in epic.ignored_apps.values(): + self.__liststore.append(ignored) + + sort_model = Gtk.SortListModel(model=self.__liststore,sorter=EpicIgnoredAppsDialogSorter()) + selection = Gtk.SingleSelection(model=sort_model) + factory = Gtk.SignalListItemFactory() + factory.connect('setup',self._on_listview_item_setup) + factory.connect('bind',self._on_listview_item_bind) + + self.__listview = Gtk.ListView(model=selection, + factory=factory, + hexpand=True, + vexpand=True) + + scrolled = Gtk.ScrolledWindow(hexpand=True, + vexpand=True) + scrolled.set_child(self.__listview) + self.get_content_area().append(scrolled) + + self.add_button("Close",Gtk.ResponseType.OK) + + def _on_listview_item_setup(self,factory,item): + child = Gtk.Grid(hexpand=True,column_spacing=4,row_spacing=2) + + child.name_label = Gtk.Label(use_markup=True,xalign=0.0,hexpand=True) + child.attach(child.name_label,1,0,1,1) + + label = Gtk.Label(label=_("CatalogItemId:"),use_markup=False,xalign=0.0,hexpand=False) + child.catalogitemid_label = Gtk.Label(use_markup=False,xalign=0.0,hexpand=True) + child.attach(label,0,1,1,1) + child.attach(child.catalogitemid_label,1,1,1,1) + + label = Gtk.Label(label=_("Reason:"),use_markup=False,xalign=0.0,hexpand=False) + child.reason_label = Gtk.Label(use_markup=True,xalign=0.0,hexpand=True) + child.attach(label,0,2,1,1) + child.attach(child.reason_label,1,2,1,1) + + action_grid = Gtk.Grid(column_spacing=2,row_spacing=2) + + icon = Gtk.Image.new_from_icon_name("document-new-symbolic") + icon.set_pixel_size(16) + child.new_game_button = Gtk.Button() + child.new_game_button.set_child(icon) + child.new_game_button.set_tooltip_text("Add ignored SteamApp as a new game.") + action_grid.attach(child.new_game_button,0,0,1,1) + + icon = Gtk.Image.new_from_icon_name("list-remove-symbolic") + icon.set_pixel_size(16) + child.remove_button = Gtk.Button() + child.remove_button.set_child(icon) + child.remove_button.set_tooltip_text("Remove ignored SteamApp from the list.") + action_grid.attach(child.remove_button,0,1,1,1) + + child.attach(action_grid,2,0,1,3) + + item.set_child(child) + + def _on_listview_item_bind(self,factory,item): + child = item.get_child() + data = item.get_item() + + child.name_label.set_markup("{}".format( + GLib.markup_escape_text(data.name))) + child.catalogitemid_label.set_text(data.catalog_item_id) + child.reason_label.set_markup("{}".format(GLib.markup_escape_text(data.reason))) + + if hasattr(child.new_game_button,"_signal_clicked_connector"): + child.new_game_button.disconnect(child.new_game_button._signal_clicked_connector) + child.new_game_button._signal_clicked_connector = child.new_game_button.connect('clicked', + self._on_listitem_new_game_button_clicked, + data) + + 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_listitem_remove_button_clicked, + data) + + + def __remove_ignored_app(self,data:EpicIgnoredApp): + epic = Epic() + epic.remove_ignored_app(data) + + for i in range(self.__liststore.get_n_items()): + item = self.__liststore.get_item(i) + if item.catalog_item_id == data.catalog_item_id: + self.__liststore.remove(i) + break + + def _on_listitem_new_game_button_clicked(self,button,data:EpicIgnoredApp): + def on_dialog_response(self,dialog,response,data): + if response == Gtk.ResponseType.APPLY: + self.__remove_item(data) + + + game = Game("",data.name,"") + epic = Epic() + game_info = epic.find_apps()[data.catalog_item_id] + + if PLATFORM_WINDOWS: + windows = EpicWindowsData("","",installdir=game_info.installdir) + else: + windows = None + + game.epic = EpicGameData(data.catalog_item_id,windows) + + parent = self.get_transient_for() + dialog=GameDialog(parent=parent,game=game) + dialog.connect_after('response',on_dialog_response,data) + + self.hide() + dialog.present() + + + def _on_listitem_remove_button_clicked(self,button,data:EpicIgnoredApp): + epic = Epic() + self.__remove_ignored_app(data) + + def do_response(self,response): + self.hide() + self.destroy() + \ No newline at end of file diff --git a/sgbackup/gui/_gamedialog.py b/sgbackup/gui/_gamedialog.py index d5e31d5..1a179e6 100644 --- a/sgbackup/gui/_gamedialog.py +++ b/sgbackup/gui/_gamedialog.py @@ -514,10 +514,10 @@ class GameDialog(Gtk.Dialog): grid.attach(page.installdir_entry,1,2,1,1) vbox.append(grid) - page.filematch = self.__create_filematch_widget(_('Match Files')) + page.filematch = self.__create_file_matcher() vbox.append(page.filematch) - page.ignorematch = self.__create_filematch_widget(_('Ignore Files')) + page.ignorematch = self.__create_ignore_matcher() vbox.append(page.ignorematch) page.lookup_regkeys = self.__create_registry_key_widget(_("Lookup Registry keys")) @@ -564,10 +564,10 @@ class GameDialog(Gtk.Dialog): grid.attach(page.binary_entry,1,2,1,1) vbox.append(grid) - page.filematch = self.__create_filematch_widget('Match Files') + page.filematch = self.__create_file_matcher() vbox.append(page.filematch) - page.ignorematch = self.__create_filematch_widget('Ignore Files') + page.ignorematch = self.__create_ignore_matcher() vbox.append(page.ignorematch) page.variables = self.__create_variables_widget() @@ -607,10 +607,10 @@ class GameDialog(Gtk.Dialog): grid.attach(page.binary_entry,1,2,1,1) vbox.append(grid) - page.filematch = self.__create_filematch_widget('Match Files') + page.filematch = self.__create_file_matcher() vbox.append(page.filematch) - page.ignorematch = self.__create_filematch_widget('Ignore Files') + page.ignorematch = self.__create_ignore_matcher() vbox.append(page.ignorematch) page.variables = self.__create_variables_widget() @@ -659,10 +659,10 @@ class GameDialog(Gtk.Dialog): nbvbox.append(nbgrid) - nbpage.filematch = self.__create_filematch_widget('Match Files') + nbpage.filematch = self.__create_file_matcher() nbvbox.append(nbpage.filematch) - nbpage.ignorematch = self.__create_filematch_widget('Ignore Files') + nbpage.ignorematch = self.__create_ignore_matcher() nbvbox.append(nbpage.ignorematch) nbpage.variables = self.__create_variables_widget() @@ -741,10 +741,10 @@ class GameDialog(Gtk.Dialog): nbvbox.append(nbgrid) - nbpage.filematch = self.__create_filematch_widget('Match Files') + nbpage.filematch = self.__create_file_matcher() nbvbox.append(nbpage.filematch) - nbpage.ignorematch = self.__create_filematch_widget('Ignore Files') + nbpage.ignorematch = self.__create_ignore_matcher() nbvbox.append(nbpage.ignorematch) nbpage.variables = self.__create_variables_widget() @@ -786,7 +786,10 @@ class GameDialog(Gtk.Dialog): widget.set_margin_bottom(margin) def __create_variables_widget(self): - widget = Gtk.Frame.new("Variables") + widget = Gtk.Frame() + widget.set_label_widget( + Gtk.Label(label="{}".format(GLib.markup_escape_text(_('Variables'))), + use_markup=True)) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,0) model = Gio.ListStore.new(GameVariableData) @@ -824,11 +827,11 @@ class GameDialog(Gtk.Dialog): widget.columnview) widget.actions.pack_start(widget.remove_button) - name_column = Gtk.ColumnViewColumn.new("Name",self.__variable_name_factory) + 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 = Gtk.ColumnViewColumn.new(_("Value"),self.__variable_value_factory) value_column.set_expand(True) widget.columnview.append_column(value_column) @@ -854,7 +857,10 @@ class GameDialog(Gtk.Dialog): return dropdown def __create_filematch_widget(self,title:str): - widget = Gtk.Frame.new(title) + widget = Gtk.Frame() + widget.set_label_widget( + Gtk.Label(label="{}".format(GLib.markup_escape_text(title)), + use_markup=True)) vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL,2) widget.actions = Gtk.ActionBar() @@ -891,8 +897,17 @@ class GameDialog(Gtk.Dialog): return widget + def __create_file_matcher(self): + return self.__create_filematch_widget(_("Match Files")) + + def __create_ignore_matcher(self): + return self.__create_filematch_widget(_("Match Files to ignore")) + def __create_registry_key_widget(self,title): - widget = Gtk.Frame.new(title) + widget = Gtk.Frame() + widget.set_label_widget( + Gtk.Label(label="{}".format(GLib.markup_escape_text(title)), + use_markup=True)) vbox=Gtk.Box.new(Gtk.Orientation.VERTICAL,2) widget.actions = Gtk.ActionBar() diff --git a/sgbackup/gui/_settingsdialog.py b/sgbackup/gui/_settingsdialog.py index ff87d36..d95c94e 100644 --- a/sgbackup/gui/_settingsdialog.py +++ b/sgbackup/gui/_settingsdialog.py @@ -225,19 +225,29 @@ class SettingsDialog(Gtk.Dialog): search_frame = self.create_frame('Search Settings') search_grid = self.create_grid() + label = self.create_label("Case sensitive search:") + page.search_casesensitive_switch = Gtk.Switch() + page.search_casesensitive_switch.set_active(settings.search_case_sensitive) + hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,0) + hbox.append(Gtk.Label(hexpand=True)) + hbox.append(page.search_casesensitive_switch) + hbox.set_hexpand(True) + search_grid.attach(label,0,0,1,1) + search_grid.attach(hbox,1,0,1,1) + 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) + search_grid.attach(label,0,1,1,1) + search_grid.attach(page.search_minchars_spinbutton,1,1,1,1) 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) - search_grid.attach(label,0,1,1,1) - search_grid.attach(page.search_maxresults_spinbutton,1,1,1,1) + search_grid.attach(label,0,2,1,1) + search_grid.attach(page.search_maxresults_spinbutton,1,2,1,1) search_frame.set_child(search_grid) vbox.append(search_frame) @@ -487,6 +497,7 @@ class SettingsDialog(Gtk.Dialog): 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_case_sensitive = self.general_page.search_casesensitive_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 diff --git a/sgbackup/settings.py b/sgbackup/settings.py index 3ea323e..806071a 100644 --- a/sgbackup/settings.py +++ b/sgbackup/settings.py @@ -359,6 +359,14 @@ class Settings(GObject.GObject): @search_min_chars.setter def search_min_chars(self,min_chars:int): self.set_integer('search','minChars',min_chars) + + @GObject.Property(type=bool,default=False) + def search_case_sensitive(self)->bool: + return self.get_boolean('search','caseSensitive',False) + + @search_case_sensitive.setter + def search_case_sensitive(self,case_sensitive): + self.set_boolean('search','caseSensitive',bool(case_sensitive)) @GObject.Property(type=bool,default=False) def gui_autoclose_backup_dialog(self)->bool: