diff --git a/README.md b/README.md
index 65b9e72..d8fbdf6 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ cd warchron_app
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
+pip install -e .
```
### Run
diff --git a/main.py b/main.py
index d5443db..4e7d4a0 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,4 @@
import sys
-import os
-
-sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src"))
from PyQt6.QtWidgets import QApplication
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..3bf3163
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,67 @@
+[project]
+name = "warchron"
+version = "1.0.0"
+description = "A simple local app to track players' campaigns for tabletop wargames."
+requires-python = ">=3.12"
+
+[build-system]
+requires = ["setuptools>=61"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+package-dir = {"" = "src"}
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+# === BLACK CONFIGURATION (FORMATTER) ===
+[tool.black]
+line-length = 88
+target-version = ['py312']
+include = '\.pyi?$' # File types to format (.py and .pyi)
+extend-exclude = '''
+/(
+ # directories
+ \.eggs
+ | \.git
+ | \.venv
+ | venv
+ | build
+ | dist
+)/
+'''
+
+[tool.flake8]
+max-line-length = 88
+extend-ignore = ["E203", "W503"]
+exclude = [
+ ".git",
+ "__pycache__",
+ ".venv",
+ "build",
+ "dist",
+ "src/warchron/view/ui"
+]
+
+[tool.mypy]
+python_version = "3.12"
+mypy_path = "src"
+strict = true
+explicit_package_bases = true
+disallow_untyped_decorators = false
+exclude = [
+ ".git",
+ "__pycache__",
+ ".venv",
+ "build",
+ "dist",
+ "src/warchron/view/ui"
+]
+
+[[tool.mypy.overrides]]
+module = "PyQt6.*"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "warchron.view.ui.*"
+ignore_errors = true
diff --git a/requirements.txt b/requirements.txt
index abcc883..b587ef0 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,5 @@
-PyQt6>=6.6,<6.8
\ No newline at end of file
+PyQt6>=6.6,<6.8
+mypy==1.19.1
+black==25.12.0
+flake8==7.3.0
+flake8-pyproject
\ No newline at end of file
diff --git a/src/warchron/controller/__ini__.py b/src/warchron/__init__.py
similarity index 100%
rename from src/warchron/controller/__ini__.py
rename to src/warchron/__init__.py
diff --git a/src/warchron/model/__ini__.py b/src/warchron/controller/__init__.py
similarity index 100%
rename from src/warchron/model/__ini__.py
rename to src/warchron/controller/__init__.py
diff --git a/src/warchron/controller/controller.py b/src/warchron/controller/controller.py
index b246f76..8d92385 100644
--- a/src/warchron/controller/controller.py
+++ b/src/warchron/controller/controller.py
@@ -1,11 +1,24 @@
+from typing import List
from pathlib import Path
from PyQt6.QtWidgets import QMessageBox, QDialog
+
from warchron.model.model import Model
from warchron.view.view import View
-
from warchron.constants import ItemType, RefreshScope
-from warchron.controller.dtos import ParticipantOption
+from warchron.controller.dtos import (
+ ParticipantOption,
+ TreeSelection,
+ WarDTO,
+ WarParticipantDTO,
+ ObjectiveDTO,
+ CampaignDTO,
+ CampaignParticipantDTO,
+ SectorDTO,
+ RoundDTO,
+ ChoiceDTO,
+ BattleDTO,
+)
from warchron.view.view import (
PlayerDialog,
WarDialog,
@@ -20,13 +33,13 @@ from warchron.view.view import (
class Controller:
- def __init__(self, model: Model, view: View):
+ def __init__(self, model: Model, view: View) -> None:
self.model: Model = model
self.view: View = view
self.current_file: Path | None = None
- self.selected_war_id: str = None
- self.selected_campaign_id: str = None
- self.selected_round_id: str = None
+ self.selected_war_id: str | None = None
+ self.selected_campaign_id: str | None = None
+ self.selected_round_id: str | None = None
self.view.on_close_callback = self.on_app_close
self.is_dirty: bool = False
self.__connect()
@@ -38,7 +51,7 @@ class Controller:
self.view.on_add_campaign = self.add_campaign
self.view.on_add_round = self.add_round
- def __connect(self):
+ def __connect(self) -> None:
self.view.actionExit.triggered.connect(self.view.close)
self.view.actionNew.triggered.connect(self.new)
self.view.actionOpen.triggered.connect(self.open_file)
@@ -73,7 +86,7 @@ class Controller:
# Menu bar methods
- def new(self):
+ def new(self) -> None:
if self.is_dirty:
reply = QMessageBox.question(
self.view,
@@ -90,7 +103,7 @@ class Controller:
self.refresh_wars_view()
self.update_window_title()
- def open_file(self):
+ def open_file(self) -> None:
if self.is_dirty:
reply = QMessageBox.question(
self.view,
@@ -110,7 +123,7 @@ class Controller:
self.refresh_wars_view()
self.update_window_title()
- def save(self):
+ def save(self) -> None:
if not self.current_file:
self.save_as()
return
@@ -118,7 +131,7 @@ class Controller:
self.is_dirty = False
self.update_window_title()
- def save_as(self):
+ def save_as(self) -> None:
path = self.view.ask_save_file()
if not path:
return
@@ -129,83 +142,102 @@ class Controller:
# Display methods
- def update_window_title(self):
+ def update_window_title(self) -> None:
base = "WarChron"
if self.current_file:
base += f" - {self.current_file.name}"
else:
- base += f" - New file"
+ base += " - New file"
if self.is_dirty:
base = base + " *"
self.view.setWindowTitle(base)
- def refresh_players_view(self):
+ def refresh_players_view(self) -> None:
players = self.model.get_all_players()
- self.view.display_players(players)
+ players_for_display: List[ParticipantOption] = [
+ ParticipantOption(id=p.id, name=p.name) for p in players
+ ]
+ self.view.display_players(players_for_display)
- def refresh_wars_view(self):
- wars = self.model.get_all_wars()
+ def refresh_wars_view(self) -> None:
+ wars: List[WarDTO] = [
+ WarDTO(
+ id=w.id,
+ name=w.name,
+ year=w.year,
+ _campaigns=[
+ CampaignDTO(
+ id=c.id,
+ name=c.name,
+ month=c.month,
+ _rounds=[
+ RoundDTO(id=r.id, index=c.get_round_index(r.id))
+ for r in c.get_all_rounds()
+ ],
+ )
+ for c in w.get_all_campaigns()
+ ],
+ )
+ for w in self.model.get_all_wars()
+ ]
self.view.display_wars_tree(wars)
- def _fill_war_details(self, war_id: str):
+ def _fill_war_details(self, war_id: str) -> None:
war = self.model.get_war(war_id)
self.view.show_war_details(name=war.name, year=war.year)
objectives = war.get_all_objectives()
- self.view.display_war_objectives(objectives)
+ objectives_for_display: List[ObjectiveDTO] = [
+ ObjectiveDTO(id=obj.id, name=obj.name, description=obj.description)
+ for obj in objectives
+ ]
+ self.view.display_war_objectives(objectives_for_display)
war_parts = war.get_all_war_participants()
- participants_for_display = [
- (
- self.model.get_player_name(
- p.player_id,
- ),
- p.faction,
- p.id,
+ participants_for_display: List[WarParticipantDTO] = [
+ WarParticipantDTO(
+ id=p.id,
+ player_name=self.model.get_player_name(p.player_id),
+ faction=p.faction,
)
for p in war_parts
]
self.view.display_war_participants(participants_for_display)
- def _fill_campaign_details(self, campaign_id: str):
+ def _fill_campaign_details(self, campaign_id: str) -> None:
camp = self.model.get_campaign(campaign_id)
self.view.show_campaign_details(name=camp.name, month=camp.month)
sectors = camp.get_all_sectors()
- sectors_for_display = []
war = self.model.get_war_by_campaign(camp.id)
- for sect in sectors:
- round_index = camp.get_round_index(sect.round_id)
- major_name = war.get_objective_name(sect.major_objective_id)
- minor_name = war.get_objective_name(sect.minor_objective_id)
- influence_name = war.get_objective_name(sect.influence_objective_id)
- sectors_for_display.append(
- (
- sect.name,
- round_index,
- major_name,
- minor_name,
- influence_name,
- sect.id,
- )
+ sectors_for_display: List[SectorDTO] = [
+ SectorDTO(
+ id=sect.id,
+ name=sect.name,
+ round_index=camp.get_round_index(sect.round_id),
+ major=war.get_objective_name(sect.major_objective_id),
+ minor=war.get_objective_name(sect.minor_objective_id),
+ influence=war.get_objective_name(sect.influence_objective_id),
)
+ for sect in sectors
+ ]
self.view.display_campaign_sectors(sectors_for_display)
camp_parts = camp.get_all_campaign_participants()
- participants_for_display = [
- (
- self.model.get_participant_name(p.war_participant_id),
- p.leader,
- p.theme,
- p.id,
+ participants_for_display: List[CampaignParticipantDTO] = [
+ CampaignParticipantDTO(
+ id=p.id,
+ player_name=self.model.get_participant_name(p.war_participant_id),
+ leader=p.leader or "",
+ theme=p.theme or "",
)
for p in camp_parts
]
self.view.display_campaign_participants(participants_for_display)
- def _fill_round_details(self, round_id: str):
+ def _fill_round_details(self, round_id: str) -> None:
rnd = self.model.get_round(round_id)
camp = self.model.get_campaign_by_round(round_id)
self.view.show_round_details(index=camp.get_round_index(round_id))
participants = self.model.get_round_participants(round_id)
- sectors = camp.get_all_sectors()
- choices_for_display = []
+ sectors = camp.get_sectors_in_round(round_id)
+ choices_for_display: List[ChoiceDTO] = []
for part in participants:
choice = rnd.get_choice(part.id)
if not choice:
@@ -213,52 +245,74 @@ 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_player_name(part.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:
battle = self.model.create_battle(round_id=rnd.id, sector_id=sect.id)
- player_1_name = (
- self.model.get_player_name(battle.player_1_id)
- if battle.player_1_id
- else ""
- )
- player_2_name = (
- self.model.get_player_name(battle.player_2_id)
- if battle.player_2_id
- else ""
- )
+ if battle.player_1_id:
+ camp_part = camp.participants[battle.player_1_id]
+ player_1_name = self.model.get_participant_name(
+ camp_part.war_participant_id
+ )
+ else:
+ player_1_name = ""
+ if battle.player_2_id:
+ camp_part = camp.participants[battle.player_2_id]
+ player_2_name = self.model.get_participant_name(
+ camp_part.war_participant_id
+ )
+ 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)
@@ -277,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()
@@ -305,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)
@@ -333,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)
@@ -347,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:
@@ -376,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,
@@ -407,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,
@@ -415,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
)
@@ -429,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",
@@ -482,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:
@@ -506,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"]
)
@@ -532,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)
@@ -548,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()
@@ -578,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(
@@ -601,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(
@@ -630,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."
@@ -638,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
@@ -667,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)
@@ -678,25 +753,36 @@ 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
- part = camp.participants[choice.participant_id]
- player = self.model.get_player(part.id)
- part_opt = ParticipantOption(id=player.id, name=player.name)
+ participant = camp.participants[choice.participant_id]
+ player = self.model.get_player_from_campaign_participant(participant)
+ part_opt = ParticipantOption(id=participant.id, name=player.name)
dialog = ChoicesDialog(
self.view,
participants=[part_opt],
- default_participant_id=part.id,
- sectors=sectors,
+ default_participant_id=participant.id,
+ sectors=sect_opts,
default_priority_id=choice.priority_sector_id,
default_secondary_id=choice.secondary_sector_id,
default_comment=choice.comment,
@@ -705,7 +791,7 @@ class Controller:
return
self.model.update_choice(
round_id=round_id,
- participant_id=part.id,
+ participant_id=participant.id,
priority_sector_id=dialog.get_priority_id(),
secondary_sector_id=dialog.get_secondary_id(),
comment=dialog.get_comment(),
@@ -713,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()
@@ -724,13 +811,22 @@ class Controller:
if not battle:
return
sect = camp.sectors[battle.sector_id]
- part_opts: list[ParticipantOption] = []
- for part in participants:
- player = self.model.get_player(part.id)
- part_opts.append(ParticipantOption(id=part.id, name=player.name))
+ sect_dto = SectorDTO(
+ id=sect.id,
+ name=sect.name,
+ round_index=camp.get_round_index(sect.round_id),
+ major=war.get_objective_name(sect.major_objective_id),
+ minor=war.get_objective_name(sect.minor_objective_id),
+ influence=war.get_objective_name(sect.influence_objective_id),
+ )
+
+ part_opts: List[ParticipantOption] = []
+ for participant in participants:
+ player = self.model.get_player_from_campaign_participant(participant)
+ part_opts.append(ParticipantOption(id=participant.id, name=player.name))
dialog = BattlesDialog(
self.view,
- sectors=[sect],
+ sectors=[sect_dto],
default_sector_id=sect.id,
players=part_opts,
default_player_1_id=battle.player_1_id,
diff --git a/src/warchron/controller/dtos.py b/src/warchron/controller/dtos.py
index 83e930b..cf063bd 100644
--- a/src/warchron/controller/dtos.py
+++ b/src/warchron/controller/dtos.py
@@ -1,3 +1,4 @@
+from typing import List
from dataclasses import dataclass
@@ -5,3 +6,90 @@ from dataclasses import dataclass
class ParticipantOption:
id: str
name: str
+
+
+@dataclass(frozen=True, slots=True)
+class TreeSelection:
+ type: str
+ id: str
+
+
+@dataclass
+class WarDTO:
+ id: str
+ name: str
+ year: int
+ _campaigns: List["CampaignDTO"] | None = None
+
+ def get_all_campaigns(self) -> List["CampaignDTO"]:
+ return self._campaigns or []
+
+
+@dataclass(frozen=True, slots=True)
+class ObjectiveDTO:
+ id: str
+ name: str
+ description: str
+
+
+@dataclass(frozen=True, slots=True)
+class WarParticipantDTO:
+ id: str
+ player_name: str
+ faction: str
+
+
+@dataclass
+class CampaignDTO:
+ id: str
+ name: str
+ month: int
+ _rounds: List["RoundDTO"] | None = None
+
+ def get_all_rounds(self) -> List["RoundDTO"]:
+ return self._rounds or []
+
+
+@dataclass(frozen=True, slots=True)
+class CampaignParticipantDTO:
+ id: str
+ player_name: str
+ leader: str
+ theme: str
+
+
+@dataclass(frozen=True, slots=True)
+class SectorDTO:
+ id: str
+ name: str
+ round_index: int
+ major: str
+ minor: str
+ influence: str
+
+
+@dataclass
+class RoundDTO:
+ id: str
+ index: int
+
+
+@dataclass(frozen=True, slots=True)
+class ChoiceDTO:
+ id: str
+ participant_name: str
+ priority_sector: str
+ secondary_sector: str
+ comment: str | None
+
+
+@dataclass(frozen=True, slots=True)
+class BattleDTO:
+ id: str
+ sector_name: str
+ player_1: str
+ player_2: str
+ winner: str | None
+ score: str | None
+ victory_condition: str | None
+ comment: str | None
diff --git a/src/warchron/view/__ini__.py b/src/warchron/model/__init__.py
similarity index 100%
rename from src/warchron/view/__ini__.py
rename to src/warchron/model/__init__.py
diff --git a/src/warchron/model/campaign.py b/src/warchron/model/campaign.py
index df6ae94..bf3431e 100644
--- a/src/warchron/model/campaign.py
+++ b/src/warchron/model/campaign.py
@@ -1,32 +1,33 @@
from __future__ import annotations
from uuid import uuid4
+from typing import Any, Dict, List
from warchron.model.round import Round, Choice, Battle
class Campaign:
- def __init__(self, name: str, month: int):
+ def __init__(self, name: str, month: int) -> None:
self.id: str = str(uuid4())
self.name: str = name
self.month: int = month
- self.participants: dict[str, CampaignParticipant] = {}
- self.sectors: dict[str, Sector] = {}
- self.rounds: list[Round] = []
+ self.participants: Dict[str, CampaignParticipant] = {}
+ self.sectors: Dict[str, Sector] = {}
+ self.rounds: List[Round] = []
self.is_over = False
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_name(self, new_name: str):
+ def set_name(self, new_name: str) -> None:
self.name = new_name
- def set_month(self, new_month: int):
+ def set_month(self, new_month: int) -> None:
self.month = new_month
- def set_state(self, new_state: bool):
+ def set_state(self, new_state: bool) -> None:
self.is_over = new_state
- def toDict(self):
+ def toDict(self) -> Dict[str, Any]:
return {
"id": self.id,
"name": self.name,
@@ -37,7 +38,7 @@ class Campaign:
}
@staticmethod
- def fromDict(data: dict):
+ def fromDict(data: Dict[str, Any]) -> Campaign:
camp = Campaign(name=data["name"], month=data["month"])
camp.set_id(data["id"])
# camp.participants = data.get("participants", {})
@@ -77,18 +78,18 @@ class Campaign:
except KeyError:
raise KeyError(f"Participant {participant_id} not in campaign {self.id}")
- def get_all_campaign_participants(self) -> list[CampaignParticipant]:
+ def get_all_campaign_participants(self) -> List[CampaignParticipant]:
return list(self.participants.values())
def update_campaign_participant(
self, participant_id: str, *, leader: str, theme: str
- ):
+ ) -> None:
part = self.get_campaign_participant(participant_id)
# Can't change referred War.participant
part.set_leader(leader)
part.set_theme(theme)
- def remove_campaign_participant(self, participant_id: str):
+ def remove_campaign_participant(self, participant_id: str) -> None:
# TODO manage choices referring to it
# TODO manage battles referring to it
del self.participants[participant_id]
@@ -110,7 +111,7 @@ class Campaign:
return ""
return self.sectors[sector_id].name
- def get_all_sectors(self) -> list[Sector]:
+ def get_all_sectors(self) -> List[Sector]:
return list(self.sectors.values())
# TODO manage choices referring to it (round order!)
@@ -123,7 +124,7 @@ class Campaign:
major_id: str,
minor_id: str,
influence_id: str,
- ):
+ ) -> None:
sect = self.get_sector(sector_id)
sect.set_name(name)
sect.set_round(round_id)
@@ -131,12 +132,12 @@ class Campaign:
sect.set_minor(minor_id)
sect.set_influence(influence_id)
- def remove_sector(self, sector_id: str):
+ def remove_sector(self, sector_id: str) -> None:
# TODO manage choices referring to it
# TODO manage battles referring to it
del self.sectors[sector_id]
- def get_sectors_in_round(self, round_id: str) -> list[Sector]:
+ def get_sectors_in_round(self, round_id: str) -> List[Sector]:
sectors = [s for s in self.sectors.values() if s.round_id == round_id]
return sectors
@@ -151,7 +152,7 @@ class Campaign:
return rnd
raise KeyError(f"Round {round_id} not found")
- def get_all_rounds(self) -> list[Round]:
+ def get_all_rounds(self) -> List[Round]:
return list(self.rounds)
def add_round(self) -> Round:
@@ -159,7 +160,7 @@ class Campaign:
self.rounds.append(round)
return round
- def remove_round(self, round_id: str):
+ def remove_round(self, round_id: str) -> None:
rnd = next((r for r in self.rounds if r.id == round_id), None)
if rnd:
self.rounds.remove(rnd)
@@ -172,14 +173,6 @@ class Campaign:
return index
raise KeyError("Round not found in campaign")
- def get_round_name(self, round_id: str | None) -> str:
- if round_id is None:
- return ""
- for rnd in self.rounds:
- if rnd.id == round_id:
- return rnd.name
- return ""
-
# Choice methods
def create_choice(self, round_id: str, participant_id: str) -> Choice:
@@ -193,13 +186,13 @@ class Campaign:
priority_sector_id: str | None,
secondary_sector_id: str | None,
comment: str | None,
- ):
+ ) -> None:
rnd = self.get_round(round_id)
rnd.update_choice(
participant_id, priority_sector_id, secondary_sector_id, comment
)
- def remove_choice(self, round_id: str, participant_id: str) -> Choice:
+ def remove_choice(self, round_id: str, participant_id: str) -> None:
rnd = self.get_round(round_id)
rnd.remove_choice(participant_id)
@@ -219,7 +212,7 @@ class Campaign:
score: str | None,
victory_condition: str | None,
comment: str | None,
- ):
+ ) -> None:
rnd = self.get_round(round_id)
rnd.update_battle(
sector_id,
@@ -231,7 +224,7 @@ class Campaign:
comment,
)
- def remove_battle(self, round_id: str, sector_id: str) -> Battle:
+ def remove_battle(self, round_id: str, sector_id: str) -> None:
rnd = self.get_round(round_id)
rnd.remove_battle(sector_id)
@@ -245,16 +238,16 @@ class CampaignParticipant:
self.leader: str | None = leader
self.theme: str | None = theme
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_war_participant(self, new_participant: str):
+ def set_war_participant(self, new_participant: str) -> None:
self.war_participant_id = new_participant
- def set_leader(self, new_faction: str):
+ def set_leader(self, new_faction: str) -> None:
self.leader = new_faction
- def set_theme(self, new_theme: str):
+ def set_theme(self, new_theme: str) -> None:
self.theme = new_theme
@@ -276,20 +269,20 @@ class Sector:
self.mission: str | None = None
self.description: str | None = None
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_name(self, new_name: str):
+ def set_name(self, new_name: str) -> None:
self.name = new_name
- def set_round(self, new_round_id: str):
+ def set_round(self, new_round_id: str) -> None:
self.round_id = new_round_id
- def set_major(self, new_major_id: str):
+ def set_major(self, new_major_id: str) -> None:
self.major_objective_id = new_major_id
- def set_minor(self, new_minor_id: str):
+ def set_minor(self, new_minor_id: str) -> None:
self.minor_objective_id = new_minor_id
- def set_influence(self, new_influence_id: str):
+ def set_influence(self, new_influence_id: str) -> None:
self.influence_objective_id = new_influence_id
diff --git a/src/warchron/model/model.py b/src/warchron/model/model.py
index d4f017b..1a17563 100644
--- a/src/warchron/model/model.py
+++ b/src/warchron/model/model.py
@@ -1,3 +1,4 @@
+from typing import Any, Dict, List
from pathlib import Path
import json
import shutil
@@ -10,25 +11,25 @@ from warchron.model.round import Round, Choice, Battle
class Model:
- def __init__(self):
- self.players: dict[str, Player] = {}
- self.wars: dict[str, War] = {}
+ def __init__(self) -> None:
+ self.players: Dict[str, Player] = {}
+ self.wars: Dict[str, War] = {}
# File management methods
- def new(self):
+ def new(self) -> None:
self.players.clear()
self.wars.clear()
- def load(self, path: Path):
+ def load(self, path: Path) -> None:
self.players.clear()
self.wars.clear()
self._load_data(path)
- def save(self, path: Path):
+ def save(self, path: Path) -> None:
self._save_data(path)
- def _load_data(self, path: Path):
+ def _load_data(self, path: Path) -> None:
if not path.exists() or path.stat().st_size == 0:
return # Start empty
try:
@@ -45,7 +46,7 @@ class Model:
except json.JSONDecodeError:
raise RuntimeError("Data file is corrupted")
- def _save_data(self, path: Path):
+ def _save_data(self, path: Path) -> None:
if path.exists():
shutil.copy(path, path.with_suffix(".json.bak"))
data = {
@@ -58,31 +59,31 @@ class Model:
# Player methods
- def add_player(self, name):
+ def add_player(self, name: str) -> Player:
player = Player(name)
self.players[player.id] = player
return player
- def get_player(self, id):
+ def get_player(self, id: str) -> Player:
return self.players[id]
def get_player_name(self, player_id: str) -> str:
return self.players[player_id].name
- def update_player(self, player_id: str, *, name: str):
+ def update_player(self, player_id: str, *, name: str) -> None:
player = self.get_player(player_id)
player.set_name(name)
- def get_all_players(self) -> list[Player]:
+ def get_all_players(self) -> List[Player]:
return list(self.players.values())
- def remove_player(self, player_id: str):
+ def remove_player(self, player_id: str) -> None:
# TODO manage war_participants referring to it
del self.players[player_id]
# War methods
- def get_default_war_values(self) -> dict:
+ def get_default_war_values(self) -> Dict[str, Any]:
return {"year": datetime.now().year}
def add_war(self, name: str, year: int) -> War:
@@ -90,7 +91,7 @@ class Model:
self.wars[war.id] = war
return war
- def get_war(self, id) -> War:
+ def get_war(self, id: str) -> War:
return self.wars[id]
def get_war_by_campaign(self, campaign_id: str) -> War:
@@ -100,20 +101,20 @@ class Model:
return war
raise KeyError(f"Campaign {campaign_id} not found in any War")
- def get_war_by_sector(self, sector_id: str) -> Campaign:
+ def get_war_by_sector(self, sector_id: str) -> War:
for war in self.wars.values():
for camp in war.campaigns:
for sect in camp.sectors.values():
if sect.id == sector_id:
- return camp
+ return war
raise KeyError(f"Sector {sector_id} not found in any War")
- def get_war_by_round(self, round_id: str) -> Campaign:
+ def get_war_by_round(self, round_id: str) -> War:
for war in self.wars.values():
for camp in war.campaigns:
for rnd in camp.rounds:
if rnd.id == round_id:
- return camp
+ return war
raise KeyError(f"Round {round_id} not found in any War")
def get_war_by_objective(self, objective_id: str) -> War:
@@ -136,15 +137,15 @@ class Model:
return war
raise KeyError(f"Participant {participant_id} not found")
- def update_war(self, war_id: str, *, name: str, year: int):
+ def update_war(self, war_id: str, *, name: str, year: int) -> None:
war = self.get_war(war_id)
war.set_name(name)
war.set_year(year)
- def get_all_wars(self) -> list[War]:
+ def get_all_wars(self) -> List[War]:
return list(self.wars.values())
- def remove_war(self, war_id: str):
+ def remove_war(self, war_id: str) -> None:
del self.wars[war_id]
# Objective methods
@@ -153,24 +154,26 @@ class Model:
war = self.get_war(war_id)
return war.add_objective(name, description)
- def get_objective(self, objective_id) -> Objective:
+ def get_objective(self, objective_id: str) -> Objective:
for war in self.wars.values():
for obj in war.objectives.values():
if obj.id == objective_id:
return obj
raise KeyError("Objective not found")
- def update_objective(self, objective_id: str, *, name: str, description: str):
+ def update_objective(
+ self, objective_id: str, *, name: str, description: str
+ ) -> None:
war = self.get_war_by_objective(objective_id)
war.update_objective(objective_id, name=name, description=description)
- def remove_objective(self, objective_id: str):
+ def remove_objective(self, objective_id: str) -> None:
war = self.get_war_by_objective(objective_id)
war.remove_objective(objective_id)
# War participant methods
- def get_available_players(self, war_id: str) -> list[Player]:
+ def get_available_players(self, war_id: str) -> List[Player]:
war = self.get_war(war_id)
return [
player for player in self.players.values() if not war.has_player(player.id)
@@ -182,24 +185,27 @@ 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:
return part
raise KeyError("Participant not found")
- def update_war_participant(self, participant_id: str, *, faction: str):
+ 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) -> 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()
@@ -207,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:
@@ -235,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)
@@ -257,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():
@@ -274,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,
@@ -285,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)
@@ -306,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():
@@ -314,17 +320,23 @@ class Model:
return part
raise KeyError("Participant not found")
+ def get_player_from_campaign_participant(
+ self, camp_part: CampaignParticipant
+ ) -> Player:
+ war_part = self.get_war_participant(camp_part.war_participant_id)
+ return self.get_player(war_part.player_id)
+
def update_campaign_participant(
self,
participant_id: str,
*,
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)
@@ -346,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)
@@ -364,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,
@@ -378,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)
@@ -404,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,
@@ -417,6 +422,6 @@ class Model:
comment,
)
- def remove_battle(self, round_id: str, sector_id: str):
+ def remove_battle(self, round_id: str, sector_id: str) -> None:
war = self.get_war_by_round(round_id)
war.remove_battle(round_id, sector_id)
diff --git a/src/warchron/model/objective.py b/src/warchron/model/objective.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/warchron/model/player.py b/src/warchron/model/player.py
index 8f07176..b8b9934 100644
--- a/src/warchron/model/player.py
+++ b/src/warchron/model/player.py
@@ -1,22 +1,24 @@
+from __future__ import annotations
+from typing import Any, Dict
from uuid import uuid4
class Player:
- def __init__(self, name):
- self.id = str(uuid4())
- self.name = name
+ def __init__(self, name: str) -> None:
+ self.id: str = str(uuid4())
+ self.name: str = name
- def set_id(self, new_id):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_name(self, name):
+ def set_name(self, name: str) -> None:
self.name = name
- def toDict(self):
+ def toDict(self) -> Dict[str, Any]:
return {"id": self.id, "name": self.name}
@staticmethod
- def fromDict(data: dict):
+ def fromDict(data: Dict[str, Any]) -> Player:
play = Player(name=data["name"])
play.set_id(data["id"])
return play
diff --git a/src/warchron/model/repository.py b/src/warchron/model/repository.py
deleted file mode 100644
index d000abf..0000000
--- a/src/warchron/model/repository.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import json
-import shutil
-from pathlib import Path
-
-DATA_FILE = Path("data/warmachron.json")
-
-
-def load_data():
- if not DATA_FILE.exists() or DATA_FILE.stat().st_size == 0:
- return {"version": 1, "players": {}, "wars": []}
-
- try:
- with open(DATA_FILE, "r", encoding="utf-8") as f:
- return json.load(f)
- except json.JSONDecodeError:
- raise RuntimeError("Data file is corrupted")
-
-
-def save_data(data):
- if DATA_FILE.exists():
- shutil.copy(DATA_FILE, DATA_FILE.with_suffix(".json.bak"))
-
- with open(DATA_FILE, "w", encoding="utf-8") as f:
- json.dump(data, f, indent=2)
diff --git a/src/warchron/model/round.py b/src/warchron/model/round.py
index a975c3a..420eea8 100644
--- a/src/warchron/model/round.py
+++ b/src/warchron/model/round.py
@@ -1,21 +1,22 @@
from __future__ import annotations
from uuid import uuid4
+from typing import Any, Dict
class Round:
- def __init__(self):
+ def __init__(self) -> None:
self.id: str = str(uuid4())
- self.choices: dict[str, Choice] = {}
- self.battles: dict[str, Battle] = {}
+ self.choices: Dict[str, Choice] = {}
+ self.battles: Dict[str, Battle] = {}
self.is_over: bool = False
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_state(self, new_state: bool):
+ def set_state(self, new_state: bool) -> None:
self.is_over = new_state
- def toDict(self):
+ def toDict(self) -> Dict[str, Any]:
return {
"id": self.id,
# "sectors" : self.sectors,
@@ -25,7 +26,7 @@ class Round:
}
@staticmethod
- def fromDict(data: dict):
+ def fromDict(data: Dict[str, Any]) -> Round:
rnd = Round()
rnd.set_id(data["id"])
# rnd.sectors = data.get("sectors", {})
@@ -55,13 +56,14 @@ class Round:
priority_sector_id: str | None,
secondary_sector_id: str | None,
comment: str | None,
- ):
+ ) -> None:
choice = self.get_choice(participant_id)
- choice.set_priority(priority_sector_id)
- choice.set_secondary(secondary_sector_id)
- choice.set_comment(comment)
+ if choice:
+ choice.set_priority(priority_sector_id)
+ choice.set_secondary(secondary_sector_id)
+ choice.set_comment(comment)
- def remove_choice(self, participant_id: str):
+ def remove_choice(self, participant_id: str) -> None:
del self.choices[participant_id]
# Battle methods
@@ -84,16 +86,17 @@ class Round:
score: str | None,
victory_condition: str | None,
comment: str | None,
- ):
+ ) -> None:
bat = self.get_battle(sector_id)
- bat.set_player_1(player_1_id)
- bat.set_player_2(player_2_id)
- bat.set_winner(winner_id)
- bat.set_score(score)
- bat.set_victory_condition(victory_condition)
- bat.set_comment(comment)
+ if bat:
+ bat.set_player_1(player_1_id)
+ bat.set_player_2(player_2_id)
+ bat.set_winner(winner_id)
+ bat.set_score(score)
+ bat.set_victory_condition(victory_condition)
+ bat.set_comment(comment)
- def remove_battle(self, sector_id: str):
+ def remove_battle(self, sector_id: str) -> None:
del self.battles[sector_id]
@@ -113,16 +116,16 @@ class Choice:
)
self.comment: str | None = None
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.participant_id = new_id
- def set_priority(self, new_priority_id: str):
+ def set_priority(self, new_priority_id: str | None) -> None:
self.priority_sector_id = new_priority_id
- def set_secondary(self, new_secondary_id: str):
+ def set_secondary(self, new_secondary_id: str | None) -> None:
self.secondary_sector_id = new_secondary_id
- def set_comment(self, new_comment: str):
+ def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment
@@ -141,23 +144,23 @@ class Battle:
self.victory_condition: str | None = None
self.comment: str | None = None
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.sector_id = new_id
- def set_player_1(self, new_player_id: str):
+ def set_player_1(self, new_player_id: str | None) -> None:
self.player_1_id = new_player_id
- def set_player_2(self, new_player_id: str):
+ def set_player_2(self, new_player_id: str | None) -> None:
self.player_2_id = new_player_id
- def set_winner(self, new_player_id: str):
+ def set_winner(self, new_player_id: str | None) -> None:
self.winner_id = new_player_id
- def set_score(self, new_score: str):
+ def set_score(self, new_score: str | None) -> None:
self.score = new_score
- def set_victory_condition(self, new_victory_condition: str):
+ def set_victory_condition(self, new_victory_condition: str | None) -> None:
self.victory_condition = new_victory_condition
- def set_comment(self, new_comment: str):
+ def set_comment(self, new_comment: str | None) -> None:
self.comment = new_comment
diff --git a/src/warchron/model/war.py b/src/warchron/model/war.py
index 8cb9464..70cf6f4 100644
--- a/src/warchron/model/war.py
+++ b/src/warchron/model/war.py
@@ -1,34 +1,35 @@
from __future__ import annotations
from uuid import uuid4
from datetime import datetime
+from typing import Any, Dict, List
from warchron.model.campaign import Campaign, Sector, CampaignParticipant
from warchron.model.round import Round, Choice, Battle
class War:
- def __init__(self, name: str, year: int):
+ def __init__(self, name: str, year: int) -> None:
self.id: str = str(uuid4())
self.name: str = name
self.year: int = year
- self.participants: dict[str, WarParticipant] = {}
- self.objectives: dict[str, Objective] = {}
- self.campaigns: list[Campaign] = []
+ self.participants: Dict[str, WarParticipant] = {}
+ self.objectives: Dict[str, Objective] = {}
+ self.campaigns: List[Campaign] = []
self.is_over: bool = False
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_name(self, new_name: str):
+ def set_name(self, new_name: str) -> None:
self.name = new_name
- def set_year(self, new_year: int):
+ def set_year(self, new_year: int) -> None:
self.year = new_year
- def set_state(self, new_state: bool):
+ def set_state(self, new_state: bool) -> None:
self.is_over = new_state
- def toDict(self):
+ def toDict(self) -> Dict[str, Any]:
return {
"id": self.id,
"name": self.name,
@@ -39,7 +40,7 @@ class War:
}
@staticmethod
- def fromDict(data: dict):
+ def fromDict(data: Dict[str, Any]) -> War:
war = War(name=data["name"], year=data["year"])
war.set_id(data["id"])
# war.participants = data.get("participants", {})
@@ -58,7 +59,7 @@ class War:
def get_objective(self, id: str) -> Objective:
return self.objectives[id]
- def get_all_objectives(self) -> list[Objective]:
+ def get_all_objectives(self) -> List[Objective]:
return list(self.objectives.values())
def get_objective_name(self, objective_id: str | None) -> str:
@@ -67,12 +68,14 @@ class War:
obj = self.objectives.get(objective_id)
return obj.name if obj else ""
- def update_objective(self, objective_id: str, *, name: str, description: str):
+ def update_objective(
+ self, objective_id: str, *, name: str, description: str
+ ) -> None:
obj = self.get_objective(objective_id)
obj.set_name(name)
obj.set_description(description)
- def remove_objective(self, objective_id: str):
+ def remove_objective(self, objective_id: str) -> None:
# TODO manage sectors referring to it
del self.objectives[objective_id]
@@ -97,15 +100,15 @@ class War:
def get_war_participant(self, id: str) -> WarParticipant:
return self.participants[id]
- def get_all_war_participants(self) -> list[WarParticipant]:
+ def get_all_war_participants(self) -> List[WarParticipant]:
return list(self.participants.values())
- def update_war_participant(self, player_id: str, *, faction: str):
+ def update_war_participant(self, player_id: str, *, faction: str) -> None:
part = self.get_war_participant(player_id)
# Can't change referred Model.players
part.set_faction(faction)
- def remove_war_participant(self, player_id: str):
+ def remove_war_participant(self, player_id: str) -> None:
# TODO manage campaign_participants referring to it
del self.participants[player_id]
@@ -114,7 +117,7 @@ class War:
def has_campaign(self, campaign_id: str) -> bool:
return any(c.id == campaign_id for c in self.campaigns)
- def get_default_campaign_values(self) -> dict:
+ def get_default_campaign_values(self) -> Dict[str, Any]:
return {"month": datetime.now().month}
def add_campaign(self, name: str, month: int | None = None) -> Campaign:
@@ -150,15 +153,15 @@ class War:
return camp
raise KeyError(f"Participant {participant_id} not found in any Campaign")
- def update_campaign(self, campaign_id: str, *, name: str, month: int):
+ def update_campaign(self, campaign_id: str, *, name: str, month: int) -> None:
camp = self.get_campaign(campaign_id)
camp.set_name(name)
camp.set_month(month)
- def get_all_campaigns(self) -> list[Campaign]:
+ def get_all_campaigns(self) -> List[Campaign]:
return list(self.campaigns)
- def remove_campaign(self, campaign_id: str):
+ def remove_campaign(self, campaign_id: str) -> None:
camp = self.get_campaign(campaign_id)
self.campaigns.remove(camp)
@@ -166,7 +169,7 @@ class War:
def add_sector(
self,
- campaign_id,
+ campaign_id: str,
name: str,
round_id: str,
major_id: str,
@@ -176,8 +179,12 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_sector(name, round_id, major_id, minor_id, influence_id)
- def get_sector(self, id: str) -> Sector:
- return self.sectors[id]
+ def get_sector(self, sector_id: str) -> Sector:
+ for camp in self.campaigns:
+ for sect in camp.sectors.values():
+ if sect.id == sector_id:
+ return sect
+ raise KeyError("Sector not found")
def update_sector(
self,
@@ -188,7 +195,7 @@ class War:
major_id: str,
minor_id: str,
influence_id: str,
- ):
+ ) -> None:
camp = self.get_campaign_by_sector(sector_id)
camp.update_sector(
sector_id,
@@ -199,13 +206,13 @@ class War:
influence_id=influence_id,
)
- def remove_sector(self, sector_id: str):
+ def remove_sector(self, sector_id: str) -> None:
camp = self.get_campaign_by_sector(sector_id)
camp.remove_sector(sector_id)
# Campaign participant methods
- def get_available_war_participants(self, campaign_id: str) -> list[WarParticipant]:
+ def get_available_war_participants(self, campaign_id: str) -> List[WarParticipant]:
camp = self.get_campaign(campaign_id)
return [
part
@@ -219,8 +226,8 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_campaign_participant(participant_id, leader, theme)
- def get_campaign_participant(self, participant_id) -> CampaignParticipant:
- for camp in self.campaigns.values():
+ def get_campaign_participant(self, participant_id: str) -> CampaignParticipant:
+ for camp in self.campaigns:
for part in camp.participants.values():
if part.id == participant_id:
return part
@@ -228,11 +235,11 @@ class War:
def update_campaign_participant(
self, participant_id: str, *, leader: str, theme: str
- ):
+ ) -> None:
camp = self.get_campaign_by_campaign_participant(participant_id)
camp.update_campaign_participant(participant_id, leader=leader, theme=theme)
- def remove_campaign_participant(self, participant_id: str):
+ def remove_campaign_participant(self, participant_id: str) -> None:
camp = self.get_campaign_by_campaign_participant(participant_id)
camp.remove_campaign_participant(participant_id)
@@ -246,7 +253,7 @@ class War:
camp = self.get_campaign(campaign_id)
return camp.add_round()
- def remove_round(self, round_id: str):
+ def remove_round(self, round_id: str) -> None:
camp = self.get_campaign_by_round(round_id)
camp.remove_round(round_id)
@@ -263,13 +270,13 @@ class War:
priority_sector_id: str | None,
secondary_sector_id: str | None,
comment: str | None,
- ):
+ ) -> None:
camp = self.get_campaign_by_round(round_id)
camp.update_choice(
- participant_id, priority_sector_id, secondary_sector_id, comment
+ round_id, participant_id, priority_sector_id, secondary_sector_id, comment
)
- def remove_choice(self, round_id: str, participant_id: str):
+ def remove_choice(self, round_id: str, participant_id: str) -> None:
camp = self.get_campaign_by_round(round_id)
camp.remove_choice(round_id, participant_id)
@@ -289,9 +296,10 @@ class War:
score: str | None,
victory_condition: str | None,
comment: str | None,
- ):
+ ) -> None:
camp = self.get_campaign_by_round(round_id)
camp.update_battle(
+ round_id,
sector_id,
player_1_id,
player_2_id,
@@ -301,7 +309,7 @@ class War:
comment,
)
- def remove_battle(self, round_id: str, sector_id: str):
+ def remove_battle(self, round_id: str, sector_id: str) -> None:
camp = self.get_campaign_by_round(round_id)
camp.remove_battle(round_id, sector_id)
@@ -312,13 +320,13 @@ class Objective:
self.name: str = name
self.description: str = description
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_name(self, new_name: str):
+ def set_name(self, new_name: str) -> None:
self.name = new_name
- def set_description(self, new_description: str):
+ def set_description(self, new_description: str) -> None:
self.description = new_description
@@ -328,11 +336,11 @@ class WarParticipant:
self.player_id: str = player_id # ref to WarModel.players
self.faction: str = faction
- def set_id(self, new_id: str):
+ def set_id(self, new_id: str) -> None:
self.id = new_id
- def set_player(self, new_player: str):
+ def set_player(self, new_player: str) -> None:
self.player_id = new_player
- def set_faction(self, new_faction: str):
+ def set_faction(self, new_faction: str) -> None:
self.faction = new_faction
diff --git a/src/warchron/model/war_participant.py b/src/warchron/model/war_participant.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/warchron/view/__init__.py b/src/warchron/view/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/warchron/view/ui/ui_main_window.py b/src/warchron/view/ui/ui_main_window.py
index 04374bf..8e1cd65 100644
--- a/src/warchron/view/ui/ui_main_window.py
+++ b/src/warchron/view/ui/ui_main_window.py
@@ -70,12 +70,12 @@ class Ui_MainWindow(object):
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
self.horizontalLayout_2.addItem(spacerItem1)
self.verticalLayout_3.addLayout(self.horizontalLayout_2)
- self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
- self.horizontalLayout_6.setObjectName("horizontalLayout_6")
- self.warsTree = QtWidgets.QTreeWidget(parent=self.warsTab)
+ self.splitter = QtWidgets.QSplitter(parent=self.warsTab)
+ self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal)
+ self.splitter.setObjectName("splitter")
+ self.warsTree = QtWidgets.QTreeWidget(parent=self.splitter)
self.warsTree.setObjectName("warsTree")
- self.horizontalLayout_6.addWidget(self.warsTree)
- self.selectedDetailsStack = QtWidgets.QStackedWidget(parent=self.warsTab)
+ self.selectedDetailsStack = QtWidgets.QStackedWidget(parent=self.splitter)
self.selectedDetailsStack.setObjectName("selectedDetailsStack")
self.pageEmpty = QtWidgets.QWidget()
self.pageEmpty.setObjectName("pageEmpty")
@@ -137,8 +137,6 @@ class Ui_MainWindow(object):
self.addObjectiveBtn.setObjectName("addObjectiveBtn")
self.horizontalLayout_9.addWidget(self.addObjectiveBtn)
self.gridLayout_3.addLayout(self.horizontalLayout_9, 2, 0, 1, 4)
- spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_3.addItem(spacerItem4, 5, 3, 1, 1)
self.endWarBtn = QtWidgets.QPushButton(parent=self.pageWar)
self.endWarBtn.setEnabled(True)
self.endWarBtn.setObjectName("endWarBtn")
@@ -179,8 +177,8 @@ class Ui_MainWindow(object):
self.endCampaignBtn.setEnabled(True)
self.endCampaignBtn.setObjectName("endCampaignBtn")
self.gridLayout_4.addWidget(self.endCampaignBtn, 5, 1, 1, 1)
- spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_4.addItem(spacerItem5, 5, 2, 1, 1)
+ spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.gridLayout_4.addItem(spacerItem4, 5, 2, 1, 1)
self.horizontalLayout_12 = QtWidgets.QHBoxLayout()
self.horizontalLayout_12.setObjectName("horizontalLayout_12")
self.campaignParticipantsTable = QtWidgets.QTableWidget(parent=self.pageCampaign)
@@ -256,8 +254,8 @@ class Ui_MainWindow(object):
self.endRoundBtn.setEnabled(True)
self.endRoundBtn.setObjectName("endRoundBtn")
self.gridLayout_5.addWidget(self.endRoundBtn, 5, 1, 1, 1)
- spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
- self.gridLayout_5.addItem(spacerItem6, 5, 2, 1, 1)
+ spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum)
+ self.gridLayout_5.addItem(spacerItem5, 5, 2, 1, 1)
self.horizontalLayout_15 = QtWidgets.QHBoxLayout()
self.horizontalLayout_15.setObjectName("horizontalLayout_15")
self.battlesTable = QtWidgets.QTableWidget(parent=self.pageRound)
@@ -304,15 +302,14 @@ class Ui_MainWindow(object):
self.horizontalLayout_14.addWidget(self.roundNb)
self.gridLayout_5.addLayout(self.horizontalLayout_14, 0, 0, 1, 3)
self.selectedDetailsStack.addWidget(self.pageRound)
- self.horizontalLayout_6.addWidget(self.selectedDetailsStack)
- self.verticalLayout_3.addLayout(self.horizontalLayout_6)
+ self.verticalLayout_3.addWidget(self.splitter)
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/swords-small.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
self.tabWidget.addTab(self.warsTab, icon2, "")
self.verticalLayout.addWidget(self.tabWidget)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0, 0, 1288, 31))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1288, 21))
self.menubar.setObjectName("menubar")
self.menuFile = QtWidgets.QMenu(parent=self.menubar)
self.menuFile.setObjectName("menuFile")
@@ -387,7 +384,7 @@ class Ui_MainWindow(object):
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(1)
- self.selectedDetailsStack.setCurrentIndex(3)
+ self.selectedDetailsStack.setCurrentIndex(1)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
diff --git a/src/warchron/view/ui/ui_main_window.ui b/src/warchron/view/ui/ui_main_window.ui
index 49cde9f..0be2351 100644
--- a/src/warchron/view/ui/ui_main_window.ui
+++ b/src/warchron/view/ui/ui_main_window.ui
@@ -137,490 +137,476 @@
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- 3
+
+
+ Qt::Horizontal
+
+
+
+
+
-
-
-
-
-
-
- QLayout::SetDefaultConstraint
-
-
-
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- Select an element within the tree to show/edit details.
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
- Name
-
-
-
-
- Faction
-
-
-
-
- Campaigns
-
-
-
-
- Victory pts.
-
-
-
-
- Theme pts
-
-
-
-
- -
-
-
- Add participant
-
-
-
-
-
- -
-
-
-
-
-
-
- Name
-
-
-
-
- Description
-
-
-
-
- -
-
-
- true
-
-
-
- 10
-
-
-
- Add objective
-
-
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
- true
-
-
- End war
-
-
-
- -
-
-
- Participants
-
-
-
- -
-
-
- Objectives
-
-
-
- -
-
-
-
-
-
-
- 12
-
-
-
- warName
-
-
-
- -
-
-
-
- 12
-
-
-
- warYear
-
-
-
-
-
-
-
-
-
- -
-
-
- Sectors
-
-
-
- -
-
-
- Participants
-
-
-
- -
-
-
- true
-
-
- End campaign
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
-
-
-
- Name
-
-
-
-
- Leader
-
-
-
-
- Theme
-
-
-
-
- Victory pts.
-
-
-
-
- Theme pts.
-
-
-
-
- -
-
-
- Add participant
-
-
-
-
-
- -
-
-
-
-
-
-
- Name
-
-
-
-
- Round
-
-
-
-
- Major obj.
-
-
-
-
- Minor opp.
-
-
-
-
- Influence imp.
-
-
-
-
- Description
-
-
-
-
- -
-
-
- true
-
-
- Add Sector
-
-
-
-
-
- -
-
-
-
-
-
-
- 12
-
-
-
- campaignName
-
-
-
- -
-
-
-
- 12
-
-
-
- campaignMonth
-
-
-
-
-
-
-
-
-
- -
-
-
- Choices
-
-
-
- -
-
-
- Battles
-
-
-
- -
-
-
- true
-
-
- End round
-
-
-
- -
-
-
- Qt::Horizontal
-
-
-
- 40
- 20
-
-
-
-
- -
-
-
-
-
-
-
- Sector
-
-
-
-
- Player 1
-
-
-
-
- Player 2
-
-
-
-
- -
-
-
- false
-
-
- Count results
-
-
-
-
-
- -
-
-
-
-
-
-
- Player
-
-
-
-
- Priority
-
-
-
-
- Secondary
-
-
-
-
- -
-
-
- true
-
-
- Resolve pairing
-
-
-
-
-
- -
-
-
-
-
-
-
- 12
-
-
-
- Round Nb
-
-
-
-
-
-
-
+
+
+
+
+ 1
+
+
+
+ -
+
+
+ QLayout::SetDefaultConstraint
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Select an element within the tree to show/edit details.
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
-
-
+
+
+ -
+
+
-
+
+
+
+ Name
+
+
+
+
+ Faction
+
+
+
+
+ Campaigns
+
+
+
+
+ Victory pts.
+
+
+
+
+ Theme pts
+
+
+
+
+ -
+
+
+ Add participant
+
+
+
+
+
+ -
+
+
-
+
+
+
+ Name
+
+
+
+
+ Description
+
+
+
+
+ -
+
+
+ true
+
+
+
+ 10
+
+
+
+ Add objective
+
+
+
+
+
+ -
+
+
+ true
+
+
+ End war
+
+
+
+ -
+
+
+ Participants
+
+
+
+ -
+
+
+ Objectives
+
+
+
+ -
+
+
-
+
+
+
+ 12
+
+
+
+ warName
+
+
+
+ -
+
+
+
+ 12
+
+
+
+ warYear
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Sectors
+
+
+
+ -
+
+
+ Participants
+
+
+
+ -
+
+
+ true
+
+
+ End campaign
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+
+ Name
+
+
+
+
+ Leader
+
+
+
+
+ Theme
+
+
+
+
+ Victory pts.
+
+
+
+
+ Theme pts.
+
+
+
+
+ -
+
+
+ Add participant
+
+
+
+
+
+ -
+
+
-
+
+
+
+ Name
+
+
+
+
+ Round
+
+
+
+
+ Major obj.
+
+
+
+
+ Minor opp.
+
+
+
+
+ Influence imp.
+
+
+
+
+ Description
+
+
+
+
+ -
+
+
+ true
+
+
+ Add Sector
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 12
+
+
+
+ campaignName
+
+
+
+ -
+
+
+
+ 12
+
+
+
+ campaignMonth
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Choices
+
+
+
+ -
+
+
+ Battles
+
+
+
+ -
+
+
+ true
+
+
+ End round
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
-
+
+
+
+ Sector
+
+
+
+
+ Player 1
+
+
+
+
+ Player 2
+
+
+
+
+ -
+
+
+ false
+
+
+ Count results
+
+
+
+
+
+ -
+
+
-
+
+
+
+ Player
+
+
+
+
+ Priority
+
+
+
+
+ Secondary
+
+
+
+
+ -
+
+
+ true
+
+
+ Resolve pairing
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 12
+
+
+
+ Round Nb
+
+
+
+
+
+
+
+
+
@@ -634,7 +620,7 @@
0
0
1288
- 31
+ 21