2025.02.16 16:06:32 (desktop)

This commit is contained in:
Christian Moser 2025-02-16 16:06:32 +01:00
parent 4aa56401e8
commit f9f4adaf22
Failed to extract signature
7 changed files with 460 additions and 63 deletions

View File

@ -224,7 +224,7 @@ class GameFileMatcher(GObject):
raise TypeError("match_type is not a GameFileType instance!") raise TypeError("match_type is not a GameFileType instance!")
self.__match_type = match_type self.__match_type = match_type
@Property @Property(type=str)
def match_file(self)->str: def match_file(self)->str:
""" """
match_file The matcher value. match_file The matcher value.

View File

@ -36,7 +36,67 @@ from ..archiver import ArchiverManager
__gtype_name__ = __name__ __gtype_name__ = __name__
class GameView(Gtk.ScrolledWindow): class GameViewKeySorter(Gtk.Sorter):
def __init__(self,sort_ascending:bool=True,*args,**kwargs):
Gtk.Sorter.__init__(self)
self.sort_ascending = sort_ascending
@Property(type=bool,default=True)
def sort_ascending(self)->bool:
return self.__sort_ascending
@sort_ascending.setter
def sort_ascending(self,asc:bool):
self.__sort_ascending = asc
def do_compare(self,game1,game2):
if self.sort_ascending:
if game1.key > game2.key:
return Gtk.Ordering.LARGER
elif game1.key < game2.key:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
else:
if game1.key < game2.key:
return Gtk.Ordering.LARGER
elif game1.key > game2.key:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
class GameViewNameSorter(Gtk.Sorter):
def __init__(self,sort_ascending:bool=True,*args,**kwargs):
Gtk.Sorter.__init__(self)
self.sort_ascending = sort_ascending
@Property(type=bool,default=True)
def sort_ascending(self)->bool:
return self.__sort_ascending
@sort_ascending.setter
def sort_ascending(self,asc:bool):
self.__sort_ascending = asc
def do_compare(self,game1,game2):
name1 = game1.name.lower()
name2 = game2.name.lower()
if self.sort_ascending:
if name1 > name2:
return Gtk.Ordering.LARGER
elif name1 < name2:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
else:
if name1 < name2:
return Gtk.Ordering.LARGER
elif name1 > name2:
return Gtk.Ordering.SMALLER
else:
return Gtk.Ordering.EQUAL
class GameView(Gtk.Box):
""" """
GameView The View for games. GameView The View for games.
@ -48,19 +108,70 @@ class GameView(Gtk.ScrolledWindow):
""" """
GameView GameView
""" """
Gtk.ScrolledWindow.__init__(self) Gtk.Box.__init__(self,orientation=Gtk.Orientation.VERTICAL)
self.__key_sorter = GameViewKeySorter(True)
self.__name_sorter = GameViewNameSorter(True)
scrolled = Gtk.ScrolledWindow()
scrolled.set_vexpand(True)
scrolled.set_hexpand(True)
# set up the ActionBar for this widget
self.__actionbar = Gtk.ActionBar()
self.actionbar.set_hexpand(True)
icon = Gtk.Image.new_from_icon_name('list-add-symbolic')
icon.set_pixel_size(16)
add_game_button=Gtk.Button()
add_game_button.set_child(icon)
add_game_button.set_tooltip_text("Add a new game.")
add_game_button.connect('clicked',self._on_add_game_button_clicked)
self.actionbar.pack_start(add_game_button)
icon = Gtk.Image.new_from_icon_name('steam-svgrepo-com-symbolic')
icon.set_pixel_size(16)
new_steam_games_button=Gtk.Button()
new_steam_games_button.set_child(icon)
new_steam_games_button.set_tooltip_text("Manage new Steam-Apps")
new_steam_games_button.connect('clicked',self._on_new_steam_games_button_clicked)
self.actionbar.pack_start(new_steam_games_button)
self.actionbar.pack_start(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
icon = Gtk.Image.new_from_icon_name('document-save-symbolic')
icon.set_pixel_size(16)
backup_active_live_button = Gtk.Button()
backup_active_live_button.set_child(icon)
backup_active_live_button.set_tooltip_markup("Backup all <i>active</i> and <i>live</i> Games.")
backup_active_live_button.connect('clicked',self._on_backup_active_live_button_clicked)
self.actionbar.pack_start(backup_active_live_button)
# Add a the search entry
self.__search_entry = Gtk.Entry()
self.__search_entry.set_hexpand(True)
self.actionbar.pack_end(self.__search_entry)
self.actionbar.pack_end(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL))
self.append(self.actionbar)
self.__liststore = Gio.ListStore.new(Game) self.__liststore = Gio.ListStore.new(Game)
for g in GameManager.get_global().games.values(): for g in GameManager.get_global().games.values():
pass pass
self.__liststore.append(g) self.__liststore.append(g)
self.__sort_model = Gtk.SortListModel.new(self._liststore,self.__name_sorter)
self.__sort_model
self.__action_dialog = None self.__action_dialog = None
self.__backup_dialog = None self.__backup_dialog = None
factory_icon = Gtk.SignalListItemFactory.new() factory_icon = Gtk.SignalListItemFactory.new()
factory_icon.connect('setup',self._on_icon_column_setup) factory_icon.connect('setup',self._on_icon_column_setup)
factory_icon.connect('bind',self._on_icon_column_bind) factory_icon.connect('bind',self._on_icon_column_bind)
factory_icon.connect('unbind',self._on_icon_column_unbind)
column_icon = Gtk.ColumnViewColumn.new("",factory_icon) column_icon = Gtk.ColumnViewColumn.new("",factory_icon)
factory_key = Gtk.SignalListItemFactory.new() factory_key = Gtk.SignalListItemFactory.new()
@ -89,10 +200,13 @@ class GameView(Gtk.ScrolledWindow):
factory_actions = Gtk.SignalListItemFactory.new() factory_actions = Gtk.SignalListItemFactory.new()
factory_actions.connect('setup',self._on_actions_column_setup) factory_actions.connect('setup',self._on_actions_column_setup)
factory_actions.connect('bind',self._on_actions_column_bind) factory_actions.connect('bind',self._on_actions_column_bind)
#factory_actions.connect('ubind',self._on_actions_column_unbind)
column_actions = Gtk.ColumnViewColumn.new("",factory_actions) column_actions = Gtk.ColumnViewColumn.new("",factory_actions)
selection = Gtk.SingleSelection.new(self._liststore) selection = Gtk.SingleSelection.new(self.__sort_model)
self.__columnview = Gtk.ColumnView.new(selection) self.__columnview = Gtk.ColumnView.new(selection)
self.columnview.set_vexpand(True)
self.columnview.set_hexpand(True)
self.columnview.append_column(column_icon) self.columnview.append_column(column_icon)
self.columnview.append_column(column_key) self.columnview.append_column(column_key)
self.columnview.append_column(column_name) self.columnview.append_column(column_name)
@ -101,8 +215,9 @@ class GameView(Gtk.ScrolledWindow):
self.columnview.append_column(column_actions) self.columnview.append_column(column_actions)
self.columnview.set_single_click_activate(True) self.columnview.set_single_click_activate(True)
scrolled.set_child(self.columnview)
self.set_child(self.columnview) self.append(scrolled)
self.refresh() self.refresh()
@property @property
@ -124,18 +239,65 @@ class GameView(Gtk.ScrolledWindow):
""" """
return self.__columnview return self.__columnview
@property
def actionbar(self)->Gtk.ActionBar:
return self.__actionbar
def refresh(self): def refresh(self):
""" """
refresh Refresh the view. refresh Refresh the view.
This method reloads the installed Games. This method reloads the installed Games.
""" """
self.__liststore.remove_all() self.emit('refresh')
@Signal(name="refresh",return_type=None,arg_types=(),flags=SignalFlags.RUN_FIRST)
def do_refresh(self):
self._liststore.remove_all()
for game in GameManager.get_global().games.values(): for game in GameManager.get_global().games.values():
self.__liststore.append(game) self._liststore.append(game)
def _on_game_dialog_response(self,dialog,response):
if response == Gtk.ResponseType.APPLY:
self.refresh()
@Signal(name='game-active-changed',return_type=None,arg_types=(Game,),flags=SignalFlags.RUN_FIRST)
def do_game_active_changed(self,game:Game):
pass
@Signal(name='game-live-changed',return_type=None,arg_types=(Game,),flags=SignalFlags.RUN_FIRST)
def do_game_live_changed(self,game:Game):
pass
def _on_new_steamapps_dialog_response(self,dialog,response):
self.refresh()
def _on_add_game_button_clicked(self,button):
dialog = GameDialog(parent=self.get_root())
dialog.connect('response',self._on_game_dialog_response)
dialog.present()
def _on_new_steam_games_button_clicked(self,button):
dialog = NewSteamAppsDialog(parent=self.get_root())
dialog.connect('response',self._on_new_steamapps_dialog_response)
dialog.present()
def _on_backup_active_live_button_clicked(self,button):
backup_games = []
for i in range(self._liststore.get_n_items()):
game = self._liststore.get_item(i)
if game.is_live and game.is_active and os.path.exists(os.path.join(game.savegame_root,game.savegame_dir)):
backup_games.append(game)
# TODO:
#dialog = BackupManyDialog(parent=self.get_root(),games=backup_games)
#dialog.set_modal(False)
#dialog.present()
def _on_icon_column_setup(self,factory,item): def _on_icon_column_setup(self,factory,item):
item.set_child(Gtk.Image()) image = Gtk.Image()
image.set_pixel_size(24)
item.set_child(image)
def _on_icon_column_bind(self,factory,item): def _on_icon_column_bind(self,factory,item):
def transform_to_icon_name(_bidning,sgtype): def transform_to_icon_name(_bidning,sgtype):
@ -145,11 +307,19 @@ class GameView(Gtk.ScrolledWindow):
return "" return ""
icon = item.get_child() icon = item.get_child()
game = item.get_item() game = item.get_item()
game.bind_property('savegame_type',icon,'icon_name',BindingFlags.SYNC_CREATE,transform_to_icon_name) if not hasattr(game,'_savegame_type_to_icon_name_binding'):
game._savegame_type_to_icon_name_binding = game.bind_property('savegame_type',icon,'icon_name',BindingFlags.SYNC_CREATE,transform_to_icon_name)
def _on_icon_column_unbind(self,factory,item):
game = item.get_item()
if hasattr(game,'_savegame_type_to_icon_name_binding'):
game._savegame_type_to_icon_name_binding.unbind()
del game._savegame_type_to_icon_name_binding
def _on_key_column_setup(self,factory,item): def _on_key_column_setup(self,factory,item):
item.set_child(Gtk.Label()) label = Gtk.Label()
label.set_xalign(0.0)
item.set_child(label)
def _on_key_column_bind(self,factory,item): def _on_key_column_bind(self,factory,item):
label = item.get_child() label = item.get_child()
@ -157,12 +327,21 @@ class GameView(Gtk.ScrolledWindow):
game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE) game.bind_property('key',label,'label',BindingFlags.SYNC_CREATE)
def _on_name_column_setup(self,factory,item): def _on_name_column_setup(self,factory,item):
item.set_child(Gtk.Label()) label = Gtk.Label()
label.set_xalign(0.0)
label.set_use_markup(True)
item.set_child(label)
def _on_name_column_bind(self,factory,item): def _on_name_column_bind(self,factory,item):
label = item.get_child() label = item.get_child()
game = item.get_item() game = item.get_item()
game.bind_property('name',label,'label',BindingFlags.SYNC_CREATE) if not hasattr(label,'_property_label_from_name_binding'):
label._property_label_from_name_binding = game.bind_property('name',
label,
'label',
BindingFlags.SYNC_CREATE,
lambda _binding,s: "<span weight='bold' size='large'>{}</span>".format(
GLib.markup_escape_text(s)))
def _on_active_column_setup(self,factory,item): def _on_active_column_setup(self,factory,item):
item.set_child(Gtk.Switch()) item.set_child(Gtk.Switch())
@ -179,8 +358,9 @@ class GameView(Gtk.ScrolledWindow):
del item._signal_active_state_set del item._signal_active_state_set
def _on_active_state_set(self,switch,state,game): def _on_active_state_set(self,switch,state,game):
game.is_active = state game.is_active = switch.get_active()
game.save() game.save()
self.emit('game-active-changed',game)
def _on_live_column_setup(self,factory,item): def _on_live_column_setup(self,factory,item):
item.set_child(Gtk.Switch()) item.set_child(Gtk.Switch())
@ -189,6 +369,7 @@ class GameView(Gtk.ScrolledWindow):
switch = item.get_child() switch = item.get_child()
game = item.get_item() game = item.get_item()
switch.set_active(game.is_live) switch.set_active(game.is_live)
if not hasattr(item,'_signal_live_state_set'):
item._signal_live_state_set = switch.connect('state-set',self._on_live_state_set,game) item._signal_live_state_set = switch.connect('state-set',self._on_live_state_set,game)
def _on_live_column_unbind(self,factory,item): def _on_live_column_unbind(self,factory,item):
@ -198,24 +379,25 @@ class GameView(Gtk.ScrolledWindow):
def _on_live_state_set(self,switch,state,game): def _on_live_state_set(self,switch,state,game):
def on_dialog_response(dialog,response): def on_dialog_response(dialog,response):
if response == Gtk.ResponseType.YES:
pass
#archiver.backup(game)
dialog.hide() dialog.hide()
dialog.destroy() dialog.destroy()
if response == Gtk.ResponseType.YES:
dialog = BackupSingleDialog(parent=self.get_root(),game=game)
dialog.run()
game.is_live = switch.get_active() game.is_live = switch.get_active()
game.save() game.save()
if not game.is_live: if not game.is_live:
dialog = Gtk.MessageDialog() dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.YES_NO)
dialog.set_transient_for(self.get_root()) dialog.set_transient_for(self.get_root())
dialog.props.buttons = Gtk.ButtonsType.YES_NO
dialog.props.text = "Do you want to create a new savegame for <i>{game}</i>?".format(game=game.name) dialog.props.text = "Do you want to create a new savegame for <i>{game}</i>?".format(game=game.name)
dialog.props.use_markup = True dialog.props.use_markup = True
dialog.props.secondary_text = "The new savegame is added to the finsihed savegames for the game." dialog.props.secondary_text = "The new savegame is added to the finsihed savegames for the game."
dialog.props.secondary_use_markup = False dialog.props.secondary_use_markup = False
dialog.connect('response',on_dialog_response) dialog.connect('response',on_dialog_response)
dialog.present() dialog.present()
self.emit('game-live-changed',game)
def _on_actions_column_setup(self,action,item): def _on_actions_column_setup(self,action,item):
child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2) child = Gtk.Box.new(Gtk.Orientation.HORIZONTAL,2)
@ -241,15 +423,39 @@ class GameView(Gtk.ScrolledWindow):
child = item.get_child() child = item.get_child()
game = item.get_item() game = item.get_item()
archiver_manager = ArchiverManager.get_global() archiver_manager = ArchiverManager.get_global()
child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item)
child.edit_button.connect('clicked',self._on_columnview_edit_button_clicked,item) if not hasattr(child.backup_button,'_signal_clicked_connection'):
child.remove_button.connect('clicked',self._on_columnview_remove_button_clicked,item) child.backup_button._signal_clicked_connection = child.backup_button.connect('clicked',self._on_columnview_backup_button_clicked,item)
archiver_manager.bind_property('backup-in-progress',child.backup_button,'sensitive', if not hasattr(child.backup_button,'_property_backup_in_progress_binding'):
BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True) child.backup_button._property_backup_in_progress_binding = archiver_manager.bind_property('backup-in-progress',
archiver_manager.bind_property('backup-in-progress',child.edit_button,'sensitive', child.backup_button,
BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True) 'sensitive',
archiver_manager.bind_property('backup-in-progress',child.remove_button,'sensitive', BindingFlags.SYNC_CREATE,
BindingFlags.SYNC_CREATE,lambda binding,x: False if x else True) 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 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',
child.edit_button,
'sensitive',
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 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',
BindingFlags.SYNC_CREATE,
lambda binding,x: False if x else True)
if os.path.exists(os.path.join(game.savegame_root,game.savegame_dir)):
child.backup_button.set_sensitive(True)
else:
child.backup_button.set_sensitive(False)
def _on_columnview_backup_button_clicked(self,button,item): def _on_columnview_backup_button_clicked(self,button,item):
def on_dialog_response(dialog,response): def on_dialog_response(dialog,response):
@ -361,7 +567,7 @@ class BackupViewData(GObject):
:type: bool :type: bool
""" """
pass return self.__is_live
@Property(type=str) @Property(type=str)
def extension(self)->str: def extension(self)->str:
@ -571,11 +777,34 @@ class AppWindow(Gtk.ApplicationWindow):
vbox.append(self.__vpaned) vbox.append(self.__vpaned)
statusbar = Gtk.Statusbar() self.__statusbar = Gtk.Statusbar()
statusbar.set_hexpand(True) self.statusbar.set_hexpand(True)
statusbar.set_vexpand(False) self.statusbar.set_vexpand(False)
statusbar.push(0,'Running ...') self.gameview.connect('refresh',self._on_gameview_refresh)
vbox.append(statusbar) self.gameview.connect('game-active-changed',lambda gv,*data: self._on_gameview_refresh(gv))
self.gameview.connect('game-live-changed',lambda gv,*data: self._on_gameview_refresh(gv))
n_games = self.gameview._liststore.get_n_items()
n_live = 0
n_active = 0
n_finished = 0
for i in range(n_games):
game = self.gameview._liststore.get_item(i)
if game.is_live:
n_live += 1
else:
n_finished += 1
if game.is_active:
n_active += 1
self.statusbar.push(0,'{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished'.format(
games=n_games,
active=n_active,
live=n_live,
finished=n_finished))
vbox.append(self.statusbar)
self.set_child(vbox) self.set_child(vbox)
@ -609,6 +838,10 @@ class AppWindow(Gtk.ApplicationWindow):
""" """
return self.__gameview return self.__gameview
@property
def statusbar(self):
return self.__statusbar
def refresh(self): def refresh(self):
""" """
refresh Refresh the views of this window. refresh Refresh the views of this window.
@ -617,6 +850,29 @@ class AppWindow(Gtk.ApplicationWindow):
self.gameview.refresh() self.gameview.refresh()
#self.backupview.refresh() #self.backupview.refresh()
def _on_gameview_refresh(self,gameview):
self.statusbar.pop(0)
n_games = gameview._liststore.get_n_items()
n_active = 0
n_live = 0
n_finished = 0
for i in range(n_games):
game = gameview._liststore.get_item(i)
if game.is_live:
n_live += 1
else:
n_finished += 1
if game.is_active:
n_active += 1
self.statusbar.push(0,'{games} Games -- {active} Games active -- {live} Games live -- {finished} Games finished'.format(
games=n_games,
active=n_active,
live=n_live,
finished=n_finished))
class Application(Gtk.Application): class Application(Gtk.Application):
""" """
Application The `Gtk.Application` for this app. Application The `Gtk.Application` for this app.

