mirror of
https://github.com/c9moser/sgbackup.git
synced 2026-01-19 11:30:13 +00:00
448 lines
16 KiB
Python
448 lines
16 KiB
Python
###############################################################################
|
|
# sgbackup - The SaveGame Backup tool #
|
|
# Copyright (C) 2024 Christian Moser #
|
|
# #
|
|
# This program is free software: you can redistribute it and/or modify #
|
|
# it under the terms of the GNU General Public License as published by #
|
|
# the Free Software Foundation, either version 3 of the License, or #
|
|
# (at your option) any later version. #
|
|
# #
|
|
# This program is distributed in the hope that it will be useful, #
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
|
# GNU General Public License for more details. #
|
|
# #
|
|
# You should have received a copy of the GNU General Public License #
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
|
###############################################################################
|
|
|
|
from gi.repository import Gtk,GObject,Gio,Gdk
|
|
|
|
import logging; logger=logging.getLogger(__name__)
|
|
|
|
import os
|
|
from datetime import datetime as DateTime
|
|
from pathlib import Path
|
|
|
|
from .. import game
|
|
from ..settings import settings
|
|
from ._settingsdialog import SettingsDialog
|
|
from ._gamedialog import GameDialog
|
|
|
|
class GameView(Gtk.ScrolledWindow):
|
|
__gtype_name__ = "sgbackup-gui-GameView"
|
|
|
|
def __init__(self):
|
|
Gtk.ScrolledWindow.__init__(self)
|
|
|
|
self.__liststore = Gio.ListStore.new(game.Game)
|
|
for g in game.GAMES.values():
|
|
pass
|
|
self.__liststore.append(g)
|
|
|
|
factory_key = Gtk.SignalListItemFactory.new()
|
|
factory_key.connect('setup',self._on_key_column_setup)
|
|
factory_key.connect('bind',self._on_key_column_bind)
|
|
column_key = Gtk.ColumnViewColumn.new("Key",factory_key)
|
|
|
|
factory_name = Gtk.SignalListItemFactory.new()
|
|
factory_name.connect('setup',self._on_name_column_setup)
|
|
factory_name.connect('bind',self._on_name_column_bind)
|
|
column_name = Gtk.ColumnViewColumn.new("Name",factory_name)
|
|
column_name.set_expand(True)
|
|
|
|
factory_active = Gtk.SignalListItemFactory.new()
|
|
factory_active.connect('setup',self._on_active_column_setup)
|
|
factory_active.connect('bind',self._on_active_column_bind)
|
|
factory_active.connect('unbind',self._on_active_column_unbind)
|
|
column_active = Gtk.ColumnViewColumn.new("Active",factory_active)
|
|
|
|
factory_live = Gtk.SignalListItemFactory.new()
|
|
factory_live.connect('setup',self._on_live_column_setup)
|
|
factory_live.connect('bind',self._on_live_column_bind)
|
|
factory_live.connect('unbind',self._on_live_column_unbind)
|
|
column_live = Gtk.ColumnViewColumn.new("Live",factory_live)
|
|
|
|
selection = Gtk.SingleSelection.new(self._liststore)
|
|
self.__columnview = Gtk.ColumnView.new(selection)
|
|
self._columnview.append_column(column_key)
|
|
self._columnview.append_column(column_name)
|
|
self._columnview.append_column(column_active)
|
|
self._columnview.append_column(column_live)
|
|
self._columnview.set_single_click_activate(True)
|
|
|
|
self.set_child(self._columnview)
|
|
|
|
@property
|
|
def _liststore(self):
|
|
return self.__liststore
|
|
|
|
@property
|
|
def _columnview(self):
|
|
return self.__columnview
|
|
|
|
def _on_key_column_setup(self,factory,item):
|
|
item.set_child(Gtk.Label())
|
|
|
|
def _on_key_column_bind(self,factory,item):
|
|
label = item.get_child()
|
|
game = item.get_item()
|
|
label.bind_property(game,'key','label',GObject.BindingFlags.DEFAULT)
|
|
|
|
def _on_name_column_setup(self,factory,item):
|
|
item.set_child(Gtk.Label())
|
|
|
|
def _on_name_column_bind(self,factory,item):
|
|
label = item.get_child()
|
|
game = item.get_item()
|
|
label.bind_proprety(game,'name','label',GObject.BindingFlags.DEFAULT)
|
|
|
|
def _on_active_column_setup(self,factory,item):
|
|
item.set_child(Gtk.Switch())
|
|
|
|
def _on_active_column_bind(self,factory,item):
|
|
switch = item.get_child()
|
|
game = item.get_data()
|
|
switch.set_active(game.is_active)
|
|
item._signal_active_state_set = switch.connect('state-set',self._on_active_state_set,game)
|
|
|
|
def _on_active_column_unbind(self,factory,item):
|
|
if hasattr(item,'_signal_active_state_set'):
|
|
item.get_child().disconnect(item._signal_active_state_set)
|
|
del item._signal_active_state_set
|
|
|
|
def _on_active_state_set(self,switch,state,game):
|
|
game.is_active = state
|
|
game.save()
|
|
|
|
def _on_live_column_setup(self,factory,item):
|
|
item.set_child(Gtk.Switch())
|
|
|
|
def _on_live_column_bind(self,factory,item):
|
|
switch = item.get_child()
|
|
game = item.get_data()
|
|
switch.set_active(game.is_active)
|
|
item._signal_live_state_set = switch.connect('state-set',self._on_live_state_set,game)
|
|
|
|
def _on_live_column_unbind(self,factory,item):
|
|
if hasattr(item,'_signal_live_state_set'):
|
|
item.get_child().disconnect(item._signal_live_state_set)
|
|
del item._signal_live_state_set
|
|
|
|
def _on_live_state_set(self,switch,state,game):
|
|
def on_dialog_response(dialog,response):
|
|
if response == Gtk.ResponseType.YES:
|
|
pass
|
|
#archiver.backup(game)
|
|
dialog.hide()
|
|
dialog.destroy()
|
|
|
|
game.is_live = state
|
|
game.save()
|
|
if not state:
|
|
dialog = Gtk.MessageDialog()
|
|
dialog.set_transient_for(self.get_toplevel())
|
|
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.use_markup = True
|
|
dialog.props.secondary_text = "The new savegame is added to the finsihed savegames for the game."
|
|
dialog.props.secondary_use_markup = False
|
|
dialog.connect('response',on_dialog_response)
|
|
dialog.present()
|
|
# GameView class
|
|
|
|
class BackupViewData(GObject.GObject):
|
|
def __init__(self,_game:game.Game,filename:str):
|
|
GObject.GObject.__init__(self)
|
|
self.__game = _game
|
|
self.__filename = filename
|
|
|
|
basename = os.path.basename(filename)
|
|
self.__is_live = (os.path.basename(os.path.dirname(filename)) == 'live')
|
|
parts = filename.split('.')
|
|
self.__savegame_name = parts[0]
|
|
self.__timestamp = DateTime.strptime(parts[1],"%Y%m%d-%H%M%S")
|
|
|
|
self.__extension = '.' + parts[3:]
|
|
|
|
@property
|
|
def game(self)->game.Game:
|
|
return self.__game
|
|
|
|
@GObject.Property
|
|
def savegame_name(self):
|
|
return self.__savegame_name
|
|
|
|
@GObject.Property(type=str)
|
|
def filename(self)->str:
|
|
return self.__filename
|
|
|
|
@GObject.Property(type=bool,default=False)
|
|
def is_live(self)->bool:
|
|
pass
|
|
|
|
@GObject.Property
|
|
def extension(self):
|
|
return self.__extension
|
|
|
|
@GObject.Property
|
|
def timestamp(self):
|
|
return self.__timestamp
|
|
|
|
class BackupView(Gtk.ScrolledWindow):
|
|
__gtype_name__ = "BackupView"
|
|
def __init__(self,gameview:GameView):
|
|
Gtk.ScrolledWindow.__init__(self)
|
|
self.__gameview = gameview
|
|
|
|
self.__liststore = Gio.ListStore()
|
|
selection = Gtk.SingleSelection.new(self.__liststore)
|
|
|
|
live_factory = Gtk.SignalListItemFactory()
|
|
live_factory.connect('setup',self._on_live_column_setup)
|
|
live_factory.connect('bind',self._on_live_column_bind)
|
|
live_column = Gtk.ColumnViewColumn.new("Live",live_factory)
|
|
|
|
sgname_factory = Gtk.SignalListItemFactory()
|
|
sgname_factory.connect('setup',self._on_savegamename_column_setup)
|
|
sgname_factory.connect('bind',self._on_savegamename_column_bind)
|
|
sgname_column = Gtk.ColumnViewColumn.new("Savegame name",sgname_factory)
|
|
sgname_column.set_expand(True)
|
|
|
|
timestamp_factory = Gtk.SignalListItemFactory()
|
|
timestamp_factory.connect('setup',self._on_timestamp_column_setup)
|
|
timestamp_factory.connect('bind',self._on_timestamp_column_bind)
|
|
timestamp_column = Gtk.ColumnViewColumn.new("Timestamp",timestamp_factory)
|
|
|
|
self.__columnview = Gtk.ColumnView.new(selection)
|
|
self.__columnview.append_column(live_column)
|
|
self.__columnview.append_column(sgname_column)
|
|
self.__columnview.append_column(timestamp_column)
|
|
|
|
self._on_gameview_selection_changed(selection)
|
|
self.gameview._columnview.get_model().connect('selection-changed',self._on_gameview_selection_changed)
|
|
|
|
self.set_child(self.__columnview)
|
|
|
|
@property
|
|
def gameview(self)->GameView:
|
|
return self.__gameview
|
|
|
|
def _on_live_column_setup(self,factory,item):
|
|
checkbutton = Gtk.CheckButton()
|
|
checkbutton.set_sensitive(False)
|
|
|
|
def _on_live_column_bind(self,factory,item):
|
|
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()
|
|
self.set_child(label)
|
|
|
|
def _on_savegamename_column_bind(self,factory,item):
|
|
label = item.get_child()
|
|
data = item.get_item()
|
|
label.set_text(data.savegame_name)
|
|
|
|
def _on_timestamp_column_setup(self,factory,item):
|
|
label = Gtk.Label()
|
|
item.set_child(label)
|
|
|
|
def _on_timestamp_column_bind(self,factory,item):
|
|
label = item.get_child()
|
|
data = item.get_item()
|
|
label.set_text(data.timestamp.strftime("%d.%m.%Y %H:%M:%S"))
|
|
|
|
def _on_size_column_setup(self,factory,item):
|
|
label = Gtk.Label()
|
|
item.set_child(label)
|
|
|
|
def _on_size_column_bind(self,factory,item):
|
|
label = item.get_child()
|
|
data = item.get_item()
|
|
file = Path(data.filename).resolve()
|
|
|
|
if not file.is_file():
|
|
label.set_text("0 B")
|
|
return
|
|
|
|
size = file.stat().st_size
|
|
if (size > 1073741824):
|
|
display_size = ".".join((str(int(size / 1073741824)),str(int(((size * 10) / 1073741824) % 10)))) + " GiB"
|
|
elif (size > 1048576):
|
|
display_size = ".".join((str(int(size / 1048576)), str(int(((size * 10) / 1048576) % 10)))) + " MiB"
|
|
elif (size > 1024):
|
|
display_size = ".".join((str(int(size / 1024)), str(int(((size * 10) / 1024) % 10)))) + " KiB"
|
|
else:
|
|
display_size = str(size) + " B"
|
|
label.set_text(display_size)
|
|
|
|
def _on_gameview_selection_changed(self,model):
|
|
game = model.get_selected_item()
|
|
if game is None:
|
|
return
|
|
|
|
|
|
class AppWindow(Gtk.ApplicationWindow):
|
|
__gtype_name__ = "AppWindow"
|
|
def __init__(self,application=None,**kwargs):
|
|
kwargs['title'] = "SGBackup"
|
|
|
|
if (application is not None):
|
|
kwargs['application']=application
|
|
if (hasattr(application,'builder')):
|
|
builder = application.builder
|
|
else:
|
|
builder = Gtk.Builder.new()
|
|
|
|
Gtk.ApplicationWindow.__init__(self,**kwargs)
|
|
self.set_default_size(800,600)
|
|
self.set_icon_name('sgbackup')
|
|
|
|
self.__builder = builder
|
|
self.builder.add_from_file(os.path.join(os.path.dirname(__file__),'appmenu.ui'))
|
|
gmenu = self.builder.get_object('appmenu')
|
|
appmenu_popover = Gtk.PopoverMenu.new_from_model(gmenu)
|
|
image = Gtk.Image.new_from_icon_name('open-menu-symbolic')
|
|
menubutton = Gtk.MenuButton.new()
|
|
menubutton.set_popover(appmenu_popover)
|
|
menubutton.set_child(image)
|
|
headerbar = Gtk.HeaderBar.new()
|
|
headerbar.pack_start(menubutton)
|
|
self.set_titlebar(headerbar)
|
|
|
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
|
|
|
self.__vpaned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
|
|
self.__vpaned.set_hexpand(True)
|
|
self.__vpaned.set_vexpand(True)
|
|
self.__vpaned.set_wide_handle(True)
|
|
self.__gameview = GameView()
|
|
self.__vpaned.set_start_child(self.gameview)
|
|
self.__backupview = BackupView(self.gameview)
|
|
self.__vpaned.set_end_child(self.backupview)
|
|
self.__vpaned.set_resize_start_child(True)
|
|
self.__vpaned.set_resize_end_child(True)
|
|
|
|
vbox.append(self.__vpaned)
|
|
|
|
statusbar = Gtk.Statusbar()
|
|
statusbar.set_hexpand(True)
|
|
statusbar.set_vexpand(False)
|
|
statusbar.push(0,'Running ...')
|
|
vbox.append(statusbar)
|
|
|
|
self.set_child(vbox)
|
|
|
|
@GObject.Property
|
|
def builder(self):
|
|
return self.__builder
|
|
|
|
@GObject.Property
|
|
def backupview(self):
|
|
return self.__backupview
|
|
|
|
@GObject.Property
|
|
def gameview(self):
|
|
return self.__gameview
|
|
|
|
|
|
class Application(Gtk.Application):
|
|
__gtype_name__ = "Application"
|
|
|
|
def __init__(self,*args,**kwargs):
|
|
AppFlags = Gio.ApplicationFlags
|
|
kwargs['application_id'] = 'org.sgbackup.sgbackup'
|
|
kwargs['flags'] = AppFlags.FLAGS_NONE
|
|
Gtk.Application.__init__(self,*args,**kwargs)
|
|
|
|
self.__logger = logger.getChild('Application')
|
|
self.__builder = None
|
|
self.__appwindow = None
|
|
|
|
@property
|
|
def _logger(self):
|
|
return self.__logger
|
|
|
|
@GObject.Property
|
|
def appwindow(self):
|
|
return self.__appwindow
|
|
|
|
def do_startup(self):
|
|
self._logger.debug('do_startup()')
|
|
if not self.__builder:
|
|
self.__builder = Gtk.Builder.new()
|
|
Gtk.Application.do_startup(self)
|
|
|
|
pkg_path = Path(__file__).resolve()
|
|
pkg_path = pkg_path.parent.parent
|
|
icons_path = pkg_path / "icons"
|
|
|
|
theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default())
|
|
theme.add_resource_path("/org/sgbackup/sgbackup/icons")
|
|
theme.add_search_path(str(icons_path))
|
|
|
|
action_about = Gio.SimpleAction.new('about',None)
|
|
action_about.connect('activate',self.on_action_about)
|
|
self.add_action(action_about)
|
|
|
|
action_new_game = Gio.SimpleAction.new('new-game',None)
|
|
action_new_game.connect('activate',self.on_action_new_game)
|
|
self.add_action(action_new_game)
|
|
|
|
action_quit = Gio.SimpleAction.new('quit',None)
|
|
action_quit.connect('activate',self.on_action_quit)
|
|
self.add_action(action_quit)
|
|
|
|
action_settings = Gio.SimpleAction.new('settings',None)
|
|
action_settings.connect('activate',self.on_action_settings)
|
|
self.add_action(action_settings)
|
|
|
|
# add accels
|
|
self.set_accels_for_action('app.quit',["<Primary>q"])
|
|
|
|
@GObject.Property
|
|
def builder(self):
|
|
return self.__builder
|
|
|
|
def do_activate(self):
|
|
self._logger.debug('do_activate()')
|
|
if not (self.__appwindow):
|
|
self.__appwindow = AppWindow(application=self)
|
|
|
|
|
|
self.appwindow.present()
|
|
|
|
def on_action_about(self,action,param):
|
|
pass
|
|
|
|
def on_action_settings(self,action,param):
|
|
dialog = self.new_settings_dialog()
|
|
dialog.present()
|
|
|
|
def on_action_quit(self,action,param):
|
|
self.quit()
|
|
|
|
def on_action_new_game(self,action,param):
|
|
def on_reponse(dialog,response):
|
|
dialog.destroy()
|
|
|
|
dialog = GameDialog(self.appwindow)
|
|
dialog.connect('response',on_reponse)
|
|
dialog.present()
|
|
|
|
def new_settings_dialog(self):
|
|
dialog = SettingsDialog(self.appwindow)
|
|
self.emit('settings-dialog-init',dialog)
|
|
return dialog
|
|
|
|
@GObject.Signal(name='settings-dialog-init',
|
|
flags=GObject.SignalFlags.RUN_LAST,
|
|
return_type=None,
|
|
arg_types=(SettingsDialog,))
|
|
def settings_dialog_init(self,dialog):
|
|
pass
|
|
|