warchron_app/src/warchron/controller/app_controller.py
2026-02-10 11:09:58 +01:00

246 lines
10 KiB
Python

from pathlib import Path
from PyQt6.QtWidgets import QMessageBox
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.navigation_controller import NavigationController
from warchron.controller.player_controller import PlayerController
from warchron.controller.war_controller import WarController
from warchron.controller.campaign_controller import CampaignController
from warchron.controller.round_controller import RoundController
class AppController:
def __init__(self, model: Model, view: View) -> None:
self.model: Model = model
self.view: View = view
self.navigation = NavigationController(self)
self.players = PlayerController(self)
self.wars = WarController(self)
self.campaigns = CampaignController(self)
self.rounds = RoundController(self)
self.current_file: Path | None = None
self.view.on_close_callback = self.on_app_close
self.is_dirty: bool = False
self.__connect()
self.navigation.refresh_players_view()
self.navigation.refresh_wars_view()
self.update_window_title()
self.view.on_tree_selection_changed = self.navigation.on_tree_selection_changed
self.view.on_add_campaign = self.campaigns.add_campaign
self.view.on_add_round = self.rounds.add_round
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)
self.view.actionSave.triggered.connect(self.save)
self.view.actionSave_as.triggered.connect(self.save_as)
self.view.addPlayerBtn.clicked.connect(self.players.add_player)
self.view.addWarBtn.clicked.connect(self.wars.add_war)
self.view.addObjectiveBtn.clicked.connect(self.wars.add_objective)
self.view.addWarParticipantBtn.clicked.connect(self.wars.add_war_participant)
self.view.addSectorBtn.clicked.connect(self.campaigns.add_sector)
self.view.addCampaignParticipantBtn.clicked.connect(
self.campaigns.add_campaign_participant
)
self.view.on_edit_item = self.edit_item
self.view.on_delete_item = self.delete_item
def on_app_close(self) -> bool:
if self.is_dirty:
reply = QMessageBox.question(
self.view,
"Unsaved changes",
"You have unsaved changes. Do you want to save before quitting?",
QMessageBox.StandardButton.Yes
| QMessageBox.StandardButton.No
| QMessageBox.StandardButton.Cancel,
)
if reply == QMessageBox.StandardButton.Yes:
self.save()
elif reply == QMessageBox.StandardButton.Cancel:
return False
return True
# Menu bar methods
def new(self) -> None:
if self.is_dirty:
reply = QMessageBox.question(
self.view,
"Unsaved changes",
"Discard current campaign?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply != QMessageBox.StandardButton.Yes:
return
self.model.new()
self.current_file = None
self.is_dirty = False
self.navigation.refresh_players_view()
self.navigation.refresh_wars_view()
self.update_window_title()
def open_file(self) -> None:
if self.is_dirty:
reply = QMessageBox.question(
self.view,
"Unsaved changes",
"Discard current campaign?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply != QMessageBox.StandardButton.Yes:
return
path = self.view.ask_open_file()
if not path:
return
self.model.load(path)
self.current_file = path
self.is_dirty = False
self.navigation.refresh_players_view()
self.navigation.refresh_wars_view()
self.update_window_title()
def save(self) -> None:
if not self.current_file:
self.save_as()
return
self.model.save(self.current_file)
self.is_dirty = False
self.update_window_title()
def save_as(self) -> None:
path = self.view.ask_save_file()
if not path:
return
self.current_file = path
self.model.save(path)
self.is_dirty = False
self.update_window_title()
# Display methods
def update_window_title(self) -> None:
base = "WarChron"
if self.current_file:
base += f" - {self.current_file.name}"
else:
base += " - New file"
if self.is_dirty:
base = base + " *"
self.view.setWindowTitle(base)
# Command methods
def edit_item(self, item_type: str, item_id: str) -> None:
try:
if item_type == ItemType.PLAYER:
self.players.edit_player(item_id)
self.navigation.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR:
self.wars.edit_war(item_id)
self.navigation.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.WAR, item_id=item_id
)
elif item_type == ItemType.CAMPAIGN:
self.campaigns.edit_campaign(item_id)
self.navigation.refresh_and_select(
RefreshScope.WARS_TREE, item_type=ItemType.CAMPAIGN, item_id=item_id
)
elif item_type == ItemType.OBJECTIVE:
self.wars.edit_objective(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT:
self.wars.edit_war_participant(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.SECTOR:
self.campaigns.edit_sector(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
self.campaigns.edit_campaign_participant(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.CHOICE:
self.rounds.edit_round_choice(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.BATTLE:
self.rounds.edit_round_battle(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_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.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
def delete_item(self, item_type: str, item_id: str) -> None:
reply = QMessageBox.question(
self.view,
"Confirm deletion",
"Are you sure you want to delete this item?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply != QMessageBox.StandardButton.Yes:
return
try:
if item_type == ItemType.PLAYER:
self.model.remove_player(item_id)
self.navigation.refresh(RefreshScope.PLAYERS_LIST)
elif item_type == ItemType.WAR:
self.model.remove_war(item_id)
self.navigation.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.navigation.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.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.WAR_PARTICIPANT:
self.model.remove_war_participant(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.SECTOR:
self.model.remove_sector(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)
elif item_type == ItemType.CAMPAIGN_PARTICIPANT:
self.model.remove_campaign_participant(item_id)
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_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.navigation.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,
)
except DeletionRequiresConfirmation as e:
reply = QMessageBox.question(
self.view,
"Confirm deletion",
e.message,
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
)
if reply == QMessageBox.StandardButton.Yes:
e.cleanup_action()
self.navigation.refresh(RefreshScope.CURRENT_SELECTION_DETAILS)