View File

@ -1025,9 +1025,9 @@ class GameDialog(Gtk.Dialog):
self.__game_linux = None self.__game_linux = None
if self.get_is_valid_savegame_type(SavegameType.MACOS): if self.get_is_valid_savegame_type(SavegameType.MACOS):
data = get_game_data(self.__linux) data = get_game_data(self.__macos)
binary = self.__linux.binary_entry.get_text() binary = self.__macos.binary_entry.get_text()
if self.__game.linux: if self.__game.macos:
mg = self.__game.macos mg = self.__game.macos
mg.savegame_root = data['sgroot'] mg.savegame_root = data['sgroot']
mg.savegame_dir = data['sgdir'] mg.savegame_dir = data['sgdir']
@ -1187,7 +1187,8 @@ class GameDialog(Gtk.Dialog):
def _on_variable_name_bind(self,factory,item): def _on_variable_name_bind(self,factory,item):
label = item.get_child() label = item.get_child()
data = item.get_item() data = item.get_item()
data.bind_property('name',label,'label',BindingFlags.SYNC_CREATE) if not hasattr(label,'_property_data_to_label_binding'):
label._property_data_to_label_binding = data.bind_property('name',label,'label',BindingFlags.SYNC_CREATE)
def _on_variable_value_setup(self,factory,item): def _on_variable_value_setup(self,factory,item):
label = Gtk.Label() label = Gtk.Label()
@ -1196,7 +1197,8 @@ class GameDialog(Gtk.Dialog):
def _on_variable_value_bind(self,factory,item): def _on_variable_value_bind(self,factory,item):
label = item.get_child() label = item.get_child()
data = item.get_item() data = item.get_item()
data.bind_property('value',label,'label',BindingFlags.SYNC_CREATE) if not hasattr(label,'_property_value_to_label_binding'):
label._property_value_to_label_binding = data.bind_property('value',label,'label',BindingFlags.SYNC_CREATE)
def _on_filematch_dropdown_selection_changed(self,dropdown,data,item): def _on_filematch_dropdown_selection_changed(self,dropdown,data,item):
data = item.get_item() data = item.get_item()
@ -1221,7 +1223,8 @@ class GameDialog(Gtk.Dialog):
if (data.match_type == self.__filematch_dropdown_model.get_item(i).match_type): if (data.match_type == self.__filematch_dropdown_model.get_item(i).match_type):
dropdown.set_selected(i) dropdown.set_selected(i)
break break
dropdown.connect('notify::selected-item',self._on_filematch_dropdown_selection_changed,item) if not hasattr(dropdown,'_signal_notify_selected_item_connector'):
dropdown._signal_notify_selected_item_connector = dropdown.connect('notify::selected-item',self._on_filematch_dropdown_selection_changed,item)
def _on_filematch_value_setup(self,factory,item,widget): def _on_filematch_value_setup(self,factory,item,widget):
label = Gtk.EditableLabel() label = Gtk.EditableLabel()
@ -1232,11 +1235,13 @@ class GameDialog(Gtk.Dialog):
data = item.get_item() data = item.get_item()
if (data.match_file): if (data.match_file):
label.set_text(data.match_file) label.set_text(data.match_file)
label.bind_property('text',data,'match_file',BindingFlags.DEFAULT)
label.connect('notify::editing',self._on_filematch_value_notify_editing,widget) if not hasattr(label,'_property_text_to_matchfile_binding'):
else: label._property_text_to_matchfile_binding = label.bind_property('text',data,'match_file',BindingFlags.DEFAULT)
label.bind_property('text',data,'match_file',BindingFlags.DEFAULT) if not hasattr(label,'_signal_notify_editing_connector'):
label.connect('notify::editing',self._on_filematch_value_notify_editing,widget) label._signal_notify_editing_connector = label.connect('notify::editing',self._on_filematch_value_notify_editing,widget)
if not label.get_text():
label.grab_focus() label.grab_focus()
label.start_editing() label.start_editing()
@ -1293,23 +1298,20 @@ class GameDialog(Gtk.Dialog):
if not label.get_text().strip(): if not label.get_text().strip():
model = widget.columnview.get_model().get_model() model = widget.columnview.get_model().get_model()
i = 0 i = 0
while i < model.get_n_items(): for i in reversed(range(model.get_n_items())):
item = model.get_item(i) item = model.get_item(i)
if not item.match_file.strip(): if not item.match_file.strip():
model.remove(i) model.remove(i)
continue
i += 1
def _on_filematch_value_notify_editing(self,label,param,widget): def _on_filematch_value_notify_editing(self,label,param,widget):
if label.props.editing == False: if label.props.editing == False:
if not label.get_text().strip(): if not label.get_text().strip():
model = widget.columnview.get_model().get_model() model = widget.columnview.get_model().get_model()
i = 0 i = 0
while i < model.get_n_items(): for i in reversed(range(model.get_n_items())):
item = model.get_item(i) item = model.get_item(i)
if not item.match_file.strip(): if not item.match_file.strip():
model.remove(i) model.remove(i)
continue
i += 1
def _on_windows_regkey_add_button_clicked(self,button,widget): def _on_windows_regkey_add_button_clicked(self,button,widget):
widget.listview.get_model().get_model().append(RegistryKeyData()) widget.listview.get_model().get_model().append(RegistryKeyData())

