diff --git a/dsst/dsst_gtk3/dialogs.py b/dsst/dsst_gtk3/dialogs.py index ded4d30..15919b5 100644 --- a/dsst/dsst_gtk3/dialogs.py +++ b/dsst/dsst_gtk3/dialogs.py @@ -1,7 +1,9 @@ """ This module contains UI functions for displaying different dialogs """ +import datetime from gi.repository import Gtk +from common import models def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str: @@ -28,6 +30,32 @@ def enter_string_dialog(builder: Gtk.Builder, title: str, value=None) -> str: return value +def edit_season(builder: 'Gtk.Builder', season: 'models.Season'=None): + if not season: + season = models.Season() + builder.get_object('season_number_spin').set_value(season.number or 1) + builder.get_object('season_game_entry').set_text(season.game_name or '') + builder.get_object('season_start_entry').set_text(season.start_date or '') + builder.get_object('season_end_entry').set_text(season.end_date or '') + + dialog = builder.get_object('edit_season_dialog') + result = dialog.run() + dialog.hide() + + if result != Gtk.ResponseType.OK: + return None + + season.number = builder.get_object('season_number_spin').get_value() + season.game_name = builder.get_object('season_game_entry').get_text() + start_string = builder.get_object('season_start_entry').get_text() + if start_string: + season.start_date = datetime.datetime.strptime(start_string, '%Y-%m-%d') + end_string = builder.get_object('season_end_entry').get_text() + if end_string: + season.end_date = datetime.datetime.strptime(end_string, '%Y-%m-%d') + return season + + def show_episode_dialog(builder: Gtk.Builder, title: str, season_id: int, episode): """ Shows a dialog to edit an episode :param builder: GtkBuilder with loaded 'dialogs.glade' diff --git a/dsst/dsst_gtk3/gtk_ui.py b/dsst/dsst_gtk3/gtk_ui.py index 7105ee3..d19b341 100644 --- a/dsst/dsst_gtk3/gtk_ui.py +++ b/dsst/dsst_gtk3/gtk_ui.py @@ -1,10 +1,10 @@ import os import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GdkPixbuf +from gi.repository import Gtk from dsst_gtk3.handlers import handlers from dsst_gtk3 import util, reload, client - +from common import models class GtkUi: """ The main UI class """ @@ -29,34 +29,51 @@ class GtkUi: # Connect to data server config = config['servers'][0] self.data_client = client.Access(config) - self.data = {} + # Create local data caches + self.players = util.Cache() + self.drinks = util.Cache() + self.seasons = util.Cache() + self.episodes = util.Cache() + self.enemies = util.Cache() + self.season_stats = util.Cache() + # Create meta data cache self.meta = {'connection': '{}:{}'.format(config.get('host'), config.get('port'))} - self.season_changed = True - self.ep_changed = False # Load base data and seasons - self.initial_load() + self.load_server_meta() + self.reload() self.update_status_bar_meta() - def initial_load(self): - with util.network_operation(self): - self.data['players'] = self.data_client.send_request('load_players') - self.data['drinks'] = self.data_client.send_request('load_drinks') - self.data['seasons'] = self.data_client.send_request('load_seasons') - self.meta['database'] = self.data_client.send_request('load_db_meta') - reload.reload_base_data(self.ui, self) + def load_server_meta(self): + self.meta['database'] = self.data_client.send_request('load_db_meta') def reload(self): - if self.season_changed: + with util.network_operation(self): + refresh_base = False + if not self.players.valid: + self.players.data = self.data_client.send_request('load_players') + refresh_base = True + if not self.drinks.valid: + self.drinks.data = self.data_client.send_request('load_drinks') + refresh_base= True + if not self.seasons.valid: + self.seasons.data = self.data_client.send_request('load_seasons') + refresh_base = True + if refresh_base: + reload.reload_base_data(self.ui, self) + + if not self.episodes.valid: with util.network_operation(self): season_id = self.get_selected_season_id() - self.data['episodes'] = self.data_client.send_request('load_episodes', season_id) - self.data['season_stats'] = self.data_client.send_request('load_season_stats', season_id) - reload.reload_episodes(self.ui, self) - reload.reload_season_stats(self) - self.season_changed = False + if season_id: + self.episodes.data = self.data_client.send_request('load_episodes', season_id) + self.season_stats.data = self.data_client.send_request('load_season_stats', season_id) + reload.reload_episodes(self.ui, self) + reload.reload_season_stats(self) - if self.ep_changed: - reload.reload_episode_stats(self) + def update_season(self, season: 'models.Season'): + with util.network_operation(self): + self.data_client.send_request('update_season', season) + self.seasons.valid = False def update_status_bar_meta(self): self.ui.get_object('connection_label').set_text(self.meta.get('connection')) diff --git a/dsst/dsst_gtk3/handlers/dialog_handlers.py b/dsst/dsst_gtk3/handlers/dialog_handlers.py index 1224b50..c4be4f3 100644 --- a/dsst/dsst_gtk3/handlers/dialog_handlers.py +++ b/dsst/dsst_gtk3/handlers/dialog_handlers.py @@ -1,4 +1,7 @@ -from dsst_gtk3 import dialogs, util +import datetime + +from dsst_gtk3 import dialogs, util, gtk_ui +from gi.repository import Gtk class DialogHandlers: @@ -27,3 +30,20 @@ class DialogHandlers: def do_manage_drinks(self, *_): result = dialogs.show_manage_drinks_dialog(self.app.ui) + + def do_show_date_picker(self, entry: 'Gtk.Entry', *_): + dialog = self.app.ui.get_object('date_picker_dialog') + result = dialog.run() + dialog.hide() + if result == Gtk.ResponseType.OK: + date = self.app.ui.get_object('date_picker_calendar').get_date() + date_string = '{}-{:02d}-{:02d}'.format(date.year, date.month +1, date.day) + entry.set_text(date_string) + + @staticmethod + def do_set_today(cal: 'Gtk.Calendar'): + """Set date of a Gtk Calendar to today + :param cal: Gtk.Calendar + """ + cal.select_month = datetime.date.today().month + cal.select_day = datetime.date.today().day diff --git a/dsst/dsst_gtk3/handlers/season_handlers.py b/dsst/dsst_gtk3/handlers/season_handlers.py index 9734381..591a920 100644 --- a/dsst/dsst_gtk3/handlers/season_handlers.py +++ b/dsst/dsst_gtk3/handlers/season_handlers.py @@ -1,4 +1,4 @@ -from dsst_gtk3 import dialogs, gtk_ui +from dsst_gtk3 import dialogs, gtk_ui, reload class SeasonHandlers: @@ -7,12 +7,14 @@ class SeasonHandlers: self.app = app def do_add_season(self, *_): - name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season') - if name: + season = dialogs.edit_season(self.app.ui) + if season: + self.app.update_season(season) self.app.reload() def do_season_selected(self, *_): - self.app.season_changed = True + self.app.episodes.valid = False + self.app.season_stats.valid = False self.app.reload() def do_add_episode(self, *_): @@ -23,8 +25,7 @@ class SeasonHandlers: self.app.reload() def on_selected_episode_changed(self, *_): - self.app.ep_changed = True - self.app.reload() + reload.reload_episode_stats(self.app) def on_episode_double_click(self, *_): self.app.ui.get_object('stats_notebook').set_current_page(1) diff --git a/dsst/dsst_gtk3/reload.py b/dsst/dsst_gtk3/reload.py index e7d7d49..10e330c 100644 --- a/dsst/dsst_gtk3/reload.py +++ b/dsst/dsst_gtk3/reload.py @@ -10,11 +10,11 @@ def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi',): """ # Rebuild all players store builder.get_object('all_players_store').clear() - for player in app.data['players']: + for player in app.players.data: builder.get_object('all_players_store').append([player.id, player.name, player.hex_id]) # Rebuild drink store builder.get_object('drink_store').clear() - for drink in app.data['drinks']: + for drink in app.drinks.data: builder.get_object('drink_store').append([drink.id, drink.name, '{:.2f}%'.format(drink.vol)]) # Rebuild seasons store combo = builder.get_object('season_combo_box') # type: Gtk.ComboBox @@ -22,7 +22,7 @@ def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi',): with util.block_handler(combo, app.handlers.do_season_selected): store = builder.get_object('seasons_store') store.clear() - for season in app.data['seasons']: + for season in app.seasons.data: store.append([season.id, season.game_name]) combo.set_active(active) @@ -37,7 +37,7 @@ def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): with util.block_handler(selection, app.handlers.on_selected_episode_changed): model, selected_paths = selection.get_selected_rows() model.clear() - for episode in app.data['episodes']: + for episode in app.episodes.data: model.append([episode.id, episode.name, str(episode.date), episode.number]) if selected_paths: selection.select_path(selected_paths[0]) @@ -47,7 +47,7 @@ def reload_season_stats(app: 'gtk_ui.GtkUi'): """Load statistic data for selected season :param app: GtkUi instance """ - season_stats = app.data.get('season_stats') + season_stats = app.season_stats.data # Load player kill/death data store = app.ui.get_object('player_season_store') store.clear() @@ -65,7 +65,7 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'): """Reload all data that is dependant on a selected episode :param app: app: GtkUi instance """ - episode = [ep for ep in app.data['episodes'] if ep.id == app.get_selected_episode_id()][0] + episode = [ep for ep in app.episodes.data if ep.id == app.get_selected_episode_id()][0] store = app.ui.get_object('episode_players_store') store.clear() for player in episode.players: @@ -75,7 +75,7 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'): store.clear() for death in episode.deaths: penalties = [x.drink.name for x in death.penalties] - penalties = [f'{number}x {drink}' for drink, number in Counter(penalties).items()] + penalties = ['{}x {}'.format(number, drink) for drink, number in Counter(penalties).items()] penalty_string = ', '.join(penalties) store.append([death.id, death.player.name, death.enemy.name, penalty_string]) # Reload victory store for notebook view @@ -100,7 +100,7 @@ def reload_episode_stats(app: 'gtk_ui.GtkUi'): sorted_list = Counter(enemy_list).most_common(1) if sorted_list: enemy_name, deaths = sorted_list[0] - app.ui.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)') + app.ui.get_object('ep_enemy_name_label').set_text('{} ({} Deaths)'.format(enemy_name, deaths)) def fill_list_store(store: Gtk.ListStore, models: list): diff --git a/dsst/dsst_gtk3/resources/glade/dialogs.glade b/dsst/dsst_gtk3/resources/glade/dialogs.glade index 8b9b6ad..108fb46 100644 --- a/dsst/dsst_gtk3/resources/glade/dialogs.glade +++ b/dsst/dsst_gtk3/resources/glade/dialogs.glade @@ -1,7 +1,7 @@ - + - + False False diff --git a/dsst/dsst_gtk3/resources/glade/window.glade b/dsst/dsst_gtk3/resources/glade/window.glade index 9dc74e5..7c2e35e 100644 --- a/dsst/dsst_gtk3/resources/glade/window.glade +++ b/dsst/dsst_gtk3/resources/glade/window.glade @@ -1,7 +1,7 @@ - + @@ -1591,23 +1591,7 @@ - - New - True - True - True - 5 - 5 - 5 - 5 - - - - False - True - end - 1 - + @@ -1665,23 +1649,7 @@ - - New - True - True - True - 5 - 5 - 5 - 5 - - - - False - True - end - 1 - + @@ -2573,4 +2541,339 @@ + + False + Pick Date + False + 300 + dialog + False + main_window + + + False + vertical + 2 + + + False + end + + + gtk-ok + True + True + True + True + + + True + True + 0 + + + + + gtk-cancel + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + + + True + False + Pick Date + + + False + True + 0 + + + + + Today + True + True + True + + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + True + 2018 + 2 + 9 + + + False + True + 2 + + + + + + button3 + button6 + + + + + + + False + Edit Season + False + dialog + False + main_window + main_window + + + False + vertical + 2 + + + False + end + + + gtk-ok + True + True + True + True + + + True + True + 0 + + + + + gtk-cancel + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + + + True + False + Season Number: + + + False + True + 0 + + + + + True + True + digits + ep_number_ajustment + True + + + True + True + end + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + Game Name: + + + False + True + 0 + + + + + True + True + + + True + True + end + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + Start Date: + + + False + True + 0 + + + + + True + False + gtk-edit + + + + True + True + end + 1 + + + + + False + True + 2 + + + + + True + False + + + True + False + End Date: + + + False + True + 0 + + + + + True + False + gtk-edit + + + + True + True + end + 1 + + + + + False + True + 3 + + + + + True + True + 1 + + + + + + button4 + button5 + + + + + diff --git a/dsst/dsst_gtk3/util.py b/dsst/dsst_gtk3/util.py index 55c480f..a535d99 100644 --- a/dsst/dsst_gtk3/util.py +++ b/dsst/dsst_gtk3/util.py @@ -16,11 +16,26 @@ DEFAULT_CONFIG = { 'host': 'localhost', 'port': 12345, 'buffer_size': 1024, - 'auth_token': 'a'} + 'auth_token': ''} ] } +class Cache: + def __init__(self, data={}, valid=False): + self._data = data + self.valid = valid + + @property + def data(self): + return self._data + + @data.setter + def data(self, value): + self._data = value + self.valid = True + + @contextmanager def block_handler(widget: 'Gtk.Widget', handler_func: Callable): """Run an operation while a signal handler for a widget is blocked @@ -35,7 +50,7 @@ def block_handler(widget: 'Gtk.Widget', handler_func: Callable): @contextmanager def network_operation(app: 'gtk_ui.GtkUi'): """Run operation in try/except block and display exception in a dialog - :param exception: + :param app: Reference to main Gtk Application """ app.ui.get_object('status_bar').push(0, 'Connecting to server') try: @@ -47,7 +62,6 @@ def network_operation(app: 'gtk_ui.GtkUi'): app.ui.get_object('status_bar').push(0, '') - def get_combo_value(combo, index: int): """ Retrieve the selected value of a combo box at the selected index in the model :param combo: Any Gtk Widget that supports 'get_active_iter()' diff --git a/dsst/dsst_server/func_write.py b/dsst/dsst_server/func_write.py index ea27bdf..7fe4891 100644 --- a/dsst/dsst_server/func_write.py +++ b/dsst/dsst_server/func_write.py @@ -1,8 +1,20 @@ from common import models +from dsst_server.data_access import sql class WriteFunctions: - @staticmethod def create_season(season: 'models.Season'): return 'Season created.' + + @staticmethod + def update_season(season: 'models.Season', *_): + (sql.Season + .insert(id=season.id, number=season.number, game_name=season.game_name, start_date=season.start_date, + end_date=season.end_date) + .on_conflict( + update={sql.Season.number: season.number, + sql.Season.game_name: season.game_name, + sql.Season.start_date: season.start_date, + sql.Season.end_date: season.end_date}) + .execute()) diff --git a/dsst/dsst_server/server.py b/dsst/dsst_server/server.py index 0a2793c..a074838 100644 --- a/dsst/dsst_server/server.py +++ b/dsst/dsst_server/server.py @@ -62,7 +62,7 @@ class DsstServer: if action_name in self.tokens[token]: action = getattr(FunctionProxy, action_name) try: - value = action(request.get('args')) + value = action(*request.get('args')) except Exception as e: response = {'success': False, 'message': 'Exception was thrown on server.\n{}'.format(e)} util.send_msg(client, pickle.dumps(response))