diff --git a/dsst/dsst_gtk3/gtk_ui.py b/dsst/dsst_gtk3/gtk_ui.py index eca0c3f..e987820 100644 --- a/dsst/dsst_gtk3/gtk_ui.py +++ b/dsst/dsst_gtk3/gtk_ui.py @@ -1,14 +1,9 @@ -from collections import Counter - import gi -import math - import os - gi.require_version('Gtk', '3.0') from gi.repository import Gtk from dsst_gtk3.handlers import handlers -from dsst_gtk3 import util +from dsst_gtk3 import util, reload from dsst_sql import sql, sql_func @@ -36,109 +31,24 @@ class GtkUi: self.set_db_status_label(db_config) # Create database if not exists sql_func.create_tables() + self.reload() - self.reload_base_data() + def reload(self): + reload.reload_base_data(self.ui, self) + season_id = self.get_selected_season_id() + if season_id: + reload.reload_episodes(self.ui, self, season_id) + reload.reload_season_stats(self.ui, self, season_id) + else: + return + episode_id = self.get_selected_episode_id() + if episode_id: + reload.reload_episode_stats(self.ui, self, episode_id) def set_db_status_label(self, db_conf: dict): self.ui.get_object('connection_label').set_text(f'{db_conf["user"]}@{db_conf["host"]}') self.ui.get_object('db_label').set_text(f'{db_conf["db_name"]}') - def reload_base_data(self): - """Reload function for all base data witch is not dependant on selected season or episode""" - # Rebuild all players store - self.ui.get_object('all_players_store').clear() - for player in sql.Player.select(): - self.ui.get_object('all_players_store').append([player.id, player.name, player.hex_id]) - # Rebuild drink store - self.ui.get_object('drink_store').clear() - for drink in sql.Drink.select(): - self.ui.get_object('drink_store').append([drink.id, drink.name, '{:.2f}%'.format(drink.vol)]) - # Rebuild seasons store - store = self.ui.get_object('seasons_store') - store.clear() - for season in sql.Season.select().order_by(sql.Season.number): - store.append([season.id, season.game_name]) - - # Reload after season was changed ################################################################################## - def reload_for_season(self): - """Reload all data that is dependant on a selected season""" - season_id = self.get_selected_season_id() - if season_id is None or season_id == -1: - return - # Rebuild episodes store - selection = self.ui.get_object('episodes_tree_view').get_selection() - selection.handler_block_by_func(self.handlers.on_selected_episode_changed) - model, selected_paths = selection.get_selected_rows() - model.clear() - for episode in sql_func.get_episodes_for_season(season_id): - model.append([episode.id, episode.name, str(episode.date), episode.number]) - if selected_paths: - selection.select_path(selected_paths[0]) - selection.handler_unblock_by_func(self.handlers.on_selected_episode_changed) - - # Load player stats for season - player_stats = {} - for episode in sql_func.get_episodes_for_season(season_id): - for player in episode.players: - player_stats[player.name] = [sql_func.get_player_deaths_for_season(season_id, player.id), - sql_func.get_player_victories_for_season(season_id, player.id)] - store = self.ui.get_object('player_season_store') - store.clear() - for name, stats in player_stats.items(): - store.append([name, stats[0], stats[1]]) - # Load enemy stats for season - season = sql.Season.get(sql.Season.id == season_id) - enemy_stats = { - enemy.name: [False, len(sql.Death.select().where(sql.Death.enemy == enemy)), enemy.id] - for enemy in season.enemies} - store = self.ui.get_object('enemy_season_store') - store.clear() - for name, stats in enemy_stats.items(): - store.append([name, stats[0], stats[1], stats[2]]) - - # Reload after episode was changed ################################################################################# - def reload_for_episode(self): - """Reload all data that is dependant on a selected episode""" - episode_id = self.get_selected_episode_id() - if not episode_id: - return - episode = sql.Episode.get(sql.Episode.id == episode_id) - store = self.ui.get_object('episode_players_store') - store.clear() - for player in episode.players: - store.append([player.id, player.name, player.hex_id]) - # Reload death store for notebook view - store = self.ui.get_object('episode_deaths_store') - 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()] - penalty_string = ', '.join(penalties) - store.append([death.id, death.player.name, death.enemy.name, penalty_string]) - # Reload victory store for notebook view - store = self.ui.get_object('episode_victories_store') - store.clear() - for victory in episode.victories: - store.append([victory.id, victory.player.name, victory.enemy.name, victory.info]) - - # Stat grid - self.ui.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name)) - self.ui.get_object('ep_death_count_label').set_text(str(len(episode.deaths))) - drink_count = sum(len(death.penalties) for death in episode.deaths) - self.ui.get_object('ep_drinks_label').set_text(str(drink_count)) - self.ui.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths))) - dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) - l_booze = round(dl_booze / 10, 2) - self.ui.get_object('ep_booze_label').set_text('{}l'.format(l_booze)) - dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) - ml_booze = round(dl_booze * 10, 0) - self.ui.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze)) - enemy_list = [death.enemy.name for death in episode.deaths] - sorted_list = Counter(enemy_list).most_common(1) - if sorted_list: - enemy_name, deaths = sorted_list[0] - self.ui.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)') - def get_selected_season_id(self) -> int: """Read ID of the selected season from the UI :return: ID of the selected season diff --git a/dsst/dsst_gtk3/handlers/base_data_handlers.py b/dsst/dsst_gtk3/handlers/base_data_handlers.py index 0a45287..b2c4d61 100644 --- a/dsst/dsst_gtk3/handlers/base_data_handlers.py +++ b/dsst/dsst_gtk3/handlers/base_data_handlers.py @@ -14,7 +14,7 @@ class BaseDataHandlers: if entry.get_text(): sql.Player.create(name=entry.get_text()) entry.set_text('') - self.app.reload_base_data() + self.app.reload() def do_manage_enemies(self, *_): dialogs.show_manage_enemies_dialog(self.app.ui, self.app.get_selected_season_id()) @@ -24,31 +24,31 @@ class BaseDataHandlers: sql.Player.update(name=value)\ .where(sql.Player.id == row[0])\ .execute() - self.app.reload_base_data() + self.app.reload() def on_player_hex_edited(self, _, index, value): row = self.app.ui.get_object('all_players_store')[index] sql.Player.update(hex_id=value)\ .where(sql.Player.id == row[0])\ .execute() - self.app.reload_base_data() + self.app.reload() def do_add_drink(self, entry): if entry.get_text(): sql.Drink.create(name=entry.get_text(), vol=0) entry.set_text('') - self.app.reload_base_data() + self.app.reload() def on_drink_name_edited(self, _, index, value): row = self.app.ui.get_object('drink_store')[index] sql.Drink.update(name=value)\ .where(sql.Drink.id == row[0])\ .execute() - self.app.reload_base_data() + self.app.reload() def on_drink_vol_edited(self, _, index, value): row = self.app.ui.get_object('drink_store')[index] sql.Drink.update(vol=value) \ .where(sql.Drink.id == row[0]) \ .execute() - self.app.reload_base_data() \ No newline at end of file + self.app.reload() \ No newline at end of file diff --git a/dsst/dsst_gtk3/handlers/death_handlers.py b/dsst/dsst_gtk3/handlers/death_handlers.py index d48d808..aa45acd 100644 --- a/dsst/dsst_gtk3/handlers/death_handlers.py +++ b/dsst/dsst_gtk3/handlers/death_handlers.py @@ -13,8 +13,7 @@ class DeathHandlers: return result = dialogs.show_edit_death_dialog(self.app.ui, ep_id) if result == Gtk.ResponseType.OK: - self.app.reload_for_season() - self.app.reload_for_episode() + self.app.reload() def on_penalty_drink_changed(self, _, path, text): self.app.ui.get_object('player_penalties_store')[path][2] = text diff --git a/dsst/dsst_gtk3/handlers/handlers.py b/dsst/dsst_gtk3/handlers/handlers.py index 1dd2d62..8856cce 100644 --- a/dsst/dsst_gtk3/handlers/handlers.py +++ b/dsst/dsst_gtk3/handlers/handlers.py @@ -6,7 +6,7 @@ from dsst_gtk3.handlers.base_data_handlers import BaseDataHandlers from dsst_gtk3.handlers.dialog_handlers import DialogHandlers from dsst_gtk3.handlers.death_handlers import DeathHandlers from dsst_gtk3.handlers.victory_handlers import VictoryHandlers -from dsst_sql import sql_func +from dsst_sql import sql, sql_func class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, VictoryHandlers): @@ -28,6 +28,7 @@ class Handlers(SeasonHandlers, BaseDataHandlers, DialogHandlers, DeathHandlers, """ Signal will be sent when app should close :param _: Arguments to the delete event """ + sql.db.close() Gtk.main_quit() # DEBUG Functions ################################################################################################## diff --git a/dsst/dsst_gtk3/handlers/season_handlers.py b/dsst/dsst_gtk3/handlers/season_handlers.py index 07205bc..1d667f7 100644 --- a/dsst/dsst_gtk3/handlers/season_handlers.py +++ b/dsst/dsst_gtk3/handlers/season_handlers.py @@ -11,21 +11,20 @@ class SeasonHandlers: name = dialogs.enter_string_dialog(self.app.ui, 'Name for the new Season') if name: sql.Season.create(game_name=name, number=1) - self.app.reload_base_data() + self.app.reload() def do_season_selected(self, *_): - self.app.reload_for_season() + self.app.reload() def do_add_episode(self, *_): season_id = self.app.get_selected_season_id() if not season_id: return dialogs.show_episode_dialog(self.app.ui, 'Create new Episode', season_id) - self.app.reload_for_season() + self.app.reload() def on_selected_episode_changed(self, *_): - self.app.reload_for_episode() + self.app.reload() def on_episode_double_click(self, *_): - self.app.reload_for_episode() self.app.ui.get_object('stats_notebook').set_current_page(1) diff --git a/dsst/dsst_gtk3/handlers/victory_handlers.py b/dsst/dsst_gtk3/handlers/victory_handlers.py index fef13fa..d130a34 100644 --- a/dsst/dsst_gtk3/handlers/victory_handlers.py +++ b/dsst/dsst_gtk3/handlers/victory_handlers.py @@ -13,5 +13,4 @@ class VictoryHandlers: return result = dialogs.show_edit_victory_dialog(self.app.ui, ep_id) if result == Gtk.ResponseType.OK: - self.app.reload_for_season() - self.app.reload_for_episode() + self.app.reload() diff --git a/dsst/dsst_gtk3/reload.py b/dsst/dsst_gtk3/reload.py new file mode 100644 index 0000000..6d1bca8 --- /dev/null +++ b/dsst/dsst_gtk3/reload.py @@ -0,0 +1,116 @@ +from collections import Counter +from gi.repository import Gtk +from dsst_gtk3 import gtk_ui +from dsst_sql import sql, sql_func +from dsst_gtk3 import util + + +def reload_base_data(builder: Gtk.Builder, app: 'gtk_ui.GtkUi'): + """Reload function for all base data witch is not dependant on a selected season or episode + :param app: GtkUi instance + :param builder: Gtk.Builder with loaded UI + """ + # Rebuild all players store + builder.get_object('all_players_store').clear() + for player in sql.Player.select(): + 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 sql.Drink.select(): + 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 + active = combo.get_active() + with util.block_handler(combo, app.handlers.do_season_selected): + store = builder.get_object('seasons_store') + store.clear() + for season in sql.Season.select().order_by(sql.Season.number): + store.append([season.id, season.game_name]) + combo.set_active(active) + + +def reload_episodes(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int): + """Reload all data that is dependant on a selected season + :param app: GtkUi instance + :param builder: Gtk.Builder with loaded UI + :param season_id: ID of the season for witch to load data + """ + # Rebuild episodes store + selection = builder.get_object('episodes_tree_view').get_selection() + with util.block_handler(selection, app.handlers.on_selected_episode_changed): + model, selected_paths = selection.get_selected_rows() + model.clear() + for episode in sql_func.get_episodes_for_season(season_id): + model.append([episode.id, episode.name, str(episode.date), episode.number]) + if selected_paths: + selection.select_path(selected_paths[0]) + + +def reload_season_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', season_id: int): + """Load statistic data for selected season + :param builder: Gtk.Builder with loaded UI + :param app: GtkUi instance + :param season_id: ID of the season for witch to load data + """ + player_stats = {} + for episode in sql_func.get_episodes_for_season(season_id): + for player in episode.players: + player_stats[player.name] = [sql_func.get_player_deaths_for_season(season_id, player.id), + sql_func.get_player_victories_for_season(season_id, player.id)] + store = builder.get_object('player_season_store') + store.clear() + for name, stats in player_stats.items(): + store.append([name, stats[0], stats[1]]) + # Load enemy stats for season + season = sql.Season.get(sql.Season.id == season_id) + enemy_stats = { + enemy.name: [False, len(sql.Death.select().where(sql.Death.enemy == enemy)), enemy.id] + for enemy in season.enemies} + store = builder.get_object('enemy_season_store') + store.clear() + for name, stats in enemy_stats.items(): + store.append([name, stats[0], stats[1], stats[2]]) + + +def reload_episode_stats(builder: Gtk.Builder, app: 'gtk_ui.GtkUi', episode_id: int): + """Reload all data that is dependant on a selected episode + :param builder: builder: Gtk.Builder with loaded UI + :param app: app: GtkUi instance + :param episode_id: ID of the episode for witch to load data + """ + episode = sql.Episode.get(sql.Episode.id == episode_id) + store = builder.get_object('episode_players_store') + store.clear() + for player in episode.players: + store.append([player.id, player.name, player.hex_id]) + # Reload death store for notebook view + store = builder.get_object('episode_deaths_store') + 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()] + penalty_string = ', '.join(penalties) + store.append([death.id, death.player.name, death.enemy.name, penalty_string]) + # Reload victory store for notebook view + store = builder.get_object('episode_victories_store') + store.clear() + for victory in episode.victories: + store.append([victory.id, victory.player.name, victory.enemy.name, victory.info]) + + # Stat grid + builder.get_object('ep_stat_title').set_text('Stats for episode {}\n{}'.format(episode.number, episode.name)) + builder.get_object('ep_death_count_label').set_text(str(len(episode.deaths))) + drink_count = sum(len(death.penalties) for death in episode.deaths) + builder.get_object('ep_drinks_label').set_text(str(drink_count)) + builder.get_object('ep_player_drinks_label').set_text(str(len(episode.deaths))) + dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) + l_booze = round(dl_booze / 10, 2) + builder.get_object('ep_booze_label').set_text('{}l'.format(l_booze)) + dl_booze = sum(len(death.penalties) * death.penalties[0].size for death in episode.deaths) + ml_booze = round(dl_booze * 10, 0) + builder.get_object('ep_player_booze_label').set_text('{}ml'.format(ml_booze)) + enemy_list = [death.enemy.name for death in episode.deaths] + sorted_list = Counter(enemy_list).most_common(1) + if sorted_list: + enemy_name, deaths = sorted_list[0] + builder.get_object('ep_enemy_name_label').set_text(f'{enemy_name} ({deaths} Deaths)') \ No newline at end of file diff --git a/dsst/dsst_gtk3/util.py b/dsst/dsst_gtk3/util.py index 6a6f567..391525f 100644 --- a/dsst/dsst_gtk3/util.py +++ b/dsst/dsst_gtk3/util.py @@ -3,6 +3,9 @@ This modules contains general utilities for the GTK application to use. """ import json import os +from contextlib import contextmanager +from gi.repository import Gtk +from typing import Callable from zipfile import ZipFile CONFIG_PATH = os.path.join(os.path.expanduser('~'), '.config', 'dsst', 'config.json') @@ -18,6 +21,17 @@ DEFAULT_CONFIG = { } +@contextmanager +def block_handler(widget: 'Gtk.Widget', handler_func: Callable): + """Run an operation while a signal handler for a widget is blocked + :param widget: A Gtk widget + :param handler_func: Signal handler of the widget to block + """ + widget.handler_block_by_func(handler_func) + yield + widget.handler_unblock_by_func(handler_func) + + 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()'