From 55abdccc6406babcc703bc31d1c70c6e6a47bacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20R=C3=A9aux?= Date: Wed, 4 Feb 2026 16:10:53 +0100 Subject: [PATCH] code quality (mypy+flake8) --- README.md | 1 + main.py | 3 - pyproject.toml | 67 +++ requirements.txt | 6 +- .../{controller/__ini__.py => __init__.py} | 0 .../__ini__.py => controller/__init__.py} | 0 src/warchron/controller/controller.py | 338 +++++++++------ src/warchron/controller/dtos.py | 88 ++++ .../{view/__ini__.py => model/__init__.py} | 0 src/warchron/model/campaign.py | 75 ++-- src/warchron/model/model.py | 104 +++-- src/warchron/model/objective.py | 0 src/warchron/model/player.py | 16 +- src/warchron/model/repository.py | 24 -- src/warchron/model/round.py | 65 +-- src/warchron/model/war.py | 92 ++-- src/warchron/model/war_participant.py | 0 src/warchron/view/__init__.py | 0 src/warchron/view/view.py | 396 ++++++++++-------- 19 files changed, 778 insertions(+), 497 deletions(-) create mode 100644 pyproject.toml rename src/warchron/{controller/__ini__.py => __init__.py} (100%) rename src/warchron/{model/__ini__.py => controller/__init__.py} (100%) rename src/warchron/{view/__ini__.py => model/__init__.py} (100%) create mode 100644 src/warchron/model/objective.py delete mode 100644 src/warchron/model/repository.py create mode 100644 src/warchron/model/war_participant.py create mode 100644 src/warchron/view/__init__.py diff --git a/README.md b/README.md index 65b9e72..d8fbdf6 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ cd warchron_app python -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt +pip install -e . ``` ### Run diff --git a/main.py b/main.py index d5443db..4e7d4a0 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,4 @@ import sys -import os - -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src")) from PyQt6.QtWidgets import QApplication diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3bf3163 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +[project] +name = "warchron" +version = "1.0.0" +description = "A simple local app to track players' campaigns for tabletop wargames." +requires-python = ">=3.12" + +[build-system] +requires = ["setuptools>=61"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +# === BLACK CONFIGURATION (FORMATTER) === +[tool.black] +line-length = 88 +target-version = ['py312'] +include = '\.pyi?$' # File types to format (.py and .pyi) +extend-exclude = ''' +/( + # directories + \.eggs + | \.git + | \.venv + | venv + | build + | dist +)/ +''' + +[tool.flake8] +max-line-length = 88 +extend-ignore = ["E203", "W503"] +exclude = [ + ".git", + "__pycache__", + ".venv", + "build", + "dist", + "src/warchron/view/ui" +] + +[tool.mypy] +python_version = "3.12" +mypy_path = "src" +strict = true +explicit_package_bases = true +disallow_untyped_decorators = false +exclude = [ + ".git", + "__pycache__", + ".venv", + "build", + "dist", + "src/warchron/view/ui" +] + +[[tool.mypy.overrides]] +module = "PyQt6.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "warchron.view.ui.*" +ignore_errors = true diff --git a/requirements.txt b/requirements.txt index abcc883..b587ef0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,5 @@ -PyQt6>=6.6,<6.8 \ No newline at end of file +PyQt6>=6.6,<6.8 +mypy==1.19.1 +black==25.12.0 +flake8==7.3.0 +flake8-pyproject \ No newline at end of file diff --git a/src/warchron/controller/__ini__.py b/src/warchron/__init__.py similarity index 100% rename from src/warchron/controller/__ini__.py rename to src/warchron/__init__.py diff --git a/src/warchron/model/__ini__.py b/src/warchron/controller/__init__.py similarity index 100% rename from src/warchron/model/__ini__.py rename to src/warchron/controller/__init__.py diff --git a/src/warchron/controller/controller.py b/src/warchron/controller/controller.py index 988e545..8d92385 100644 --- a/src/warchron/controller/controller.py +++ b/src/warchron/controller/controller.py @@ -1,11 +1,24 @@ +from typing import List from pathlib import Path from PyQt6.QtWidgets import QMessageBox, QDialog + from warchron.model.model import Model from warchron.view.view import View - from warchron.constants import ItemType, RefreshScope -from warchron.controller.dtos import ParticipantOption +from warchron.controller.dtos import ( + ParticipantOption, + TreeSelection, + WarDTO, + WarParticipantDTO, + ObjectiveDTO, + CampaignDTO, + CampaignParticipantDTO, + SectorDTO, + RoundDTO, + ChoiceDTO, + BattleDTO, +) from warchron.view.view import ( PlayerDialog, WarDialog, @@ -20,13 +33,13 @@ from warchron.view.view import ( class Controller: - def __init__(self, model: Model, view: View): + def __init__(self, model: Model, view: View) -> None: self.model: Model = model self.view: View = view self.current_file: Path | None = None - self.selected_war_id: str = None - self.selected_campaign_id: str = None - self.selected_round_id: str = None + self.selected_war_id: str | None = None + self.selected_campaign_id: str | None = None + self.selected_round_id: str | None = None self.view.on_close_callback = self.on_app_close self.is_dirty: bool = False self.__connect() @@ -38,7 +51,7 @@ class Controller: self.view.on_add_campaign = self.add_campaign self.view.on_add_round = self.add_round - def __connect(self): + def __connect(self) -> None: self.view.actionExit.triggered.connect(self.view.close) self.view.actionNew.triggered.connect(self.new) self.view.actionOpen.triggered.connect(self.open_file) @@ -73,7 +86,7 @@ class Controller: # Menu bar methods - def new(self): + def new(self) -> None: if self.is_dirty: reply = QMessageBox.question( self.view, @@ -90,7 +103,7 @@ class Controller: self.refresh_wars_view() self.update_window_title() - def open_file(self): + def open_file(self) -> None: if self.is_dirty: reply = QMessageBox.question( self.view, @@ -110,7 +123,7 @@ class Controller: self.refresh_wars_view() self.update_window_title() - def save(self): + def save(self) -> None: if not self.current_file: self.save_as() return @@ -118,7 +131,7 @@ class Controller: self.is_dirty = False self.update_window_title() - def save_as(self): + def save_as(self) -> None: path = self.view.ask_save_file() if not path: return @@ -129,83 +142,102 @@ class Controller: # Display methods - def update_window_title(self): + def update_window_title(self) -> None: base = "WarChron" if self.current_file: base += f" - {self.current_file.name}" else: - base += f" - New file" + base += " - New file" if self.is_dirty: base = base + " *" self.view.setWindowTitle(base) - def refresh_players_view(self): + def refresh_players_view(self) -> None: players = self.model.get_all_players() - self.view.display_players(players) + players_for_display: List[ParticipantOption] = [ + ParticipantOption(id=p.id, name=p.name) for p in players + ] + self.view.display_players(players_for_display) - def refresh_wars_view(self): - wars = self.model.get_all_wars() + def refresh_wars_view(self) -> None: + wars: List[WarDTO] = [ + WarDTO( + id=w.id, + name=w.name, + year=w.year, + _campaigns=[ + CampaignDTO( + id=c.id, + name=c.name, + month=c.month, + _rounds=[ + RoundDTO(id=r.id, index=c.get_round_index(r.id)) + for r in c.get_all_rounds() + ], + ) + for c in w.get_all_campaigns() + ], + ) + for w in self.model.get_all_wars() + ] self.view.display_wars_tree(wars) - def _fill_war_details(self, war_id: str): + def _fill_war_details(self, war_id: str) -> None: war = self.model.get_war(war_id) self.view.show_war_details(name=war.name, year=war.year) objectives = war.get_all_objectives() - self.view.display_war_objectives(objectives) + objectives_for_display: List[ObjectiveDTO] = [ + ObjectiveDTO(id=obj.id, name=obj.name, description=obj.description) + for obj in objectives + ] + self.view.display_war_objectives(objectives_for_display) war_parts = war.get_all_war_participants() - participants_for_display = [ - ( - self.model.get_player_name( - p.player_id, - ), - p.faction, - p.id, + participants_for_display: List[WarParticipantDTO] = [ + WarParticipantDTO( + id=p.id, + player_name=self.model.get_player_name(p.player_id), + faction=p.faction, ) for p in war_parts ] self.view.display_war_participants(participants_for_display) - def _fill_campaign_details(self, campaign_id: str): + def _fill_campaign_details(self, campaign_id: str) -> None: camp = self.model.get_campaign(campaign_id) self.view.show_campaign_details(name=camp.name, month=camp.month) sectors = camp.get_all_sectors() - sectors_for_display = [] war = self.model.get_war_by_campaign(camp.id) - for sect in sectors: - round_index = camp.get_round_index(sect.round_id) - major_name = war.get_objective_name(sect.major_objective_id) - minor_name = war.get_objective_name(sect.minor_objective_id) - influence_name = war.get_objective_name(sect.influence_objective_id) - sectors_for_display.append( - ( - sect.name, - round_index, - major_name, - minor_name, - influence_name, - sect.id, - ) + sectors_for_display: List[SectorDTO] = [ + SectorDTO( + id=sect.id, + name=sect.name, + round_index=camp.get_round_index(sect.round_id), + major=war.get_objective_name(sect.major_objective_id), + minor=war.get_objective_name(sect.minor_objective_id), + influence=war.get_objective_name(sect.influence_objective_id), ) + for sect in sectors + ] self.view.display_campaign_sectors(sectors_for_display) camp_parts = camp.get_all_campaign_participants() - participants_for_display = [ - ( - self.model.get_participant_name(p.war_participant_id), - p.leader, - p.theme, - p.id, + participants_for_display: List[CampaignParticipantDTO] = [ + CampaignParticipantDTO( + id=p.id, + player_name=self.model.get_participant_name(p.war_participant_id), + leader=p.leader or "", + theme=p.theme or "", ) for p in camp_parts ] self.view.display_campaign_participants(participants_for_display) - def _fill_round_details(self, round_id: str): + def _fill_round_details(self, round_id: str) -> None: rnd = self.model.get_round(round_id) camp = self.model.get_campaign_by_round(round_id) self.view.show_round_details(index=camp.get_round_index(round_id)) participants = self.model.get_round_participants(round_id) sectors = camp.get_sectors_in_round(round_id) - choices_for_display = [] + choices_for_display: List[ChoiceDTO] = [] for part in participants: choice = rnd.get_choice(part.id) if not choice: @@ -213,21 +245,28 @@ class Controller: round_id=rnd.id, participant_id=part.id ) priority_name = ( - camp.get_sector_name(choice.priority_sector_id) if choice else "" + camp.get_sector_name(choice.priority_sector_id) + if choice.priority_sector_id is not None + else "" ) secondary_name = ( - camp.get_sector_name(choice.secondary_sector_id) if choice else "" + camp.get_sector_name(choice.secondary_sector_id) + if choice.secondary_sector_id is not None + else "" ) choices_for_display.append( - ( - self.model.get_participant_name(part.war_participant_id), - priority_name, - secondary_name, - choice.participant_id, + ChoiceDTO( + id=choice.participant_id, + participant_name=self.model.get_participant_name( + part.war_participant_id + ), + priority_sector=priority_name, + secondary_sector=secondary_name, + comment=choice.comment, ) ) self.view.display_round_choices(choices_for_display) - battles_for_display = [] + battles_for_display: List[BattleDTO] = [] for sect in sectors: battle = rnd.get_battle(sect.id) if not battle: @@ -246,23 +285,34 @@ class Controller: ) else: player_2_name = "" + if battle.winner_id: + camp_part = camp.participants[battle.winner_id] + winner_name = self.model.get_participant_name( + camp_part.war_participant_id + ) + else: + winner_name = "" battles_for_display.append( - ( - camp.get_sector_name(battle.sector_id), - player_1_name, - player_2_name, - battle.sector_id, + BattleDTO( + id=battle.sector_id, + sector_name=camp.get_sector_name(battle.sector_id), + player_1=player_1_name, + player_2=player_2_name, + winner=winner_name, + score=battle.score, + victory_condition=battle.victory_condition, + comment=battle.comment, ) ) self.view.display_round_battles(battles_for_display) - def on_tree_selection_changed(self, selection): + def on_tree_selection_changed(self, selection: TreeSelection | None) -> None: self.selected_war_id = None self.selected_campaign_id = None self.selected_round_id = None if selection: - item_type = selection["type"] - item_id = selection["id"] + item_type = selection.type + item_id = selection.id if item_type == ItemType.WAR: self.selected_war_id = item_id self.view.show_details(ItemType.WAR) @@ -281,11 +331,11 @@ class Controller: return self.update_actions_state() - def update_actions_state(self): + def update_actions_state(self) -> None: self.view.set_add_campaign_enabled(self.selected_war_id is not None) self.view.set_add_round_enabled(self.selected_campaign_id is not None) - def refresh(self, scope: RefreshScope): + def refresh(self, scope: RefreshScope) -> None: match scope: case RefreshScope.PLAYERS_LIST: self.refresh_players_view() @@ -309,26 +359,28 @@ class Controller: def refresh_and_select( self, scope: RefreshScope, *, item_type: ItemType, item_id: str - ): + ) -> None: self.refresh(scope) self.view.select_tree_item(item_type=item_type, item_id=item_id) - def edit_item(self, item_type: str, item_id: str): + def edit_item(self, item_type: str, item_id: str) -> None: if item_type == ItemType.PLAYER: play = self.model.get_player(item_id) - dialog = PlayerDialog(self.view, default_name=play.name) - if dialog.exec() == QDialog.DialogCode.Accepted: - name = dialog.get_player_name() + player_dialog = PlayerDialog(self.view, default_name=play.name) + if player_dialog.exec() == QDialog.DialogCode.Accepted: + name = player_dialog.get_player_name() if not self._validate_player_inputs(name): return self.model.update_player(item_id, name=name) self.refresh(RefreshScope.PLAYERS_LIST) elif item_type == ItemType.WAR: war = self.model.get_war(item_id) - dialog = WarDialog(self.view, default_name=war.name, default_year=war.year) - if dialog.exec() == QDialog.DialogCode.Accepted: - name = dialog.get_war_name() - year = dialog.get_war_year() + war_dialog = WarDialog( + self.view, default_name=war.name, default_year=war.year + ) + if war_dialog.exec() == QDialog.DialogCode.Accepted: + name = war_dialog.get_war_name() + year = war_dialog.get_war_year() if not self._validate_war_inputs(name, year): return self.model.update_war(item_id, name=name, year=year) @@ -337,12 +389,12 @@ class Controller: ) elif item_type == ItemType.CAMPAIGN: camp = self.model.get_campaign(item_id) - dialog = CampaignDialog( + camp_dialog = CampaignDialog( self.view, default_name=camp.name, default_month=camp.month ) - if dialog.exec() == QDialog.DialogCode.Accepted: - name = dialog.get_campaign_name() - month = dialog.get_campaign_month() + if camp_dialog.exec() == QDialog.DialogCode.Accepted: + name = camp_dialog.get_campaign_name() + month = camp_dialog.get_campaign_month() if not self._validate_campaign_inputs(name, month): return self.model.update_campaign(item_id, name=name, month=month) @@ -351,28 +403,29 @@ class Controller: ) elif item_type == ItemType.OBJECTIVE: obj = self.model.get_objective(item_id) - dialog = ObjectiveDialog( + obj_dialog = ObjectiveDialog( self.view, default_name=obj.name, default_description=obj.description ) - if dialog.exec() == QDialog.DialogCode.Accepted: - name = dialog.get_objective_name() - description = dialog.get_objective_description() + if obj_dialog.exec() == QDialog.DialogCode.Accepted: + name = obj_dialog.get_objective_name() + description = obj_dialog.get_objective_description() if not self._validate_objective_inputs(name, description): return self.model.update_objective(item_id, name=name, description=description) self.refresh(RefreshScope.WAR_DETAILS) elif item_type == ItemType.WAR_PARTICIPANT: - camp_part = self.model.get_war_participant(item_id) - player = self.model.get_player(camp_part.player_id) - dialog = WarParticipantDialog( + war_part = self.model.get_war_participant(item_id) + player = self.model.get_player(war_part.player_id) + play_opt = ParticipantOption(id=player.id, name=player.name) + war_part_dialog = WarParticipantDialog( self.view, - players=[player], - default_player_id=camp_part.id, - default_faction=camp_part.faction, + players=[play_opt], + default_player_id=war_part.id, + default_faction=war_part.faction, editable_player=False, ) - if dialog.exec() == QDialog.DialogCode.Accepted: - faction = dialog.get_participant_faction() + if war_part_dialog.exec() == QDialog.DialogCode.Accepted: + faction = war_part_dialog.get_participant_faction() self.model.update_war_participant(item_id, faction=faction) self.refresh(RefreshScope.WAR_DETAILS) elif item_type == ItemType.SECTOR: @@ -380,23 +433,30 @@ class Controller: camp = self.model.get_campaign_by_sector(item_id) war = self.model.get_war_by_campaign(camp.id) rounds = camp.get_all_rounds() + rnd_dto: List[RoundDTO] = [ + RoundDTO(id=rnd.id, index=i) for i, rnd in enumerate(rounds, start=1) + ] objectives = war.get_all_objectives() - dialog = SectorDialog( + obj_dto: List[ObjectiveDTO] = [ + ObjectiveDTO(id=obj.id, name=obj.name, description=obj.description) + for obj in objectives + ] + sect_dialog = SectorDialog( self.view, default_name=sect.name, - rounds=rounds, + rounds=rnd_dto, default_round_id=sect.round_id, - objectives=objectives, + objectives=obj_dto, default_major_id=sect.major_objective_id, default_minor_id=sect.minor_objective_id, default_influence_id=sect.influence_objective_id, ) - if dialog.exec() == QDialog.DialogCode.Accepted: - name = dialog.get_sector_name() - round_id = dialog.get_round_id() - major_id = dialog.get_major_id() - minor_id = dialog.get_minor_id() - influence_id = dialog.get_influence_id() + if sect_dialog.exec() == QDialog.DialogCode.Accepted: + name = sect_dialog.get_sector_name() + round_id = sect_dialog.get_round_id() + major_id = sect_dialog.get_major_id() + minor_id = sect_dialog.get_minor_id() + influence_id = sect_dialog.get_influence_id() self.model.update_sector( item_id, name=name, @@ -411,7 +471,7 @@ class Controller: war_part = self.model.get_war_participant(camp_part.war_participant_id) player = self.model.get_player(war_part.player_id) part_opt = [ParticipantOption(id=player.id, name=player.name)] - dialog = CampaignParticipantDialog( + camp_part_dialog = CampaignParticipantDialog( self.view, participants=part_opt, default_participant_id=camp_part.id, @@ -419,9 +479,9 @@ class Controller: default_theme=camp_part.theme, editable_player=False, ) - if dialog.exec() == QDialog.DialogCode.Accepted: - leader = dialog.get_participant_leader() - theme = dialog.get_participant_theme() + if camp_part_dialog.exec() == QDialog.DialogCode.Accepted: + leader = camp_part_dialog.get_participant_leader() + theme = camp_part_dialog.get_participant_theme() self.model.update_campaign_participant( item_id, leader=leader, theme=theme ) @@ -433,7 +493,7 @@ class Controller: self.edit_round_battle(item_id) self.refresh(RefreshScope.ROUND_DETAILS) - def delete_item(self, item_type: str, item_id: str): + def delete_item(self, item_type: str, item_id: str) -> None: reply = QMessageBox.question( self.view, "Confirm deletion", @@ -486,7 +546,7 @@ class Controller: return False return True - def add_player(self): + def add_player(self) -> None: dialog = PlayerDialog(self.view) result = dialog.exec() # modal blocking dialog if result == QDialog.DialogCode.Accepted: @@ -510,7 +570,7 @@ class Controller: return False return True - def add_war(self): + def add_war(self) -> None: dialog = WarDialog( self.view, default_year=self.model.get_default_war_values()["year"] ) @@ -536,7 +596,7 @@ class Controller: return False return True - def add_objective(self): + def add_objective(self) -> None: if not self.selected_war_id: return dialog = ObjectiveDialog(self.view) @@ -552,11 +612,14 @@ class Controller: # War participant methods - def add_war_participant(self): + def add_war_participant(self) -> None: if not self.selected_war_id: return players = self.model.get_available_players(self.selected_war_id) - dialog = WarParticipantDialog(self.view, players=players) + play_opts: List[ParticipantOption] = [ + ParticipantOption(id=p.id, name=p.name) for p in players + ] + dialog = WarParticipantDialog(self.view, players=play_opts) if dialog.exec() != QDialog.DialogCode.Accepted: return player_id = dialog.get_player_id() @@ -582,7 +645,7 @@ class Controller: return False return True - def add_campaign(self): + def add_campaign(self) -> None: if not self.selected_war_id: return dialog = CampaignDialog( @@ -605,7 +668,7 @@ class Controller: # Campaign participant methods - def add_campaign_participant(self): + def add_campaign_participant(self) -> None: if not self.selected_campaign_id: return participants = self.model.get_available_war_participants( @@ -634,6 +697,7 @@ class Controller: def _validate_sector_inputs( self, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str ) -> bool: + if not name.strip(): QMessageBox.warning( self.view, "Invalid name", "Sector name cannot be empty." @@ -642,15 +706,22 @@ class Controller: # allow same objectives in different fields? return True - def add_sector(self): + def add_sector(self) -> None: if not self.selected_campaign_id: return war = self.model.get_war_by_campaign(self.selected_campaign_id) camp = self.model.get_campaign(self.selected_campaign_id) rounds = camp.get_all_rounds() + rnd_objs: List[RoundDTO] = [ + RoundDTO(id=rnd.id, index=camp.get_round_index(rnd.id)) for rnd in rounds + ] objectives = war.get_all_objectives() + obj_dtos: List[ObjectiveDTO] = [ + ObjectiveDTO(id=obj.id, name=obj.name, description=obj.description) + for obj in objectives + ] dialog = SectorDialog( - self.view, default_name="", rounds=rounds, objectives=objectives + self.view, default_name="", rounds=rnd_objs, objectives=obj_dtos ) if dialog.exec() != QDialog.DialogCode.Accepted: return @@ -671,7 +742,7 @@ class Controller: # Round methods - def add_round(self): + def add_round(self) -> None: if not self.selected_campaign_id: return rnd = self.model.add_round(self.selected_campaign_id) @@ -682,14 +753,25 @@ class Controller: # Choice methods - def edit_round_choice(self, choice_id: str): + def edit_round_choice(self, choice_id: str) -> None: round_id = self.selected_round_id if not round_id: return - # camp, rnd, participants, sectors = self.model.get_round_choices_data(round_id) + war = self.model.get_war_by_round(round_id) camp = self.model.get_campaign_by_round(round_id) rnd = camp.get_round(round_id) sectors = camp.get_sectors_in_round(round_id) + sect_opts: List[SectorDTO] = [ + SectorDTO( + id=sect.id, + name=sect.name, + round_index=camp.get_round_index(sect.round_id), + major=war.get_objective_name(sect.major_objective_id), + minor=war.get_objective_name(sect.minor_objective_id), + influence=war.get_objective_name(sect.influence_objective_id), + ) + for sect in sectors + ] choice = rnd.get_choice(choice_id) if not choice: return @@ -700,7 +782,7 @@ class Controller: self.view, participants=[part_opt], default_participant_id=participant.id, - sectors=sectors, + sectors=sect_opts, default_priority_id=choice.priority_sector_id, default_secondary_id=choice.secondary_sector_id, default_comment=choice.comment, @@ -717,10 +799,11 @@ class Controller: # Battle methods - def edit_round_battle(self, battle_id: str): + def edit_round_battle(self, battle_id: str) -> None: round_id = self.selected_round_id if not round_id: return + war = self.model.get_war_by_round(round_id) camp = self.model.get_campaign_by_round(round_id) rnd = camp.get_round(round_id) participants = camp.get_all_campaign_participants() @@ -728,13 +811,22 @@ class Controller: if not battle: return sect = camp.sectors[battle.sector_id] - part_opts: list[ParticipantOption] = [] + sect_dto = SectorDTO( + id=sect.id, + name=sect.name, + round_index=camp.get_round_index(sect.round_id), + major=war.get_objective_name(sect.major_objective_id), + minor=war.get_objective_name(sect.minor_objective_id), + influence=war.get_objective_name(sect.influence_objective_id), + ) + + part_opts: List[ParticipantOption] = [] for participant in participants: player = self.model.get_player_from_campaign_participant(participant) part_opts.append(ParticipantOption(id=participant.id, name=player.name)) dialog = BattlesDialog( self.view, - sectors=[sect], + sectors=[sect_dto], default_sector_id=sect.id, players=part_opts, default_player_1_id=battle.player_1_id, diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py index 83e930b..cf063bd 100644 --- a/src/warchron/controller/dtos.py +++ b/src/warchron/controller/dtos.py @@ -1,3 +1,4 @@ +from typing import List from dataclasses import dataclass @@ -5,3 +6,90 @@ from dataclasses import dataclass class ParticipantOption: id: str name: str + + +@dataclass(frozen=True, slots=True) +class TreeSelection: + type: str + id: str + + +@dataclass +class WarDTO: + id: str + name: str + year: int + _campaigns: List["CampaignDTO"] | None = None + + def get_all_campaigns(self) -> List["CampaignDTO"]: + return self._campaigns or [] + + +@dataclass(frozen=True, slots=True) +class ObjectiveDTO: + id: str + name: str + description: str + + +@dataclass(frozen=True, slots=True) +class WarParticipantDTO: + id: str + player_name: str + faction: str + + +@dataclass +class CampaignDTO: + id: str + name: str + month: int + _rounds: List["RoundDTO"] | None = None + + def get_all_rounds(self) -> List["RoundDTO"]: + return self._rounds or [] + + +@dataclass(frozen=True, slots=True) +class CampaignParticipantDTO: + id: str + player_name: str + leader: str + theme: str + + +@dataclass(frozen=True, slots=True) +class SectorDTO: + id: str + name: str + round_index: int + major: str + minor: str + influence: str + + +@dataclass +class RoundDTO: + id: str + index: int + + +@dataclass(frozen=True, slots=True) +class ChoiceDTO: + id: str + participant_name: str + priority_sector: str + secondary_sector: str + comment: str | None + + +@dataclass(frozen=True, slots=True) +class BattleDTO: + id: str + sector_name: str + player_1: str + player_2: str + winner: str | None + score: str | None + victory_condition: str | None + comment: str | None diff --git a/src/warchron/view/__ini__.py b/src/warchron/model/__init__.py similarity index 100% rename from src/warchron/view/__ini__.py rename to src/warchron/model/__init__.py diff --git a/src/warchron/model/campaign.py b/src/warchron/model/campaign.py index df6ae94..bf3431e 100644 --- a/src/warchron/model/campaign.py +++ b/src/warchron/model/campaign.py @@ -1,32 +1,33 @@ from __future__ import annotations from uuid import uuid4 +from typing import Any, Dict, List from warchron.model.round import Round, Choice, Battle class Campaign: - def __init__(self, name: str, month: int): + def __init__(self, name: str, month: int) -> None: self.id: str = str(uuid4()) self.name: str = name self.month: int = month - self.participants: dict[str, CampaignParticipant] = {} - self.sectors: dict[str, Sector] = {} - self.rounds: list[Round] = [] + self.participants: Dict[str, CampaignParticipant] = {} + self.sectors: Dict[str, Sector] = {} + self.rounds: List[Round] = [] self.is_over = False - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_name(self, new_name: str): + def set_name(self, new_name: str) -> None: self.name = new_name - def set_month(self, new_month: int): + def set_month(self, new_month: int) -> None: self.month = new_month - def set_state(self, new_state: bool): + def set_state(self, new_state: bool) -> None: self.is_over = new_state - def toDict(self): + def toDict(self) -> Dict[str, Any]: return { "id": self.id, "name": self.name, @@ -37,7 +38,7 @@ class Campaign: } @staticmethod - def fromDict(data: dict): + def fromDict(data: Dict[str, Any]) -> Campaign: camp = Campaign(name=data["name"], month=data["month"]) camp.set_id(data["id"]) # camp.participants = data.get("participants", {}) @@ -77,18 +78,18 @@ class Campaign: except KeyError: raise KeyError(f"Participant {participant_id} not in campaign {self.id}") - def get_all_campaign_participants(self) -> list[CampaignParticipant]: + def get_all_campaign_participants(self) -> List[CampaignParticipant]: return list(self.participants.values()) def update_campaign_participant( self, participant_id: str, *, leader: str, theme: str - ): + ) -> None: part = self.get_campaign_participant(participant_id) # Can't change referred War.participant part.set_leader(leader) part.set_theme(theme) - def remove_campaign_participant(self, participant_id: str): + def remove_campaign_participant(self, participant_id: str) -> None: # TODO manage choices referring to it # TODO manage battles referring to it del self.participants[participant_id] @@ -110,7 +111,7 @@ class Campaign: return "" return self.sectors[sector_id].name - def get_all_sectors(self) -> list[Sector]: + def get_all_sectors(self) -> List[Sector]: return list(self.sectors.values()) # TODO manage choices referring to it (round order!) @@ -123,7 +124,7 @@ class Campaign: major_id: str, minor_id: str, influence_id: str, - ): + ) -> None: sect = self.get_sector(sector_id) sect.set_name(name) sect.set_round(round_id) @@ -131,12 +132,12 @@ class Campaign: sect.set_minor(minor_id) sect.set_influence(influence_id) - def remove_sector(self, sector_id: str): + def remove_sector(self, sector_id: str) -> None: # TODO manage choices referring to it # TODO manage battles referring to it del self.sectors[sector_id] - def get_sectors_in_round(self, round_id: str) -> list[Sector]: + def get_sectors_in_round(self, round_id: str) -> List[Sector]: sectors = [s for s in self.sectors.values() if s.round_id == round_id] return sectors @@ -151,7 +152,7 @@ class Campaign: return rnd raise KeyError(f"Round {round_id} not found") - def get_all_rounds(self) -> list[Round]: + def get_all_rounds(self) -> List[Round]: return list(self.rounds) def add_round(self) -> Round: @@ -159,7 +160,7 @@ class Campaign: self.rounds.append(round) return round - def remove_round(self, round_id: str): + def remove_round(self, round_id: str) -> None: rnd = next((r for r in self.rounds if r.id == round_id), None) if rnd: self.rounds.remove(rnd) @@ -172,14 +173,6 @@ class Campaign: return index raise KeyError("Round not found in campaign") - def get_round_name(self, round_id: str | None) -> str: - if round_id is None: - return "" - for rnd in self.rounds: - if rnd.id == round_id: - return rnd.name - return "" - # Choice methods def create_choice(self, round_id: str, participant_id: str) -> Choice: @@ -193,13 +186,13 @@ class Campaign: priority_sector_id: str | None, secondary_sector_id: str | None, comment: str | None, - ): + ) -> None: rnd = self.get_round(round_id) rnd.update_choice( participant_id, priority_sector_id, secondary_sector_id, comment ) - def remove_choice(self, round_id: str, participant_id: str) -> Choice: + def remove_choice(self, round_id: str, participant_id: str) -> None: rnd = self.get_round(round_id) rnd.remove_choice(participant_id) @@ -219,7 +212,7 @@ class Campaign: score: str | None, victory_condition: str | None, comment: str | None, - ): + ) -> None: rnd = self.get_round(round_id) rnd.update_battle( sector_id, @@ -231,7 +224,7 @@ class Campaign: comment, ) - def remove_battle(self, round_id: str, sector_id: str) -> Battle: + def remove_battle(self, round_id: str, sector_id: str) -> None: rnd = self.get_round(round_id) rnd.remove_battle(sector_id) @@ -245,16 +238,16 @@ class CampaignParticipant: self.leader: str | None = leader self.theme: str | None = theme - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_war_participant(self, new_participant: str): + def set_war_participant(self, new_participant: str) -> None: self.war_participant_id = new_participant - def set_leader(self, new_faction: str): + def set_leader(self, new_faction: str) -> None: self.leader = new_faction - def set_theme(self, new_theme: str): + def set_theme(self, new_theme: str) -> None: self.theme = new_theme @@ -276,20 +269,20 @@ class Sector: self.mission: str | None = None self.description: str | None = None - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_name(self, new_name: str): + def set_name(self, new_name: str) -> None: self.name = new_name - def set_round(self, new_round_id: str): + def set_round(self, new_round_id: str) -> None: self.round_id = new_round_id - def set_major(self, new_major_id: str): + def set_major(self, new_major_id: str) -> None: self.major_objective_id = new_major_id - def set_minor(self, new_minor_id: str): + def set_minor(self, new_minor_id: str) -> None: self.minor_objective_id = new_minor_id - def set_influence(self, new_influence_id: str): + def set_influence(self, new_influence_id: str) -> None: self.influence_objective_id = new_influence_id diff --git a/src/warchron/model/model.py b/src/warchron/model/model.py index ce64605..1a17563 100644 --- a/src/warchron/model/model.py +++ b/src/warchron/model/model.py @@ -1,3 +1,4 @@ +from typing import Any, Dict, List from pathlib import Path import json import shutil @@ -10,25 +11,25 @@ from warchron.model.round import Round, Choice, Battle class Model: - def __init__(self): - self.players: dict[str, Player] = {} - self.wars: dict[str, War] = {} + def __init__(self) -> None: + self.players: Dict[str, Player] = {} + self.wars: Dict[str, War] = {} # File management methods - def new(self): + def new(self) -> None: self.players.clear() self.wars.clear() - def load(self, path: Path): + def load(self, path: Path) -> None: self.players.clear() self.wars.clear() self._load_data(path) - def save(self, path: Path): + def save(self, path: Path) -> None: self._save_data(path) - def _load_data(self, path: Path): + def _load_data(self, path: Path) -> None: if not path.exists() or path.stat().st_size == 0: return # Start empty try: @@ -45,7 +46,7 @@ class Model: except json.JSONDecodeError: raise RuntimeError("Data file is corrupted") - def _save_data(self, path: Path): + def _save_data(self, path: Path) -> None: if path.exists(): shutil.copy(path, path.with_suffix(".json.bak")) data = { @@ -58,31 +59,31 @@ class Model: # Player methods - def add_player(self, name): + def add_player(self, name: str) -> Player: player = Player(name) self.players[player.id] = player return player - def get_player(self, id): + def get_player(self, id: str) -> Player: return self.players[id] def get_player_name(self, player_id: str) -> str: return self.players[player_id].name - def update_player(self, player_id: str, *, name: str): + def update_player(self, player_id: str, *, name: str) -> None: player = self.get_player(player_id) player.set_name(name) - def get_all_players(self) -> list[Player]: + def get_all_players(self) -> List[Player]: return list(self.players.values()) - def remove_player(self, player_id: str): + def remove_player(self, player_id: str) -> None: # TODO manage war_participants referring to it del self.players[player_id] # War methods - def get_default_war_values(self) -> dict: + def get_default_war_values(self) -> Dict[str, Any]: return {"year": datetime.now().year} def add_war(self, name: str, year: int) -> War: @@ -90,7 +91,7 @@ class Model: self.wars[war.id] = war return war - def get_war(self, id) -> War: + def get_war(self, id: str) -> War: return self.wars[id] def get_war_by_campaign(self, campaign_id: str) -> War: @@ -100,20 +101,20 @@ class Model: return war raise KeyError(f"Campaign {campaign_id} not found in any War") - def get_war_by_sector(self, sector_id: str) -> Campaign: + def get_war_by_sector(self, sector_id: str) -> War: for war in self.wars.values(): for camp in war.campaigns: for sect in camp.sectors.values(): if sect.id == sector_id: - return camp + return war raise KeyError(f"Sector {sector_id} not found in any War") - def get_war_by_round(self, round_id: str) -> Campaign: + def get_war_by_round(self, round_id: str) -> War: for war in self.wars.values(): for camp in war.campaigns: for rnd in camp.rounds: if rnd.id == round_id: - return camp + return war raise KeyError(f"Round {round_id} not found in any War") def get_war_by_objective(self, objective_id: str) -> War: @@ -136,15 +137,15 @@ class Model: return war raise KeyError(f"Participant {participant_id} not found") - def update_war(self, war_id: str, *, name: str, year: int): + def update_war(self, war_id: str, *, name: str, year: int) -> None: war = self.get_war(war_id) war.set_name(name) war.set_year(year) - def get_all_wars(self) -> list[War]: + def get_all_wars(self) -> List[War]: return list(self.wars.values()) - def remove_war(self, war_id: str): + def remove_war(self, war_id: str) -> None: del self.wars[war_id] # Objective methods @@ -153,24 +154,26 @@ class Model: war = self.get_war(war_id) return war.add_objective(name, description) - def get_objective(self, objective_id) -> Objective: + def get_objective(self, objective_id: str) -> Objective: for war in self.wars.values(): for obj in war.objectives.values(): if obj.id == objective_id: return obj raise KeyError("Objective not found") - def update_objective(self, objective_id: str, *, name: str, description: str): + def update_objective( + self, objective_id: str, *, name: str, description: str + ) -> None: war = self.get_war_by_objective(objective_id) war.update_objective(objective_id, name=name, description=description) - def remove_objective(self, objective_id: str): + def remove_objective(self, objective_id: str) -> None: war = self.get_war_by_objective(objective_id) war.remove_objective(objective_id) # War participant methods - def get_available_players(self, war_id: str) -> list[Player]: + def get_available_players(self, war_id: str) -> List[Player]: war = self.get_war(war_id) return [ player for player in self.players.values() if not war.has_player(player.id) @@ -182,7 +185,7 @@ class Model: war = self.get_war(war_id) return war.add_war_participant(player_id, faction) - def get_war_participant(self, participant_id) -> WarParticipant: + def get_war_participant(self, participant_id: str) -> WarParticipant: for war in self.wars.values(): for part in war.participants.values(): if part.id == participant_id: @@ -192,17 +195,17 @@ class Model: def get_player_from_war_participant(self, war_part: WarParticipant) -> Player: return self.get_player(war_part.player_id) - def update_war_participant(self, participant_id: str, *, faction: str): + def update_war_participant(self, participant_id: str, *, faction: str) -> None: war = self.get_war_by_war_participant(participant_id) war.update_war_participant(participant_id, faction=faction) - def remove_war_participant(self, participant_id: str): + def remove_war_participant(self, participant_id: str) -> None: war = self.get_war_by_war_participant(participant_id) war.remove_war_participant(participant_id) # Campaign methods - def get_default_campaign_values(self, war_id: str) -> dict: + def get_default_campaign_values(self, war_id: str) -> Dict[str, Any]: war = self.get_war(war_id) return war.get_default_campaign_values() @@ -210,7 +213,7 @@ class Model: war = self.get_war(war_id) return war.add_campaign(name, month) - def get_campaign(self, campaign_id) -> Campaign: + def get_campaign(self, campaign_id: str) -> Campaign: for war in self.wars.values(): for campaign in war.campaigns: if campaign.id == campaign_id: @@ -238,11 +241,11 @@ class Model: return camp raise KeyError(f"Sector {sector_id} not found") - def update_campaign(self, campaign_id: str, *, name: str, month: int): + def update_campaign(self, campaign_id: str, *, name: str, month: int) -> None: war = self.get_war_by_campaign(campaign_id) war.update_campaign(campaign_id, name=name, month=month) - def remove_campaign(self, campaign_id: str): + def remove_campaign(self, campaign_id: str) -> None: war = self.get_war_by_campaign(campaign_id) war.remove_campaign(campaign_id) @@ -260,7 +263,7 @@ class Model: camp = self.get_campaign(campaign_id) return camp.add_sector(name, round_id, major_id, minor_id, influence_id) - def get_sector(self, sector_id) -> Sector: + def get_sector(self, sector_id: str) -> Sector: for war in self.wars.values(): for camp in war.campaigns: for sect in camp.sectors.values(): @@ -277,7 +280,7 @@ class Model: major_id: str, minor_id: str, influence_id: str, - ): + ) -> None: war = self.get_war_by_sector(sector_id) war.update_sector( sector_id, @@ -288,13 +291,13 @@ class Model: influence_id=influence_id, ) - def remove_sector(self, sector_id: str): + def remove_sector(self, sector_id: str) -> None: camp = self.get_campaign_by_sector(sector_id) camp.remove_sector(sector_id) # Campaign participant methods - def get_available_war_participants(self, campaign_id: str) -> list[WarParticipant]: + def get_available_war_participants(self, campaign_id: str) -> List[WarParticipant]: war = self.get_war_by_campaign(campaign_id) return war.get_available_war_participants(campaign_id) @@ -309,7 +312,7 @@ class Model: war_part = war.get_war_participant(participant_id) return self.players[war_part.player_id].name - def get_campaign_participant(self, participant_id) -> CampaignParticipant: + def get_campaign_participant(self, participant_id: str) -> CampaignParticipant: for war in self.wars.values(): for camp in war.campaigns: for part in camp.participants.values(): @@ -329,11 +332,11 @@ class Model: *, leader: str, theme: str, - ): + ) -> None: war = self.get_war_by_campaign_participant(participant_id) war.update_campaign_participant(participant_id, leader=leader, theme=theme) - def remove_campaign_participant(self, participant_id: str): + def remove_campaign_participant(self, participant_id: str) -> None: war = self.get_war_by_campaign_participant(participant_id) war.remove_campaign_participant(participant_id) @@ -355,15 +358,15 @@ class Model: camp = self.get_campaign_by_round(round_id) return camp.get_round_index(round_id) - def get_round_sectors(self, round_id: str) -> list[Sector]: + def get_round_sectors(self, round_id: str) -> List[Sector]: camp = self.get_campaign_by_round(round_id) return [s for s in camp.sectors.values() if s.round_id == round_id] - def get_round_participants(self, round_id: str) -> list[CampaignParticipant]: + def get_round_participants(self, round_id: str) -> List[CampaignParticipant]: camp = self.get_campaign_by_round(round_id) return list(camp.participants.values()) - def remove_round(self, round_id: str): + def remove_round(self, round_id: str) -> None: war = self.get_war_by_round(round_id) war.remove_round(round_id) @@ -373,13 +376,6 @@ class Model: war = self.get_war_by_round(round_id) return war.create_choice(round_id, participant_id) - def get_round_choices_data(self, round_id: str): - camp = self.get_campaign_by_round(round_id) - rnd = self.get_round(round_id) - participants = camp.participants.values() - sectors = [s for s in camp.sectors.values() if s.round_id == round_id] - return camp, rnd, participants, sectors - def update_choice( self, round_id: str, @@ -387,13 +383,13 @@ class Model: priority_sector_id: str | None, secondary_sector_id: str | None, comment: str | None, - ): + ) -> None: war = self.get_war_by_round(round_id) war.update_choice( round_id, participant_id, priority_sector_id, secondary_sector_id, comment ) - def remove_choice(self, round_id: str, participant_id: str): + def remove_choice(self, round_id: str, participant_id: str) -> None: war = self.get_war_by_round(round_id) war.remove_choice(round_id, participant_id) @@ -413,7 +409,7 @@ class Model: score: str | None, victory_condition: str | None, comment: str | None, - ): + ) -> None: war = self.get_war_by_round(round_id) war.update_battle( round_id, @@ -426,6 +422,6 @@ class Model: comment, ) - def remove_battle(self, round_id: str, sector_id: str): + def remove_battle(self, round_id: str, sector_id: str) -> None: war = self.get_war_by_round(round_id) war.remove_battle(round_id, sector_id) diff --git a/src/warchron/model/objective.py b/src/warchron/model/objective.py new file mode 100644 index 0000000..e69de29 diff --git a/src/warchron/model/player.py b/src/warchron/model/player.py index 8f07176..b8b9934 100644 --- a/src/warchron/model/player.py +++ b/src/warchron/model/player.py @@ -1,22 +1,24 @@ +from __future__ import annotations +from typing import Any, Dict from uuid import uuid4 class Player: - def __init__(self, name): - self.id = str(uuid4()) - self.name = name + def __init__(self, name: str) -> None: + self.id: str = str(uuid4()) + self.name: str = name - def set_id(self, new_id): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_name(self, name): + def set_name(self, name: str) -> None: self.name = name - def toDict(self): + def toDict(self) -> Dict[str, Any]: return {"id": self.id, "name": self.name} @staticmethod - def fromDict(data: dict): + def fromDict(data: Dict[str, Any]) -> Player: play = Player(name=data["name"]) play.set_id(data["id"]) return play diff --git a/src/warchron/model/repository.py b/src/warchron/model/repository.py deleted file mode 100644 index d000abf..0000000 --- a/src/warchron/model/repository.py +++ /dev/null @@ -1,24 +0,0 @@ -import json -import shutil -from pathlib import Path - -DATA_FILE = Path("data/warmachron.json") - - -def load_data(): - if not DATA_FILE.exists() or DATA_FILE.stat().st_size == 0: - return {"version": 1, "players": {}, "wars": []} - - try: - with open(DATA_FILE, "r", encoding="utf-8") as f: - return json.load(f) - except json.JSONDecodeError: - raise RuntimeError("Data file is corrupted") - - -def save_data(data): - if DATA_FILE.exists(): - shutil.copy(DATA_FILE, DATA_FILE.with_suffix(".json.bak")) - - with open(DATA_FILE, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2) diff --git a/src/warchron/model/round.py b/src/warchron/model/round.py index a975c3a..420eea8 100644 --- a/src/warchron/model/round.py +++ b/src/warchron/model/round.py @@ -1,21 +1,22 @@ from __future__ import annotations from uuid import uuid4 +from typing import Any, Dict class Round: - def __init__(self): + def __init__(self) -> None: self.id: str = str(uuid4()) - self.choices: dict[str, Choice] = {} - self.battles: dict[str, Battle] = {} + self.choices: Dict[str, Choice] = {} + self.battles: Dict[str, Battle] = {} self.is_over: bool = False - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_state(self, new_state: bool): + def set_state(self, new_state: bool) -> None: self.is_over = new_state - def toDict(self): + def toDict(self) -> Dict[str, Any]: return { "id": self.id, # "sectors" : self.sectors, @@ -25,7 +26,7 @@ class Round: } @staticmethod - def fromDict(data: dict): + def fromDict(data: Dict[str, Any]) -> Round: rnd = Round() rnd.set_id(data["id"]) # rnd.sectors = data.get("sectors", {}) @@ -55,13 +56,14 @@ class Round: priority_sector_id: str | None, secondary_sector_id: str | None, comment: str | None, - ): + ) -> None: choice = self.get_choice(participant_id) - choice.set_priority(priority_sector_id) - choice.set_secondary(secondary_sector_id) - choice.set_comment(comment) + if choice: + choice.set_priority(priority_sector_id) + choice.set_secondary(secondary_sector_id) + choice.set_comment(comment) - def remove_choice(self, participant_id: str): + def remove_choice(self, participant_id: str) -> None: del self.choices[participant_id] # Battle methods @@ -84,16 +86,17 @@ class Round: score: str | None, victory_condition: str | None, comment: str | None, - ): + ) -> None: bat = self.get_battle(sector_id) - bat.set_player_1(player_1_id) - bat.set_player_2(player_2_id) - bat.set_winner(winner_id) - bat.set_score(score) - bat.set_victory_condition(victory_condition) - bat.set_comment(comment) + if bat: + bat.set_player_1(player_1_id) + bat.set_player_2(player_2_id) + bat.set_winner(winner_id) + bat.set_score(score) + bat.set_victory_condition(victory_condition) + bat.set_comment(comment) - def remove_battle(self, sector_id: str): + def remove_battle(self, sector_id: str) -> None: del self.battles[sector_id] @@ -113,16 +116,16 @@ class Choice: ) self.comment: str | None = None - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.participant_id = new_id - def set_priority(self, new_priority_id: str): + def set_priority(self, new_priority_id: str | None) -> None: self.priority_sector_id = new_priority_id - def set_secondary(self, new_secondary_id: str): + def set_secondary(self, new_secondary_id: str | None) -> None: self.secondary_sector_id = new_secondary_id - def set_comment(self, new_comment: str): + def set_comment(self, new_comment: str | None) -> None: self.comment = new_comment @@ -141,23 +144,23 @@ class Battle: self.victory_condition: str | None = None self.comment: str | None = None - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.sector_id = new_id - def set_player_1(self, new_player_id: str): + def set_player_1(self, new_player_id: str | None) -> None: self.player_1_id = new_player_id - def set_player_2(self, new_player_id: str): + def set_player_2(self, new_player_id: str | None) -> None: self.player_2_id = new_player_id - def set_winner(self, new_player_id: str): + def set_winner(self, new_player_id: str | None) -> None: self.winner_id = new_player_id - def set_score(self, new_score: str): + def set_score(self, new_score: str | None) -> None: self.score = new_score - def set_victory_condition(self, new_victory_condition: str): + def set_victory_condition(self, new_victory_condition: str | None) -> None: self.victory_condition = new_victory_condition - def set_comment(self, new_comment: str): + def set_comment(self, new_comment: str | None) -> None: self.comment = new_comment diff --git a/src/warchron/model/war.py b/src/warchron/model/war.py index 8cb9464..70cf6f4 100644 --- a/src/warchron/model/war.py +++ b/src/warchron/model/war.py @@ -1,34 +1,35 @@ from __future__ import annotations from uuid import uuid4 from datetime import datetime +from typing import Any, Dict, List from warchron.model.campaign import Campaign, Sector, CampaignParticipant from warchron.model.round import Round, Choice, Battle class War: - def __init__(self, name: str, year: int): + def __init__(self, name: str, year: int) -> None: self.id: str = str(uuid4()) self.name: str = name self.year: int = year - self.participants: dict[str, WarParticipant] = {} - self.objectives: dict[str, Objective] = {} - self.campaigns: list[Campaign] = [] + self.participants: Dict[str, WarParticipant] = {} + self.objectives: Dict[str, Objective] = {} + self.campaigns: List[Campaign] = [] self.is_over: bool = False - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_name(self, new_name: str): + def set_name(self, new_name: str) -> None: self.name = new_name - def set_year(self, new_year: int): + def set_year(self, new_year: int) -> None: self.year = new_year - def set_state(self, new_state: bool): + def set_state(self, new_state: bool) -> None: self.is_over = new_state - def toDict(self): + def toDict(self) -> Dict[str, Any]: return { "id": self.id, "name": self.name, @@ -39,7 +40,7 @@ class War: } @staticmethod - def fromDict(data: dict): + def fromDict(data: Dict[str, Any]) -> War: war = War(name=data["name"], year=data["year"]) war.set_id(data["id"]) # war.participants = data.get("participants", {}) @@ -58,7 +59,7 @@ class War: def get_objective(self, id: str) -> Objective: return self.objectives[id] - def get_all_objectives(self) -> list[Objective]: + def get_all_objectives(self) -> List[Objective]: return list(self.objectives.values()) def get_objective_name(self, objective_id: str | None) -> str: @@ -67,12 +68,14 @@ class War: obj = self.objectives.get(objective_id) return obj.name if obj else "" - def update_objective(self, objective_id: str, *, name: str, description: str): + def update_objective( + self, objective_id: str, *, name: str, description: str + ) -> None: obj = self.get_objective(objective_id) obj.set_name(name) obj.set_description(description) - def remove_objective(self, objective_id: str): + def remove_objective(self, objective_id: str) -> None: # TODO manage sectors referring to it del self.objectives[objective_id] @@ -97,15 +100,15 @@ class War: def get_war_participant(self, id: str) -> WarParticipant: return self.participants[id] - def get_all_war_participants(self) -> list[WarParticipant]: + def get_all_war_participants(self) -> List[WarParticipant]: return list(self.participants.values()) - def update_war_participant(self, player_id: str, *, faction: str): + def update_war_participant(self, player_id: str, *, faction: str) -> None: part = self.get_war_participant(player_id) # Can't change referred Model.players part.set_faction(faction) - def remove_war_participant(self, player_id: str): + def remove_war_participant(self, player_id: str) -> None: # TODO manage campaign_participants referring to it del self.participants[player_id] @@ -114,7 +117,7 @@ class War: def has_campaign(self, campaign_id: str) -> bool: return any(c.id == campaign_id for c in self.campaigns) - def get_default_campaign_values(self) -> dict: + def get_default_campaign_values(self) -> Dict[str, Any]: return {"month": datetime.now().month} def add_campaign(self, name: str, month: int | None = None) -> Campaign: @@ -150,15 +153,15 @@ class War: return camp raise KeyError(f"Participant {participant_id} not found in any Campaign") - def update_campaign(self, campaign_id: str, *, name: str, month: int): + def update_campaign(self, campaign_id: str, *, name: str, month: int) -> None: camp = self.get_campaign(campaign_id) camp.set_name(name) camp.set_month(month) - def get_all_campaigns(self) -> list[Campaign]: + def get_all_campaigns(self) -> List[Campaign]: return list(self.campaigns) - def remove_campaign(self, campaign_id: str): + def remove_campaign(self, campaign_id: str) -> None: camp = self.get_campaign(campaign_id) self.campaigns.remove(camp) @@ -166,7 +169,7 @@ class War: def add_sector( self, - campaign_id, + campaign_id: str, name: str, round_id: str, major_id: str, @@ -176,8 +179,12 @@ class War: camp = self.get_campaign(campaign_id) return camp.add_sector(name, round_id, major_id, minor_id, influence_id) - def get_sector(self, id: str) -> Sector: - return self.sectors[id] + def get_sector(self, sector_id: str) -> Sector: + for camp in self.campaigns: + for sect in camp.sectors.values(): + if sect.id == sector_id: + return sect + raise KeyError("Sector not found") def update_sector( self, @@ -188,7 +195,7 @@ class War: major_id: str, minor_id: str, influence_id: str, - ): + ) -> None: camp = self.get_campaign_by_sector(sector_id) camp.update_sector( sector_id, @@ -199,13 +206,13 @@ class War: influence_id=influence_id, ) - def remove_sector(self, sector_id: str): + def remove_sector(self, sector_id: str) -> None: camp = self.get_campaign_by_sector(sector_id) camp.remove_sector(sector_id) # Campaign participant methods - def get_available_war_participants(self, campaign_id: str) -> list[WarParticipant]: + def get_available_war_participants(self, campaign_id: str) -> List[WarParticipant]: camp = self.get_campaign(campaign_id) return [ part @@ -219,8 +226,8 @@ class War: camp = self.get_campaign(campaign_id) return camp.add_campaign_participant(participant_id, leader, theme) - def get_campaign_participant(self, participant_id) -> CampaignParticipant: - for camp in self.campaigns.values(): + def get_campaign_participant(self, participant_id: str) -> CampaignParticipant: + for camp in self.campaigns: for part in camp.participants.values(): if part.id == participant_id: return part @@ -228,11 +235,11 @@ class War: def update_campaign_participant( self, participant_id: str, *, leader: str, theme: str - ): + ) -> None: camp = self.get_campaign_by_campaign_participant(participant_id) camp.update_campaign_participant(participant_id, leader=leader, theme=theme) - def remove_campaign_participant(self, participant_id: str): + def remove_campaign_participant(self, participant_id: str) -> None: camp = self.get_campaign_by_campaign_participant(participant_id) camp.remove_campaign_participant(participant_id) @@ -246,7 +253,7 @@ class War: camp = self.get_campaign(campaign_id) return camp.add_round() - def remove_round(self, round_id: str): + def remove_round(self, round_id: str) -> None: camp = self.get_campaign_by_round(round_id) camp.remove_round(round_id) @@ -263,13 +270,13 @@ class War: priority_sector_id: str | None, secondary_sector_id: str | None, comment: str | None, - ): + ) -> None: camp = self.get_campaign_by_round(round_id) camp.update_choice( - participant_id, priority_sector_id, secondary_sector_id, comment + round_id, participant_id, priority_sector_id, secondary_sector_id, comment ) - def remove_choice(self, round_id: str, participant_id: str): + def remove_choice(self, round_id: str, participant_id: str) -> None: camp = self.get_campaign_by_round(round_id) camp.remove_choice(round_id, participant_id) @@ -289,9 +296,10 @@ class War: score: str | None, victory_condition: str | None, comment: str | None, - ): + ) -> None: camp = self.get_campaign_by_round(round_id) camp.update_battle( + round_id, sector_id, player_1_id, player_2_id, @@ -301,7 +309,7 @@ class War: comment, ) - def remove_battle(self, round_id: str, sector_id: str): + def remove_battle(self, round_id: str, sector_id: str) -> None: camp = self.get_campaign_by_round(round_id) camp.remove_battle(round_id, sector_id) @@ -312,13 +320,13 @@ class Objective: self.name: str = name self.description: str = description - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_name(self, new_name: str): + def set_name(self, new_name: str) -> None: self.name = new_name - def set_description(self, new_description: str): + def set_description(self, new_description: str) -> None: self.description = new_description @@ -328,11 +336,11 @@ class WarParticipant: self.player_id: str = player_id # ref to WarModel.players self.faction: str = faction - def set_id(self, new_id: str): + def set_id(self, new_id: str) -> None: self.id = new_id - def set_player(self, new_player: str): + def set_player(self, new_player: str) -> None: self.player_id = new_player - def set_faction(self, new_faction: str): + def set_faction(self, new_faction: str) -> None: self.faction = new_faction diff --git a/src/warchron/model/war_participant.py b/src/warchron/model/war_participant.py new file mode 100644 index 0000000..e69de29 diff --git a/src/warchron/view/__init__.py b/src/warchron/view/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index 7656060..3b34fcf 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -1,20 +1,42 @@ +from typing import cast, Callable, List from pathlib import Path import calendar from PyQt6 import QtWidgets -from PyQt6.QtCore import Qt -from PyQt6.QtWidgets import QDialog, QFileDialog, QTreeWidgetItem, QMenu +from PyQt6.QtCore import Qt, QPoint +from PyQt6.QtWidgets import ( + QWidget, + QDialog, + QFileDialog, + QTreeWidgetItem, + QMenu, + QComboBox, +) from PyQt6.QtGui import QCloseEvent from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType -from warchron.controller.dtos import ParticipantOption +from warchron.controller.dtos import ( + ParticipantOption, + TreeSelection, + WarDTO, + WarParticipantDTO, + ObjectiveDTO, + CampaignDTO, + CampaignParticipantDTO, + SectorDTO, + RoundDTO, + ChoiceDTO, + BattleDTO, +) from warchron.view.ui.ui_main_window import Ui_MainWindow from warchron.view.ui.ui_player_dialog import Ui_playerDialog from warchron.view.ui.ui_war_dialog import Ui_warDialog from warchron.view.ui.ui_campaign_dialog import Ui_campaignDialog from warchron.view.ui.ui_objective_dialog import Ui_objectiveDialog from warchron.view.ui.ui_war_participant_dialog import Ui_warParticipantDialog -from warchron.view.ui.ui_campaign_participant_dialog import Ui_campaignParticipantDialog +from warchron.view.ui.ui_campaign_participant_dialog import ( + Ui_campaignParticipantDialog, +) from warchron.view.ui.ui_sector_dialog import Ui_sectorDialog from warchron.view.ui.ui_choices_dialog import Ui_choicesDialog from warchron.view.ui.ui_battle_result_dialog import Ui_battleResultDialog @@ -22,7 +44,7 @@ from warchron.view.ui.ui_battle_result_dialog import Ui_battleResultDialog # utils... -def select_if_exists(combo, value): +def select_if_exists(combo: QComboBox, value: str | None) -> None: if value is None: return idx = combo.findData(value) @@ -30,30 +52,32 @@ def select_if_exists(combo, value): combo.setCurrentIndex(idx) -def format_war_label(war) -> str: +def format_war_label(war: WarDTO) -> str: return f"{war.name} ({war.year})" -def format_campaign_label(camp) -> str: +def format_campaign_label(camp: CampaignDTO) -> str: return f"{camp.name} ({calendar.month_name[camp.month]})" -def format_round_label(round, index: int) -> str: +def format_round_label(index: int) -> str: if index is None: return "" return f"Round {index}" class View(QtWidgets.QMainWindow, Ui_MainWindow): - def __init__(self, parent=None): + def __init__(self, parent: QWidget | None = None) -> None: super(View, self).__init__(parent) - self.setupUi(self) - self.on_close_callback = None - self.on_selection_changed = None - self.on_add_campaign = None - self.on_add_round = None - self.on_edit_item = None - self.on_delete_item = None + self.setupUi(self) # type: ignore + self.on_close_callback: Callable[[], bool] | None = None + self.on_tree_selection_changed: ( + Callable[[TreeSelection | None], None] | None + ) = None + self.on_add_campaign: Callable[[], None] | None = None + self.on_add_round: Callable[[], None] | None = None + self.on_edit_item: Callable[[str, str], None] | None = None + self.on_delete_item: Callable[[str, str], None] | None = None self.splitter.setSizes([200, 800]) self.show_details(None) self.playersTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) @@ -98,17 +122,17 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): self._on_battles_table_context_menu ) - def _emit_selection_changed(self, current, previous): + def _emit_selection_changed(self, current: QTreeWidgetItem | None) -> None: if not self.on_tree_selection_changed: return if not current: self.on_tree_selection_changed(None) return self.on_tree_selection_changed( - { - "type": current.data(0, ROLE_TYPE), - "id": current.data(0, ROLE_ID), - } + TreeSelection( + type=current.data(0, ROLE_TYPE), + id=current.data(0, ROLE_ID), + ) ) def get_current_tab(self) -> str: @@ -121,7 +145,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): # General popups - def closeEvent(self, event: QCloseEvent): + def closeEvent(self, event: QCloseEvent | None = None) -> None: + if event is None: + return if self.on_close_callback: proceed = self.on_close_callback() if not proceed: @@ -143,7 +169,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): # Players view - def _on_players_table_context_menu(self, pos): + def _on_players_table_context_menu(self, pos: QPoint) -> None: item = self.playersTable.itemAt(pos) if not item: return @@ -155,13 +181,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.playersTable.viewport().mapToGlobal(pos)) + viewport = self.playersTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.PLAYER, player_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.PLAYER, player_id) - def display_players(self, players: list): + def display_players(self, players: List[ParticipantOption]) -> None: table = self.playersTable table.setRowCount(len(players)) for row, player in enumerate(players): @@ -172,21 +200,21 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): # Wars view - def _on_add_campaign_clicked(self): + def _on_add_campaign_clicked(self) -> None: if self.on_add_campaign: self.on_add_campaign() - def _on_add_round_clicked(self): + def _on_add_round_clicked(self) -> None: if self.on_add_round: self.on_add_round() - def set_add_campaign_enabled(self, enabled: bool): + def set_add_campaign_enabled(self, enabled: bool) -> None: self.addCampaignBtn.setEnabled(enabled) - def set_add_round_enabled(self, enabled: bool): + def set_add_round_enabled(self, enabled: bool) -> None: self.addRoundBtn.setEnabled(enabled) - def _on_wars_tree_context_menu(self, pos): + def _on_wars_tree_context_menu(self, pos: QPoint) -> None: item = self.warsTree.itemAt(pos) if not item: return @@ -197,7 +225,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): if item_type != ItemType.ROUND: edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.warsTree.viewport().mapToGlobal(pos)) + viewport = self.warsTree.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action: if self.on_edit_item: self.on_edit_item(item_type, item_id) @@ -205,7 +235,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): if self.on_delete_item: self.on_delete_item(item_type, item_id) - def display_wars_tree(self, wars: list): + def display_wars_tree(self, wars: List[WarDTO]) -> None: tree = self.warsTree tree.clear() tree.setColumnCount(1) @@ -221,15 +251,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): camp_item.setData(0, ROLE_ID, camp.id) war_item.addChild(camp_item) for index, rnd in enumerate(camp.get_all_rounds(), start=1): - rnd_item = QTreeWidgetItem([format_round_label(rnd, index)]) + rnd_item = QTreeWidgetItem([format_round_label(index)]) rnd_item.setData(0, ROLE_TYPE, ItemType.ROUND) rnd_item.setData(0, ROLE_ID, rnd.id) camp_item.addChild(rnd_item) tree.currentItemChanged.connect(self._emit_selection_changed) tree.expandAll() - def select_tree_item(self, *, item_type: ItemType, item_id: str): - def walk(item: QTreeWidgetItem): + def select_tree_item(self, *, item_type: ItemType, item_id: str) -> None: + def walk(item: QTreeWidgetItem) -> bool: if ( item.data(0, ROLE_TYPE) == item_type and item.data(0, ROLE_ID) == item_id @@ -237,21 +267,25 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): self.warsTree.setCurrentItem(item) return True for i in range(item.childCount()): - if walk(item.child(i)): + # if walk(item.child(i)): + ytem = item.child(i) + if ytem is not None and walk(ytem): return True return False for i in range(self.warsTree.topLevelItemCount()): - if walk(self.warsTree.topLevelItem(i)): + # if walk(self.warsTree.topLevelItem(i)): + item = self.warsTree.topLevelItem(i) + if item is not None and walk(item): return - def get_selected_tree_item(self): + def get_selected_tree_item(self) -> dict[str, str] | None: item = self.warsTree.currentItem() if not item: return None return {"type": item.data(0, ROLE_TYPE), "id": item.data(0, ROLE_ID)} - def show_details(self, item_type: str | None): + def show_details(self, item_type: str | None) -> None: if item_type == ItemType.WAR: self.selectedDetailsStack.setCurrentWidget(self.pageWar) elif item_type == ItemType.CAMPAIGN: @@ -263,7 +297,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): # War page - def _on_objectives_table_context_menu(self, pos): + def _on_objectives_table_context_menu(self, pos: QPoint) -> None: item = self.objectivesTable.itemAt(pos) if not item: return @@ -275,13 +309,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.objectivesTable.viewport().mapToGlobal(pos)) + viewport = self.objectivesTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.OBJECTIVE, objective_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.OBJECTIVE, objective_id) - def _on_war_participants_table_context_menu(self, pos): + def _on_war_participants_table_context_menu(self, pos: QPoint) -> None: item = self.warParticipantsTable.itemAt(pos) if not item: return @@ -293,17 +329,19 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.warParticipantsTable.viewport().mapToGlobal(pos)) + viewport = self.warParticipantsTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.WAR_PARTICIPANT, participant_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.WAR_PARTICIPANT, participant_id) - def show_war_details(self, *, name: str, year: int): + def show_war_details(self, *, name: str, year: int) -> None: self.warName.setText(name) self.warYear.setText(str(year)) - def display_war_objectives(self, objectives: list): + def display_war_objectives(self, objectives: List[ObjectiveDTO]) -> None: table = self.objectivesTable table.clearContents() table.setRowCount(len(objectives)) @@ -315,21 +353,21 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): table.setItem(row, 1, desc_item) table.resizeColumnsToContents() - def display_war_participants(self, participants: list[tuple[str, str, str]]): + def display_war_participants(self, participants: List[WarParticipantDTO]) -> None: table = self.warParticipantsTable table.clearContents() table.setRowCount(len(participants)) - for row, (name, faction, pid) in enumerate(participants): - name_item = QtWidgets.QTableWidgetItem(name) - fact_item = QtWidgets.QTableWidgetItem(faction) - name_item.setData(Qt.ItemDataRole.UserRole, pid) + for row, part in enumerate(participants): + name_item = QtWidgets.QTableWidgetItem(part.player_name) + fact_item = QtWidgets.QTableWidgetItem(part.faction) + name_item.setData(Qt.ItemDataRole.UserRole, part.id) table.setItem(row, 0, name_item) table.setItem(row, 1, fact_item) table.resizeColumnsToContents() # Campaign page - def _on_sectors_table_context_menu(self, pos): + def _on_sectors_table_context_menu(self, pos: QPoint) -> None: item = self.sectorsTable.itemAt(pos) if not item: return @@ -341,13 +379,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.sectorsTable.viewport().mapToGlobal(pos)) + viewport = self.sectorsTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.SECTOR, sector_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.SECTOR, sector_id) - def _on_campaign_participants_table_context_menu(self, pos): + def _on_campaign_participants_table_context_menu(self, pos: QPoint) -> None: item = self.campaignParticipantsTable.itemAt(pos) if not item: return @@ -359,33 +399,31 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") - action = menu.exec(self.campaignParticipantsTable.viewport().mapToGlobal(pos)) + viewport = self.campaignParticipantsTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id) - def show_campaign_details(self, *, name: str, month: int): + def show_campaign_details(self, *, name: str, month: int) -> None: self.campaignName.setText(name) self.campaignMonth.setText(calendar.month_name[month]) - def display_campaign_sectors( - self, sectors: list[tuple[str, str, str, str, str, str]] - ): + def display_campaign_sectors(self, sectors: List[SectorDTO]) -> None: table = self.sectorsTable table.clearContents() table.setRowCount(len(sectors)) - for row, (name, round_index, major, minor, influence, pid) in enumerate( - sectors - ): - name_item = QtWidgets.QTableWidgetItem(name) + for row, sect in enumerate(sectors): + name_item = QtWidgets.QTableWidgetItem(sect.name) round_item = QtWidgets.QTableWidgetItem( - format_round_label(None, round_index) + format_round_label(sect.round_index) ) - major_item = QtWidgets.QTableWidgetItem(major) - minor_item = QtWidgets.QTableWidgetItem(minor) - influence_item = QtWidgets.QTableWidgetItem(influence) - name_item.setData(Qt.ItemDataRole.UserRole, pid) + major_item = QtWidgets.QTableWidgetItem(sect.major) + minor_item = QtWidgets.QTableWidgetItem(sect.minor) + influence_item = QtWidgets.QTableWidgetItem(sect.influence) + name_item.setData(Qt.ItemDataRole.UserRole, sect.id) table.setItem(row, 0, name_item) table.setItem(row, 1, round_item) table.setItem(row, 2, major_item) @@ -394,16 +432,16 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): table.resizeColumnsToContents() def display_campaign_participants( - self, participants: list[tuple[str, str, str, str]] - ): + self, participants: List[CampaignParticipantDTO] + ) -> None: table = self.campaignParticipantsTable table.clearContents() table.setRowCount(len(participants)) - for row, (name, leader, theme, pid) in enumerate(participants): - name_item = QtWidgets.QTableWidgetItem(name) - lead_item = QtWidgets.QTableWidgetItem(leader) - theme_item = QtWidgets.QTableWidgetItem(theme) - name_item.setData(Qt.ItemDataRole.UserRole, pid) + for row, part in enumerate(participants): + name_item = QtWidgets.QTableWidgetItem(part.player_name) + lead_item = QtWidgets.QTableWidgetItem(part.leader) + theme_item = QtWidgets.QTableWidgetItem(part.theme) + name_item.setData(Qt.ItemDataRole.UserRole, part.id) table.setItem(row, 0, name_item) table.setItem(row, 1, lead_item) table.setItem(row, 2, theme_item) @@ -411,7 +449,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): # Round page - def _on_choices_table_context_menu(self, pos): + def _on_choices_table_context_menu(self, pos: QPoint) -> None: item = self.choicesTable.itemAt(pos) if not item: return @@ -424,11 +462,13 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return menu = QMenu(self) edit_action = menu.addAction("Edit") - action = menu.exec(self.choicesTable.viewport().mapToGlobal(pos)) + viewport = self.choicesTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.CHOICE, choice_id) - def _on_battles_table_context_menu(self, pos): + def _on_battles_table_context_menu(self, pos: QPoint) -> None: item = self.battlesTable.itemAt(pos) if not item: return @@ -441,38 +481,38 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return menu = QMenu(self) edit_action = menu.addAction("Edit") - action = menu.exec(self.battlesTable.viewport().mapToGlobal(pos)) + viewport = self.battlesTable.viewport() + assert viewport is not None + action = menu.exec(viewport.mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.BATTLE, battle_id) - def show_round_details(self, *, index: int): + def show_round_details(self, *, index: int) -> None: self.roundNb.setText(f"Round {index}") - def display_round_choices(self, participants: list[tuple[str, str, str, str]]): + def display_round_choices(self, participants: List[ChoiceDTO]) -> None: table = self.choicesTable table.clearContents() table.setRowCount(len(participants)) - for row, (participant, priority, secondary, choice_id) in enumerate( - participants - ): - participant_item = QtWidgets.QTableWidgetItem(participant) - priority_item = QtWidgets.QTableWidgetItem(priority) - secondary_item = QtWidgets.QTableWidgetItem(secondary) - participant_item.setData(Qt.ItemDataRole.UserRole, choice_id) + for row, choice in enumerate(participants): + participant_item = QtWidgets.QTableWidgetItem(choice.participant_name) + priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector) + secondary_item = QtWidgets.QTableWidgetItem(choice.secondary_sector) + participant_item.setData(Qt.ItemDataRole.UserRole, choice.id) table.setItem(row, 0, participant_item) table.setItem(row, 1, priority_item) table.setItem(row, 2, secondary_item) table.resizeColumnsToContents() - def display_round_battles(self, sectors: list[tuple[str, str, str, str]]): + def display_round_battles(self, sectors: List[BattleDTO]) -> None: table = self.battlesTable table.clearContents() table.setRowCount(len(sectors)) - for row, (sector, player_1, player_2, battle_id) in enumerate(sectors): - sector_item = QtWidgets.QTableWidgetItem(sector) - player_1_item = QtWidgets.QTableWidgetItem(player_1) - player_2_item = QtWidgets.QTableWidgetItem(player_2) - sector_item.setData(Qt.ItemDataRole.UserRole, battle_id) + for row, battle in enumerate(sectors): + sector_item = QtWidgets.QTableWidgetItem(battle.sector_name) + player_1_item = QtWidgets.QTableWidgetItem(battle.player_1) + player_2_item = QtWidgets.QTableWidgetItem(battle.player_2) + sector_item.setData(Qt.ItemDataRole.UserRole, battle.id) table.setItem(row, 0, sector_item) table.setItem(row, 1, player_1_item) table.setItem(row, 2, player_2_item) @@ -480,10 +520,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): class PlayerDialog(QDialog): - def __init__(self, parent=None, *, default_name: str = ""): + def __init__( + self, parent: QWidget | None = None, *, default_name: str = "" + ) -> None: super().__init__(parent) - self.ui = Ui_playerDialog() - self.ui.setupUi(self) + self.ui: Ui_playerDialog = Ui_playerDialog() + self.ui.setupUi(self) # type: ignore self.ui.playerName.setText(default_name) def get_player_name(self) -> str: @@ -492,11 +534,14 @@ class PlayerDialog(QDialog): class WarDialog(QDialog): def __init__( - self, parent=None, default_name: str = "", default_year: int | None = None - ): + self, + parent: QWidget | None = None, + default_name: str = "", + default_year: int | None = None, + ) -> None: super().__init__(parent) - self.ui = Ui_warDialog() - self.ui.setupUi(self) + self.ui: Ui_warDialog = Ui_warDialog() + self.ui.setupUi(self) # type: ignore self.ui.warName.setText(default_name) if default_year is not None: self.ui.warYear.setValue(default_year) @@ -510,11 +555,14 @@ class WarDialog(QDialog): class CampaignDialog(QDialog): def __init__( - self, parent=None, default_name: str = "", default_month: int | None = None - ): + self, + parent: QWidget | None = None, + default_name: str = "", + default_month: int | None = None, + ) -> None: super().__init__(parent) - self.ui = Ui_campaignDialog() - self.ui.setupUi(self) + self.ui: Ui_campaignDialog = Ui_campaignDialog() + self.ui.setupUi(self) # type: ignore self.ui.campaignName.setText(default_name) if default_month is not None: self.ui.campaignMonth.setValue(default_month) @@ -527,10 +575,16 @@ class CampaignDialog(QDialog): class ObjectiveDialog(QDialog): - def __init__(self, parent=None, *, default_name="", default_description=""): + def __init__( + self, + parent: QWidget | None = None, + *, + default_name: str = "", + default_description: str | None = "", + ) -> None: super().__init__(parent) - self.ui = Ui_objectiveDialog() - self.ui.setupUi(self) + self.ui: Ui_objectiveDialog = Ui_objectiveDialog() + self.ui.setupUi(self) # type: ignore self.ui.objectiveName.setText(default_name) self.ui.objectiveDescription.setPlainText(default_description) @@ -544,16 +598,16 @@ class ObjectiveDialog(QDialog): class WarParticipantDialog(QDialog): def __init__( self, - parent=None, + parent: QWidget | None = None, *, - players: list, - default_player_id=None, - default_faction="", - editable_player=True, + players: List[ParticipantOption], + default_player_id: str | None = None, + default_faction: str | None = "", + editable_player: bool = True, ): super().__init__(parent) - self.ui = Ui_warParticipantDialog() - self.ui.setupUi(self) + self.ui: Ui_warParticipantDialog = Ui_warParticipantDialog() + self.ui.setupUi(self) # type: ignore for player in players: self.ui.playerComboBox.addItem(player.name, player.id) select_if_exists(self.ui.playerComboBox, default_player_id) @@ -561,7 +615,7 @@ class WarParticipantDialog(QDialog): self.ui.faction.setText(default_faction) def get_player_id(self) -> str: - return self.ui.playerComboBox.currentData() + return cast(str, self.ui.playerComboBox.currentData()) def get_participant_faction(self) -> str: return self.ui.faction.text().strip() @@ -570,17 +624,17 @@ class WarParticipantDialog(QDialog): class CampaignParticipantDialog(QDialog): def __init__( self, - parent=None, + parent: QWidget | None = None, *, - participants: list[ParticipantOption], - default_participant_id=None, - default_leader="", - default_theme="", - editable_player=True, - ): + participants: List[ParticipantOption], + default_participant_id: str | None = None, + default_leader: str | None = "", + default_theme: str | None = "", + editable_player: bool = True, + ) -> None: super().__init__(parent) - self.ui = Ui_campaignParticipantDialog() - self.ui.setupUi(self) + self.ui: Ui_campaignParticipantDialog = Ui_campaignParticipantDialog() + self.ui.setupUi(self) # type: ignore for part in participants: self.ui.playerComboBox.addItem(part.name, part.id) select_if_exists(self.ui.playerComboBox, default_participant_id) @@ -589,7 +643,7 @@ class CampaignParticipantDialog(QDialog): self.ui.theme.setText(default_theme) def get_player_id(self) -> str: - return self.ui.playerComboBox.currentData() + return cast(str, self.ui.playerComboBox.currentData()) def get_participant_leader(self) -> str: return self.ui.leader.text().strip() @@ -601,25 +655,25 @@ class CampaignParticipantDialog(QDialog): class SectorDialog(QDialog): def __init__( self, - parent=None, + parent: QWidget | None = None, *, - default_name="", - rounds: list, - default_round_id=None, - objectives: list, - default_major_id=None, - default_minor_id=None, - default_influence_id=None, - ): + default_name: str = "", + rounds: List[RoundDTO], + default_round_id: str | None = None, + objectives: List[ObjectiveDTO], + default_major_id: str | None = None, + default_minor_id: str | None = None, + default_influence_id: str | None = None, + ) -> None: super().__init__(parent) - self.ui = Ui_sectorDialog() - self.ui.setupUi(self) + self.ui: Ui_sectorDialog = Ui_sectorDialog() + self.ui.setupUi(self) # type: ignore self.ui.majorComboBox.addItem("(none)", None) self.ui.minorComboBox.addItem("(none)", None) self.ui.influenceComboBox.addItem("(none)", None) self.ui.sectorName.setText(default_name) for index, rnd in enumerate(rounds, start=1): - self.ui.roundComboBox.addItem(format_round_label(rnd, index), rnd.id) + self.ui.roundComboBox.addItem(format_round_label(index), rnd.id) select_if_exists(self.ui.roundComboBox, default_round_id) for obj in objectives: self.ui.majorComboBox.addItem(obj.name, obj.id) @@ -633,33 +687,33 @@ class SectorDialog(QDialog): return self.ui.sectorName.text().strip() def get_round_id(self) -> str: - return self.ui.roundComboBox.currentData() + return cast(str, self.ui.roundComboBox.currentData()) def get_major_id(self) -> str: - return self.ui.majorComboBox.currentData() + return cast(str, self.ui.majorComboBox.currentData()) def get_minor_id(self) -> str: - return self.ui.minorComboBox.currentData() + return cast(str, self.ui.minorComboBox.currentData()) def get_influence_id(self) -> str: - return self.ui.influenceComboBox.currentData() + return cast(str, self.ui.influenceComboBox.currentData()) class ChoicesDialog(QDialog): def __init__( self, - parent=None, + parent: QWidget | None = None, *, - participants: list, - default_participant_id=None, - sectors: list, - default_priority_id=None, - default_secondary_id=None, - default_comment=None, - ): + participants: List[ParticipantOption], + default_participant_id: str | None = None, + sectors: List[SectorDTO], + default_priority_id: str | None = None, + default_secondary_id: str | None = None, + default_comment: str | None = None, + ) -> None: super().__init__(parent) - self.ui = Ui_choicesDialog() - self.ui.setupUi(self) + self.ui: Ui_choicesDialog = Ui_choicesDialog() + self.ui.setupUi(self) # type: ignore for part in participants: self.ui.playerComboBox.addItem(part.name, part.id) select_if_exists(self.ui.playerComboBox, default_participant_id) @@ -674,13 +728,13 @@ class ChoicesDialog(QDialog): self.ui.choiceComment.setPlainText(default_comment) def get_participant_id(self) -> str: - return self.ui.playerComboBox.currentData() + return cast(str, self.ui.playerComboBox.currentData()) def get_priority_id(self) -> str: - return self.ui.priorityComboBox.currentData() + return cast(str, self.ui.priorityComboBox.currentData()) def get_secondary_id(self) -> str: - return self.ui.secondaryComboBox.currentData() + return cast(str, self.ui.secondaryComboBox.currentData()) def get_comment(self) -> str: return self.ui.choiceComment.toPlainText().strip() @@ -689,21 +743,21 @@ class ChoicesDialog(QDialog): class BattlesDialog(QDialog): def __init__( self, - parent=None, + parent: QWidget | None = None, *, - sectors: list, - default_sector_id=None, - players: list, - default_player_1_id=None, - default_player_2_id=None, - default_winner_id=None, - default_score=None, - default_victory_condition=None, - default_comment=None, - ): + sectors: List[SectorDTO], + default_sector_id: str | None = None, + players: List[ParticipantOption], + default_player_1_id: str | None = None, + default_player_2_id: str | None = None, + default_winner_id: str | None = None, + default_score: str | None = None, + default_victory_condition: str | None = None, + default_comment: str | None = None, + ) -> None: super().__init__(parent) - self.ui = Ui_battleResultDialog() - self.ui.setupUi(self) + self.ui: Ui_battleResultDialog = Ui_battleResultDialog() + self.ui.setupUi(self) # type: ignore for sect in sectors: self.ui.sectorComboBox.addItem(sect.name, sect.id) select_if_exists(self.ui.sectorComboBox, default_sector_id) @@ -726,16 +780,16 @@ class BattlesDialog(QDialog): self.ui.battleComment.setPlainText(default_comment) def get_sector_id(self) -> str: - return self.ui.sectorComboBox.currentData() + return cast(str, self.ui.sectorComboBox.currentData()) def get_player_1_id(self) -> str: - return self.ui.player1ComboBox.currentData() + return cast(str, self.ui.player1ComboBox.currentData()) def get_player_2_id(self) -> str: - return self.ui.player2ComboBox.currentData() + return cast(str, self.ui.player2ComboBox.currentData()) def get_winner_id(self) -> str: - return self.ui.winnerComboBox.currentData() + return cast(str, self.ui.winnerComboBox.currentData()) def get_score(self) -> str: return self.ui.score.text().strip()