rename app ; add new,open,save actions
4
LICENSE
|
|
@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
Wargame_campain_app
|
warchron_app
|
||||||
Copyright (C) 2025 maximator
|
Copyright (C) 2025 maximator
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
Wargame_campain_app Copyright (C) 2025 maximator
|
warchron_app Copyright (C) 2025 maximator
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
|
|
||||||
6
Makefile
|
|
@ -1,5 +1,5 @@
|
||||||
ress:
|
ress:
|
||||||
pyrcc5 .\src\wargame_campaign\view\resources\ui_ressources.qrc -o .\src\wargame_campaign\view\resources\ui_ressources_rc.py
|
pyrcc5 .\src\warchron\view\resources\ui_ressources.qrc -o .\src\warchron\view\resources\ui_ressources_rc.py
|
||||||
|
|
||||||
installer :
|
installer :
|
||||||
python -m PyInstaller .\main.spec
|
python -m PyInstaller .\main.spec
|
||||||
|
|
@ -14,11 +14,11 @@ ui: $(PY_FILES)
|
||||||
|
|
||||||
# Pattern rule: .ui -> .py using pyuic5
|
# Pattern rule: .ui -> .py using pyuic5
|
||||||
$(UI_DIR)/%.py: $(UI_DIR)/%.ui
|
$(UI_DIR)/%.py: $(UI_DIR)/%.ui
|
||||||
pyuic5 -x $< -o $@ --import-from wargame_campaign.view.resources
|
pyuic5 -x $< -o $@ --import-from warchron.view.resources
|
||||||
|
|
||||||
# Function to generate UI file from given name
|
# Function to generate UI file from given name
|
||||||
_ui_generate:
|
_ui_generate:
|
||||||
pyuic6 -x .\src\wargame_campaign\view\ui\$(UI_NAME).ui -o .\src\wargame_campaign\view\ui\$(UI_NAME).py --import-from wargame_campaign.view.resources
|
pyuic6 -x .\src\warchron\view\ui\$(UI_NAME).ui -o .\src\warchron\view\ui\$(UI_NAME).py --import-from warchron.view.resources
|
||||||
|
|
||||||
# Set default UI_NAME if not provided
|
# Set default UI_NAME if not provided
|
||||||
UI_NAME ?= ui_main_window
|
UI_NAME ?= ui_main_window
|
||||||
|
|
|
||||||
21
README.md
|
|
@ -1,21 +1,22 @@
|
||||||
# Wargame_campaign_app
|
# WarChron
|
||||||
|
|
||||||
A simple local app to manage players and their scores throughout several organised games of a tabletop wargame.
|
A simple local app to track players' campaigns for tabletop wargames.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Main logic
|
### Main logic
|
||||||
|
|
||||||
Manage a list of players to sign them up to be selectable for war(s) and campaign(s).
|
Manage a list of players to sign them up to be selectable for war(s) and campaign(s).
|
||||||
A year "war" contains several "campaign" events which contain several "battle" games organised in successive rounds.
|
A "war" year contains several "campaign" events which contain several "battle" games organised in successive rounds.
|
||||||
Battle results determine campaign score which determines the war score. Wars are independent.
|
Battle results determine campaign score which determines the war score.
|
||||||
|
Wars are independent.
|
||||||
|
|
||||||
### Design notes
|
### Design notes
|
||||||
|
|
||||||
Players are global identities
|
* Players are global identities
|
||||||
Influence tokens are scoped to a war
|
* Influence tokens are scoped to a war
|
||||||
Campaign order enables historical tie-breakers
|
* Campaign order enables historical tie-breakers
|
||||||
Effects are generic → future-proof
|
* Effects are generic → future-proof
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
@ -27,8 +28,8 @@ Effects are generic → future-proof
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone <your-forge-address>/Wargame_campaign_app.git
|
git clone <your-forge-address>/warchron_app.git
|
||||||
cd Wargame_campaign_app
|
cd warchron_app
|
||||||
python -m venv .venv
|
python -m venv .venv
|
||||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,24 @@
|
||||||
|
|
||||||
"players": [
|
"players": [
|
||||||
{
|
{
|
||||||
"id" : "p1",
|
"id": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
|
||||||
"name" :"Alice"
|
"name": "Alice"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : "p2",
|
"id": "b7eebce7-cf04-40bc-b80c-400585adb3cd",
|
||||||
"name" :"Bob"
|
"name": "Bob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id" : "p3",
|
"id": "f87e6d53-30a2-4dd8-a359-d860404ef2ee",
|
||||||
"name" :"Charlie"
|
"name": "Charlie"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "056011da-b0c7-4dc7-8b7c-14213a8df009",
|
||||||
|
"name": "Dave"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "50e83cb3-b828-4f1e-ad89-7644a84f3d8c",
|
||||||
|
"name": "Eve"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
@ -23,11 +31,11 @@
|
||||||
"year": 2025,
|
"year": 2025,
|
||||||
|
|
||||||
"registered_players": {
|
"registered_players": {
|
||||||
"p1": {
|
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": {
|
||||||
"war_points": 0,
|
"war_points": 0,
|
||||||
"influence_tokens": 1
|
"influence_tokens": 1
|
||||||
},
|
},
|
||||||
"p2": {
|
"b7eebce7-cf04-40bc-b80c-400585adb3cd": {
|
||||||
"war_points": 0,
|
"war_points": 0,
|
||||||
"influence_tokens": 0
|
"influence_tokens": 0
|
||||||
}
|
}
|
||||||
|
|
@ -40,8 +48,8 @@
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"month" : "June",
|
"month" : "June",
|
||||||
"participants": {
|
"participants": {
|
||||||
"p1": { "campaign_points": 0 },
|
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "campaign_points": 0 },
|
||||||
"p2": { "campaign_points": 0 }
|
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "campaign_points": 0 }
|
||||||
},
|
},
|
||||||
|
|
||||||
"rounds": [
|
"rounds": [
|
||||||
|
|
@ -50,15 +58,15 @@
|
||||||
"sectors": ["North", "South"],
|
"sectors": ["North", "South"],
|
||||||
|
|
||||||
"choices": {
|
"choices": {
|
||||||
"p1": { "primary": "North", "secondary": "South" },
|
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "primary": "North", "secondary": "South" },
|
||||||
"p2": { "primary": "North", "secondary": "South" }
|
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "primary": "North", "secondary": "South" }
|
||||||
},
|
},
|
||||||
|
|
||||||
"battles": [
|
"battles": [
|
||||||
{
|
{
|
||||||
"sector": "North",
|
"sector": "North",
|
||||||
"players": ["p1", "p2"],
|
"players": ["e7844fbb-8366-44e4-bb43-89b9eef6ef64", "b7eebce7-cf04-40bc-b80c-400585adb3cd"],
|
||||||
"winner": "p1",
|
"winner": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
|
||||||
|
|
||||||
"effects": {
|
"effects": {
|
||||||
"campaign_points": 1,
|
"campaign_points": 1,
|
||||||
|
|
|
||||||
6
main.py
|
|
@ -4,9 +4,9 @@ sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src")
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
from wargame_campaign.view.view import View
|
from warchron.view.view import View
|
||||||
from wargame_campaign.model.model import Model
|
from warchron.model.model import Model
|
||||||
from wargame_campaign.controller.controller import Controller
|
from warchron.controller.controller import Controller
|
||||||
|
|
||||||
if sys.version_info < (3, 12):
|
if sys.version_info < (3, 12):
|
||||||
raise RuntimeError("Python 3.12 or higher is required")
|
raise RuntimeError("Python 3.12 or higher is required")
|
||||||
|
|
|
||||||
119
src/warchron/controller/controller.py
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import QMessageBox, QDialog
|
||||||
|
|
||||||
|
from warchron.view.view import PlayerDialog
|
||||||
|
|
||||||
|
class Controller:
|
||||||
|
def __init__(self, model, view):
|
||||||
|
self.model = model
|
||||||
|
self.view = view
|
||||||
|
self.current_file: Path | None = None
|
||||||
|
self.view.on_close_callback = self.on_app_close
|
||||||
|
self.is_dirty = False
|
||||||
|
self.__connect()
|
||||||
|
self.refresh_players_view()
|
||||||
|
|
||||||
|
def __connect(self):
|
||||||
|
self.view.addPlayerBtn.clicked.connect(self.add_player)
|
||||||
|
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)
|
||||||
|
|
||||||
|
def refresh_players_view(self):
|
||||||
|
players = self.model.get_all_players()
|
||||||
|
self.view.display_players(players)
|
||||||
|
|
||||||
|
def new(self):
|
||||||
|
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.refresh_players_view()
|
||||||
|
self.update_window_title()
|
||||||
|
|
||||||
|
def open_file(self):
|
||||||
|
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.refresh_players_view()
|
||||||
|
self.update_window_title()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
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):
|
||||||
|
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()
|
||||||
|
|
||||||
|
def update_window_title(self):
|
||||||
|
base = "WarChron"
|
||||||
|
if self.current_file:
|
||||||
|
base += f" - {self.current_file.name}"
|
||||||
|
if self.is_dirty:
|
||||||
|
base = "*" + base
|
||||||
|
self.view.setWindowTitle(base)
|
||||||
|
|
||||||
|
|
||||||
|
def add_player(self):
|
||||||
|
dialog = PlayerDialog(self.view)
|
||||||
|
result = dialog.exec() # modal blocking dialog
|
||||||
|
if result == QDialog.DialogCode.Accepted:
|
||||||
|
name = dialog.get_player_name()
|
||||||
|
if not name:
|
||||||
|
QMessageBox.warning(
|
||||||
|
self.view,
|
||||||
|
"Invalid name",
|
||||||
|
"Player name cannot be empty."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
self.model.add_player(name)
|
||||||
|
self.is_dirty = True
|
||||||
|
self.refresh_players_view()
|
||||||
|
self.update_window_title()
|
||||||
|
|
@ -2,20 +2,30 @@ from pathlib import Path
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from wargame_campaign.model.player import Player
|
from warchron.model.player import Player
|
||||||
|
|
||||||
class Model:
|
class Model:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.players = {}
|
self.players = {}
|
||||||
data_file_path = Path("data/warmachron.json")
|
|
||||||
self.load_data(data_file_path)
|
|
||||||
self.save_data(data_file_path)
|
|
||||||
|
|
||||||
def load_data(self, data_file_path):
|
def new(self):
|
||||||
if not data_file_path.exists() or data_file_path.stat().st_size == 0:
|
self.players.clear()
|
||||||
pass # Create empty json
|
# self.wars.clear()
|
||||||
|
# self.campaigns.clear()
|
||||||
|
# self.rounds.clear()
|
||||||
|
|
||||||
|
def load(self, path: Path):
|
||||||
|
self.players.clear()
|
||||||
|
self._load_data(path)
|
||||||
|
|
||||||
|
def save(self, path: Path):
|
||||||
|
self._save_data(path)
|
||||||
|
|
||||||
|
def _load_data(self, path: Path):
|
||||||
|
if not path.exists() or path.stat().st_size == 0:
|
||||||
|
return # Start empty
|
||||||
try:
|
try:
|
||||||
with open(data_file_path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
for player in data["players"] :
|
for player in data["players"] :
|
||||||
saved_player = Player.fromDict(player["id"], player['name'])
|
saved_player = Player.fromDict(player["id"], player['name'])
|
||||||
|
|
@ -26,16 +36,16 @@ class Model:
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
raise RuntimeError("Data file is corrupted")
|
raise RuntimeError("Data file is corrupted")
|
||||||
|
|
||||||
def save_data(self, data_file_path):
|
def _save_data(self, path: Path):
|
||||||
if data_file_path.exists():
|
if path.exists():
|
||||||
shutil.copy(data_file_path, data_file_path.with_suffix(".json.bak"))
|
shutil.copy(path, path.with_suffix(".json.bak"))
|
||||||
data = {}
|
data = {}
|
||||||
data['version'] = "1.0"
|
data['version'] = "1.0"
|
||||||
data['players'] = []
|
data['players'] = []
|
||||||
data['wars'] = []
|
data['wars'] = []
|
||||||
for player in self.players.values():
|
for player in self.players.values():
|
||||||
data['players'].append(player.toDict())
|
data['players'].append(player.toDict())
|
||||||
with open(data_file_path, "w", encoding="utf-8") as f:
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
def add_player(self, name):
|
def add_player(self, name):
|
||||||
|
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 613 B After Width: | Height: | Size: 613 B |
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 677 B |
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 507 B |
|
Before Width: | Height: | Size: 485 B After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 618 B After Width: | Height: | Size: 618 B |
|
Before Width: | Height: | Size: 476 B After Width: | Height: | Size: 476 B |
|
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 692 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 521 B After Width: | Height: | Size: 521 B |
|
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
BIN
src/warchron/view/resources/swords-small.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/warchron/view/resources/swords.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
|
@ -1,4 +1,4 @@
|
||||||
# Form implementation generated from reading ui file '.\src\wargame_campaign\view\ui\ui_main_window.ui'
|
# Form implementation generated from reading ui file '.\src\warchron\view\ui\ui_main_window.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt6 UI code generator 6.7.1
|
# Created by: PyQt6 UI code generator 6.7.1
|
||||||
#
|
#
|
||||||
|
|
@ -14,7 +14,7 @@ class Ui_MainWindow(object):
|
||||||
MainWindow.setObjectName("MainWindow")
|
MainWindow.setObjectName("MainWindow")
|
||||||
MainWindow.resize(800, 600)
|
MainWindow.resize(800, 600)
|
||||||
icon = QtGui.QIcon()
|
icon = QtGui.QIcon()
|
||||||
icon.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/wargame_campaign_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/warchron_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
MainWindow.setWindowIcon(icon)
|
MainWindow.setWindowIcon(icon)
|
||||||
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||||
self.centralwidget.setObjectName("centralwidget")
|
self.centralwidget.setObjectName("centralwidget")
|
||||||
|
|
@ -36,7 +36,7 @@ class Ui_MainWindow(object):
|
||||||
self.addPlayerBtn.setGeometry(QtCore.QRect(20, 20, 75, 23))
|
self.addPlayerBtn.setGeometry(QtCore.QRect(20, 20, 75, 23))
|
||||||
self.addPlayerBtn.setObjectName("addPlayerBtn")
|
self.addPlayerBtn.setObjectName("addPlayerBtn")
|
||||||
icon1 = QtGui.QIcon()
|
icon1 = QtGui.QIcon()
|
||||||
icon1.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/users.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon1.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/users.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.tabWidget.addTab(self.playersTab, icon1, "")
|
self.tabWidget.addTab(self.playersTab, icon1, "")
|
||||||
self.warsTab = QtWidgets.QWidget()
|
self.warsTab = QtWidgets.QWidget()
|
||||||
self.warsTab.setObjectName("warsTab")
|
self.warsTab.setObjectName("warsTab")
|
||||||
|
|
@ -52,7 +52,7 @@ class Ui_MainWindow(object):
|
||||||
self.addCampaignBtn.setGeometry(QtCore.QRect(110, 20, 91, 23))
|
self.addCampaignBtn.setGeometry(QtCore.QRect(110, 20, 91, 23))
|
||||||
self.addCampaignBtn.setObjectName("addCampaignBtn")
|
self.addCampaignBtn.setObjectName("addCampaignBtn")
|
||||||
icon2 = QtGui.QIcon()
|
icon2 = QtGui.QIcon()
|
||||||
icon2.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/swords-small.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
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.tabWidget.addTab(self.warsTab, icon2, "")
|
||||||
MainWindow.setCentralWidget(self.centralwidget)
|
MainWindow.setCentralWidget(self.centralwidget)
|
||||||
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
||||||
|
|
@ -70,48 +70,48 @@ class Ui_MainWindow(object):
|
||||||
MainWindow.setStatusBar(self.statusbar)
|
MainWindow.setStatusBar(self.statusbar)
|
||||||
self.actionNew = QtGui.QAction(parent=MainWindow)
|
self.actionNew = QtGui.QAction(parent=MainWindow)
|
||||||
icon3 = QtGui.QIcon()
|
icon3 = QtGui.QIcon()
|
||||||
icon3.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/document.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon3.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/document.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionNew.setIcon(icon3)
|
self.actionNew.setIcon(icon3)
|
||||||
self.actionNew.setObjectName("actionNew")
|
self.actionNew.setObjectName("actionNew")
|
||||||
self.actionOpen = QtGui.QAction(parent=MainWindow)
|
self.actionOpen = QtGui.QAction(parent=MainWindow)
|
||||||
icon4 = QtGui.QIcon()
|
icon4 = QtGui.QIcon()
|
||||||
icon4.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/folder.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon4.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/folder.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionOpen.setIcon(icon4)
|
self.actionOpen.setIcon(icon4)
|
||||||
self.actionOpen.setObjectName("actionOpen")
|
self.actionOpen.setObjectName("actionOpen")
|
||||||
self.actionSave = QtGui.QAction(parent=MainWindow)
|
self.actionSave = QtGui.QAction(parent=MainWindow)
|
||||||
icon5 = QtGui.QIcon()
|
icon5 = QtGui.QIcon()
|
||||||
icon5.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/disk.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon5.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/disk.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionSave.setIcon(icon5)
|
self.actionSave.setIcon(icon5)
|
||||||
self.actionSave.setObjectName("actionSave")
|
self.actionSave.setObjectName("actionSave")
|
||||||
self.actionExit = QtGui.QAction(parent=MainWindow)
|
self.actionExit = QtGui.QAction(parent=MainWindow)
|
||||||
icon6 = QtGui.QIcon()
|
icon6 = QtGui.QIcon()
|
||||||
icon6.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/door--arrow.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon6.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/door--arrow.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionExit.setIcon(icon6)
|
self.actionExit.setIcon(icon6)
|
||||||
self.actionExit.setObjectName("actionExit")
|
self.actionExit.setObjectName("actionExit")
|
||||||
self.actionUndo = QtGui.QAction(parent=MainWindow)
|
self.actionUndo = QtGui.QAction(parent=MainWindow)
|
||||||
icon7 = QtGui.QIcon()
|
icon7 = QtGui.QIcon()
|
||||||
icon7.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/arrow-curve-180-left.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon7.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/arrow-curve-180-left.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionUndo.setIcon(icon7)
|
self.actionUndo.setIcon(icon7)
|
||||||
self.actionUndo.setObjectName("actionUndo")
|
self.actionUndo.setObjectName("actionUndo")
|
||||||
self.actionRedo = QtGui.QAction(parent=MainWindow)
|
self.actionRedo = QtGui.QAction(parent=MainWindow)
|
||||||
icon8 = QtGui.QIcon()
|
icon8 = QtGui.QIcon()
|
||||||
icon8.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/arrow-curve.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon8.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/arrow-curve.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionRedo.setIcon(icon8)
|
self.actionRedo.setIcon(icon8)
|
||||||
self.actionRedo.setObjectName("actionRedo")
|
self.actionRedo.setObjectName("actionRedo")
|
||||||
self.actionAbout = QtGui.QAction(parent=MainWindow)
|
self.actionAbout = QtGui.QAction(parent=MainWindow)
|
||||||
icon9 = QtGui.QIcon()
|
icon9 = QtGui.QIcon()
|
||||||
icon9.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/question.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon9.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/question.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionAbout.setIcon(icon9)
|
self.actionAbout.setIcon(icon9)
|
||||||
self.actionAbout.setObjectName("actionAbout")
|
self.actionAbout.setObjectName("actionAbout")
|
||||||
self.actionExport = QtGui.QAction(parent=MainWindow)
|
self.actionExport = QtGui.QAction(parent=MainWindow)
|
||||||
self.actionExport.setEnabled(False)
|
self.actionExport.setEnabled(False)
|
||||||
icon10 = QtGui.QIcon()
|
icon10 = QtGui.QIcon()
|
||||||
icon10.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/notebook--arrow.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon10.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/notebook--arrow.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionExport.setIcon(icon10)
|
self.actionExport.setIcon(icon10)
|
||||||
self.actionExport.setObjectName("actionExport")
|
self.actionExport.setObjectName("actionExport")
|
||||||
self.actionSave_as = QtGui.QAction(parent=MainWindow)
|
self.actionSave_as = QtGui.QAction(parent=MainWindow)
|
||||||
icon11 = QtGui.QIcon()
|
icon11 = QtGui.QIcon()
|
||||||
icon11.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/disk--pencil.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon11.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/disk--pencil.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
self.actionSave_as.setIcon(icon11)
|
self.actionSave_as.setIcon(icon11)
|
||||||
self.actionSave_as.setObjectName("actionSave_as")
|
self.actionSave_as.setObjectName("actionSave_as")
|
||||||
self.menuFile.addAction(self.actionNew)
|
self.menuFile.addAction(self.actionNew)
|
||||||
|
|
@ -135,7 +135,7 @@ class Ui_MainWindow(object):
|
||||||
|
|
||||||
def retranslateUi(self, MainWindow):
|
def retranslateUi(self, MainWindow):
|
||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
MainWindow.setWindowTitle(_translate("MainWindow", "Wargame campaign"))
|
MainWindow.setWindowTitle(_translate("MainWindow", "WarChron"))
|
||||||
item = self.playersTable.horizontalHeaderItem(0)
|
item = self.playersTable.horizontalHeaderItem(0)
|
||||||
item.setText(_translate("MainWindow", "Name"))
|
item.setText(_translate("MainWindow", "Name"))
|
||||||
item = self.playersTable.horizontalHeaderItem(1)
|
item = self.playersTable.horizontalHeaderItem(1)
|
||||||
|
|
@ -151,8 +151,11 @@ class Ui_MainWindow(object):
|
||||||
self.actionNew.setText(_translate("MainWindow", "New"))
|
self.actionNew.setText(_translate("MainWindow", "New"))
|
||||||
self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))
|
self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))
|
||||||
self.actionOpen.setText(_translate("MainWindow", "Open"))
|
self.actionOpen.setText(_translate("MainWindow", "Open"))
|
||||||
|
self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
|
||||||
self.actionSave.setText(_translate("MainWindow", "Save"))
|
self.actionSave.setText(_translate("MainWindow", "Save"))
|
||||||
|
self.actionSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
|
||||||
self.actionExit.setText(_translate("MainWindow", "Exit"))
|
self.actionExit.setText(_translate("MainWindow", "Exit"))
|
||||||
|
self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Shift+Q"))
|
||||||
self.actionUndo.setText(_translate("MainWindow", "Undo"))
|
self.actionUndo.setText(_translate("MainWindow", "Undo"))
|
||||||
self.actionRedo.setText(_translate("MainWindow", "Redo"))
|
self.actionRedo.setText(_translate("MainWindow", "Redo"))
|
||||||
self.actionAbout.setText(_translate("MainWindow", "About"))
|
self.actionAbout.setText(_translate("MainWindow", "About"))
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Wargame campaign</string>
|
<string>WarChron</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset>
|
||||||
<normaloff>../resources/wargame_campaign_logo.png</normaloff>../resources/wargame_campaign_logo.png</iconset>
|
<normaloff>../resources/warchron_logo.png</normaloff>../resources/warchron_logo.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
|
@ -187,6 +187,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Open</string>
|
<string>Open</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+O</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionSave">
|
<action name="actionSave">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
|
|
@ -196,6 +199,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save</string>
|
<string>Save</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+S</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExit">
|
<action name="actionExit">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
|
|
@ -205,6 +211,9 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Exit</string>
|
<string>Exit</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Shift+Q</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionUndo">
|
<action name="actionUndo">
|
||||||
<property name="icon">
|
<property name="icon">
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Form implementation generated from reading ui file '.\src\wargame_campaign\view\ui\ui_player_dialog.ui'
|
# Form implementation generated from reading ui file '.\src\warchron\view\ui\ui_player_dialog.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt6 UI code generator 6.7.1
|
# Created by: PyQt6 UI code generator 6.7.1
|
||||||
#
|
#
|
||||||
|
|
@ -15,7 +15,7 @@ class Ui_playerDialog(object):
|
||||||
playerDialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
|
playerDialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
|
||||||
playerDialog.resize(378, 98)
|
playerDialog.resize(378, 98)
|
||||||
icon = QtGui.QIcon()
|
icon = QtGui.QIcon()
|
||||||
icon.addPixmap(QtGui.QPixmap(".\\src\\wargame_campaign\\view\\ui\\../resources/wargame_campaign_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
icon.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/warchron_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
|
||||||
playerDialog.setWindowIcon(icon)
|
playerDialog.setWindowIcon(icon)
|
||||||
self.buttonBox = QtWidgets.QDialogButtonBox(parent=playerDialog)
|
self.buttonBox = QtWidgets.QDialogButtonBox(parent=playerDialog)
|
||||||
self.buttonBox.setGeometry(QtCore.QRect(10, 60, 341, 32))
|
self.buttonBox.setGeometry(QtCore.QRect(10, 60, 341, 32))
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
</property>
|
</property>
|
||||||
<property name="windowIcon">
|
<property name="windowIcon">
|
||||||
<iconset>
|
<iconset>
|
||||||
<normaloff>../resources/wargame_campaign_logo.png</normaloff>../resources/wargame_campaign_logo.png</iconset>
|
<normaloff>../resources/warchron_logo.png</normaloff>../resources/warchron_logo.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
57
src/warchron/view/view.py
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PyQt6 import QtWidgets
|
||||||
|
from PyQt6.QtWidgets import QDialog, QFileDialog
|
||||||
|
from PyQt6.QtGui import QCloseEvent
|
||||||
|
|
||||||
|
from warchron.view.ui.ui_main_window import Ui_MainWindow
|
||||||
|
from warchron.view.ui.ui_player_dialog import Ui_playerDialog
|
||||||
|
|
||||||
|
class View(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super(View, self).__init__(parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.on_close_callback = None
|
||||||
|
|
||||||
|
def display_players(self, players: list):
|
||||||
|
table = self.playersTable
|
||||||
|
table.setRowCount(len(players))
|
||||||
|
for row, player in enumerate(players):
|
||||||
|
table.setItem(row, 0, QtWidgets.QTableWidgetItem(player.name))
|
||||||
|
table.setItem(row, 1, QtWidgets.QTableWidgetItem(player.id))
|
||||||
|
table.resizeColumnsToContents()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class PlayerDialog(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.ui = Ui_playerDialog()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
|
||||||
|
def get_player_name(self) -> str:
|
||||||
|
return self.ui.playerName.text().strip()
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
from PyQt6.QtWidgets import QMessageBox, QDialog
|
|
||||||
|
|
||||||
from wargame_campaign.view.view import PlayerDialog
|
|
||||||
|
|
||||||
class Controller:
|
|
||||||
def __init__(self, model, view):
|
|
||||||
self.model = model
|
|
||||||
self.view = view
|
|
||||||
self.__connect()
|
|
||||||
self.refresh_players_view()
|
|
||||||
|
|
||||||
def __connect(self):
|
|
||||||
self.view.addPlayerBtn.clicked.connect(self.add_player)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def refresh_players_view(self):
|
|
||||||
players = self.model.get_all_players()
|
|
||||||
self.view.display_players(players)
|
|
||||||
|
|
||||||
def add_player(self):
|
|
||||||
dialog = PlayerDialog(self.view)
|
|
||||||
result = dialog.exec() # modal blocking dialog
|
|
||||||
if result == QDialog.DialogCode.Accepted:
|
|
||||||
name = dialog.get_player_name()
|
|
||||||
if not name:
|
|
||||||
QMessageBox.warning(
|
|
||||||
self.view,
|
|
||||||
"Invalid name",
|
|
||||||
"Player name cannot be empty."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
self.model.add_player(name)
|
|
||||||
self.refresh_players_view()
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 345 B |
|
Before Width: | Height: | Size: 30 KiB |
|
|
@ -1,29 +0,0 @@
|
||||||
from PyQt6 import QtWidgets
|
|
||||||
from PyQt6.QtWidgets import QDialog
|
|
||||||
|
|
||||||
from wargame_campaign.view.ui.ui_main_window import Ui_MainWindow
|
|
||||||
from wargame_campaign.view.ui.ui_player_dialog import Ui_playerDialog
|
|
||||||
|
|
||||||
class View(QtWidgets.QMainWindow, Ui_MainWindow):
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super(View, self).__init__(parent)
|
|
||||||
self.setupUi(self)
|
|
||||||
|
|
||||||
def display_players(self, players: list):
|
|
||||||
table = self.playersTable
|
|
||||||
table.setRowCount(len(players))
|
|
||||||
|
|
||||||
for row, player in enumerate(players):
|
|
||||||
table.setItem(row, 0, QtWidgets.QTableWidgetItem(player.name))
|
|
||||||
table.setItem(row, 1, QtWidgets.QTableWidgetItem(player.id))
|
|
||||||
|
|
||||||
table.resizeColumnsToContents()
|
|
||||||
|
|
||||||
class PlayerDialog(QDialog):
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.ui = Ui_playerDialog()
|
|
||||||
self.ui.setupUi(self)
|
|
||||||
|
|
||||||
def get_player_name(self) -> str:
|
|
||||||
return self.ui.playerName.text().strip()
|
|
||||||