Compare commits

..

4 commits

Author SHA1 Message Date
Maxime Réaux
df846b8e4b manage deleted refered player, participant, objective, sector 2026-02-05 16:17:18 +01:00
Maxime Réaux
7afbb5ea1d split edit methods in controller 2026-02-05 10:57:22 +01:00
Maxime Réaux
4593400fd4 fix round listing from different wars 2026-02-05 10:19:14 +01:00
Maxime Réaux
019e62565f split dialogs and war/campaign/round classes to files 2026-02-05 08:42:38 +01:00
23 changed files with 1028 additions and 696 deletions

View file

@ -4,6 +4,11 @@ from pathlib import Path
from PyQt6.QtWidgets import QMessageBox, QDialog
from warchron.model.model import Model
from warchron.model.exception import (
DeletionForbidden,
DeletionRequiresConfirmation,
UpdateRequiresConfirmation,
)
from warchron.view.view import View
from warchron.constants import ItemType, RefreshScope
from warchron.controller.dtos import (
@ -19,17 +24,15 @@ from warchron.controller.dtos import (
ChoiceDTO,
BattleDTO,
)
from warchron.view.view import (
PlayerDialog,
WarDialog,
CampaignDialog,
ObjectiveDialog,
WarParticipantDialog,
CampaignParticipantDialog,
SectorDialog,
ChoicesDialog,
BattlesDialog,
)
from warchron.view.player_dialog import PlayerDialog
from warchron.view.war_dialog import WarDialog
from warchron.view.campaign_dialog import CampaignDialog
from warchron.view.objective_dialog import ObjectiveDialog
from warchron.view.war_participant_dialog import WarParticipantDialog
from warchron.view.campaign_participant_dialog import CampaignParticipantDialog
from warchron.view.sector_dialog import SectorDialog
from warchron.view.choices_dialog import ChoicesDialog
from warchron.view.battles_dialog import BattlesDialog
class Controller:
@ -364,134 +367,49 @@ class Controller:
self.view.select_tree_item(item_type=item_type, item_id=item_id)
def edit_item(self, item_type: str, item_id: str) -> None:
if item_type == ItemType.PLAYER:
play = self.model.get_player(item_id)
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)
try:
if item_type == ItemType.PLAYER:
self.edit_player(item_id)
self.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR:
war = self.model.get_war(item_id)
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)
elif item_type == ItemType.WAR:
self.edit_war(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war.id
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=item_id
)
elif item_type == ItemType.CAMPAIGN:
camp = self.model.get_campaign(item_id)
camp_dialog = CampaignDialog(
self.view, default_name=camp.name, default_month=camp.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)
elif item_type == ItemType.CAMPAIGN:
self.edit_campaign(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=camp.id
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=item_id
)
elif item_type == ItemType.OBJECTIVE:
obj = self.model.get_objective(item_id)
obj_dialog = ObjectiveDialog(
self.view, default_name=obj.name, default_description=obj.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)
elif item_type == ItemType.OBJECTIVE:
self.edit_objective(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT:
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=[play_opt],
default_player_id=war_part.id,
default_faction=war_part.faction,
editable_player=False,
)
if war_part_dialog.exec() == QDialog.DialogCode.Accepted:
faction = war_part_dialog.get_participant_faction()
self.model.update_war_participant(item_id, faction=faction)
elif item_type == ItemType.WAR_PARTICIPANT:
self.edit_war_participant(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.SECTOR:
sect = self.model.get_sector(item_id)
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()
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=rnd_dto,
default_round_id=sect.round_id,
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 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,
round_id=round_id,
major_id=major_id,
minor_id=minor_id,
influence_id=influence_id,
)
elif item_type == ItemType.SECTOR:
self.edit_sector(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
camp_part = self.model.get_campaign_participant(item_id)
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)]
camp_part_dialog = CampaignParticipantDialog(
self.view,
participants=part_opt,
default_participant_id=camp_part.id,
default_leader=camp_part.leader,
default_theme=camp_part.theme,
editable_player=False,
)
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
)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
self.edit_campaign_participant(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.CHOICE:
self.edit_round_choice(item_id)
self.refresh(RefreshScope.ROUND_DETAILS)
elif item_type == ItemType.BATTLE:
self.edit_round_battle(item_id)
self.refresh(RefreshScope.ROUND_DETAILS)
self.is_dirty = True
except UpdateRequiresConfirmation as e:
reply = QMessageBox.question(
self.view,
"Confirm update",
e.message,
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
e.apply_update()
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.CHOICE:
self.edit_round_choice(item_id)
self.refresh(RefreshScope.ROUND_DETAILS)
elif item_type == ItemType.BATTLE:
self.edit_round_battle(item_id)
self.refresh(RefreshScope.ROUND_DETAILS)
def delete_item(self, item_type: str, item_id: str) -> None:
reply = QMessageBox.question(
@ -502,39 +420,56 @@ class Controller:
)
if reply != QMessageBox.StandardButton.Yes:
return
if item_type == ItemType.PLAYER:
self.model.remove_player(item_id)
self.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR:
self.model.remove_war(item_id)
self.refresh(RefreshScope.WARS_TREE)
elif item_type == ItemType.CAMPAIGN:
war = self.model.get_war_by_campaign(item_id)
war_id = war.id
self.model.remove_campaign(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id
try:
if item_type == ItemType.PLAYER:
self.model.remove_player(item_id)
self.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR:
self.model.remove_war(item_id)
self.refresh(RefreshScope.WARS_TREE)
elif item_type == ItemType.CAMPAIGN:
war = self.model.get_war_by_campaign(item_id)
war_id = war.id
self.model.remove_campaign(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war_id
)
elif item_type == ItemType.OBJECTIVE:
self.model.remove_objective(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT:
self.model.remove_war_participant(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.SECTOR:
self.model.remove_sector(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
self.model.remove_campaign_participant(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.ROUND:
camp = self.model.get_campaign_by_round(item_id)
camp_id = camp.id
self.model.remove_round(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=camp_id
)
self.is_dirty = True
except DeletionForbidden as e:
QMessageBox.warning(
self.view,
"Deletion forbidden",
e.reason,
)
elif item_type == ItemType.OBJECTIVE:
self.model.remove_objective(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT:
self.model.remove_war_participant(item_id)
self.refresh(RefreshScope.WAR_DETAILS)
elif item_type == ItemType.SECTOR:
self.model.remove_sector(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
self.model.remove_campaign_participant(item_id)
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
elif item_type == ItemType.ROUND:
camp = self.model.get_campaign_by_round(item_id)
camp_id = camp.id
self.model.remove_round(item_id)
self.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=camp_id
except DeletionRequiresConfirmation as e:
reply = QMessageBox.question(
self.view,
"Confirm deletion",
e.message,
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
self.is_dirty = True
if reply == QMessageBox.StandardButton.Yes:
e.cleanup_action()
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
# Player methods
@ -557,6 +492,15 @@ class Controller:
self.is_dirty = True
self.refresh(RefreshScope.PLAYERS_LIST)
def edit_player(self, player_id: str) -> None:
play = self.model.get_player(player_id)
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(player_id, name=name)
# War methods
def _validate_war_inputs(self, name: str, year: int) -> bool:
@ -586,6 +530,16 @@ class Controller:
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=war.id
)
def edit_war(self, war_id: str) -> None:
war = self.model.get_war(war_id)
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(war_id, name=name, year=year)
# Objective methods
def _validate_objective_inputs(self, name: str, description: str) -> bool:
@ -610,6 +564,20 @@ class Controller:
self.is_dirty = True
self.refresh(RefreshScope.WAR_DETAILS)
def edit_objective(self, objective_id: str) -> None:
obj = self.model.get_objective(objective_id)
obj_dialog = ObjectiveDialog(
self.view, default_name=obj.name, default_description=obj.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(
objective_id, name=name, description=description
)
# War participant methods
def add_war_participant(self) -> None:
@ -630,6 +598,21 @@ class Controller:
self.is_dirty = True
self.refresh(RefreshScope.WAR_DETAILS)
def edit_war_participant(self, participant_id: str) -> None:
war_part = self.model.get_war_participant(participant_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=[play_opt],
default_player_id=war_part.id,
default_faction=war_part.faction,
editable_player=False,
)
if war_part_dialog.exec() == QDialog.DialogCode.Accepted:
faction = war_part_dialog.get_participant_faction()
self.model.update_war_participant(participant_id, faction=faction)
# Campaign methods
def _validate_campaign_inputs(self, name: str, month: int) -> bool:
@ -666,6 +649,18 @@ class Controller:
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=camp.id
)
def edit_campaign(self, campaign_id: str) -> None:
camp = self.model.get_campaign(campaign_id)
camp_dialog = CampaignDialog(
self.view, default_name=camp.name, default_month=camp.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(campaign_id, name=name, month=month)
# Campaign participant methods
def add_campaign_participant(self) -> None:
@ -692,6 +687,26 @@ class Controller:
self.is_dirty = True
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
def edit_campaign_participant(self, participant_id: str) -> None:
camp_part = self.model.get_campaign_participant(participant_id)
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)]
camp_part_dialog = CampaignParticipantDialog(
self.view,
participants=part_opt,
default_participant_id=camp_part.id,
default_leader=camp_part.leader,
default_theme=camp_part.theme,
editable_player=False,
)
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(
participant_id, leader=leader, theme=theme
)
# Sector methods
def _validate_sector_inputs(
@ -740,6 +755,44 @@ class Controller:
self.is_dirty = True
self.refresh(RefreshScope.CAMPAIGN_DETAILS)
def edit_sector(self, sector_id: str) -> None:
sect = self.model.get_sector(sector_id)
camp = self.model.get_campaign_by_sector(sector_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()
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=rnd_dto,
default_round_id=sect.round_id,
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 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(
sector_id,
name=name,
round_id=round_id,
major_id=major_id,
minor_id=minor_id,
influence_id=influence_id,
)
# Round methods
def add_round(self) -> None:

View file

@ -0,0 +1,35 @@
class Battle:
def __init__(
self,
sector_id: str,
player_1_id: str | None = None,
player_2_id: str | None = None,
):
self.sector_id: str = sector_id # ref to Campaign.sector
self.player_1_id: str | None = player_1_id # ref to Campaign.participants
self.player_2_id: str | None = player_2_id # ref to Campaign.participants
self.winner_id: str | None = None
self.score: str | None = None
self.victory_condition: str | None = None
self.comment: str | None = None
def set_id(self, new_id: str) -> None:
self.sector_id = new_id
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 | None) -> None:
self.player_2_id = new_player_id
def set_winner(self, new_player_id: str | None) -> None:
self.winner_id = new_player_id
def set_score(self, new_score: str | None) -> None:
self.score = new_score
def set_victory_condition(self, new_victory_condition: str | None) -> None:
self.victory_condition = new_victory_condition
def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment

View file

@ -2,7 +2,15 @@ from __future__ import annotations
from uuid import uuid4
from typing import Any, Dict, List
from warchron.model.round import Round, Choice, Battle
from warchron.model.exception import (
DeletionRequiresConfirmation,
UpdateRequiresConfirmation,
)
from warchron.model.campaign_participant import CampaignParticipant
from warchron.model.sector import Sector
from warchron.model.round import Round
from warchron.model.choice import Choice
from warchron.model.battle import Battle
class Campaign:
@ -90,12 +98,47 @@ class Campaign:
part.set_theme(theme)
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]
rounds_blocking: list[Round] = []
for rnd in self.rounds:
if rnd.has_choice_with_participant(
participant_id
) or rnd.has_battle_with_participant(participant_id):
rounds_blocking.append(rnd)
if not rounds_blocking:
del self.participants[participant_id]
return
def cleanup() -> None:
for rnd in rounds_blocking:
rnd.clear_participant_references(participant_id)
rnd.remove_choice(participant_id)
del self.participants[participant_id]
rounds_str = ", ".join(
str(self.get_round_index(rnd.id)) for rnd in rounds_blocking
)
raise DeletionRequiresConfirmation(
message=(
f"This participant is used in round(s): {rounds_str}.\n"
"Related choices and battles will be cleared.\n"
"Do you want to continue?"
),
cleanup_action=cleanup,
)
# Sector methods
def has_sector(self, sector_id: str) -> bool:
return sector_id in self.sectors
def has_sector_with_objective(self, objective_id: str) -> bool:
return any(
sect.major_objective_id == objective_id
or sect.minor_objective_id == objective_id
or sect.influence_objective_id == objective_id
for sect in self.sectors.values()
)
def add_sector(
self, name: str, round_id: str, major_id: str, minor_id: str, influence_id: str
) -> Sector:
@ -126,16 +169,75 @@ class Campaign:
influence_id: str,
) -> None:
sect = self.get_sector(sector_id)
sect.set_name(name)
sect.set_round(round_id)
sect.set_major(major_id)
sect.set_minor(minor_id)
sect.set_influence(influence_id)
old_round_id = sect.round_id
def apply_update() -> None:
sect.set_name(name)
sect.set_round(round_id)
sect.set_major(major_id)
sect.set_minor(minor_id)
sect.set_influence(influence_id)
if old_round_id == round_id:
apply_update()
return
affected_rounds: list[Round] = []
for rnd in self.rounds:
if rnd.id == old_round_id and (
rnd.has_choice_with_sector(sector_id)
or rnd.has_battle_with_sector(sector_id)
):
affected_rounds.append(rnd)
if not affected_rounds:
apply_update()
return
def cleanup_and_update() -> None:
for rnd in affected_rounds:
rnd.clear_sector_references(sector_id)
rnd.remove_battle(sector_id)
apply_update()
rounds_str = ", ".join(
str(self.get_round_index(rnd.id)) for rnd in affected_rounds
)
raise UpdateRequiresConfirmation(
message=(
f"Changing the round of this sector will affect round(s): {rounds_str}."
"\nRelated battles and choices will be cleared.\n"
"Do you want to continue?"
),
apply_update=cleanup_and_update,
)
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]
rounds_blocking: list[Round] = []
for rnd in self.rounds:
if rnd.has_battle_with_sector(sector_id) or rnd.has_choice_with_sector(
sector_id
):
rounds_blocking.append(rnd)
if not rounds_blocking:
del self.sectors[sector_id]
return
def cleanup() -> None:
for rnd in rounds_blocking:
rnd.clear_sector_references(sector_id)
rnd.remove_battle(sector_id)
del self.sectors[sector_id]
rounds_str = ", ".join(
str(self.get_round_index(rnd.id)) for rnd in rounds_blocking
)
raise DeletionRequiresConfirmation(
message=(
f"This sector is used in round(s): {rounds_str}.\n"
"Battles and choices using this sector will be cleared.\n"
"Do you want to continue?"
),
cleanup_action=cleanup,
)
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]
@ -227,62 +329,3 @@ class Campaign:
def remove_battle(self, round_id: str, sector_id: str) -> None:
rnd = self.get_round(round_id)
rnd.remove_battle(sector_id)
class CampaignParticipant:
def __init__(
self, *, war_participant_id: str, leader: str | None, theme: str | None
):
self.id: str = str(uuid4())
self.war_participant_id: str = war_participant_id # ref to War.participants
self.leader: str | None = leader
self.theme: str | None = theme
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_war_participant(self, new_participant: str) -> None:
self.war_participant_id = new_participant
def set_leader(self, new_faction: str) -> None:
self.leader = new_faction
def set_theme(self, new_theme: str) -> None:
self.theme = new_theme
class Sector:
def __init__(
self,
name: str,
round_id: str,
major_id: str | None,
minor_id: str | None,
influence_id: str | None,
):
self.id: str = str(uuid4())
self.name: str = name
self.round_id: str = round_id
self.major_objective_id: str | None = major_id # ref to War.objectives
self.minor_objective_id: str | None = minor_id # ref to War.objectives
self.influence_objective_id: str | None = influence_id # ref to War.objectives
self.mission: str | None = None
self.description: str | None = None
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_name(self, new_name: str) -> None:
self.name = new_name
def set_round(self, new_round_id: str) -> None:
self.round_id = new_round_id
def set_major(self, new_major_id: str) -> None:
self.major_objective_id = new_major_id
def set_minor(self, new_minor_id: str) -> None:
self.minor_objective_id = new_minor_id
def set_influence(self, new_influence_id: str) -> None:
self.influence_objective_id = new_influence_id

View file

@ -0,0 +1,24 @@
from __future__ import annotations
from uuid import uuid4
class CampaignParticipant:
def __init__(
self, *, war_participant_id: str, leader: str | None, theme: str | None
):
self.id: str = str(uuid4())
self.war_participant_id: str = war_participant_id # ref to War.participants
self.leader: str | None = leader
self.theme: str | None = theme
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_war_participant(self, new_participant: str) -> None:
self.war_participant_id = new_participant
def set_leader(self, new_faction: str) -> None:
self.leader = new_faction
def set_theme(self, new_theme: str) -> None:
self.theme = new_theme

View file

@ -0,0 +1,27 @@
class Choice:
def __init__(
self,
participant_id: str,
priority_sector_id: str | None = None,
secondary_sector_id: str | None = None,
):
self.participant_id: str = participant_id # ref to Campaign.participants
self.priority_sector_id: str | None = (
priority_sector_id # ref to Campaign.sectors
)
self.secondary_sector_id: str | None = (
secondary_sector_id # ref to Campaign.sectors
)
self.comment: str | None = None
def set_id(self, new_id: str) -> None:
self.participant_id = new_id
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 | None) -> None:
self.secondary_sector_id = new_secondary_id
def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment

View file

@ -0,0 +1,31 @@
from typing import Callable
class DeletionForbidden(Exception):
def __init__(self, reason: str):
self.reason = reason
super().__init__(reason)
class DeletionRequiresConfirmation(Exception):
def __init__(
self,
message: str,
*,
cleanup_action: Callable[[], None],
):
self.message = message
self.cleanup_action = cleanup_action
super().__init__(message)
class UpdateRequiresConfirmation(Exception):
def __init__(
self,
message: str,
*,
apply_update: Callable[[], None],
):
self.message = message
self.apply_update = apply_update
super().__init__(message)

View file

@ -4,10 +4,17 @@ import json
import shutil
from datetime import datetime
from warchron.model.exception import DeletionForbidden
from warchron.model.player import Player
from warchron.model.war import War, Objective, WarParticipant
from warchron.model.campaign import Campaign, Sector, CampaignParticipant
from warchron.model.round import Round, Choice, Battle
from warchron.model.war import War
from warchron.model.war_participant import WarParticipant
from warchron.model.objective import Objective
from warchron.model.campaign import Campaign
from warchron.model.campaign_participant import CampaignParticipant
from warchron.model.sector import Sector
from warchron.model.round import Round
from warchron.model.choice import Choice
from warchron.model.battle import Battle
class Model:
@ -78,7 +85,16 @@ class Model:
return list(self.players.values())
def remove_player(self, player_id: str) -> None:
# TODO manage war_participants referring to it
wars_using_player: List[str] = []
for war in self.wars.values():
if war.has_player(player_id):
wars_using_player.append(war.name)
if wars_using_player:
wars_str = ", ".join(wars_using_player)
raise DeletionForbidden(
f"This player is participating in war(s): {wars_str}.\n"
"Remove it from participants first."
)
del self.players[player_id]
# War methods
@ -94,6 +110,7 @@ class Model:
def get_war(self, id: str) -> War:
return self.wars[id]
# TODO replace multiloops by internal has_* method
def get_war_by_campaign(self, campaign_id: str) -> War:
for war in self.wars.values():
for camp in war.campaigns:
@ -101,6 +118,7 @@ class Model:
return war
raise KeyError(f"Campaign {campaign_id} not found in any War")
# TODO replace multiloops by internal has_* method
def get_war_by_sector(self, sector_id: str) -> War:
for war in self.wars.values():
for camp in war.campaigns:
@ -109,6 +127,7 @@ class Model:
return war
raise KeyError(f"Sector {sector_id} not found in any War")
# TODO replace multiloops by internal has_* method
def get_war_by_round(self, round_id: str) -> War:
for war in self.wars.values():
for camp in war.campaigns:
@ -117,6 +136,7 @@ class Model:
return war
raise KeyError(f"Round {round_id} not found in any War")
# TODO replace multiloops by internal has_* method
def get_war_by_objective(self, objective_id: str) -> War:
for war in self.wars.values():
for obj in war.objectives.values():
@ -154,6 +174,7 @@ class Model:
war = self.get_war(war_id)
return war.add_objective(name, description)
# TODO replace multiloops by internal has_* method
def get_objective(self, objective_id: str) -> Objective:
for war in self.wars.values():
for obj in war.objectives.values():
@ -185,6 +206,7 @@ class Model:
war = self.get_war(war_id)
return war.add_war_participant(player_id, faction)
# TODO replace multiloops by internal has_* method
def get_war_participant(self, participant_id: str) -> WarParticipant:
for war in self.wars.values():
for part in war.participants.values():
@ -213,6 +235,7 @@ class Model:
war = self.get_war(war_id)
return war.add_campaign(name, month)
# TODO replace multiloops by internal has_* method
def get_campaign(self, campaign_id: str) -> Campaign:
for war in self.wars.values():
for campaign in war.campaigns:
@ -263,6 +286,7 @@ class Model:
camp = self.get_campaign(campaign_id)
return camp.add_sector(name, round_id, major_id, minor_id, influence_id)
# TODO replace multiloops by internal has_* method
def get_sector(self, sector_id: str) -> Sector:
for war in self.wars.values():
for camp in war.campaigns:
@ -312,6 +336,7 @@ class Model:
war_part = war.get_war_participant(participant_id)
return self.players[war_part.player_id].name
# TODO replace multiloops by internal has_* method
def get_campaign_participant(self, participant_id: str) -> CampaignParticipant:
for war in self.wars.values():
for camp in war.campaigns:
@ -343,9 +368,10 @@ class Model:
# Round methods
def add_round(self, campaign_id: str) -> Round:
camp = self.get_campaign(campaign_id)
return camp.add_round()
war = self.get_war_by_campaign(campaign_id)
return war.add_round(campaign_id)
# TODO replace multiloops by internal has_* method
def get_round(self, round_id: str) -> Round:
for war in self.wars.values():
for camp in war.campaigns:

View file

@ -0,0 +1,18 @@
from __future__ import annotations
from uuid import uuid4
class Objective:
def __init__(self, name: str, description: str):
self.id: str = str(uuid4())
self.name: str = name
self.description: str = description
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_name(self, new_name: str) -> None:
self.name = new_name
def set_description(self, new_description: str) -> None:
self.description = new_description

View file

@ -2,6 +2,9 @@ from __future__ import annotations
from uuid import uuid4
from typing import Any, Dict
from warchron.model.choice import Choice
from warchron.model.battle import Battle
class Round:
def __init__(self) -> None:
@ -40,6 +43,18 @@ class Round:
def get_choice(self, participant_id: str) -> Choice | None:
return self.choices.get(participant_id)
def has_choice_with_sector(self, sector_id: str) -> bool:
return any(
choice.priority_sector_id == sector_id
or choice.secondary_sector_id == sector_id
for choice in self.choices.values()
)
def has_choice_with_participant(self, participant_id: str) -> bool:
return any(
choice.participant_id == participant_id for choice in self.choices.values()
)
def create_choice(self, participant_id: str) -> Choice:
if participant_id not in self.choices:
choice = Choice(
@ -63,6 +78,13 @@ class Round:
choice.set_secondary(secondary_sector_id)
choice.set_comment(comment)
def clear_sector_references(self, sector_id: str) -> None:
for choice in self.choices.values():
if choice.priority_sector_id == sector_id:
choice.priority_sector_id = None
if choice.secondary_sector_id == sector_id:
choice.secondary_sector_id = None
def remove_choice(self, participant_id: str) -> None:
del self.choices[participant_id]
@ -71,6 +93,15 @@ class Round:
def get_battle(self, sector_id: str) -> Battle | None:
return self.battles.get(sector_id)
def has_battle_with_sector(self, sector_id: str) -> bool:
return any(bat.sector_id == sector_id for bat in self.battles.values())
def has_battle_with_participant(self, participant_id: str) -> bool:
return any(
bat.player_1_id == participant_id or bat.player_2_id == participant_id
for bat in self.battles.values()
)
def create_battle(self, sector_id: str) -> Battle:
if sector_id not in self.battles:
battle = Battle(sector_id=sector_id, player_1_id=None, player_2_id=None)
@ -96,71 +127,14 @@ class Round:
bat.set_victory_condition(victory_condition)
bat.set_comment(comment)
def clear_participant_references(self, participant_id: str) -> None:
for battle in self.battles.values():
if battle.player_1_id == participant_id:
battle.player_1_id = None
if battle.player_2_id == participant_id:
battle.player_2_id = None
if battle.winner_id == participant_id:
battle.winner_id = None
def remove_battle(self, sector_id: str) -> None:
del self.battles[sector_id]
class Choice:
def __init__(
self,
participant_id: str,
priority_sector_id: str | None = None,
secondary_sector_id: str | None = None,
):
self.participant_id: str = participant_id # ref to Campaign.participants
self.priority_sector_id: str | None = (
priority_sector_id # ref to Campaign.sectors
)
self.secondary_sector_id: str | None = (
secondary_sector_id # ref to Campaign.sectors
)
self.comment: str | None = None
def set_id(self, new_id: str) -> None:
self.participant_id = new_id
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 | None) -> None:
self.secondary_sector_id = new_secondary_id
def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment
class Battle:
def __init__(
self,
sector_id: str,
player_1_id: str | None = None,
player_2_id: str | None = None,
):
self.sector_id: str = sector_id # ref to Campaign.sector
self.player_1_id: str | None = player_1_id # ref to Campaign.participants
self.player_2_id: str | None = player_2_id # ref to Campaign.participants
self.winner_id: str | None = None
self.score: str | None = None
self.victory_condition: str | None = None
self.comment: str | None = None
def set_id(self, new_id: str) -> None:
self.sector_id = new_id
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 | None) -> None:
self.player_2_id = new_player_id
def set_winner(self, new_player_id: str | None) -> None:
self.winner_id = new_player_id
def set_score(self, new_score: str | None) -> None:
self.score = new_score
def set_victory_condition(self, new_victory_condition: str | None) -> None:
self.victory_condition = new_victory_condition
def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment

View file

@ -0,0 +1,39 @@
from __future__ import annotations
from uuid import uuid4
class Sector:
def __init__(
self,
name: str,
round_id: str,
major_id: str | None,
minor_id: str | None,
influence_id: str | None,
):
self.id: str = str(uuid4())
self.name: str = name
self.round_id: str = round_id
self.major_objective_id: str | None = major_id # ref to War.objectives
self.minor_objective_id: str | None = minor_id # ref to War.objectives
self.influence_objective_id: str | None = influence_id # ref to War.objectives
self.mission: str | None = None
self.description: str | None = None
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_name(self, new_name: str) -> None:
self.name = new_name
def set_round(self, new_round_id: str) -> None:
self.round_id = new_round_id
def set_major(self, new_major_id: str) -> None:
self.major_objective_id = new_major_id
def set_minor(self, new_minor_id: str) -> None:
self.minor_objective_id = new_minor_id
def set_influence(self, new_influence_id: str) -> None:
self.influence_objective_id = new_influence_id

View file

@ -3,8 +3,15 @@ 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
from warchron.model.exception import DeletionForbidden
from warchron.model.war_participant import WarParticipant
from warchron.model.objective import Objective
from warchron.model.campaign_participant import CampaignParticipant
from warchron.model.sector import Sector
from warchron.model.campaign import Campaign
from warchron.model.round import Round
from warchron.model.choice import Choice
from warchron.model.battle import Battle
class War:
@ -76,7 +83,16 @@ class War:
obj.set_description(description)
def remove_objective(self, objective_id: str) -> None:
# TODO manage sectors referring to it
camp_using_obj: List[str] = []
for camp in self.campaigns:
if camp.has_sector_with_objective(objective_id):
camp_using_obj.append(camp.name)
if camp_using_obj:
camps_str = ", ".join(camp_using_obj)
raise DeletionForbidden(
f"This objective is used in campaign(s) sector(s): {camps_str}.\n"
"Remove it from campaign(s) first."
)
del self.objectives[objective_id]
# War participant methods
@ -103,14 +119,23 @@ class War:
def get_all_war_participants(self) -> List[WarParticipant]:
return list(self.participants.values())
def update_war_participant(self, player_id: str, *, faction: str) -> None:
part = self.get_war_participant(player_id)
def update_war_participant(self, participant_id: str, *, faction: str) -> None:
part = self.get_war_participant(participant_id)
# Can't change referred Model.players
part.set_faction(faction)
def remove_war_participant(self, player_id: str) -> None:
# TODO manage campaign_participants referring to it
del self.participants[player_id]
def remove_war_participant(self, participant_id: str) -> None:
camp_using_part: List[str] = []
for camp in self.campaigns:
if camp.has_war_participant(participant_id):
camp_using_part.append(camp.name)
if camp_using_part:
camps_str = ", ".join(camp_using_part)
raise DeletionForbidden(
f"This war participant is used in campaign(s): {camps_str}.\n"
"Remove it from campaign(s) first."
)
del self.participants[participant_id]
# Campaign methods
@ -133,13 +158,15 @@ class War:
return camp
raise KeyError(f"Campaign {campaign_id} not found in War {self.id}")
def get_campaign_by_round(self, round_id: str) -> Campaign:
# TODO replace multiloops by internal has_* method
def get_campaign_by_round(self, round_id: str) -> Campaign | None:
for camp in self.campaigns:
for rnd in camp.rounds:
if rnd.id == round_id:
return camp
raise KeyError(f"Round {round_id} not found in any Campaign")
return None
# TODO replace multiloops by internal has_* method
def get_campaign_by_sector(self, sector_id: str) -> Campaign:
for camp in self.campaigns:
for sect in camp.sectors.values():
@ -179,6 +206,7 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_sector(name, round_id, major_id, minor_id, influence_id)
# TODO replace multiloops by internal has_* method
def get_sector(self, sector_id: str) -> Sector:
for camp in self.campaigns:
for sect in camp.sectors.values():
@ -226,6 +254,7 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_campaign_participant(participant_id, leader, theme)
# TODO replace multiloops by internal has_* method
def get_campaign_participant(self, participant_id: str) -> CampaignParticipant:
for camp in self.campaigns:
for part in camp.participants.values():
@ -249,19 +278,18 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_round()
def add_battle(self, campaign_id: str) -> Round:
camp = self.get_campaign(campaign_id)
return camp.add_round()
def remove_round(self, round_id: str) -> None:
camp = self.get_campaign_by_round(round_id)
camp.remove_round(round_id)
if camp is not None:
camp.remove_round(round_id)
# Choice methods
def create_choice(self, round_id: str, participant_id: str) -> Choice:
camp = self.get_campaign_by_round(round_id)
return camp.create_choice(round_id, participant_id)
if camp is not None:
return camp.create_choice(round_id, participant_id)
raise KeyError("Campaign with round {round_id} doesn't exist")
def update_choice(
self,
@ -272,19 +300,27 @@ class War:
comment: str | None,
) -> None:
camp = self.get_campaign_by_round(round_id)
camp.update_choice(
round_id, participant_id, priority_sector_id, secondary_sector_id, comment
)
if camp is not None:
camp.update_choice(
round_id,
participant_id,
priority_sector_id,
secondary_sector_id,
comment,
)
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)
if camp is not None:
camp.remove_choice(round_id, participant_id)
# Battle methods
def create_battle(self, round_id: str, sector_id: str) -> Battle:
camp = self.get_campaign_by_round(round_id)
return camp.create_battle(round_id, sector_id)
if camp is not None:
return camp.create_battle(round_id, sector_id)
raise KeyError("Campaign with round {round_id} doesn't exist")
def update_battle(
self,
@ -298,49 +334,19 @@ class War:
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,
winner_id,
score,
victory_condition,
comment,
)
if camp is not None:
camp.update_battle(
round_id,
sector_id,
player_1_id,
player_2_id,
winner_id,
score,
victory_condition,
comment,
)
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)
class Objective:
def __init__(self, name: str, description: str):
self.id: str = str(uuid4())
self.name: str = name
self.description: str = description
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_name(self, new_name: str) -> None:
self.name = new_name
def set_description(self, new_description: str) -> None:
self.description = new_description
class WarParticipant:
def __init__(self, *, player_id: str, faction: str):
self.id: str = str(uuid4())
self.player_id: str = player_id # ref to WarModel.players
self.faction: str = faction
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_player(self, new_player: str) -> None:
self.player_id = new_player
def set_faction(self, new_faction: str) -> None:
self.faction = new_faction
if camp is not None:
camp.remove_battle(round_id, sector_id)

View file

@ -0,0 +1,18 @@
from __future__ import annotations
from uuid import uuid4
class WarParticipant:
def __init__(self, *, player_id: str, faction: str):
self.id: str = str(uuid4())
self.player_id: str = player_id # ref to WarModel.players
self.faction: str = faction
def set_id(self, new_id: str) -> None:
self.id = new_id
def set_player(self, new_player: str) -> None:
self.player_id = new_player
def set_faction(self, new_faction: str) -> None:
self.faction = new_faction

View file

@ -0,0 +1,68 @@
from typing import cast, List
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.controller.dtos import ParticipantOption, SectorDTO
from warchron.view.helpers import select_if_exists
from warchron.view.ui.ui_battle_result_dialog import Ui_battleResultDialog
class BattlesDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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)
self.ui.sectorComboBox.setEnabled(False)
self.ui.player1ComboBox.addItem("(none)", None)
self.ui.player2ComboBox.addItem("(none)", None)
for play in players:
self.ui.player1ComboBox.addItem(play.name, play.id)
self.ui.player2ComboBox.addItem(play.name, play.id)
select_if_exists(self.ui.player1ComboBox, default_player_1_id)
select_if_exists(self.ui.player2ComboBox, default_player_2_id)
self.ui.winnerComboBox.addItem("(none)", None)
for play in players:
if play.id in (default_player_1_id, default_player_2_id):
self.ui.winnerComboBox.addItem(play.name, play.id)
select_if_exists(self.ui.winnerComboBox, default_winner_id)
self.ui.score.setText(default_score)
self.ui.victoryCondition.setText(default_victory_condition)
self.ui.battleComment.setPlainText(default_comment)
def get_sector_id(self) -> str:
return cast(str, self.ui.sectorComboBox.currentData())
def get_player_1_id(self) -> str:
return cast(str, self.ui.player1ComboBox.currentData())
def get_player_2_id(self) -> str:
return cast(str, self.ui.player2ComboBox.currentData())
def get_winner_id(self) -> str:
return cast(str, self.ui.winnerComboBox.currentData())
def get_score(self) -> str:
return self.ui.score.text().strip()
def get_victory_condition(self) -> str:
return self.ui.victoryCondition.text().strip()
def get_comment(self) -> str:
return self.ui.battleComment.toPlainText().strip()

