code quality (mypy+flake8)

This commit is contained in:
Maxime Réaux 2026-02-04 16:10:53 +01:00
parent 7210ddc927
commit 55abdccc64
19 changed files with 778 additions and 497 deletions

View file

@ -33,6 +33,7 @@ cd warchron_app
python -m venv .venv python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt pip install -r requirements.txt
pip install -e .
``` ```
### Run ### Run

View file

@ -1,7 +1,4 @@
import sys import sys
import os
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src"))
from PyQt6.QtWidgets import QApplication from PyQt6.QtWidgets import QApplication

67
pyproject.toml Normal file
View file

@ -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

View file

@ -1 +1,5 @@
PyQt6>=6.6,<6.8 PyQt6>=6.6,<6.8
mypy==1.19.1
black==25.12.0
flake8==7.3.0
flake8-pyproject

View file

@ -1,11 +1,24 @@
from typing import List
from pathlib import Path from pathlib import Path
from PyQt6.QtWidgets import QMessageBox, QDialog from PyQt6.QtWidgets import QMessageBox, QDialog
from warchron.model.model import Model from warchron.model.model import Model
from warchron.view.view import View from warchron.view.view import View
from warchron.constants import ItemType, RefreshScope 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 ( from warchron.view.view import (
PlayerDialog, PlayerDialog,
WarDialog, WarDialog,
@ -20,13 +33,13 @@ from warchron.view.view import (
class Controller: class Controller:
def __init__(self, model: Model, view: View): def __init__(self, model: Model, view: View) -> None:
self.model: Model = model self.model: Model = model
self.view: View = view self.view: View = view
self.current_file: Path | None = None self.current_file: Path | None = None
self.selected_war_id: str = None self.selected_war_id: str | None = None
self.selected_campaign_id: str = None self.selected_campaign_id: str | None = None
self.selected_round_id: str = None self.selected_round_id: str | None = None
self.view.on_close_callback = self.on_app_close self.view.on_close_callback = self.on_app_close
self.is_dirty: bool = False self.is_dirty: bool = False
self.__connect() self.__connect()
@ -38,7 +51,7 @@ class Controller:
self.view.on_add_campaign = self.add_campaign self.view.on_add_campaign = self.add_campaign
self.view.on_add_round = self.add_round 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.actionExit.triggered.connect(self.view.close)
self.view.actionNew.triggered.connect(self.new) self.view.actionNew.triggered.connect(self.new)
self.view.actionOpen.triggered.connect(self.open_file) self.view.actionOpen.triggered.connect(self.open_file)
@ -73,7 +86,7 @@ class Controller:
# Menu bar methods # Menu bar methods
def new(self): def new(self) -> None:
if self.is_dirty: if self.is_dirty:
reply = QMessageBox.question( reply = QMessageBox.question(
self.view, self.view,
@ -90,7 +103,7 @@ class Controller:
self.refresh_wars_view() self.refresh_wars_view()
self.update_window_title() self.update_window_title()
def open_file(self): def open_file(self) -> None:
if self.is_dirty: if self.is_dirty:
reply = QMessageBox.question( reply = QMessageBox.question(
self.view, self.view,
@ -110,7 +123,7 @@ class Controller:
self.refresh_wars_view() self.refresh_wars_view()
self.update_window_title() self.update_window_title()
def save(self): def save(self) -> None:
if not self.current_file: if not self.current_file:
self.save_as() self.save_as()
return return
@ -118,7 +131,7 @@ class Controller:
self.is_dirty = False self.is_dirty = False
self.update_window_title() self.update_window_title()
def save_as(self): def save_as(self) -> None:
path = self.view.ask_save_file() path = self.view.ask_save_file()
if not path: if not path:
return return
@ -129,83 +142,102 @@ class Controller:
# Display methods # Display methods
def update_window_title(self): def update_window_title(self) -> None:
base = "WarChron" base = "WarChron"
if self.current_file: if self.current_file:
base += f" - {self.current_file.name}" base += f" - {self.current_file.name}"
else: else:
base += f" - New file" base += " - New file"
if self.is_dirty: if self.is_dirty:
base = base + " *" base = base + " *"
self.view.setWindowTitle(base) self.view.setWindowTitle(base)
def refresh_players_view(self): def refresh_players_view(self) -> None:
players = self.model.get_all_players() 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): def refresh_wars_view(self) -> None:
wars = self.model.get_all_wars() 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) 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) war = self.model.get_war(war_id)
self.view.show_war_details(name=war.name, year=war.year) self.view.show_war_details(name=war.name, year=war.year)
objectives = war.get_all_objectives() 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() war_parts = war.get_all_war_participants()
participants_for_display = [ participants_for_display: List[WarParticipantDTO] = [
( WarParticipantDTO(
self.model.get_player_name( id=p.id,
p.player_id, player_name=self.model.get_player_name(p.player_id),
), faction=p.faction,
p.faction,
p.id,
) )
for p in war_parts for p in war_parts
] ]
self.view.display_war_participants(participants_for_display) 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) camp = self.model.get_campaign(campaign_id)
self.view.show_campaign_details(name=camp.name, month=camp.month) self.view.show_campaign_details(name=camp.name, month=camp.month)
sectors = camp.get_all_sectors() sectors = camp.get_all_sectors()
sectors_for_display = []
war = self.model.get_war_by_campaign(camp.id) war = self.model.get_war_by_campaign(camp.id)
for sect in sectors: sectors_for_display: List[SectorDTO] = [
round_index = camp.get_round_index(sect.round_id) SectorDTO(
major_name = war.get_objective_name(sect.major_objective_id) id=sect.id,
minor_name = war.get_objective_name(sect.minor_objective_id) name=sect.name,
influence_name = war.get_objective_name(sect.influence_objective_id) round_index=camp.get_round_index(sect.round_id),
sectors_for_display.append( major=war.get_objective_name(sect.major_objective_id),
( minor=war.get_objective_name(sect.minor_objective_id),
sect.name, influence=war.get_objective_name(sect.influence_objective_id),
round_index,
major_name,
minor_name,
influence_name,
sect.id,
)
) )
for sect in sectors
]
self.view.display_campaign_sectors(sectors_for_display) self.view.display_campaign_sectors(sectors_for_display)
camp_parts = camp.get_all_campaign_participants() camp_parts = camp.get_all_campaign_participants()
participants_for_display = [ participants_for_display: List[CampaignParticipantDTO] = [
( CampaignParticipantDTO(
self.model.get_participant_name(p.war_participant_id), id=p.id,
p.leader, player_name=self.model.get_participant_name(p.war_participant_id),
p.theme, leader=p.leader or "",
p.id, theme=p.theme or "",
) )
for p in camp_parts for p in camp_parts
] ]
self.view.display_campaign_participants(participants_for_display) 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) rnd = self.model.get_round(round_id)
camp = self.model.get_campaign_by_round(round_id) camp = self.model.get_campaign_by_round(round_id)
self.view.show_round_details(index=camp.get_round_index(round_id)) self.view.show_round_details(index=camp.get_round_index(round_id))
participants = self.model.get_round_participants(round_id) participants = self.model.get_round_participants(round_id)
sectors = camp.get_sectors_in_round(round_id) sectors = camp.get_sectors_in_round(round_id)
choices_for_display = [] choices_for_display: List[ChoiceDTO] = []
for part in participants: for part in participants:
choice = rnd.get_choice(part.id) choice = rnd.get_choice(part.id)
if not choice: if not choice:
@ -213,21 +245,28 @@ class Controller:
round_id=rnd.id, participant_id=part.id round_id=rnd.id, participant_id=part.id
) )
priority_name = ( 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 = ( 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( choices_for_display.append(
( ChoiceDTO(
self.model.get_participant_name(part.war_participant_id), id=choice.participant_id,
priority_name, participant_name=self.model.get_participant_name(
secondary_name, part.war_participant_id
choice.participant_id, ),
priority_sector=priority_name,
secondary_sector=secondary_name,
comment=choice.comment,
) )
) )
self.view.display_round_choices(choices_for_display) self.view.display_round_choices(choices_for_display)
battles_for_display = [] battles_for_display: List[BattleDTO] = []
for sect in sectors: for sect in sectors:
battle = rnd.get_battle(sect.id) battle = rnd.get_battle(sect.id)
if not battle: if not battle:
@ -246,23 +285,34 @@ class Controller:
) )
else: else:
player_2_name = "" 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( battles_for_display.append(
( BattleDTO(
camp.get_sector_name(battle.sector_id), id=battle.sector_id,
player_1_name, sector_name=camp.get_sector_name(battle.sector_id),
player_2_name, player_1=player_1_name,
battle.sector_id, 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) 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_war_id = None
self.selected_campaign_id = None self.selected_campaign_id = None
self.selected_round_id = None self.selected_round_id = None
if selection: if selection:
item_type = selection["type"] item_type = selection.type
item_id = selection["id"] item_id = selection.id
if item_type == ItemType.WAR: if item_type == ItemType.WAR:
self.selected_war_id = item_id self.selected_war_id = item_id
self.view.show_details(ItemType.WAR) self.view.show_details(ItemType.WAR)
@ -281,11 +331,11 @@ class Controller:
return return
self.update_actions_state() 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_campaign_enabled(self.selected_war_id is not None)
self.view.set_add_round_enabled(self.selected_campaign_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: match scope:
case RefreshScope.PLAYERS_LIST: case RefreshScope.PLAYERS_LIST:
self.refresh_players_view() self.refresh_players_view()
@ -309,26 +359,28 @@ class Controller:
def refresh_and_select( def refresh_and_select(
self, scope: RefreshScope, *, item_type: ItemType, item_id: str self, scope: RefreshScope, *, item_type: ItemType, item_id: str
): ) -> None:
self.refresh(scope) self.refresh(scope)
self.view.select_tree_item(item_type=item_type, item_id=item_id) 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: if item_type == ItemType.PLAYER:
play = self.model.get_player(item_id) play = self.model.get_player(item_id)
dialog = PlayerDialog(self.view, default_name=play.name) player_dialog = PlayerDialog(self.view, default_name=play.name)
if dialog.exec() == QDialog.DialogCode.Accepted: if player_dialog.exec() == QDialog.DialogCode.Accepted:
name = dialog.get_player_name() name = player_dialog.get_player_name()
if not self._validate_player_inputs(name): if not self._validate_player_inputs(name):
return return
self.model.update_player(item_id, name=name) self.model.update_player(item_id, name=name)
self.refresh(RefreshScope.PLAYERS_LIST) self.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR: elif item_type == ItemType.WAR:
war = self.model.get_war(item_id) war = self.model.get_war(item_id)
dialog = WarDialog(self.view, default_name=war.name, default_year=war.year) war_dialog = WarDialog(
if dialog.exec() == QDialog.DialogCode.Accepted: self.view, default_name=war.name, default_year=war.year
name = dialog.get_war_name() )
year = dialog.get_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): if not self._validate_war_inputs(name, year):
return return
self.model.update_war(item_id, name=name, year=year) self.model.update_war(item_id, name=name, year=year)
@ -337,12 +389,12 @@ class Controller:
) )
elif item_type == ItemType.CAMPAIGN: elif item_type == ItemType.CAMPAIGN:
camp = self.model.get_campaign(item_id) camp = self.model.get_campaign(item_id)
dialog = CampaignDialog( camp_dialog = CampaignDialog(
self.view, default_name=camp.name, default_month=camp.month self.view, default_name=camp.name, default_month=camp.month
) )
if dialog.exec() == QDialog.DialogCode.Accepted: if camp_dialog.exec() == QDialog.DialogCode.Accepted:
name = dialog.get_campaign_name() name = camp_dialog.get_campaign_name()
month = dialog.get_campaign_month() month = camp_dialog.get_campaign_month()
if not self._validate_campaign_inputs(name, month): if not self._validate_campaign_inputs(name, month):
return return
self.model.update_campaign(item_id, name=name, month=month) self.model.update_campaign(item_id, name=name, month=month)
@ -351,28 +403,29 @@ class Controller:
) )
elif item_type == ItemType.OBJECTIVE: elif item_type == ItemType.OBJECTIVE:
obj = self.model.get_objective(item_id) obj = self.model.get_objective(item_id)
dialog = ObjectiveDialog( obj_dialog = ObjectiveDialog(
self.view, default_name=obj.name, default_description=obj.description self.view, default_name=obj.name, default_description=obj.description
) )
if dialog.exec() == QDialog.DialogCode.Accepted: if obj_dialog.exec() == QDialog.DialogCode.Accepted:
name = dialog.get_objective_name() name = obj_dialog.get_objective_name()
description = dialog.get_objective_description() description = obj_dialog.get_objective_description()
if not self._validate_objective_inputs(name, description): if not self._validate_objective_inputs(name, description):
return return
self.model.update_objective(item_id, name=name, description=description) self.model.update_objective(item_id, name=name, description=description)
self.refresh(RefreshScope.WAR_DETAILS) self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT: elif item_type == ItemType.WAR_PARTICIPANT:
camp_part = self.model.get_war_participant(item_id) war_part = self.model.get_war_participant(item_id)
player = self.model.get_player(camp_part.player_id) player = self.model.get_player(war_part.player_id)
dialog = WarParticipantDialog( play_opt = ParticipantOption(id=player.id, name=player.name)
war_part_dialog = WarParticipantDialog(
self.view, self.view,
players=[player], players=[play_opt],
default_player_id=camp_part.id, default_player_id=war_part.id,
default_faction=camp_part.faction, default_faction=war_part.faction,
editable_player=False, editable_player=False,
) )
if dialog.exec() == QDialog.DialogCode.Accepted: if war_part_dialog.exec() == QDialog.DialogCode.Accepted:
faction = dialog.get_participant_faction() faction = war_part_dialog.get_participant_faction()
self.model.update_war_participant(item_id, faction=faction) self.model.update_war_participant(item_id, faction=faction)
self.refresh(RefreshScope.WAR_DETAILS) self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.SECTOR: elif item_type == ItemType.SECTOR:
@ -380,23 +433,30 @@ class Controller:
camp = self.model.get_campaign_by_sector(item_id) camp = self.model.get_campaign_by_sector(item_id)
war = self.model.get_war_by_campaign(camp.id) war = self.model.get_war_by_campaign(camp.id)
rounds = camp.get_all_rounds() 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() 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, self.view,
default_name=sect.name, default_name=sect.name,
rounds=rounds, rounds=rnd_dto,
default_round_id=sect.round_id, default_round_id=sect.round_id,
objectives=objectives, objectives=obj_dto,
default_major_id=sect.major_objective_id, default_major_id=sect.major_objective_id,
default_minor_id=sect.minor_objective_id, default_minor_id=sect.minor_objective_id,
default_influence_id=sect.influence_objective_id, default_influence_id=sect.influence_objective_id,
) )
if dialog.exec() == QDialog.DialogCode.Accepted: if sect_dialog.exec() == QDialog.DialogCode.Accepted:
name = dialog.get_sector_name() name = sect_dialog.get_sector_name()
round_id = dialog.get_round_id() round_id = sect_dialog.get_round_id()
major_id = dialog.get_major_id() major_id = sect_dialog.get_major_id()
minor_id = dialog.get_minor_id() minor_id = sect_dialog.get_minor_id()
influence_id = dialog.get_influence_id() influence_id = sect_dialog.get_influence_id()
self.model.update_sector( self.model.update_sector(
item_id, item_id,
name=name, name=name,
@ -411,7 +471,7 @@ class Controller:
war_part = self.model.get_war_participant(camp_part.war_participant_id) war_part = self.model.get_war_participant(camp_part.war_participant_id)
player = self.model.get_player(war_part.player_id) player = self.model.get_player(war_part.player_id)
part_opt = [ParticipantOption(id=player.id, name=player.name)] part_opt = [ParticipantOption(id=player.id, name=player.name)]
dialog = CampaignParticipantDialog( camp_part_dialog = CampaignParticipantDialog(
self.view, self.view,
participants=part_opt, participants=part_opt,
default_participant_id=camp_part.id, default_participant_id=camp_part.id,
@ -419,9 +479,9 @@ class Controller:
default_theme=camp_part.theme, default_theme=camp_part.theme,
editable_player=False, editable_player=False,
) )
if dialog.exec() == QDialog.DialogCode.Accepted: if camp_part_dialog.exec() == QDialog.DialogCode.Accepted:
leader = dialog.get_participant_leader() leader = camp_part_dialog.get_participant_leader()
theme = dialog.get_participant_theme() theme = camp_part_dialog.get_participant_theme()
self.model.update_campaign_participant( self.model.update_campaign_participant(
item_id, leader=leader, theme=theme item_id, leader=leader, theme=theme
) )
@ -433,7 +493,7 @@ class Controller:
self.edit_round_battle(item_id) self.edit_round_battle(item_id)
self.refresh(RefreshScope.ROUND_DETAILS) 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( reply = QMessageBox.question(
self.view, self.view,
"Confirm deletion", "Confirm deletion",
@ -486,7 +546,7 @@ class Controller:
return False return False
return True return True
def add_player(self): def add_player(self) -> None:
dialog = PlayerDialog(self.view) dialog = PlayerDialog(self.view)
result = dialog.exec() # modal blocking dialog result = dialog.exec() # modal blocking dialog
if result == QDialog.DialogCode.Accepted: if result == QDialog.DialogCode.Accepted:
@ -510,7 +570,7 @@ class Controller:
return False return False
return True return True
def add_war(self): def add_war(self) -> None:
dialog = WarDialog( dialog = WarDialog(
self.view, default_year=self.model.get_default_war_values()["year"] self.view, default_year=self.model.get_default_war_values()["year"]
) )
@ -536,7 +596,7 @@ class Controller:
return False return False
return True return True
def add_objective(self): def add_objective(self) -> None:
if not self.selected_war_id: if not self.selected_war_id:
return return
dialog = ObjectiveDialog(self.view) dialog = ObjectiveDialog(self.view)
@ -552,11 +612,14 @@ class Controller:
# War participant methods # War participant methods
def add_war_participant(self): def add_war_participant(self) -> None:
if not self.selected_war_id: if not self.selected_war_id:
return return
players = self.model.get_available_players(self.selected_war_id) 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: if dialog.exec() != QDialog.DialogCode.Accepted:
return return
player_id = dialog.get_player_id() player_id = dialog.get_player_id()
@ -582,7 +645,7 @@ class Controller:
return False return False
return True return True
def add_campaign(self): def add_campaign(self) -> None:
if not self.selected_war_id: if not self.selected_war_id:
return return
dialog = CampaignDialog( dialog = CampaignDialog(
@ -605,7 +668,7 @@ class Controller:
# Campaign participant methods # Campaign participant methods
def add_campaign_participant(self): def add_campaign_participant(self) -> None:
if not self.selected_campaign_id: if not self.selected_campaign_id:
return return
participants = self.model.get_available_war_participants( participants = self.model.get_available_war_participants(
@ -634,6 +697,7 @@ class Controller:
def _validate_sector_inputs( def _validate_sector_inputs(
self, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str self, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str
) -> bool: ) -> bool:
if not name.strip(): if not name.strip():
QMessageBox.warning( QMessageBox.warning(
self.view, "Invalid name", "Sector name cannot be empty." self.view, "Invalid name", "Sector name cannot be empty."
@ -642,15 +706,22 @@ class Controller:
# allow same objectives in different fields? # allow same objectives in different fields?
return True return True
def add_sector(self): def add_sector(self) -> None:
if not self.selected_campaign_id: if not self.selected_campaign_id:
return return
war = self.model.get_war_by_campaign(self.selected_campaign_id) war = self.model.get_war_by_campaign(self.selected_campaign_id)
camp = self.model.get_campaign(self.selected_campaign_id) camp = self.model.get_campaign(self.selected_campaign_id)
rounds = camp.get_all_rounds() 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() 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( 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: if dialog.exec() != QDialog.DialogCode.Accepted:
return return
@ -671,7 +742,7 @@ class Controller:
# Round methods # Round methods
def add_round(self): def add_round(self) -> None:
if not self.selected_campaign_id: if not self.selected_campaign_id:
return return
rnd = self.model.add_round(self.selected_campaign_id) rnd = self.model.add_round(self.selected_campaign_id)
@ -682,14 +753,25 @@ class Controller:
# Choice methods # 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 round_id = self.selected_round_id
if not round_id: if not round_id:
return 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) camp = self.model.get_campaign_by_round(round_id)
rnd = camp.get_round(round_id) rnd = camp.get_round(round_id)
sectors = camp.get_sectors_in_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) choice = rnd.get_choice(choice_id)
if not choice: if not choice:
return return
@ -700,7 +782,7 @@ class Controller:
self.view, self.view,
participants=[part_opt], participants=[part_opt],
default_participant_id=participant.id, default_participant_id=participant.id,
sectors=sectors, sectors=sect_opts,
default_priority_id=choice.priority_sector_id, default_priority_id=choice.priority_sector_id,
default_secondary_id=choice.secondary_sector_id, default_secondary_id=choice.secondary_sector_id,
default_comment=choice.comment, default_comment=choice.comment,
@ -717,10 +799,11 @@ class Controller:
# Battle methods # 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 round_id = self.selected_round_id
if not round_id: if not round_id:
return return
war = self.model.get_war_by_round(round_id)
camp = self.model.get_campaign_by_round(round_id) camp = self.model.get_campaign_by_round(round_id)
rnd = camp.get_round(round_id) rnd = camp.get_round(round_id)
participants = camp.get_all_campaign_participants() participants = camp.get_all_campaign_participants()
@ -728,13 +811,22 @@ class Controller:
if not battle: if not battle:
return return
sect = camp.sectors[battle.sector_id] 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: for participant in participants:
player = self.model.get_player_from_campaign_participant(participant) player = self.model.get_player_from_campaign_participant(participant)
part_opts.append(ParticipantOption(id=participant.id, name=player.name)) part_opts.append(ParticipantOption(id=participant.id, name=player.name))
dialog = BattlesDialog( dialog = BattlesDialog(
self.view, self.view,
sectors=[sect], sectors=[sect_dto],
default_sector_id=sect.id, default_sector_id=sect.id,
players=part_opts, players=part_opts,
default_player_1_id=battle.player_1_id, default_player_1_id=battle.player_1_id,

View file

@ -1,3 +1,4 @@
from typing import List
from dataclasses import dataclass from dataclasses import dataclass
@ -5,3 +6,90 @@ from dataclasses import dataclass
class ParticipantOption: class ParticipantOption:
id: str id: str
name: 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

View file

@ -1,32 +1,33 @@
from __future__ import annotations from __future__ import annotations
from uuid import uuid4 from uuid import uuid4
from typing import Any, Dict, List
from warchron.model.round import Round, Choice, Battle from warchron.model.round import Round, Choice, Battle
class Campaign: class Campaign:
def __init__(self, name: str, month: int): def __init__(self, name: str, month: int) -> None:
self.id: str = str(uuid4()) self.id: str = str(uuid4())
self.name: str = name self.name: str = name
self.month: int = month self.month: int = month
self.participants: dict[str, CampaignParticipant] = {} self.participants: Dict[str, CampaignParticipant] = {}
self.sectors: dict[str, Sector] = {} self.sectors: Dict[str, Sector] = {}
self.rounds: list[Round] = [] self.rounds: List[Round] = []
self.is_over = False self.is_over = False
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_name(self, new_name: str): def set_name(self, new_name: str) -> None:
self.name = new_name self.name = new_name
def set_month(self, new_month: int): def set_month(self, new_month: int) -> None:
self.month = new_month self.month = new_month
def set_state(self, new_state: bool): def set_state(self, new_state: bool) -> None:
self.is_over = new_state self.is_over = new_state
def toDict(self): def toDict(self) -> Dict[str, Any]:
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
@ -37,7 +38,7 @@ class Campaign:
} }
@staticmethod @staticmethod
def fromDict(data: dict): def fromDict(data: Dict[str, Any]) -> Campaign:
camp = Campaign(name=data["name"], month=data["month"]) camp = Campaign(name=data["name"], month=data["month"])
camp.set_id(data["id"]) camp.set_id(data["id"])
# camp.participants = data.get("participants", {}) # camp.participants = data.get("participants", {})
@ -77,18 +78,18 @@ class Campaign:
except KeyError: except KeyError:
raise KeyError(f"Participant {participant_id} not in campaign {self.id}") 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()) return list(self.participants.values())
def update_campaign_participant( def update_campaign_participant(
self, participant_id: str, *, leader: str, theme: str self, participant_id: str, *, leader: str, theme: str
): ) -> None:
part = self.get_campaign_participant(participant_id) part = self.get_campaign_participant(participant_id)
# Can't change referred War.participant # Can't change referred War.participant
part.set_leader(leader) part.set_leader(leader)
part.set_theme(theme) 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 choices referring to it
# TODO manage battles referring to it # TODO manage battles referring to it
del self.participants[participant_id] del self.participants[participant_id]
@ -110,7 +111,7 @@ class Campaign:
return "" return ""
return self.sectors[sector_id].name 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()) return list(self.sectors.values())
# TODO manage choices referring to it (round order!) # TODO manage choices referring to it (round order!)
@ -123,7 +124,7 @@ class Campaign:
major_id: str, major_id: str,
minor_id: str, minor_id: str,
influence_id: str, influence_id: str,
): ) -> None:
sect = self.get_sector(sector_id) sect = self.get_sector(sector_id)
sect.set_name(name) sect.set_name(name)
sect.set_round(round_id) sect.set_round(round_id)
@ -131,12 +132,12 @@ class Campaign:
sect.set_minor(minor_id) sect.set_minor(minor_id)
sect.set_influence(influence_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 choices referring to it
# TODO manage battles referring to it # TODO manage battles referring to it
del self.sectors[sector_id] 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] sectors = [s for s in self.sectors.values() if s.round_id == round_id]
return sectors return sectors
@ -151,7 +152,7 @@ class Campaign:
return rnd return rnd
raise KeyError(f"Round {round_id} not found") 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) return list(self.rounds)
def add_round(self) -> Round: def add_round(self) -> Round:
@ -159,7 +160,7 @@ class Campaign:
self.rounds.append(round) self.rounds.append(round)
return 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) rnd = next((r for r in self.rounds if r.id == round_id), None)
if rnd: if rnd:
self.rounds.remove(rnd) self.rounds.remove(rnd)
@ -172,14 +173,6 @@ class Campaign:
return index return index
raise KeyError("Round not found in campaign") 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 # Choice methods
def create_choice(self, round_id: str, participant_id: str) -> Choice: def create_choice(self, round_id: str, participant_id: str) -> Choice:
@ -193,13 +186,13 @@ class Campaign:
priority_sector_id: str | None, priority_sector_id: str | None,
secondary_sector_id: str | None, secondary_sector_id: str | None,
comment: str | None, comment: str | None,
): ) -> None:
rnd = self.get_round(round_id) rnd = self.get_round(round_id)
rnd.update_choice( rnd.update_choice(
participant_id, priority_sector_id, secondary_sector_id, comment 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 = self.get_round(round_id)
rnd.remove_choice(participant_id) rnd.remove_choice(participant_id)
@ -219,7 +212,7 @@ class Campaign:
score: str | None, score: str | None,
victory_condition: str | None, victory_condition: str | None,
comment: str | None, comment: str | None,
): ) -> None:
rnd = self.get_round(round_id) rnd = self.get_round(round_id)
rnd.update_battle( rnd.update_battle(
sector_id, sector_id,
@ -231,7 +224,7 @@ class Campaign:
comment, 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 = self.get_round(round_id)
rnd.remove_battle(sector_id) rnd.remove_battle(sector_id)
@ -245,16 +238,16 @@ class CampaignParticipant:
self.leader: str | None = leader self.leader: str | None = leader
self.theme: str | None = theme self.theme: str | None = theme
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id 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 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 self.leader = new_faction
def set_theme(self, new_theme: str): def set_theme(self, new_theme: str) -> None:
self.theme = new_theme self.theme = new_theme
@ -276,20 +269,20 @@ class Sector:
self.mission: str | None = None self.mission: str | None = None
self.description: 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 self.id = new_id
def set_name(self, new_name: str): def set_name(self, new_name: str) -> None:
self.name = new_name 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 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 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 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 self.influence_objective_id = new_influence_id

View file

@ -1,3 +1,4 @@
from typing import Any, Dict, List
from pathlib import Path from pathlib import Path
import json import json
import shutil import shutil
@ -10,25 +11,25 @@ from warchron.model.round import Round, Choice, Battle
class Model: class Model:
def __init__(self): def __init__(self) -> None:
self.players: dict[str, Player] = {} self.players: Dict[str, Player] = {}
self.wars: dict[str, War] = {} self.wars: Dict[str, War] = {}
# File management methods # File management methods
def new(self): def new(self) -> None:
self.players.clear() self.players.clear()
self.wars.clear() self.wars.clear()
def load(self, path: Path): def load(self, path: Path) -> None:
self.players.clear() self.players.clear()
self.wars.clear() self.wars.clear()
self._load_data(path) self._load_data(path)
def save(self, path: Path): def save(self, path: Path) -> None:
self._save_data(path) 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: if not path.exists() or path.stat().st_size == 0:
return # Start empty return # Start empty
try: try:
@ -45,7 +46,7 @@ class Model:
except json.JSONDecodeError: except json.JSONDecodeError:
raise RuntimeError("Data file is corrupted") raise RuntimeError("Data file is corrupted")
def _save_data(self, path: Path): def _save_data(self, path: Path) -> None:
if path.exists(): if path.exists():
shutil.copy(path, path.with_suffix(".json.bak")) shutil.copy(path, path.with_suffix(".json.bak"))
data = { data = {
@ -58,31 +59,31 @@ class Model:
# Player methods # Player methods
def add_player(self, name): def add_player(self, name: str) -> Player:
player = Player(name) player = Player(name)
self.players[player.id] = player self.players[player.id] = player
return player return player
def get_player(self, id): def get_player(self, id: str) -> Player:
return self.players[id] return self.players[id]
def get_player_name(self, player_id: str) -> str: def get_player_name(self, player_id: str) -> str:
return self.players[player_id].name 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 = self.get_player(player_id)
player.set_name(name) player.set_name(name)
def get_all_players(self) -> list[Player]: def get_all_players(self) -> List[Player]:
return list(self.players.values()) 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 # TODO manage war_participants referring to it
del self.players[player_id] del self.players[player_id]
# War methods # War methods
def get_default_war_values(self) -> dict: def get_default_war_values(self) -> Dict[str, Any]:
return {"year": datetime.now().year} return {"year": datetime.now().year}
def add_war(self, name: str, year: int) -> War: def add_war(self, name: str, year: int) -> War:
@ -90,7 +91,7 @@ class Model:
self.wars[war.id] = war self.wars[war.id] = war
return war return war
def get_war(self, id) -> War: def get_war(self, id: str) -> War:
return self.wars[id] return self.wars[id]
def get_war_by_campaign(self, campaign_id: str) -> War: def get_war_by_campaign(self, campaign_id: str) -> War:
@ -100,20 +101,20 @@ class Model:
return war return war
raise KeyError(f"Campaign {campaign_id} not found in any 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 war in self.wars.values():
for camp in war.campaigns: for camp in war.campaigns:
for sect in camp.sectors.values(): for sect in camp.sectors.values():
if sect.id == sector_id: if sect.id == sector_id:
return camp return war
raise KeyError(f"Sector {sector_id} not found in any 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 war in self.wars.values():
for camp in war.campaigns: for camp in war.campaigns:
for rnd in camp.rounds: for rnd in camp.rounds:
if rnd.id == round_id: if rnd.id == round_id:
return camp return war
raise KeyError(f"Round {round_id} not found in any War") raise KeyError(f"Round {round_id} not found in any War")
def get_war_by_objective(self, objective_id: str) -> War: def get_war_by_objective(self, objective_id: str) -> War:
@ -136,15 +137,15 @@ class Model:
return war return war
raise KeyError(f"Participant {participant_id} not found") 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 = self.get_war(war_id)
war.set_name(name) war.set_name(name)
war.set_year(year) war.set_year(year)
def get_all_wars(self) -> list[War]: def get_all_wars(self) -> List[War]:
return list(self.wars.values()) 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] del self.wars[war_id]
# Objective methods # Objective methods
@ -153,24 +154,26 @@ class Model:
war = self.get_war(war_id) war = self.get_war(war_id)
return war.add_objective(name, description) 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 war in self.wars.values():
for obj in war.objectives.values(): for obj in war.objectives.values():
if obj.id == objective_id: if obj.id == objective_id:
return obj return obj
raise KeyError("Objective not found") 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 = self.get_war_by_objective(objective_id)
war.update_objective(objective_id, name=name, description=description) 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 = self.get_war_by_objective(objective_id)
war.remove_objective(objective_id) war.remove_objective(objective_id)
# War participant methods # 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) war = self.get_war(war_id)
return [ return [
player for player in self.players.values() if not war.has_player(player.id) 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) war = self.get_war(war_id)
return war.add_war_participant(player_id, faction) 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 war in self.wars.values():
for part in war.participants.values(): for part in war.participants.values():
if part.id == participant_id: if part.id == participant_id:
@ -192,17 +195,17 @@ class Model:
def get_player_from_war_participant(self, war_part: WarParticipant) -> Player: def get_player_from_war_participant(self, war_part: WarParticipant) -> Player:
return self.get_player(war_part.player_id) 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 = self.get_war_by_war_participant(participant_id)
war.update_war_participant(participant_id, faction=faction) 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 = self.get_war_by_war_participant(participant_id)
war.remove_war_participant(participant_id) war.remove_war_participant(participant_id)
# Campaign methods # 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) war = self.get_war(war_id)
return war.get_default_campaign_values() return war.get_default_campaign_values()
@ -210,7 +213,7 @@ class Model:
war = self.get_war(war_id) war = self.get_war(war_id)
return war.add_campaign(name, month) 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 war in self.wars.values():
for campaign in war.campaigns: for campaign in war.campaigns:
if campaign.id == campaign_id: if campaign.id == campaign_id:
@ -238,11 +241,11 @@ class Model:
return camp return camp
raise KeyError(f"Sector {sector_id} not found") 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 = self.get_war_by_campaign(campaign_id)
war.update_campaign(campaign_id, name=name, month=month) 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 = self.get_war_by_campaign(campaign_id)
war.remove_campaign(campaign_id) war.remove_campaign(campaign_id)
@ -260,7 +263,7 @@ class Model:
camp = self.get_campaign(campaign_id) camp = self.get_campaign(campaign_id)
return camp.add_sector(name, round_id, major_id, minor_id, influence_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 war in self.wars.values():
for camp in war.campaigns: for camp in war.campaigns:
for sect in camp.sectors.values(): for sect in camp.sectors.values():
@ -277,7 +280,7 @@ class Model:
major_id: str, major_id: str,
minor_id: str, minor_id: str,
influence_id: str, influence_id: str,
): ) -> None:
war = self.get_war_by_sector(sector_id) war = self.get_war_by_sector(sector_id)
war.update_sector( war.update_sector(
sector_id, sector_id,
@ -288,13 +291,13 @@ class Model:
influence_id=influence_id, 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 = self.get_campaign_by_sector(sector_id)
camp.remove_sector(sector_id) camp.remove_sector(sector_id)
# Campaign participant methods # 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) war = self.get_war_by_campaign(campaign_id)
return war.get_available_war_participants(campaign_id) return war.get_available_war_participants(campaign_id)
@ -309,7 +312,7 @@ class Model:
war_part = war.get_war_participant(participant_id) war_part = war.get_war_participant(participant_id)
return self.players[war_part.player_id].name 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 war in self.wars.values():
for camp in war.campaigns: for camp in war.campaigns:
for part in camp.participants.values(): for part in camp.participants.values():
@ -329,11 +332,11 @@ class Model:
*, *,
leader: str, leader: str,
theme: str, theme: str,
): ) -> None:
war = self.get_war_by_campaign_participant(participant_id) war = self.get_war_by_campaign_participant(participant_id)
war.update_campaign_participant(participant_id, leader=leader, theme=theme) 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 = self.get_war_by_campaign_participant(participant_id)
war.remove_campaign_participant(participant_id) war.remove_campaign_participant(participant_id)
@ -355,15 +358,15 @@ class Model:
camp = self.get_campaign_by_round(round_id) camp = self.get_campaign_by_round(round_id)
return camp.get_round_index(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) camp = self.get_campaign_by_round(round_id)
return [s for s in camp.sectors.values() if s.round_id == 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) camp = self.get_campaign_by_round(round_id)
return list(camp.participants.values()) 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 = self.get_war_by_round(round_id)
war.remove_round(round_id) war.remove_round(round_id)
@ -373,13 +376,6 @@ class Model:
war = self.get_war_by_round(round_id) war = self.get_war_by_round(round_id)
return war.create_choice(round_id, participant_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( def update_choice(
self, self,
round_id: str, round_id: str,
@ -387,13 +383,13 @@ class Model:
priority_sector_id: str | None, priority_sector_id: str | None,
secondary_sector_id: str | None, secondary_sector_id: str | None,
comment: str | None, comment: str | None,
): ) -> None:
war = self.get_war_by_round(round_id) war = self.get_war_by_round(round_id)
war.update_choice( war.update_choice(
round_id, 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:
war = self.get_war_by_round(round_id) war = self.get_war_by_round(round_id)
war.remove_choice(round_id, participant_id) war.remove_choice(round_id, participant_id)
@ -413,7 +409,7 @@ class Model:
score: str | None, score: str | None,
victory_condition: str | None, victory_condition: str | None,
comment: str | None, comment: str | None,
): ) -> None:
war = self.get_war_by_round(round_id) war = self.get_war_by_round(round_id)
war.update_battle( war.update_battle(
round_id, round_id,
@ -426,6 +422,6 @@ class Model:
comment, 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 = self.get_war_by_round(round_id)
war.remove_battle(round_id, sector_id) war.remove_battle(round_id, sector_id)

View file

View file

@ -1,22 +1,24 @@
from __future__ import annotations
from typing import Any, Dict
from uuid import uuid4 from uuid import uuid4
class Player: class Player:
def __init__(self, name): def __init__(self, name: str) -> None:
self.id = str(uuid4()) self.id: str = str(uuid4())
self.name = name self.name: str = name
def set_id(self, new_id): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_name(self, name): def set_name(self, name: str) -> None:
self.name = name self.name = name
def toDict(self): def toDict(self) -> Dict[str, Any]:
return {"id": self.id, "name": self.name} return {"id": self.id, "name": self.name}
@staticmethod @staticmethod
def fromDict(data: dict): def fromDict(data: Dict[str, Any]) -> Player:
play = Player(name=data["name"]) play = Player(name=data["name"])
play.set_id(data["id"]) play.set_id(data["id"])
return play return play

View file

@ -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)

View file

@ -1,21 +1,22 @@
from __future__ import annotations from __future__ import annotations
from uuid import uuid4 from uuid import uuid4
from typing import Any, Dict
class Round: class Round:
def __init__(self): def __init__(self) -> None:
self.id: str = str(uuid4()) self.id: str = str(uuid4())
self.choices: dict[str, Choice] = {} self.choices: Dict[str, Choice] = {}
self.battles: dict[str, Battle] = {} self.battles: Dict[str, Battle] = {}
self.is_over: bool = False self.is_over: bool = False
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_state(self, new_state: bool): def set_state(self, new_state: bool) -> None:
self.is_over = new_state self.is_over = new_state
def toDict(self): def toDict(self) -> Dict[str, Any]:
return { return {
"id": self.id, "id": self.id,
# "sectors" : self.sectors, # "sectors" : self.sectors,
@ -25,7 +26,7 @@ class Round:
} }
@staticmethod @staticmethod
def fromDict(data: dict): def fromDict(data: Dict[str, Any]) -> Round:
rnd = Round() rnd = Round()
rnd.set_id(data["id"]) rnd.set_id(data["id"])
# rnd.sectors = data.get("sectors", {}) # rnd.sectors = data.get("sectors", {})
@ -55,13 +56,14 @@ class Round:
priority_sector_id: str | None, priority_sector_id: str | None,
secondary_sector_id: str | None, secondary_sector_id: str | None,
comment: str | None, comment: str | None,
): ) -> None:
choice = self.get_choice(participant_id) choice = self.get_choice(participant_id)
choice.set_priority(priority_sector_id) if choice:
choice.set_secondary(secondary_sector_id) choice.set_priority(priority_sector_id)
choice.set_comment(comment) 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] del self.choices[participant_id]
# Battle methods # Battle methods
@ -84,16 +86,17 @@ class Round:
score: str | None, score: str | None,
victory_condition: str | None, victory_condition: str | None,
comment: str | None, comment: str | None,
): ) -> None:
bat = self.get_battle(sector_id) bat = self.get_battle(sector_id)
bat.set_player_1(player_1_id) if bat:
bat.set_player_2(player_2_id) bat.set_player_1(player_1_id)
bat.set_winner(winner_id) bat.set_player_2(player_2_id)
bat.set_score(score) bat.set_winner(winner_id)
bat.set_victory_condition(victory_condition) bat.set_score(score)
bat.set_comment(comment) 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] del self.battles[sector_id]
@ -113,16 +116,16 @@ class Choice:
) )
self.comment: str | None = None 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 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 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 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 self.comment = new_comment
@ -141,23 +144,23 @@ class Battle:
self.victory_condition: str | None = None self.victory_condition: str | None = None
self.comment: 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 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 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 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 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 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 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 self.comment = new_comment

