from pathlib import Path import calendar from PyQt6 import QtWidgets from PyQt6.QtCore import Qt from PyQt6.QtWidgets import QDialog, QFileDialog, QTreeWidgetItem, QMenu from PyQt6.QtGui import QCloseEvent from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType 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_participant_dialog import Ui_participantDialog class View(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(View, self).__init__(parent) self.setupUi(self) self.on_close_callback = None self.on_selection_changed = None self.on_add_campaign = None self.on_add_round = None self.on_edit_item = None self.on_delete_item = None self.show_details(None) self.playersTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.playersTable.customContextMenuRequested.connect(self._on_players_table_context_menu) self.warParticipantsTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.warParticipantsTable.customContextMenuRequested.connect(self._on_war_participants_table_context_menu) self.objectivesTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.objectivesTable.customContextMenuRequested.connect(self._on_objectives_table_context_menu) self.warsTree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.warsTree.customContextMenuRequested.connect(self._on_wars_tree_context_menu) self.addCampaignBtn.clicked.connect(self._on_add_campaign_clicked) self.addRoundBtn.clicked.connect(self._on_add_round_clicked) def _emit_selection_changed(self, current, previous): if not self.on_tree_selection_changed: return if not current: self.on_tree_selection_changed(None) return self.on_tree_selection_changed({ "type": current.data(0, ROLE_TYPE), "id": current.data(0, ROLE_ID), }) def get_current_tab(self) -> str: index = self.tabWidget.currentIndex() if index == 0: return "players" elif index == 1: return "wars" return "" # General popups def closeEvent(self, event: QCloseEvent): if self.on_close_callback: proceed = self.on_close_callback() if not proceed: event.ignore() return event.accept() def ask_open_file(self) -> Path | None: filename, _ = QFileDialog.getOpenFileName( self, "Open war history", "", "WarChron files (*.json)" ) return Path(filename) if filename else None def ask_save_file(self) -> Path | None: filename, _ = QFileDialog.getSaveFileName( self, "Save war history", "", "WarChron files (*.json)" ) return Path(filename) if filename else None # Players view def _on_players_table_context_menu(self, pos): item = self.playersTable.itemAt(pos) if not item: return row = item.row() name_item = self.playersTable.item(row, 0) if not name_item: return player_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") action = menu.exec(self.playersTable.viewport().mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.PLAYER, player_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.PLAYER, player_id) def display_players(self, players: list): table = self.playersTable table.setRowCount(len(players)) for row, player in enumerate(players): play_item = QtWidgets.QTableWidgetItem(player.name) play_item.setData(Qt.ItemDataRole.UserRole, player.id) table.setItem(row, 0, play_item) table.resizeColumnsToContents() # Wars view def _on_add_campaign_clicked(self): if self.on_add_campaign: self.on_add_campaign() def _on_add_round_clicked(self): if self.on_add_round: self.on_add_round() def set_add_campaign_enabled(self, enabled: bool): self.addCampaignBtn.setEnabled(enabled) def set_add_round_enabled(self, enabled: bool): self.addRoundBtn.setEnabled(enabled) def _on_wars_tree_context_menu(self, pos): item = self.warsTree.itemAt(pos) if not item: return item_type = item.data(0, ROLE_TYPE) item_id = item.data(0, ROLE_ID) menu = QMenu(self) edit_action = None if item_type != ItemType.ROUND: edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") action = menu.exec(self.warsTree.viewport().mapToGlobal(pos)) if action == edit_action: if self.on_edit_item: self.on_edit_item(item_type, item_id) elif action == delete_action: if self.on_delete_item: self.on_delete_item(item_type, item_id) def _format_war_label(self, war) -> str: return f"{war.name} ({war.year})" def _format_campaign_label(self, camp) -> str: return f"{camp.name} ({calendar.month_name[camp.month]})" def _format_round_label(self, round, index: int) -> str: return f"Round {index}" def display_wars_tree(self, wars: list): tree = self.warsTree tree.clear() tree.setColumnCount(1) tree.setHeaderLabels(["Wars"]) for war in wars: war_item = QTreeWidgetItem([self._format_war_label(war)]) war_item.setData(0, ROLE_TYPE, ItemType.WAR) war_item.setData(0, ROLE_ID, war.id) tree.addTopLevelItem(war_item) for camp in war.get_all_campaigns(): camp_item = QTreeWidgetItem([self._format_campaign_label(camp)]) camp_item.setData(0, ROLE_TYPE, ItemType.CAMPAIGN) camp_item.setData(0, ROLE_ID, camp.id) war_item.addChild(camp_item) for index, rnd in enumerate(camp.get_all_rounds(), start=1): rnd_item = QTreeWidgetItem([self._format_round_label(rnd, index)]) rnd_item.setData(0, ROLE_TYPE, ItemType.ROUND) rnd_item.setData(0, ROLE_ID, rnd.id) camp_item.addChild(rnd_item) tree.currentItemChanged.connect(self._emit_selection_changed) tree.expandAll() def select_tree_item(self, *, item_type: ItemType, item_id: str): def walk(item: QTreeWidgetItem): if ( item.data(0, ROLE_TYPE) == item_type and item.data(0, ROLE_ID) == item_id ): self.warsTree.setCurrentItem(item) return True for i in range(item.childCount()): if walk(item.child(i)): return True return False for i in range(self.warsTree.topLevelItemCount()): if walk(self.warsTree.topLevelItem(i)): return def get_selected_tree_item(self): item = self.warsTree.currentItem() if not item: return None return { "type": item.data(0, ROLE_TYPE), "id": item.data(0, ROLE_ID) } def show_details(self, item_type: str | None): if item_type == ItemType.WAR: self.selectedDetailsStack.setCurrentWidget(self.pageWar) elif item_type == ItemType.CAMPAIGN: self.selectedDetailsStack.setCurrentWidget(self.pageCampaign) elif item_type == ItemType.ROUND: self.selectedDetailsStack.setCurrentWidget(self.pageRound) else: self.selectedDetailsStack.setCurrentWidget(self.pageEmpty) # War page def _on_objectives_table_context_menu(self, pos): item = self.objectivesTable.itemAt(pos) if not item: return row = item.row() name_item = self.objectivesTable.item(row, 0) if not name_item: return objective_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") action = menu.exec(self.objectivesTable.viewport().mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.OBJECTIVE, objective_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.OBJECTIVE, objective_id) def _on_war_participants_table_context_menu(self, pos): item = self.warParticipantsTable.itemAt(pos) if not item: return row = item.row() name_item = self.warParticipantsTable.item(row, 0) if not name_item: return participant_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) edit_action = menu.addAction("Edit") delete_action = menu.addAction("Delete") action = menu.exec(self.warParticipantsTable.viewport().mapToGlobal(pos)) if action == edit_action and self.on_edit_item: self.on_edit_item(ItemType.WAR_PARTICIPANT, participant_id) elif action == delete_action and self.on_delete_item: self.on_delete_item(ItemType.WAR_PARTICIPANT, participant_id) def show_war_details(self, *, name: str, year: int): self.warName.setText(name) self.warYear.setText(str(year)) def display_war_objectives(self, objectives: list): table = self.objectivesTable table.clearContents() table.setRowCount(len(objectives)) for row, obj in enumerate(objectives): name_item = QtWidgets.QTableWidgetItem(obj.name) desc_item = QtWidgets.QTableWidgetItem(obj.description) name_item.setData(Qt.ItemDataRole.UserRole, obj.id) table.setItem(row, 0, name_item) table.setItem(row, 1, desc_item) table.resizeColumnsToContents() def display_war_participants(self, participants: list[tuple[str, str, str]]): table = self.warParticipantsTable table.clearContents() table.setRowCount(len(participants)) for row, (name, faction, pid) in enumerate(participants): name_item = QtWidgets.QTableWidgetItem(name) fact_item = QtWidgets.QTableWidgetItem(faction) name_item.setData(Qt.ItemDataRole.UserRole, pid) table.setItem(row, 0, name_item) table.setItem(row, 1, fact_item) table.resizeColumnsToContents() # Campaign page def show_campaign_details(self, *, name: str, month: int): self.campaignName.setText(name) self.campaignMonth.setText(calendar.month_name[month]) # Round page def show_round_details(self, *, index: int): self.roundNb.setText(f"Round {index}") class PlayerDialog(QDialog): def __init__(self, parent=None, *, default_name: str = ""): super().__init__(parent) self.ui = Ui_playerDialog() self.ui.setupUi(self) 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=None, default_name: str = "", default_year: int | None = None): super().__init__(parent) self.ui = Ui_warDialog() self.ui.setupUi(self) 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=None, default_name: str = "", default_month: int | None = None): super().__init__(parent) self.ui = Ui_campaignDialog() self.ui.setupUi(self) 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=None, *, default_name="", default_description=""): super().__init__(parent) self.ui = Ui_objectiveDialog() self.ui.setupUi(self) 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 ParticipantDialog(QDialog): def __init__(self, parent=None, *, players: list, default_player_id=None, default_faction="", editable_player=True): super().__init__(parent) self.ui = Ui_participantDialog() self.ui.setupUi(self) for player in players: self.ui.playerComboBox.addItem(player.name, player.id) if default_player_id: index = self.ui.playerComboBox.findData(default_player_id) if index != -1: self.ui.playerComboBox.setCurrentIndex(index) self.ui.playerComboBox.setEnabled(editable_player) self.ui.faction.setText(default_faction) def get_player_id(self) -> str: return self.ui.playerComboBox.currentData() def get_participant_faction(self) -> str: return self.ui.faction.text().strip()