View file

@ -0,0 +1,25 @@
from PyQt6.QtWidgets import QDialog
from warchron.view.ui.ui_campaign_dialog import Ui_campaignDialog
from PyQt6.QtWidgets import QWidget
class CampaignDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
default_name: str = "",
default_month: int | None = None,
) -> None:
super().__init__(parent)
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)
def get_campaign_name(self) -> str:
return self.ui.campaignName.text().strip()
def get_campaign_month(self) -> int:
return int(self.ui.campaignMonth.value())

View file

@ -0,0 +1,40 @@
from typing import cast, List
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.controller.dtos import ParticipantOption
from warchron.view.helpers import select_if_exists
from warchron.view.ui.ui_campaign_participant_dialog import (
Ui_campaignParticipantDialog,
)
class CampaignParticipantDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
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 = 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)
self.ui.playerComboBox.setEnabled(editable_player)
self.ui.leader.setText(default_leader)
self.ui.theme.setText(default_theme)
def get_player_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_participant_leader(self) -> str:
return self.ui.leader.text().strip()
def get_participant_theme(self) -> str:
return self.ui.theme.text().strip()

View file

@ -0,0 +1,48 @@
from typing import cast, List
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.controller.dtos import ParticipantOption, SectorDTO
from warchron.view.helpers import select_if_exists
from warchron.view.ui.ui_choices_dialog import Ui_choicesDialog
class ChoicesDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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)
self.ui.playerComboBox.setEnabled(False)
self.ui.priorityComboBox.addItem("(none)", None)
self.ui.secondaryComboBox.addItem("(none)", None)
for sect in sectors:
self.ui.priorityComboBox.addItem(sect.name, sect.id)
self.ui.secondaryComboBox.addItem(sect.name, sect.id)
select_if_exists(self.ui.priorityComboBox, default_priority_id)
select_if_exists(self.ui.secondaryComboBox, default_secondary_id)
self.ui.choiceComment.setPlainText(default_comment)
def get_participant_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_priority_id(self) -> str:
return cast(str, self.ui.priorityComboBox.currentData())
def get_secondary_id(self) -> str:
return cast(str, self.ui.secondaryComboBox.currentData())
def get_comment(self) -> str:
return self.ui.choiceComment.toPlainText().strip()