View file

@ -1,34 +1,35 @@
from __future__ import annotations from __future__ import annotations
from uuid import uuid4 from uuid import uuid4
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List
from warchron.model.campaign import Campaign, Sector, CampaignParticipant from warchron.model.campaign import Campaign, Sector, CampaignParticipant
from warchron.model.round import Round, Choice, Battle from warchron.model.round import Round, Choice, Battle
class War: class War:
def __init__(self, name: str, year: int): def __init__(self, name: str, year: int) -> None:
self.id: str = str(uuid4()) self.id: str = str(uuid4())
self.name: str = name self.name: str = name
self.year: int = year self.year: int = year
self.participants: dict[str, WarParticipant] = {} self.participants: Dict[str, WarParticipant] = {}
self.objectives: dict[str, Objective] = {} self.objectives: Dict[str, Objective] = {}
self.campaigns: list[Campaign] = [] self.campaigns: List[Campaign] = []
self.is_over: bool = False self.is_over: bool = False
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_name(self, new_name: str): def set_name(self, new_name: str) -> None:
self.name = new_name self.name = new_name
def set_year(self, new_year: int): def set_year(self, new_year: int) -> None:
self.year = new_year self.year = new_year
def set_state(self, new_state: bool): def set_state(self, new_state: bool) -> None:
self.is_over = new_state self.is_over = new_state
def toDict(self): def toDict(self) -> Dict[str, Any]:
return { return {
"id": self.id, "id": self.id,
"name": self.name, "name": self.name,
@ -39,7 +40,7 @@ class War:
} }
@staticmethod @staticmethod
def fromDict(data: dict): def fromDict(data: Dict[str, Any]) -> War:
war = War(name=data["name"], year=data["year"]) war = War(name=data["name"], year=data["year"])
war.set_id(data["id"]) war.set_id(data["id"])
# war.participants = data.get("participants", {}) # war.participants = data.get("participants", {})
@ -58,7 +59,7 @@ class War:
def get_objective(self, id: str) -> Objective: def get_objective(self, id: str) -> Objective:
return self.objectives[id] return self.objectives[id]
def get_all_objectives(self) -> list[Objective]: def get_all_objectives(self) -> List[Objective]:
return list(self.objectives.values()) return list(self.objectives.values())
def get_objective_name(self, objective_id: str | None) -> str: def get_objective_name(self, objective_id: str | None) -> str:
@ -67,12 +68,14 @@ class War:
obj = self.objectives.get(objective_id) obj = self.objectives.get(objective_id)
return obj.name if obj else "" 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 = self.get_objective(objective_id)
obj.set_name(name) obj.set_name(name)
obj.set_description(description) 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 # TODO manage sectors referring to it
del self.objectives[objective_id] del self.objectives[objective_id]
@ -97,15 +100,15 @@ class War:
def get_war_participant(self, id: str) -> WarParticipant: def get_war_participant(self, id: str) -> WarParticipant:
return self.participants[id] 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()) 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) part = self.get_war_participant(player_id)
# Can't change referred Model.players # Can't change referred Model.players
part.set_faction(faction) 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 # TODO manage campaign_participants referring to it
del self.participants[player_id] del self.participants[player_id]
@ -114,7 +117,7 @@ class War:
def has_campaign(self, campaign_id: str) -> bool: def has_campaign(self, campaign_id: str) -> bool:
return any(c.id == campaign_id for c in self.campaigns) 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} return {"month": datetime.now().month}
def add_campaign(self, name: str, month: int | None = None) -> Campaign: def add_campaign(self, name: str, month: int | None = None) -> Campaign:
@ -150,15 +153,15 @@ class War:
return camp return camp
raise KeyError(f"Participant {participant_id} not found in any Campaign") 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 = self.get_campaign(campaign_id)
camp.set_name(name) camp.set_name(name)
camp.set_month(month) camp.set_month(month)
def get_all_campaigns(self) -> list[Campaign]: def get_all_campaigns(self) -> List[Campaign]:
return list(self.campaigns) 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) camp = self.get_campaign(campaign_id)
self.campaigns.remove(camp) self.campaigns.remove(camp)
@ -166,7 +169,7 @@ class War:
def add_sector( def add_sector(
self, self,
campaign_id, campaign_id: str,
name: str, name: str,
round_id: str, round_id: str,
major_id: str, major_id: str,
@ -176,8 +179,12 @@ class War:
camp = self.get_campaign(campaign_id) camp = self.get_campaign(campaign_id)
return camp.add_sector(name, round_id, major_id, minor_id, influence_id) return camp.add_sector(name, round_id, major_id, minor_id, influence_id)
def get_sector(self, id: str) -> Sector: def get_sector(self, sector_id: str) -> Sector:
return self.sectors[id] 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( def update_sector(
self, self,
@ -188,7 +195,7 @@ class War:
major_id: str, major_id: str,
minor_id: str, minor_id: str,
influence_id: str, influence_id: str,
): ) -> None:
camp = self.get_campaign_by_sector(sector_id) camp = self.get_campaign_by_sector(sector_id)
camp.update_sector( camp.update_sector(
sector_id, sector_id,
@ -199,13 +206,13 @@ class War:
influence_id=influence_id, 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 = self.get_campaign_by_sector(sector_id)
camp.remove_sector(sector_id) camp.remove_sector(sector_id)
# Campaign participant methods # 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) camp = self.get_campaign(campaign_id)
return [ return [
part part
@ -219,8 +226,8 @@ class War:
camp = self.get_campaign(campaign_id) camp = self.get_campaign(campaign_id)
return camp.add_campaign_participant(participant_id, leader, theme) return camp.add_campaign_participant(participant_id, leader, theme)
def get_campaign_participant(self, participant_id) -> CampaignParticipant: def get_campaign_participant(self, participant_id: str) -> CampaignParticipant:
for camp in self.campaigns.values(): for camp in self.campaigns:
for part in camp.participants.values(): for part in camp.participants.values():
if part.id == participant_id: if part.id == participant_id:
return part return part
@ -228,11 +235,11 @@ class War:
def update_campaign_participant( def update_campaign_participant(
self, participant_id: str, *, leader: str, theme: str self, participant_id: str, *, leader: str, theme: str
): ) -> None:
camp = self.get_campaign_by_campaign_participant(participant_id) camp = self.get_campaign_by_campaign_participant(participant_id)
camp.update_campaign_participant(participant_id, leader=leader, theme=theme) 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 = self.get_campaign_by_campaign_participant(participant_id)
camp.remove_campaign_participant(participant_id) camp.remove_campaign_participant(participant_id)
@ -246,7 +253,7 @@ class War:
camp = self.get_campaign(campaign_id) camp = self.get_campaign(campaign_id)
return camp.add_round() 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 = self.get_campaign_by_round(round_id)
camp.remove_round(round_id) camp.remove_round(round_id)
@ -263,13 +270,13 @@ class War:
priority_sector_id: str | None, priority_sector_id: str | None,
secondary_sector_id: str | None, secondary_sector_id: str | None,
comment: str | None, comment: str | None,
): ) -> None:
camp = self.get_campaign_by_round(round_id) camp = self.get_campaign_by_round(round_id)
camp.update_choice( 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 = self.get_campaign_by_round(round_id)
camp.remove_choice(round_id, participant_id) camp.remove_choice(round_id, participant_id)
@ -289,9 +296,10 @@ class War:
score: str | None, score: str | None,
victory_condition: str | None, victory_condition: str | None,
comment: str | None, comment: str | None,
): ) -> None:
camp = self.get_campaign_by_round(round_id) camp = self.get_campaign_by_round(round_id)
camp.update_battle( camp.update_battle(
round_id,
sector_id, sector_id,
player_1_id, player_1_id,
player_2_id, player_2_id,
@ -301,7 +309,7 @@ class War:
comment, 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 = self.get_campaign_by_round(round_id)
camp.remove_battle(round_id, sector_id) camp.remove_battle(round_id, sector_id)
@ -312,13 +320,13 @@ class Objective:
self.name: str = name self.name: str = name
self.description: str = description self.description: str = description
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_name(self, new_name: str): def set_name(self, new_name: str) -> None:
self.name = new_name self.name = new_name
def set_description(self, new_description: str): def set_description(self, new_description: str) -> None:
self.description = new_description self.description = new_description
@ -328,11 +336,11 @@ class WarParticipant:
self.player_id: str = player_id # ref to WarModel.players self.player_id: str = player_id # ref to WarModel.players
self.faction: str = faction self.faction: str = faction
def set_id(self, new_id: str): def set_id(self, new_id: str) -> None:
self.id = new_id self.id = new_id
def set_player(self, new_player: str): def set_player(self, new_player: str) -> None:
self.player_id = new_player self.player_id = new_player
def set_faction(self, new_faction: str): def set_faction(self, new_faction: str) -> None:
self.faction = new_faction self.faction = new_faction

View file

View file

View file

@ -1,20 +1,42 @@
from typing import cast, Callable, List
from pathlib import Path from pathlib import Path
import calendar import calendar
from PyQt6 import QtWidgets from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt, QPoint
from PyQt6.QtWidgets import QDialog, QFileDialog, QTreeWidgetItem, QMenu from PyQt6.QtWidgets import (
QWidget,
QDialog,
QFileDialog,
QTreeWidgetItem,
QMenu,
QComboBox,
)
from PyQt6.QtGui import QCloseEvent from PyQt6.QtGui import QCloseEvent
from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType 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_main_window import Ui_MainWindow
from warchron.view.ui.ui_player_dialog import Ui_playerDialog 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_war_dialog import Ui_warDialog
from warchron.view.ui.ui_campaign_dialog import Ui_campaignDialog 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_objective_dialog import Ui_objectiveDialog
from warchron.view.ui.ui_war_participant_dialog import Ui_warParticipantDialog 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_sector_dialog import Ui_sectorDialog
from warchron.view.ui.ui_choices_dialog import Ui_choicesDialog from warchron.view.ui.ui_choices_dialog import Ui_choicesDialog
from warchron.view.ui.ui_battle_result_dialog import Ui_battleResultDialog 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... # utils...
def select_if_exists(combo, value): def select_if_exists(combo: QComboBox, value: str | None) -> None:
if value is None: if value is None:
return return
idx = combo.findData(value) idx = combo.findData(value)
@ -30,30 +52,32 @@ def select_if_exists(combo, value):
combo.setCurrentIndex(idx) combo.setCurrentIndex(idx)
def format_war_label(war) -> str: def format_war_label(war: WarDTO) -> str:
return f"{war.name} ({war.year})" 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]})" 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: if index is None:
return "" return ""
return f"Round {index}" return f"Round {index}"
class View(QtWidgets.QMainWindow, Ui_MainWindow): class View(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None): def __init__(self, parent: QWidget | None = None) -> None:
super(View, self).__init__(parent) super(View, self).__init__(parent)
self.setupUi(self) self.setupUi(self) # type: ignore
self.on_close_callback = None self.on_close_callback: Callable[[], bool] | None = None
self.on_selection_changed = None self.on_tree_selection_changed: (
self.on_add_campaign = None Callable[[TreeSelection | None], None] | None
self.on_add_round = None ) = None
self.on_edit_item = None self.on_add_campaign: Callable[[], None] | None = None
self.on_delete_item = 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.splitter.setSizes([200, 800])
self.show_details(None) self.show_details(None)
self.playersTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.playersTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
@ -98,17 +122,17 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
self._on_battles_table_context_menu 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: if not self.on_tree_selection_changed:
return return
if not current: if not current:
self.on_tree_selection_changed(None) self.on_tree_selection_changed(None)
return return
self.on_tree_selection_changed( self.on_tree_selection_changed(
{ TreeSelection(
"type": current.data(0, ROLE_TYPE), type=current.data(0, ROLE_TYPE),
"id": current.data(0, ROLE_ID), id=current.data(0, ROLE_ID),
} )
) )
def get_current_tab(self) -> str: def get_current_tab(self) -> str:
@ -121,7 +145,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
# General popups # 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: if self.on_close_callback:
proceed = self.on_close_callback() proceed = self.on_close_callback()
if not proceed: if not proceed:
@ -143,7 +169,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
# Players view # 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) item = self.playersTable.itemAt(pos)
if not item: if not item:
return return
@ -155,13 +181,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.PLAYER, player_id) self.on_edit_item(ItemType.PLAYER, player_id)
elif action == delete_action and self.on_delete_item: elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.PLAYER, player_id) 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 = self.playersTable
table.setRowCount(len(players)) table.setRowCount(len(players))
for row, player in enumerate(players): for row, player in enumerate(players):
@ -172,21 +200,21 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
# Wars view # Wars view
def _on_add_campaign_clicked(self): def _on_add_campaign_clicked(self) -> None:
if self.on_add_campaign: if self.on_add_campaign:
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: if self.on_add_round:
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) 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) 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) item = self.warsTree.itemAt(pos)
if not item: if not item:
return return
@ -197,7 +225,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
if item_type != ItemType.ROUND: if item_type != ItemType.ROUND:
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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 action == edit_action:
if self.on_edit_item: if self.on_edit_item:
self.on_edit_item(item_type, item_id) self.on_edit_item(item_type, item_id)
@ -205,7 +235,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
if self.on_delete_item: if self.on_delete_item:
self.on_delete_item(item_type, item_id) 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 = self.warsTree
tree.clear() tree.clear()
tree.setColumnCount(1) tree.setColumnCount(1)
@ -221,15 +251,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
camp_item.setData(0, ROLE_ID, camp.id) camp_item.setData(0, ROLE_ID, camp.id)
war_item.addChild(camp_item) war_item.addChild(camp_item)
for index, rnd in enumerate(camp.get_all_rounds(), start=1): 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_TYPE, ItemType.ROUND)
rnd_item.setData(0, ROLE_ID, rnd.id) rnd_item.setData(0, ROLE_ID, rnd.id)
camp_item.addChild(rnd_item) camp_item.addChild(rnd_item)
tree.currentItemChanged.connect(self._emit_selection_changed) tree.currentItemChanged.connect(self._emit_selection_changed)
tree.expandAll() tree.expandAll()
def select_tree_item(self, *, item_type: ItemType, item_id: str): def select_tree_item(self, *, item_type: ItemType, item_id: str) -> None:
def walk(item: QTreeWidgetItem): def walk(item: QTreeWidgetItem) -> bool:
if ( if (
item.data(0, ROLE_TYPE) == item_type item.data(0, ROLE_TYPE) == item_type
and item.data(0, ROLE_ID) == item_id and item.data(0, ROLE_ID) == item_id
@ -237,21 +267,25 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
self.warsTree.setCurrentItem(item) self.warsTree.setCurrentItem(item)
return True return True
for i in range(item.childCount()): 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 True
return False return False
for i in range(self.warsTree.topLevelItemCount()): 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 return
def get_selected_tree_item(self): def get_selected_tree_item(self) -> dict[str, str] | None:
item = self.warsTree.currentItem() item = self.warsTree.currentItem()
if not item: if not item:
return None return None
return {"type": item.data(0, ROLE_TYPE), "id": item.data(0, ROLE_ID)} 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: if item_type == ItemType.WAR:
self.selectedDetailsStack.setCurrentWidget(self.pageWar) self.selectedDetailsStack.setCurrentWidget(self.pageWar)
elif item_type == ItemType.CAMPAIGN: elif item_type == ItemType.CAMPAIGN:
@ -263,7 +297,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
# War page # 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) item = self.objectivesTable.itemAt(pos)
if not item: if not item:
return return
@ -275,13 +309,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.OBJECTIVE, objective_id) self.on_edit_item(ItemType.OBJECTIVE, objective_id)
elif action == delete_action and self.on_delete_item: elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.OBJECTIVE, objective_id) 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) item = self.warParticipantsTable.itemAt(pos)
if not item: if not item:
return return
@ -293,17 +329,19 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.WAR_PARTICIPANT, participant_id) self.on_edit_item(ItemType.WAR_PARTICIPANT, participant_id)
elif action == delete_action and self.on_delete_item: elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.WAR_PARTICIPANT, participant_id) 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.warName.setText(name)
self.warYear.setText(str(year)) 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 = self.objectivesTable
table.clearContents() table.clearContents()
table.setRowCount(len(objectives)) table.setRowCount(len(objectives))
@ -315,21 +353,21 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, 1, desc_item) table.setItem(row, 1, desc_item)
table.resizeColumnsToContents() 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 = self.warParticipantsTable
table.clearContents() table.clearContents()
table.setRowCount(len(participants)) table.setRowCount(len(participants))
for row, (name, faction, pid) in enumerate(participants): for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(name) name_item = QtWidgets.QTableWidgetItem(part.player_name)
fact_item = QtWidgets.QTableWidgetItem(faction) fact_item = QtWidgets.QTableWidgetItem(part.faction)
name_item.setData(Qt.ItemDataRole.UserRole, pid) name_item.setData(Qt.ItemDataRole.UserRole, part.id)
table.setItem(row, 0, name_item) table.setItem(row, 0, name_item)
table.setItem(row, 1, fact_item) table.setItem(row, 1, fact_item)
table.resizeColumnsToContents() table.resizeColumnsToContents()
# Campaign page # 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) item = self.sectorsTable.itemAt(pos)
if not item: if not item:
return return
@ -341,13 +379,15 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.SECTOR, sector_id) self.on_edit_item(ItemType.SECTOR, sector_id)
elif action == delete_action and self.on_delete_item: elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.SECTOR, sector_id) 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) item = self.campaignParticipantsTable.itemAt(pos)
if not item: if not item:
return return
@ -359,33 +399,31 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") edit_action = menu.addAction("Edit")
delete_action = menu.addAction("Delete") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id) self.on_edit_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id)
elif action == delete_action and self.on_delete_item: elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id) 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.campaignName.setText(name)
self.campaignMonth.setText(calendar.month_name[month]) self.campaignMonth.setText(calendar.month_name[month])
def display_campaign_sectors( def display_campaign_sectors(self, sectors: List[SectorDTO]) -> None:
self, sectors: list[tuple[str, str, str, str, str, str]]
):
table = self.sectorsTable table = self.sectorsTable
table.clearContents() table.clearContents()
table.setRowCount(len(sectors)) table.setRowCount(len(sectors))
for row, (name, round_index, major, minor, influence, pid) in enumerate( for row, sect in enumerate(sectors):
sectors name_item = QtWidgets.QTableWidgetItem(sect.name)
):
name_item = QtWidgets.QTableWidgetItem(name)
round_item = QtWidgets.QTableWidgetItem( round_item = QtWidgets.QTableWidgetItem(
format_round_label(None, round_index) format_round_label(sect.round_index)
) )
major_item = QtWidgets.QTableWidgetItem(major) major_item = QtWidgets.QTableWidgetItem(sect.major)
minor_item = QtWidgets.QTableWidgetItem(minor) minor_item = QtWidgets.QTableWidgetItem(sect.minor)
influence_item = QtWidgets.QTableWidgetItem(influence) influence_item = QtWidgets.QTableWidgetItem(sect.influence)
name_item.setData(Qt.ItemDataRole.UserRole, pid) name_item.setData(Qt.ItemDataRole.UserRole, sect.id)
table.setItem(row, 0, name_item) table.setItem(row, 0, name_item)
table.setItem(row, 1, round_item) table.setItem(row, 1, round_item)
table.setItem(row, 2, major_item) table.setItem(row, 2, major_item)
@ -394,16 +432,16 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.resizeColumnsToContents() table.resizeColumnsToContents()
def display_campaign_participants( def display_campaign_participants(
self, participants: list[tuple[str, str, str, str]] self, participants: List[CampaignParticipantDTO]
): ) -> None:
table = self.campaignParticipantsTable table = self.campaignParticipantsTable
table.clearContents() table.clearContents()
table.setRowCount(len(participants)) table.setRowCount(len(participants))
for row, (name, leader, theme, pid) in enumerate(participants): for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(name) name_item = QtWidgets.QTableWidgetItem(part.player_name)
lead_item = QtWidgets.QTableWidgetItem(leader) lead_item = QtWidgets.QTableWidgetItem(part.leader)
theme_item = QtWidgets.QTableWidgetItem(theme) theme_item = QtWidgets.QTableWidgetItem(part.theme)
name_item.setData(Qt.ItemDataRole.UserRole, pid) name_item.setData(Qt.ItemDataRole.UserRole, part.id)
table.setItem(row, 0, name_item) table.setItem(row, 0, name_item)
table.setItem(row, 1, lead_item) table.setItem(row, 1, lead_item)
table.setItem(row, 2, theme_item) table.setItem(row, 2, theme_item)
@ -411,7 +449,7 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
# Round page # 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) item = self.choicesTable.itemAt(pos)
if not item: if not item:
return return
@ -424,11 +462,13 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
return return
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.CHOICE, choice_id) 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) item = self.battlesTable.itemAt(pos)
if not item: if not item:
return return
@ -441,38 +481,38 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
return return
menu = QMenu(self) menu = QMenu(self)
edit_action = menu.addAction("Edit") 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: if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.BATTLE, battle_id) 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}") 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 = self.choicesTable
table.clearContents() table.clearContents()
table.setRowCount(len(participants)) table.setRowCount(len(participants))
for row, (participant, priority, secondary, choice_id) in enumerate( for row, choice in enumerate(participants):
participants participant_item = QtWidgets.QTableWidgetItem(choice.participant_name)
): priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector)
participant_item = QtWidgets.QTableWidgetItem(participant) secondary_item = QtWidgets.QTableWidgetItem(choice.secondary_sector)
priority_item = QtWidgets.QTableWidgetItem(priority) participant_item.setData(Qt.ItemDataRole.UserRole, choice.id)
secondary_item = QtWidgets.QTableWidgetItem(secondary)
participant_item.setData(Qt.ItemDataRole.UserRole, choice_id)
table.setItem(row, 0, participant_item) table.setItem(row, 0, participant_item)
table.setItem(row, 1, priority_item) table.setItem(row, 1, priority_item)
table.setItem(row, 2, secondary_item) table.setItem(row, 2, secondary_item)
table.resizeColumnsToContents() 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 = self.battlesTable
table.clearContents() table.clearContents()
table.setRowCount(len(sectors)) table.setRowCount(len(sectors))
for row, (sector, player_1, player_2, battle_id) in enumerate(sectors): for row, battle in enumerate(sectors):
sector_item = QtWidgets.QTableWidgetItem(sector) sector_item = QtWidgets.QTableWidgetItem(battle.sector_name)
player_1_item = QtWidgets.QTableWidgetItem(player_1) player_1_item = QtWidgets.QTableWidgetItem(battle.player_1)
player_2_item = QtWidgets.QTableWidgetItem(player_2) player_2_item = QtWidgets.QTableWidgetItem(battle.player_2)
sector_item.setData(Qt.ItemDataRole.UserRole, battle_id) sector_item.setData(Qt.ItemDataRole.UserRole, battle.id)
table.setItem(row, 0, sector_item) table.setItem(row, 0, sector_item)
table.setItem(row, 1, player_1_item) table.setItem(row, 1, player_1_item)
table.setItem(row, 2, player_2_item) table.setItem(row, 2, player_2_item)
@ -480,10 +520,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
class PlayerDialog(QDialog): 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) super().__init__(parent)
self.ui = Ui_playerDialog() self.ui: Ui_playerDialog = Ui_playerDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
self.ui.playerName.setText(default_name) self.ui.playerName.setText(default_name)
def get_player_name(self) -> str: def get_player_name(self) -> str:
@ -492,11 +534,14 @@ class PlayerDialog(QDialog):
class WarDialog(QDialog): class WarDialog(QDialog):
def __init__( 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) super().__init__(parent)
self.ui = Ui_warDialog() self.ui: Ui_warDialog = Ui_warDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
self.ui.warName.setText(default_name) self.ui.warName.setText(default_name)
if default_year is not None: if default_year is not None:
self.ui.warYear.setValue(default_year) self.ui.warYear.setValue(default_year)
@ -510,11 +555,14 @@ class WarDialog(QDialog):
class CampaignDialog(QDialog): class CampaignDialog(QDialog):
def __init__( 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) super().__init__(parent)
self.ui = Ui_campaignDialog() self.ui: Ui_campaignDialog = Ui_campaignDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
self.ui.campaignName.setText(default_name) self.ui.campaignName.setText(default_name)
if default_month is not None: if default_month is not None:
self.ui.campaignMonth.setValue(default_month) self.ui.campaignMonth.setValue(default_month)
@ -527,10 +575,16 @@ class CampaignDialog(QDialog):
class ObjectiveDialog(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) super().__init__(parent)
self.ui = Ui_objectiveDialog() self.ui: Ui_objectiveDialog = Ui_objectiveDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
self.ui.objectiveName.setText(default_name) self.ui.objectiveName.setText(default_name)
self.ui.objectiveDescription.setPlainText(default_description) self.ui.objectiveDescription.setPlainText(default_description)
@ -544,16 +598,16 @@ class ObjectiveDialog(QDialog):
class WarParticipantDialog(QDialog): class WarParticipantDialog(QDialog):
def __init__( def __init__(
self, self,
parent=None, parent: QWidget | None = None,
*, *,
players: list, players: List[ParticipantOption],
default_player_id=None, default_player_id: str | None = None,
default_faction="", default_faction: str | None = "",
editable_player=True, editable_player: bool = True,
): ):
super().__init__(parent) super().__init__(parent)
self.ui = Ui_warParticipantDialog() self.ui: Ui_warParticipantDialog = Ui_warParticipantDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
for player in players: for player in players:
self.ui.playerComboBox.addItem(player.name, player.id) self.ui.playerComboBox.addItem(player.name, player.id)
select_if_exists(self.ui.playerComboBox, default_player_id) select_if_exists(self.ui.playerComboBox, default_player_id)
@ -561,7 +615,7 @@ class WarParticipantDialog(QDialog):
self.ui.faction.setText(default_faction) self.ui.faction.setText(default_faction)
def get_player_id(self) -> str: def get_player_id(self) -> str:
return self.ui.playerComboBox.currentData() return cast(str, self.ui.playerComboBox.currentData())
def get_participant_faction(self) -> str: def get_participant_faction(self) -> str:
return self.ui.faction.text().strip() return self.ui.faction.text().strip()
@ -570,17 +624,17 @@ class WarParticipantDialog(QDialog):
class CampaignParticipantDialog(QDialog): class CampaignParticipantDialog(QDialog):
def __init__( def __init__(
self, self,
parent=None, parent: QWidget | None = None,
*, *,
participants: list[ParticipantOption], participants: List[ParticipantOption],
default_participant_id=None, default_participant_id: str | None = None,
default_leader="", default_leader: str | None = "",
default_theme="", default_theme: str | None = "",
editable_player=True, editable_player: bool = True,
): ) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_campaignParticipantDialog() self.ui: Ui_campaignParticipantDialog = Ui_campaignParticipantDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
for part in participants: for part in participants:
self.ui.playerComboBox.addItem(part.name, part.id) self.ui.playerComboBox.addItem(part.name, part.id)
select_if_exists(self.ui.playerComboBox, default_participant_id) select_if_exists(self.ui.playerComboBox, default_participant_id)
@ -589,7 +643,7 @@ class CampaignParticipantDialog(QDialog):
self.ui.theme.setText(default_theme) self.ui.theme.setText(default_theme)
def get_player_id(self) -> str: def get_player_id(self) -> str:
return self.ui.playerComboBox.currentData() return cast(str, self.ui.playerComboBox.currentData())
def get_participant_leader(self) -> str: def get_participant_leader(self) -> str:
return self.ui.leader.text().strip() return self.ui.leader.text().strip()
@ -601,25 +655,25 @@ class CampaignParticipantDialog(QDialog):
class SectorDialog(QDialog): class SectorDialog(QDialog):
def __init__( def __init__(
self, self,
parent=None, parent: QWidget | None = None,
*, *,
default_name="", default_name: str = "",
rounds: list, rounds: List[RoundDTO],
default_round_id=None, default_round_id: str | None = None,
objectives: list, objectives: List[ObjectiveDTO],
default_major_id=None, default_major_id: str | None = None,
default_minor_id=None, default_minor_id: str | None = None,
default_influence_id=None, default_influence_id: str | None = None,
): ) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_sectorDialog() self.ui: Ui_sectorDialog = Ui_sectorDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
self.ui.majorComboBox.addItem("(none)", None) self.ui.majorComboBox.addItem("(none)", None)
self.ui.minorComboBox.addItem("(none)", None) self.ui.minorComboBox.addItem("(none)", None)
self.ui.influenceComboBox.addItem("(none)", None) self.ui.influenceComboBox.addItem("(none)", None)
self.ui.sectorName.setText(default_name) self.ui.sectorName.setText(default_name)
for index, rnd in enumerate(rounds, start=1): 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) select_if_exists(self.ui.roundComboBox, default_round_id)
for obj in objectives: for obj in objectives:
self.ui.majorComboBox.addItem(obj.name, obj.id) self.ui.majorComboBox.addItem(obj.name, obj.id)
@ -633,33 +687,33 @@ class SectorDialog(QDialog):
return self.ui.sectorName.text().strip() return self.ui.sectorName.text().strip()
def get_round_id(self) -> str: def get_round_id(self) -> str:
return self.ui.roundComboBox.currentData() return cast(str, self.ui.roundComboBox.currentData())
def get_major_id(self) -> str: def get_major_id(self) -> str:
return self.ui.majorComboBox.currentData() return cast(str, self.ui.majorComboBox.currentData())
def get_minor_id(self) -> str: def get_minor_id(self) -> str:
return self.ui.minorComboBox.currentData() return cast(str, self.ui.minorComboBox.currentData())
def get_influence_id(self) -> str: def get_influence_id(self) -> str:
return self.ui.influenceComboBox.currentData() return cast(str, self.ui.influenceComboBox.currentData())
class ChoicesDialog(QDialog): class ChoicesDialog(QDialog):
def __init__( def __init__(
self, self,
parent=None, parent: QWidget | None = None,
*, *,
participants: list, participants: List[ParticipantOption],
default_participant_id=None, default_participant_id: str | None = None,
sectors: list, sectors: List[SectorDTO],
default_priority_id=None, default_priority_id: str | None = None,
default_secondary_id=None, default_secondary_id: str | None = None,
default_comment=None, default_comment: str | None = None,
): ) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_choicesDialog() self.ui: Ui_choicesDialog = Ui_choicesDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
for part in participants: for part in participants:
self.ui.playerComboBox.addItem(part.name, part.id) self.ui.playerComboBox.addItem(part.name, part.id)
select_if_exists(self.ui.playerComboBox, default_participant_id) select_if_exists(self.ui.playerComboBox, default_participant_id)
@ -674,13 +728,13 @@ class ChoicesDialog(QDialog):
self.ui.choiceComment.setPlainText(default_comment) self.ui.choiceComment.setPlainText(default_comment)
def get_participant_id(self) -> str: def get_participant_id(self) -> str:
return self.ui.playerComboBox.currentData() return cast(str, self.ui.playerComboBox.currentData())
def get_priority_id(self) -> str: def get_priority_id(self) -> str:
return self.ui.priorityComboBox.currentData() return cast(str, self.ui.priorityComboBox.currentData())
def get_secondary_id(self) -> str: def get_secondary_id(self) -> str:
return self.ui.secondaryComboBox.currentData() return cast(str, self.ui.secondaryComboBox.currentData())
def get_comment(self) -> str: def get_comment(self) -> str:
return self.ui.choiceComment.toPlainText().strip() return self.ui.choiceComment.toPlainText().strip()
@ -689,21 +743,21 @@ class ChoicesDialog(QDialog):
class BattlesDialog(QDialog): class BattlesDialog(QDialog):
def __init__( def __init__(
self, self,
parent=None, parent: QWidget | None = None,
*, *,
sectors: list, sectors: List[SectorDTO],
default_sector_id=None, default_sector_id: str | None = None,
players: list, players: List[ParticipantOption],
default_player_1_id=None, default_player_1_id: str | None = None,
default_player_2_id=None, default_player_2_id: str | None = None,
default_winner_id=None, default_winner_id: str | None = None,
default_score=None, default_score: str | None = None,
default_victory_condition=None, default_victory_condition: str | None = None,
default_comment=None, default_comment: str | None = None,
): ) -> None:
super().__init__(parent) super().__init__(parent)
self.ui = Ui_battleResultDialog() self.ui: Ui_battleResultDialog = Ui_battleResultDialog()
self.ui.setupUi(self) self.ui.setupUi(self) # type: ignore
for sect in sectors: for sect in sectors:
self.ui.sectorComboBox.addItem(sect.name, sect.id) self.ui.sectorComboBox.addItem(sect.name, sect.id)
select_if_exists(self.ui.sectorComboBox, default_sector_id) select_if_exists(self.ui.sectorComboBox, default_sector_id)
@ -726,16 +780,16 @@ class BattlesDialog(QDialog):
self.ui.battleComment.setPlainText(default_comment) self.ui.battleComment.setPlainText(default_comment)
def get_sector_id(self) -> str: 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: 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: 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: def get_winner_id(self) -> str:
return self.ui.winnerComboBox.currentData() return cast(str, self.ui.winnerComboBox.currentData())
def get_score(self) -> str: def get_score(self) -> str:
return self.ui.score.text().strip() return self.ui.score.text().strip()