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
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install -e .
```
### Run

View file

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

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

View file

@ -1,3 +1,4 @@
from typing import List
from dataclasses import dataclass
@ -5,3 +6,90 @@ from dataclasses import dataclass
class ParticipantOption:
id: str
name: str
@dataclass(frozen=True, slots=True)
class TreeSelection:
type: str
id: str
@dataclass
class WarDTO:
id: str
name: str
year: int
_campaigns: List["CampaignDTO"] | None = None
def get_all_campaigns(self) -> List["CampaignDTO"]:
return self._campaigns or []
@dataclass(frozen=True, slots=True)
class ObjectiveDTO:
id: str
name: str
description: str
@dataclass(frozen=True, slots=True)
class WarParticipantDTO:
id: str
player_name: str
faction: str
@dataclass
class CampaignDTO:
id: str
name: str
month: int
_rounds: List["RoundDTO"] | None = None
def get_all_rounds(self) -> List["RoundDTO"]:
return self._rounds or []
@dataclass(frozen=True, slots=True)
class CampaignParticipantDTO:
id: str
player_name: str
leader: str
theme: str
@dataclass(frozen=True, slots=True)
class SectorDTO:
id: str
name: str
round_index: int
major: str
minor: str
influence: str
@dataclass
class RoundDTO:
id: str
index: int
@dataclass(frozen=True, slots=True)
class ChoiceDTO:
id: str
participant_name: str
priority_sector: str
secondary_sector: str
comment: str | None
@dataclass(frozen=True, slots=True)
class BattleDTO:
id: str
sector_name: str
player_1: str
player_2: str
winner: str | None
score: str | None
victory_condition: str | None
comment: str | None

View file

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

View file

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

View file

View file

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

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

View file

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

View file

View file

View file

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