View file

@ -0,0 +1,27 @@
import calendar
from PyQt6.QtWidgets import QComboBox
from warchron.controller.dtos import WarDTO, CampaignDTO
def select_if_exists(combo: QComboBox, value: str | None) -> None:
if value is None:
return
idx = combo.findData(value)
if idx != -1:
combo.setCurrentIndex(idx)
def format_war_label(war: WarDTO) -> str:
return f"{war.name} ({war.year})"
def format_campaign_label(camp: CampaignDTO) -> str:
return f"{camp.name} ({calendar.month_name[camp.month]})"
def format_round_label(index: int) -> str:
if index is None:
return ""
return f"Round {index}"

View file

@ -0,0 +1,24 @@
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.view.ui.ui_objective_dialog import Ui_objectiveDialog
class ObjectiveDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
default_name: str = "",
default_description: str | None = "",
) -> None:
super().__init__(parent)
self.ui: Ui_objectiveDialog = Ui_objectiveDialog()
self.ui.setupUi(self) # type: ignore
self.ui.objectiveName.setText(default_name)
self.ui.objectiveDescription.setPlainText(default_description)
def get_objective_name(self) -> str:
return self.ui.objectiveName.text().strip()
def get_objective_description(self) -> str:
return self.ui.objectiveDescription.toPlainText().strip()

