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.
|
||||
|
||||
Wargame_campain_app
|
||||
warchron_app
|
||||
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.
|
||||
|
|
@ -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:
|
||||
|
||||
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 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:
|
||||
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 :
|
||||
python -m PyInstaller .\main.spec
|
||||
|
|
@ -14,11 +14,11 @@ ui: $(PY_FILES)
|
|||
|
||||
# Pattern rule: .ui -> .py using pyuic5
|
||||
$(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
|
||||
_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
|
||||
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
|
||||
|
||||
### Main logic
|
||||
|
||||
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.
|
||||
Battle results determine campaign score which determines the war score. Wars are independent.
|
||||
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.
|
||||
|
||||
### Design notes
|
||||
|
||||
Players are global identities
|
||||
Influence tokens are scoped to a war
|
||||
Campaign order enables historical tie-breakers
|
||||
Effects are generic → future-proof
|
||||
* Players are global identities
|
||||
* Influence tokens are scoped to a war
|
||||
* Campaign order enables historical tie-breakers
|
||||
* Effects are generic → future-proof
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -27,8 +28,8 @@ Effects are generic → future-proof
|
|||
### Setup
|
||||
|
||||
```bash
|
||||
git clone <your-forge-address>/Wargame_campaign_app.git
|
||||
cd Wargame_campaign_app
|
||||
git clone <your-forge-address>/warchron_app.git
|
||||
cd warchron_app
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
|
|
|
|||
|
|
@ -3,16 +3,24 @@
|
|||
|
||||
"players": [
|
||||
{
|
||||
"id" : "p1",
|
||||
"name" :"Alice"
|
||||
"id": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
|
||||
"name": "Alice"
|
||||
},
|
||||
{
|
||||
"id" : "p2",
|
||||
"name" :"Bob"
|
||||
"id": "b7eebce7-cf04-40bc-b80c-400585adb3cd",
|
||||
"name": "Bob"
|
||||
},
|
||||
{
|
||||
"id" : "p3",
|
||||
"name" :"Charlie"
|
||||
"id": "f87e6d53-30a2-4dd8-a359-d860404ef2ee",
|
||||
"name": "Charlie"
|
||||
},
|
||||
{
|
||||
"id": "056011da-b0c7-4dc7-8b7c-14213a8df009",
|
||||
"name": "Dave"
|
||||
},
|
||||
{
|
||||
"id": "50e83cb3-b828-4f1e-ad89-7644a84f3d8c",
|
||||
"name": "Eve"
|
||||
}
|
||||
],
|
||||
|
||||
|
|
@ -23,11 +31,11 @@
|
|||
"year": 2025,
|
||||
|
||||
"registered_players": {
|
||||
"p1": {
|
||||
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": {
|
||||
"war_points": 0,
|
||||
"influence_tokens": 1
|
||||
},
|
||||
"p2": {
|
||||
"b7eebce7-cf04-40bc-b80c-400585adb3cd": {
|
||||
"war_points": 0,
|
||||
"influence_tokens": 0
|
||||
}
|
||||
|
|
@ -40,8 +48,8 @@
|
|||
"order": 1,
|
||||
"month" : "June",
|
||||
"participants": {
|
||||
"p1": { "campaign_points": 0 },
|
||||
"p2": { "campaign_points": 0 }
|
||||
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "campaign_points": 0 },
|
||||
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "campaign_points": 0 }
|
||||
},
|
||||
|
||||
"rounds": [
|
||||
|
|
@ -50,15 +58,15 @@
|
|||
"sectors": ["North", "South"],
|
||||
|
||||
"choices": {
|
||||
"p1": { "primary": "North", "secondary": "South" },
|
||||
"p2": { "primary": "North", "secondary": "South" }
|
||||
"e7844fbb-8366-44e4-bb43-89b9eef6ef64": { "primary": "North", "secondary": "South" },
|
||||
"b7eebce7-cf04-40bc-b80c-400585adb3cd": { "primary": "North", "secondary": "South" }
|
||||
},
|
||||
|
||||
"battles": [
|
||||
{
|
||||
"sector": "North",
|
||||
"players": ["p1", "p2"],
|
||||
"winner": "p1",
|
||||
"players": ["e7844fbb-8366-44e4-bb43-89b9eef6ef64", "b7eebce7-cf04-40bc-b80c-400585adb3cd"],
|
||||
"winner": "e7844fbb-8366-44e4-bb43-89b9eef6ef64",
|
||||
|
||||
"effects": {
|
||||
"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 wargame_campaign.view.view import View
|
||||
from wargame_campaign.model.model import Model
|
||||
from wargame_campaign.controller.controller import Controller
|
||||
from warchron.view.view import View
|
||||
from warchron.model.model import Model
|
||||
from warchron.controller.controller import Controller
|
||||
|
||||
if sys.version_info < (3, 12):
|
||||
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 shutil
|
||||
|
||||
from wargame_campaign.model.player import Player
|
||||
from warchron.model.player import Player
|
||||
|
||||
class Model:
|
||||
def __init__(self):
|
||||
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):
|
||||
if not data_file_path.exists() or data_file_path.stat().st_size == 0:
|
||||
pass # Create empty json
|
||||
def new(self):
|
||||
self.players.clear()
|
||||
# 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:
|
||||
with open(data_file_path, "r", encoding="utf-8") as f:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
for player in data["players"] :
|
||||
saved_player = Player.fromDict(player["id"], player['name'])
|
||||
|
|
@ -26,16 +36,16 @@ class Model:
|
|||
except json.JSONDecodeError:
|
||||
raise RuntimeError("Data file is corrupted")
|
||||
|
||||
def save_data(self, data_file_path):
|
||||
if data_file_path.exists():
|
||||
shutil.copy(data_file_path, data_file_path.with_suffix(".json.bak"))
|
||||
def _save_data(self, path: Path):
|
||||
if path.exists():
|
||||
shutil.copy(path, path.with_suffix(".json.bak"))
|
||||
data = {}
|
||||
data['version'] = "1.0"
|
||||
data['players'] = []
|
||||
data['wars'] = []
|
||||
for player in self.players.values():
|
||||
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)
|
||||
|
||||
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
|
||||
#
|
||||
|
|
@ -14,7 +14,7 @@ class Ui_MainWindow(object):
|
|||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(800, 600)
|
||||
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)
|
||||
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
|
|
@ -36,7 +36,7 @@ class Ui_MainWindow(object):
|
|||
self.addPlayerBtn.setGeometry(QtCore.QRect(20, 20, 75, 23))
|
||||
self.addPlayerBtn.setObjectName("addPlayerBtn")
|
||||
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.warsTab = QtWidgets.QWidget()
|
||||
self.warsTab.setObjectName("warsTab")
|
||||
|
|
@ -52,7 +52,7 @@ class Ui_MainWindow(object):
|
|||
self.addCampaignBtn.setGeometry(QtCore.QRect(110, 20, 91, 23))
|
||||
self.addCampaignBtn.setObjectName("addCampaignBtn")
|
||||
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, "")
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
|
||||
|
|
@ -70,48 +70,48 @@ class Ui_MainWindow(object):
|
|||
MainWindow.setStatusBar(self.statusbar)
|
||||
self.actionNew = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionNew")
|
||||
self.actionOpen = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionOpen")
|
||||
self.actionSave = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionSave")
|
||||
self.actionExit = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionExit")
|
||||
self.actionUndo = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionUndo")
|
||||
self.actionRedo = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionRedo")
|
||||
self.actionAbout = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionAbout")
|
||||
self.actionExport = QtGui.QAction(parent=MainWindow)
|
||||
self.actionExport.setEnabled(False)
|
||||
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.setObjectName("actionExport")
|
||||
self.actionSave_as = QtGui.QAction(parent=MainWindow)
|
||||
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.setObjectName("actionSave_as")
|
||||
self.menuFile.addAction(self.actionNew)
|
||||
|
|
@ -135,7 +135,7 @@ class Ui_MainWindow(object):
|
|||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "Wargame campaign"))
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "WarChron"))
|
||||
item = self.playersTable.horizontalHeaderItem(0)
|
||||
item.setText(_translate("MainWindow", "Name"))
|
||||
item = self.playersTable.horizontalHeaderItem(1)
|
||||
|
|
@ -151,8 +151,11 @@ class Ui_MainWindow(object):
|
|||
self.actionNew.setText(_translate("MainWindow", "New"))
|
||||
self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))
|
||||
self.actionOpen.setText(_translate("MainWindow", "Open"))
|
||||
self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
|
||||
self.actionSave.setText(_translate("MainWindow", "Save"))
|
||||
self.actionSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
|
||||
self.actionExit.setText(_translate("MainWindow", "Exit"))
|
||||
self.actionExit.setShortcut(_translate("MainWindow", "Ctrl+Shift+Q"))
|
||||
self.actionUndo.setText(_translate("MainWindow", "Undo"))
|
||||
self.actionRedo.setText(_translate("MainWindow", "Redo"))
|
||||
self.actionAbout.setText(_translate("MainWindow", "About"))
|
||||
|
|
@ -11,11 +11,11 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Wargame campaign</string>
|
||||
<string>WarChron</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<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>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
|
|
@ -187,6 +187,9 @@
|
|||
<property name="text">
|
||||
<string>Open</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+O</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSave">
|
||||
<property name="icon">
|
||||
|
|
@ -196,6 +199,9 @@
|
|||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+S</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionExit">
|
||||
<property name="icon">
|
||||
|
|
@ -205,6 +211,9 @@
|
|||
<property name="text">
|
||||
<string>Exit</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Shift+Q</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUndo">
|
||||
<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
|
||||
#
|
||||
|
|
@ -15,7 +15,7 @@ class Ui_playerDialog(object):
|
|||
playerDialog.setWindowModality(QtCore.Qt.WindowModality.ApplicationModal)
|
||||
playerDialog.resize(378, 98)
|
||||
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)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(parent=playerDialog)
|
||||
self.buttonBox.setGeometry(QtCore.QRect(10, 60, 341, 32))
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
</property>
|
||||
<property name="windowIcon">
|
||||
<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>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<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()
|
||||