View File

@ -17,7 +17,7 @@
############################################################################### ###############################################################################
from gi.repository import Gtk,GLib,Gio from gi.repository import Gtk,GLib,Gio
from gi.repository.GObject import GObject,Signal,Property,SignalFlags from gi.repository.GObject import GObject,Signal,Property,SignalFlags,BindingFlags
from ..settings import settings from ..settings import settings
from ..archiver import ArchiverManager,Archiver from ..archiver import ArchiverManager,Archiver
@ -65,6 +65,38 @@ class ZipfileCompressorDataSorter(Gtk.Sorter):
return Gtk.Ordering.EQUAL return Gtk.Ordering.EQUAL
class VariableData(GObject):
def __init__(self,name:str,value:str):
GObject.__init__(self)
self.__name = name
self.__value = value
@Property(type=str)
def name(self)->str:
return self.__name
@name.setter
def name(self,name:str):
self.__name = name
@Property(type=str)
def value(self)->str:
return self.__value
@value.setter
def value(self,value:str):
self.__value = value
class VariableDataSorter(Gtk.Sorter):
def do_compare(self,item1,item2):
if (item1.name < item2.name):
return Gtk.Ordering.SMALLER
elif (item1.name > item2.name):
return Gtk.Ordering.LARGER
else:
return Gtk.Ordering.EQUAL
class SettingsDialog(Gtk.Dialog): class SettingsDialog(Gtk.Dialog):
def __init__(self,parent=None): def __init__(self,parent=None):
Gtk.Dialog.__init__(self) Gtk.Dialog.__init__(self)
@ -80,6 +112,7 @@ class SettingsDialog(Gtk.Dialog):
self.__stack_sidebar = Gtk.StackSidebar.new() self.__stack_sidebar = Gtk.StackSidebar.new()
self.__general_page = self.__add_general_settings_page() self.__general_page = self.__add_general_settings_page()
self.__archiver_page = self.__add_archiver_settings_page() self.__archiver_page = self.__add_archiver_settings_page()
self.__variables_page = self.__add_variables_settings_page()
sidebar_scrolled=Gtk.ScrolledWindow() sidebar_scrolled=Gtk.ScrolledWindow()
sidebar_scrolled.set_child(self.__stack_sidebar) sidebar_scrolled.set_child(self.__stack_sidebar)
@ -90,7 +123,6 @@ class SettingsDialog(Gtk.Dialog):
paned.set_vexpand(True) paned.set_vexpand(True)
self.__stack_sidebar.set_stack(self.__stack) self.__stack_sidebar.set_stack(self.__stack)
vbox.append(paned) vbox.append(paned)
self.add_button("Apply",Gtk.ResponseType.APPLY) self.add_button("Apply",Gtk.ResponseType.APPLY)
@ -157,7 +189,6 @@ class SettingsDialog(Gtk.Dialog):
grid.attach(page.archiver_dropdown,1,3,2,1) grid.attach(page.archiver_dropdown,1,3,2,1)
vbox.append(grid) vbox.append(grid)
page.set_child(vbox) page.set_child(vbox)
@ -212,6 +243,99 @@ class SettingsDialog(Gtk.Dialog):
self.add_page(page,"zipfile","Archiver Settings") self.add_page(page,"zipfile","Archiver Settings")
return page return page
def _on_variable_add_button_clicked(self,button):
variable_model = self.__variables_page.variable_columnview.get_model()
while hasattr(variable_model,'get_model'):
variable_model = variable_model.get_model()
variable_model.append(VariableData("",""))
def __add_variables_settings_page(self):
page = Gtk.Box.new(Gtk.Orientation.VERTICAL,0)
actions = Gtk.ActionBar()
icon=Gtk.Image.new_from_icon_name('list-add-symbolic')
button = Gtk.Button()
button.set_child(icon)
button.connect('clicked',self._on_variable_add_button_clicked)
actions.pack_end(button)
page.append(actions)
scrolled = Gtk.ScrolledWindow()
model = Gio.ListStore.new(VariableData)
for vname,vvalue in settings.variables.items():
model.append(VariableData(vname,vvalue))
sorted_model = Gtk.SortListModel.new(model,VariableDataSorter())
vname_factory = Gtk.SignalListItemFactory()
vname_factory.connect('setup',self._on_variable_name_factory_setup)
vname_factory.connect('bind',self._on_variable_name_factory_bind)
vvalue_factory = Gtk.SignalListItemFactory()
vvalue_factory.connect('setup',self._on_variable_value_factory_setup)
vvalue_factory.connect('bind',self._on_variable_value_factory_bind)
selection = Gtk.SingleSelection(model=sorted_model)
page.variable_columnview = Gtk.ColumnView.new(selection)
vname_column = Gtk.ColumnViewColumn.new("Name",vname_factory)
vname_column.set_expand(True)
page.variable_columnview.append_column(vname_column)
vvalue_column = Gtk.ColumnViewColumn.new("Value",vvalue_factory)
page.variable_columnview.append_column(vvalue_column)
page.variable_columnview.set_vexpand(True)
scrolled.set_child(page.variable_columnview)
scrolled.set_hexpand(True)
page.append(scrolled)
self.add_page(page,"variables","Variables")
return page
def _on_variable_name_notify_editing(self,label,param,*data):
if label.props.editing == False:
if not label.get_text():
model = self.__variables_page.variable_columnview.get_model()
while hasattr(model,'get_model'):
model = model.get_model()
for i in reversed(range(model.get_n_items())):
if not model.get_item(i).name:
model.remove(i)
def _on_variable_name_factory_setup(self,factory,item):
label = Gtk.EditableLabel()
item.set_child(label)
def _on_variable_name_factory_bind(self,factory,item):
data = item.get_item()
label = item.get_child()
label.set_text(data.name)
if not hasattr(label,'_property_text_to_name_binding'):
label._property_text_to_name_binding = label.bind_property('text',data,'name',BindingFlags.DEFAULT)
if not hasattr(label,'_signal_notify_editing_connection'):
label._signal_notify_editing_connection = label.connect('notify::editing',self._on_variable_name_notify_editing)
if not data.name:
label.grab_focus()
label.start_editing()
def _on_variable_value_factory_setup(self,factory,item):
label = Gtk.EditableLabel()
item.set_child(label)
def _on_variable_value_factory_bind(self,factory,item):
label = item.get_child()
data = item.get_item()
label.set_text(data.value)
if not hasattr(label,'_property_text_to_value_binding'):
label._property_text_to_value_binding = label.bind_property('text',data,'name',BindingFlags.DEFAULT)
def _on_archiver_factory_setup(self,factory,item): def _on_archiver_factory_setup(self,factory,item):
label = Gtk.Label() label = Gtk.Label()
item.set_child(label) item.set_child(label)
@ -277,4 +401,13 @@ class SettingsDialog(Gtk.Dialog):
settings.zipfile_compression = self.archiver_page.zf_compressor_dropdown.get_selected_item().compressor 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() 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'):
variable_model = variable_model.get_model()
for i in range(variable_model.get_n_items()):
vdata = variable_model.get_item(i)
if vdata.name:
variables[vdata.name] = vdata.value
settings.variables = variables

