diff --git a/Makefile b/Makefile index 266e199..992766b 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,4 @@ +# rcc not working as is in Qt6... ress: pyrcc5 .\src\warchron\view\resources\ui_ressources.qrc -o .\src\warchron\view\resources\ui_ressources_rc.py @@ -14,7 +15,7 @@ ui: $(PY_FILES) # Pattern rule: .ui -> .py using pyuic5 $(UI_DIR)/%.py: $(UI_DIR)/%.ui - pyuic5 -x $< -o $@ --import-from warchron.view.resources + pyuic6 -x $< -o $@ --import-from warchron.view.resources # Function to generate UI file from given name _ui_generate: diff --git a/README.md b/README.md index 2de4b02..1ca9e9e 100644 --- a/README.md +++ b/README.md @@ -44,5 +44,5 @@ pip install -e . ### UI with QT Designer -Save UI design from QT designer as `ui_*_.ui` file and then convert them to python using : +Save UI design from QT designer as `ui_*_.ui` file and then convert them into python using: `pyuic6 -x .ui -o .py ` \ No newline at end of file diff --git a/THIRD_PARTY_LICENCES.md b/THIRD_PARTY_LICENCES.md new file mode 100644 index 0000000..e3a2ade --- /dev/null +++ b/THIRD_PARTY_LICENCES.md @@ -0,0 +1,4 @@ +Fugue Icons 3.5.6 +Author: Yusuke Kamiyamane +License: Creative Commons Attribution 3.0 +Source: https://p.yusukekamiyamane.com/ \ No newline at end of file diff --git a/main.py b/main.py index eecc802..aa918f6 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,9 @@ import sys from PyQt6.QtWidgets import QApplication +from setuptools_scm import get_version +from setuptools_scm.version import get_local_node_and_date + from warchron.view.view import View from warchron.model.model import Model from warchron.controller.app_controller import AppController @@ -9,12 +12,27 @@ from warchron.controller.app_controller import AppController if sys.version_info < (3, 12): raise RuntimeError("Python 3.12 or higher is required") + +def get_app_version() -> str: + return get_version( + root=".", + relative_to=__file__, + fallback_version="0.0.0", + tag_regex=r"^v(?P\d+\.\d+\.\d+)$", + version_scheme="guess-next-dev", + local_scheme=get_local_node_and_date, + ) + + +app_version = get_app_version() +# app_version = get_version(root=".", relative_to=__file__) + if __name__ == "__main__": app = QApplication(sys.argv) view = View() model = Model() - controller = AppController(model, view) + controller = AppController(model, view, app_version) view.show() diff --git a/pyproject.toml b/pyproject.toml index 3bf3163..63cfcea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,13 @@ [project] name = "warchron" -version = "1.0.0" +dynamic = ["version"] description = "A simple local app to track players' campaigns for tabletop wargames." requires-python = ">=3.12" +[tool.setuptools_scm] +write_to = "src/warchron/_version.py" +fallback_version = "0.0.0" + [build-system] requires = ["setuptools>=61"] build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index b587ef0..bd94f7d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ 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 +flake8-pyproject +setuptools_scm \ No newline at end of file diff --git a/src/warchron/controller/app_controller.py b/src/warchron/controller/app_controller.py index fdb16d5..9ca5341 100644 --- a/src/warchron/controller/app_controller.py +++ b/src/warchron/controller/app_controller.py @@ -18,9 +18,10 @@ from warchron.controller.round_controller import RoundController class AppController: - def __init__(self, model: Model, view: View) -> None: + def __init__(self, model: Model, view: View, version: str) -> None: self.model: Model = model self.view: View = view + self.app_version = version self.navigation = NavigationController(self) self.players = PlayerController(self) self.wars = WarController(self) @@ -43,6 +44,7 @@ class AppController: 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.actionAbout.triggered.connect(self.show_about) self.view.addPlayerBtn.clicked.connect(self.players.add_player) self.view.addWarBtn.clicked.connect(self.wars.add_war) self.view.majorValue.valueChanged.connect(self.wars.set_major_value) @@ -129,6 +131,23 @@ class AppController: self.is_dirty = False self.update_window_title() + def show_about(self) -> None: + QMessageBox.about( + self.view, + "About WarChron", + f""" +

WarChron

+

Version: {self.app_version}

+

Campaign & War management tool

+

© 2026 Your Name

+

Licensed under GNU GPL v3

+
+

Icons from Fugue Icons 3.5.6
+ © Yusuke Kamiyamane
+ Licensed under Creative Commons Attribution 3.0