View file

@ -0,0 +1,16 @@
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.view.ui.ui_player_dialog import Ui_playerDialog
class PlayerDialog(QDialog):
def __init__(
self, parent: QWidget | None = None, *, default_name: str = ""
) -> None:
super().__init__(parent)
self.ui: Ui_playerDialog = Ui_playerDialog()
self.ui.setupUi(self) # type: ignore
self.ui.playerName.setText(default_name)
def get_player_name(self) -> str:
return self.ui.playerName.text().strip()

View file

@ -0,0 +1,54 @@
from typing import cast, List
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.controller.dtos import ObjectiveDTO, RoundDTO
from warchron.view.helpers import select_if_exists, format_round_label
from warchron.view.ui.ui_sector_dialog import Ui_sectorDialog
class SectorDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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(index), rnd.id)
select_if_exists(self.ui.roundComboBox, default_round_id)
for obj in objectives:
self.ui.majorComboBox.addItem(obj.name, obj.id)
self.ui.minorComboBox.addItem(obj.name, obj.id)
self.ui.influenceComboBox.addItem(obj.name, obj.id)
select_if_exists(self.ui.majorComboBox, default_major_id)
select_if_exists(self.ui.minorComboBox, default_minor_id)
select_if_exists(self.ui.influenceComboBox, default_influence_id)
def get_sector_name(self) -> str:
return self.ui.sectorName.text().strip()
def get_round_id(self) -> str:
return cast(str, self.ui.roundComboBox.currentData())
def get_major_id(self) -> str:
return cast(str, self.ui.majorComboBox.currentData())
def get_minor_id(self) -> str:
return cast(str, self.ui.minorComboBox.currentData())
def get_influence_id(self) -> str:
return cast(str, self.ui.influenceComboBox.currentData())