View File

@ -223,6 +223,7 @@ class NewSteamAppsDialog(Gtk.Dialog):
self.__listview = Gtk.ListView.new(selection,factory) self.__listview = Gtk.ListView.new(selection,factory)
self.__listview.set_vexpand(True) self.__listview.set_vexpand(True)
self.__listview.set_show_separators(True) self.__listview.set_show_separators(True)
self.__listview.set_single_click_activate(True)
scrolled = Gtk.ScrolledWindow() scrolled = Gtk.ScrolledWindow()
scrolled.set_child(self.__listview) scrolled.set_child(self.__listview)
@ -286,8 +287,10 @@ class NewSteamAppsDialog(Gtk.Dialog):
child.name_label.set_markup("<span weight='bold' size='large'>{}</span>".format(GLib.markup_escape_text(data.name))) child.name_label.set_markup("<span weight='bold' size='large'>{}</span>".format(GLib.markup_escape_text(data.name)))
child.appid_label.set_text(str(data.appid)) child.appid_label.set_text(str(data.appid))
child.installdir_label.set_text(data.installdir) child.installdir_label.set_text(data.installdir)
child.add_app_button.connect('clicked',self._on_add_steamapp_button_clicked,data) if not hasattr(child.add_app_button,'_signal_clicked_connector'):
child.ignore_app_button.connect('clicked',self._on_ignore_steamapp_button_clicked,data) 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)
def _on_add_steamapp_button_clicked(self,button,data:SteamApp,*args): def _on_add_steamapp_button_clicked(self,button,data:SteamApp,*args):
def on_dialog_response(dialog,response): def on_dialog_response(dialog,response):