+ """, + ) + # Display methods def update_window_title(self) -> None: diff --git a/src/warchron/model/battle.py b/src/warchron/model/battle.py index 1f0fd07..df6e91b 100644 --- a/src/warchron/model/battle.py +++ b/src/warchron/model/battle.py @@ -12,7 +12,9 @@ class Battle: self.sector_id: str = sector_id # ref to Campaign.sector self.player_1_id: str | None = player_1_id # ref to Campaign.participants self.player_2_id: str | None = player_2_id # ref to Campaign.participants - self.winner_id: str | None = None + self.winner_id: str | None = ( + None # ref to Campaign.participants within player_1/2 + ) self.score: str | None = None self.victory_condition: str | None = None self.comment: str | None = None diff --git a/src/warchron/view/resources/resources.qrc b/src/warchron/view/resources/resources.qrc new file mode 100644 index 0000000..5053e13 --- /dev/null +++ b/src/warchron/view/resources/resources.qrc @@ -0,0 +1,20 @@ + + + arrow-curve.png + arrow-curve-180-left.png + cross.png + disk.png + disk--pencil.png + document.png + door--arrow.png + folder.png + notebook--arrow.png + pencil.png + plus.png + question.png + swords-small.png + users.png + warchron_logo + .png + + \ No newline at end of file diff --git a/src/warchron/view/ui/ui_main_window.py b/src/warchron/view/ui/ui_main_window.py index 8633950..5c43d0f 100644 --- a/src/warchron/view/ui/ui_main_window.py +++ b/src/warchron/view/ui/ui_main_window.py @@ -52,7 +52,7 @@ class Ui_MainWindow(object): self.playersTable.setHorizontalHeaderItem(2, item) item = QtWidgets.QTableWidgetItem() self.playersTable.setHorizontalHeaderItem(3, item) - self.playersTable.horizontalHeader().setStretchLastSection(True) + self.playersTable.horizontalHeader().setStretchLastSection(False) self.gridLayout.addWidget(self.playersTable, 1, 0, 1, 1) icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/users.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) @@ -187,7 +187,7 @@ class Ui_MainWindow(object): self.objectivesTable.setHorizontalHeaderItem(0, item) item = QtWidgets.QTableWidgetItem() self.objectivesTable.setHorizontalHeaderItem(1, item) - self.objectivesTable.horizontalHeader().setStretchLastSection(True) + self.objectivesTable.horizontalHeader().setStretchLastSection(False) self.verticalLayout.addWidget(self.objectivesTable) self.verticalLayout_10.addWidget(self.groupBox) self.groupBox_2 = QtWidgets.QGroupBox(parent=self.pageWar) @@ -220,7 +220,7 @@ class Ui_MainWindow(object): self.warParticipantsTable.setHorizontalHeaderItem(3, item) item = QtWidgets.QTableWidgetItem() self.warParticipantsTable.setHorizontalHeaderItem(4, item) - self.warParticipantsTable.horizontalHeader().setStretchLastSection(True) + self.warParticipantsTable.horizontalHeader().setStretchLastSection(False) self.verticalLayout_2.addWidget(self.warParticipantsTable) self.verticalLayout_10.addWidget(self.groupBox_2) self.horizontalLayout_6 = QtWidgets.QHBoxLayout() @@ -286,7 +286,7 @@ class Ui_MainWindow(object): self.sectorsTable.setHorizontalHeaderItem(4, item) item = QtWidgets.QTableWidgetItem() self.sectorsTable.setHorizontalHeaderItem(5, item) - self.sectorsTable.horizontalHeader().setStretchLastSection(True) + self.sectorsTable.horizontalHeader().setStretchLastSection(False) self.verticalLayout_5.addWidget(self.sectorsTable) self.verticalLayout_7.addWidget(self.groupBox_3) self.groupBox_4 = QtWidgets.QGroupBox(parent=self.pageCampaign) @@ -318,7 +318,7 @@ class Ui_MainWindow(object): self.campaignParticipantsTable.setHorizontalHeaderItem(3, item) item = QtWidgets.QTableWidgetItem() self.campaignParticipantsTable.setHorizontalHeaderItem(4, item) - self.campaignParticipantsTable.horizontalHeader().setStretchLastSection(True) + self.campaignParticipantsTable.horizontalHeader().setStretchLastSection(False) self.verticalLayout_6.addWidget(self.campaignParticipantsTable) self.verticalLayout_7.addWidget(self.groupBox_4) self.horizontalLayout_10 = QtWidgets.QHBoxLayout() @@ -361,7 +361,7 @@ class Ui_MainWindow(object): self.choicesTable.setHorizontalHeaderItem(1, item) item = QtWidgets.QTableWidgetItem() self.choicesTable.setHorizontalHeaderItem(2, item) - self.choicesTable.horizontalHeader().setStretchLastSection(True) + self.choicesTable.horizontalHeader().setStretchLastSection(False) self.horizontalLayout_4.addWidget(self.choicesTable) self.verticalLayout_8.addWidget(self.groupBox_5) self.horizontalLayout_13 = QtWidgets.QHBoxLayout() @@ -389,7 +389,7 @@ class Ui_MainWindow(object): self.battlesTable.setHorizontalHeaderItem(1, item) item = QtWidgets.QTableWidgetItem() self.battlesTable.setHorizontalHeaderItem(2, item) - self.battlesTable.horizontalHeader().setStretchLastSection(True) + self.battlesTable.horizontalHeader().setStretchLastSection(False) self.horizontalLayout_12.addWidget(self.battlesTable) self.verticalLayout_8.addWidget(self.groupBox_6) self.horizontalLayout_9 = QtWidgets.QHBoxLayout() @@ -409,7 +409,7 @@ class Ui_MainWindow(object): self.verticalLayout_9.addWidget(self.tabWidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(parent=MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 1235, 21)) + self.menubar.setGeometry(QtCore.QRect(0, 0, 1235, 22)) self.menubar.setObjectName("menubar") self.menuFile = QtWidgets.QMenu(parent=self.menubar) self.menuFile.setObjectName("menuFile") @@ -442,11 +442,13 @@ class Ui_MainWindow(object): self.actionExit.setIcon(icon7) self.actionExit.setObjectName("actionExit") self.actionUndo = QtGui.QAction(parent=MainWindow) + self.actionUndo.setEnabled(False) icon8 = QtGui.QIcon() icon8.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/arrow-curve-180-left.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) self.actionUndo.setIcon(icon8) self.actionUndo.setObjectName("actionUndo") self.actionRedo = QtGui.QAction(parent=MainWindow) + self.actionRedo.setEnabled(False) icon9 = QtGui.QIcon() icon9.addPixmap(QtGui.QPixmap(".\\src\\warchron\\view\\ui\\../resources/arrow-curve.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off) self.actionRedo.setIcon(icon9) diff --git a/src/warchron/view/ui/ui_main_window.ui b/src/warchron/view/ui/ui_main_window.ui index a740546..83c88b2 100644 --- a/src/warchron/view/ui/ui_main_window.ui +++ b/src/warchron/view/ui/ui_main_window.ui @@ -76,7 +76,7 @@ true - true + false @@ -431,7 +431,7 @@ true - true + false @@ -497,7 +497,7 @@ true - true + false @@ -650,7 +650,7 @@ true - true + false @@ -733,7 +733,7 @@ true - true + false @@ -833,7 +833,7 @@ true - true + false @@ -900,7 +900,7 @@ true - true + false @@ -966,7 +966,7 @@ 0 0 1235 - 21 + 22 @@ -1049,6 +1049,9 @@ + + false + ../resources/arrow-curve-180-left.png../resources/arrow-curve-180-left.png @@ -1061,6 +1064,9 @@ + + false + ../resources/arrow-curve.png../resources/arrow-curve.png diff --git a/src/warchron/view/view.py b/src/warchron/view/view.py index 527c543..e7b999b 100644 --- a/src/warchron/view/view.py +++ b/src/warchron/view/view.py @@ -5,7 +5,7 @@ import calendar from PyQt6 import QtWidgets from PyQt6.QtCore import Qt, QPoint from PyQt6.QtWidgets import QWidget, QFileDialog, QTreeWidgetItem, QMenu -from PyQt6.QtGui import QCloseEvent +from PyQt6.QtGui import QCloseEvent, QIcon from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType from warchron.controller.dtos import ( @@ -147,8 +147,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return player_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.playersTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -191,8 +195,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): menu = QMenu(self) edit_action = None if item_type != ItemType.ROUND: - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.warsTree.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -275,8 +283,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return objective_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.objectivesTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -295,8 +307,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return participant_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.warParticipantsTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -364,8 +380,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return sector_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.sectorsTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -384,8 +404,12 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): return participant_id = name_item.data(Qt.ItemDataRole.UserRole) menu = QMenu(self) - edit_action = menu.addAction("Edit") - delete_action = menu.addAction("Delete") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) + delete_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/cross.png"), "Delete" + ) viewport = self.campaignParticipantsTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -448,7 +472,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): if choice_id is None: return menu = QMenu(self) - edit_action = menu.addAction("Edit") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) viewport = self.choicesTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos)) @@ -467,7 +493,9 @@ class View(QtWidgets.QMainWindow, Ui_MainWindow): if battle_id is None: return menu = QMenu(self) - edit_action = menu.addAction("Edit") + edit_action = menu.addAction( + QIcon(".\\src\\warchron\\view\\ui\\../resources/pencil.png"), "Edit" + ) viewport = self.battlesTable.viewport() assert viewport is not None action = menu.exec(viewport.mapToGlobal(pos))