gsgbackup/sgbackup/gui/_app.py

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