View File

@ -14,12 +14,12 @@
<attribute name='label' translatable='true'>_Steam</attribute> <attribute name='label' translatable='true'>_Steam</attribute>
<section> <section>
<item> <item>
<attribute name='label' translatable='true'>Manage libraries</attribute> <attribute name='label' translatable='true'>New Steam Apps</attribute>
<attribute name='action'>app.steam-manage-libraries</attribute> <attribute name='action'>app.steam-new-apps</attribute>
</item> </item>
<item> <item>
<attribute name='label' translatable='true'>Manage new apps</attribute> <attribute name='label' translatable='true'>Manage Steam Libraries</attribute>
<attribute name='action'>app.steam-new-apps</attribute> <attribute name='action'>app.steam-manage-libraries</attribute>
</item> </item>
</section> </section>
</submenu> </submenu>

View File

@ -396,6 +396,9 @@ class Settings(GObject.GObject):
return self.get_string('variables',name,"") return self.get_string('variables',name,"")
def get_variables(self)->dict[str:str]: def get_variables(self)->dict[str:str]:
if PLATFORM_WINDOWS:
ret = dict(((name.upper(),value) for name,value in os.environ.items()))
else:
ret = dict(os.environ) ret = dict(os.environ)
ret.update({ ret.update({
"DOCUMENTS": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS), "DOCUMENTS": GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DOCUMENTS),