View file

@ -1,17 +1,10 @@
from typing import cast, Callable, List
from typing import Callable, List
from pathlib import Path
import calendar
from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt, QPoint
from PyQt6.QtWidgets import (
QWidget,
QDialog,
QFileDialog,
QTreeWidgetItem,
QMenu,
QComboBox,
)
from PyQt6.QtWidgets import QWidget, QFileDialog, QTreeWidgetItem, QMenu
from PyQt6.QtGui import QCloseEvent
from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType
@ -21,49 +14,17 @@ from warchron.controller.dtos import (
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.helpers import (
format_campaign_label,
format_round_label,
format_war_label,
)
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
# utils...
def select_if_exists(combo: QComboBox, value: str | None) -> None:
if value is None:
return
idx = combo.findData(value)
if idx != -1:
combo.setCurrentIndex(idx)
def format_war_label(war: WarDTO) -> str:
return f"{war.name} ({war.year})"
def format_campaign_label(camp: CampaignDTO) -> str:
return f"{camp.name} ({calendar.month_name[camp.month]})"
def format_round_label(index: int) -> str:
if index is None:
return ""
return f"Round {index}"
from warchron.view.ui.ui_main_window import Ui_MainWindow
class View(QtWidgets.QMainWindow, Ui_MainWindow):
@ -517,285 +478,3 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow):
table.setItem(row, 1, player_1_item)
table.setItem(row, 2, player_2_item)
table.resizeColumnsToContents()
class PlayerDialog(QDialog):
def __init__(
self, parent: QWidget | None = None, *, default_name: str = ""
) -> None:
super().__init__(parent)
self.ui: Ui_playerDialog = Ui_playerDialog()
self.ui.setupUi(self) # type: ignore
self.ui.playerName.setText(default_name)
def get_player_name(self) -> str:
return self.ui.playerName.text().strip()
class WarDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
default_name: str = "",
default_year: int | None = None,
) -> None:
super().__init__(parent)
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)
def get_war_name(self) -> str:
return self.ui.warName.text().strip()
def get_war_year(self) -> int:
return int(self.ui.warYear.value())
class CampaignDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
default_name: str = "",
default_month: int | None = None,
) -> None:
super().__init__(parent)
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)
def get_campaign_name(self) -> str:
return self.ui.campaignName.text().strip()
def get_campaign_month(self) -> int:
return int(self.ui.campaignMonth.value())
class ObjectiveDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
default_name: str = "",
default_description: str | None = "",
) -> None:
super().__init__(parent)
self.ui: Ui_objectiveDialog = Ui_objectiveDialog()
self.ui.setupUi(self) # type: ignore
self.ui.objectiveName.setText(default_name)
self.ui.objectiveDescription.setPlainText(default_description)
def get_objective_name(self) -> str:
return self.ui.objectiveName.text().strip()
def get_objective_description(self) -> str:
return self.ui.objectiveDescription.toPlainText().strip()
class WarParticipantDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
players: List[ParticipantOption],
default_player_id: str | None = None,
default_faction: str | None = "",
editable_player: bool = True,
):
super().__init__(parent)
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)
self.ui.playerComboBox.setEnabled(editable_player)
self.ui.faction.setText(default_faction)
def get_player_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_participant_faction(self) -> str:
return self.ui.faction.text().strip()
class CampaignParticipantDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
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 = 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)
self.ui.playerComboBox.setEnabled(editable_player)
self.ui.leader.setText(default_leader)
self.ui.theme.setText(default_theme)
def get_player_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_participant_leader(self) -> str:
return self.ui.leader.text().strip()
def get_participant_theme(self) -> str:
return self.ui.theme.text().strip()
class SectorDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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(index), rnd.id)
select_if_exists(self.ui.roundComboBox, default_round_id)
for obj in objectives:
self.ui.majorComboBox.addItem(obj.name, obj.id)
self.ui.minorComboBox.addItem(obj.name, obj.id)
self.ui.influenceComboBox.addItem(obj.name, obj.id)
select_if_exists(self.ui.majorComboBox, default_major_id)
select_if_exists(self.ui.minorComboBox, default_minor_id)
select_if_exists(self.ui.influenceComboBox, default_influence_id)
def get_sector_name(self) -> str:
return self.ui.sectorName.text().strip()
def get_round_id(self) -> str:
return cast(str, self.ui.roundComboBox.currentData())
def get_major_id(self) -> str:
return cast(str, self.ui.majorComboBox.currentData())
def get_minor_id(self) -> str:
return cast(str, self.ui.minorComboBox.currentData())
def get_influence_id(self) -> str:
return cast(str, self.ui.influenceComboBox.currentData())
class ChoicesDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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)
self.ui.playerComboBox.setEnabled(False)
self.ui.priorityComboBox.addItem("(none)", None)
self.ui.secondaryComboBox.addItem("(none)", None)
for sect in sectors:
self.ui.priorityComboBox.addItem(sect.name, sect.id)
self.ui.secondaryComboBox.addItem(sect.name, sect.id)
select_if_exists(self.ui.priorityComboBox, default_priority_id)
select_if_exists(self.ui.secondaryComboBox, default_secondary_id)
self.ui.choiceComment.setPlainText(default_comment)
def get_participant_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_priority_id(self) -> str:
return cast(str, self.ui.priorityComboBox.currentData())
def get_secondary_id(self) -> str:
return cast(str, self.ui.secondaryComboBox.currentData())
def get_comment(self) -> str:
return self.ui.choiceComment.toPlainText().strip()
class BattlesDialog(QDialog):
def __init__(
self,
parent: QWidget | None = 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 = 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)
self.ui.sectorComboBox.setEnabled(False)
self.ui.player1ComboBox.addItem("(none)", None)
self.ui.player2ComboBox.addItem("(none)", None)
for play in players:
self.ui.player1ComboBox.addItem(play.name, play.id)
self.ui.player2ComboBox.addItem(play.name, play.id)
select_if_exists(self.ui.player1ComboBox, default_player_1_id)
select_if_exists(self.ui.player2ComboBox, default_player_2_id)
self.ui.winnerComboBox.addItem("(none)", None)
for play in players:
if play.id in (default_player_1_id, default_player_2_id):
self.ui.winnerComboBox.addItem(play.name, play.id)
select_if_exists(self.ui.winnerComboBox, default_winner_id)
self.ui.score.setText(default_score)
self.ui.victoryCondition.setText(default_victory_condition)
self.ui.battleComment.setPlainText(default_comment)
def get_sector_id(self) -> str:
return cast(str, self.ui.sectorComboBox.currentData())
def get_player_1_id(self) -> str:
return cast(str, self.ui.player1ComboBox.currentData())
def get_player_2_id(self) -> str:
return cast(str, self.ui.player2ComboBox.currentData())
def get_winner_id(self) -> str:
return cast(str, self.ui.winnerComboBox.currentData())
def get_score(self) -> str:
return self.ui.score.text().strip()
def get_victory_condition(self) -> str:
return self.ui.victoryCondition.text().strip()
def get_comment(self) -> str:
return self.ui.battleComment.toPlainText().strip()

View file

@ -0,0 +1,24 @@
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.view.ui.ui_war_dialog import Ui_warDialog
class WarDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
default_name: str = "",
default_year: int | None = None,
) -> None:
super().__init__(parent)
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)
def get_war_name(self) -> str:
return self.ui.warName.text().strip()
def get_war_year(self) -> int:
return int(self.ui.warYear.value())

View file

@ -0,0 +1,33 @@
from typing import cast, List
from PyQt6.QtWidgets import QWidget, QDialog
from warchron.controller.dtos import ParticipantOption
from warchron.view.helpers import select_if_exists
from warchron.view.ui.ui_war_participant_dialog import Ui_warParticipantDialog
class WarParticipantDialog(QDialog):
def __init__(
self,
parent: QWidget | None = None,
*,
players: List[ParticipantOption],
default_player_id: str | None = None,
default_faction: str | None = "",
editable_player: bool = True,
):
super().__init__(parent)
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)
self.ui.playerComboBox.setEnabled(editable_player)
self.ui.faction.setText(default_faction)
def get_player_id(self) -> str:
return cast(str, self.ui.playerComboBox.currentData())
def get_participant_faction(self) -> str:
return self.ui.faction.text().strip()