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: