warchron_app/src/warchron/view/view.py
2026-02-26 16:16:48 +01:00

629 lines
26 KiB
Python

from __future__ import annotations
from typing import Callable, List, Dict
from pathlib import Path
import calendar
from PyQt6 import QtWidgets
from PyQt6.QtCore import Qt, QPoint, QSize
from PyQt6.QtWidgets import QWidget, QFileDialog, QTreeWidgetItem, QMenu
from PyQt6.QtGui import QCloseEvent
from warchron.constants import ROLE_TYPE, ROLE_ID, ItemType, Icons, IconName
from warchron.controller.dtos import (
ParticipantOption,
TreeSelection,
WarDTO,
ObjectiveDTO,
SectorDTO,
ChoiceDTO,
BattleDTO,
CampaignParticipantScoreDTO,
WarParticipantScoreDTO,
)
from warchron.view.helpers import (
format_campaign_label,
format_round_label,
format_war_label,
)
from warchron.view.ui.ui_main_window import Ui_MainWindow
class View(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent: QWidget | None = None) -> None:
super(View, self).__init__(parent)
self.setupUi(self) # type: ignore
self.on_close_callback: Callable[[], bool] | None = None
self.on_tree_selection_changed: (
Callable[[TreeSelection | None], None] | None
) = None
self.on_major_value_changed: Callable[[int], None] | None = None
self.on_minot_value_changed: Callable[[int], None] | None = None
self.majorValue.setMinimum(0)
self.minorValue.setMinimum(0)
self.on_influence_token_changed: Callable[[int], None] | None = None
self.on_add_item: Callable[[str], None] | None = None
self.on_edit_item: Callable[[str, str], None] | None = None
self.on_delete_item: Callable[[str, str], None] | None = None
self.splitter.setSizes([200, 800])
self.show_details(None)
self.playersTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.playersTable.customContextMenuRequested.connect(
self._on_players_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)
# Pages
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.campaignParticipantsTable.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu
)
self.campaignParticipantsTable.customContextMenuRequested.connect(
self._on_campaign_participants_table_context_menu
)
self.sectorsTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.sectorsTable.customContextMenuRequested.connect(
self._on_sectors_table_context_menu
)
self.choicesTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.choicesTable.customContextMenuRequested.connect(
self._on_choices_table_context_menu
)
self.battlesTable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.battlesTable.customContextMenuRequested.connect(
self._on_battles_table_context_menu
)
self.majorValue.valueChanged.connect(self._on_major_changed)
self.minorValue.valueChanged.connect(self._on_minor_changed)
self.warsTree.currentItemChanged.connect(self._emit_selection_changed)
self._apply_icons()
def _apply_icons(self) -> None:
# Window
self.setWindowIcon(Icons.get(IconName.WARCHRON))
# Menu bar
self.actionNew.setIcon(Icons.get(IconName.NEW))
self.actionOpen.setIcon(Icons.get(IconName.OPEN))
self.actionSave.setIcon(Icons.get(IconName.SAVE))
self.actionSave_as.setIcon(Icons.get(IconName.SAVE_AS))
self.actionExit.setIcon(Icons.get(IconName.EXIT))
self.actionUndo.setIcon(Icons.get(IconName.UNDO))
self.actionRedo.setIcon(Icons.get(IconName.REDO))
self.actionExport.setIcon(Icons.get(IconName.EXPORT))
self.actionAbout.setIcon(Icons.get(IconName.ABOUT))
# Tabs
self.tabWidget.setTabIcon(0, Icons.get(IconName.PLAYERS))
self.tabWidget.setTabIcon(1, Icons.get(IconName.WARS))
# Buttons
self.addPlayerBtn.setIcon(Icons.get(IconName.ADD))
self.addWarBtn.setIcon(Icons.get(IconName.ADD))
self.addCampaignBtn.setIcon(Icons.get(IconName.ADD))
self.addRoundBtn.setIcon(Icons.get(IconName.ADD))
self.addObjectiveBtn.setIcon(Icons.get(IconName.ADD))
self.addWarParticipantBtn.setIcon(Icons.get(IconName.ADD))
self.endWarBtn.setIcon(Icons.get(IconName.END))
self.addSectorBtn.setIcon(Icons.get(IconName.ADD))
self.addCampaignParticipantBtn.setIcon(Icons.get(IconName.ADD))
self.endCampaignBtn.setIcon(Icons.get(IconName.END))
self.resolvePairingBtn.setIcon(Icons.get(IconName.PAIRING))
self.endRoundBtn.setIcon(Icons.get(IconName.END))
def _emit_selection_changed(self, current: QTreeWidgetItem | None) -> None:
if not self.on_tree_selection_changed:
return
if not current:
self.on_tree_selection_changed(None)
return
self.on_tree_selection_changed(
TreeSelection(
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 ""
def clear_tree_selection(self) -> None:
self.warsTree.blockSignals(True)
self.warsTree.setCurrentItem(None)
self.warsTree.blockSignals(False)
# General popups
def closeEvent(self, event: QCloseEvent | None = None) -> None:
if event is None:
return
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: QPoint) -> None:
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(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.playersTable.viewport()
assert viewport is not None
action = menu.exec(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[ParticipantOption]) -> None:
table = self.playersTable
table.setSortingEnabled(False)
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.setSortingEnabled(True)
table.sortItems(0, Qt.SortOrder.AscendingOrder)
table.resizeColumnsToContents()
# Wars view
def _on_add_campaign_clicked(self) -> None:
if self.on_add_item:
self.on_add_item(ItemType.CAMPAIGN)
def _on_add_round_clicked(self) -> None:
if self.on_add_item:
self.on_add_item(ItemType.ROUND)
def set_add_campaign_enabled(self, enabled: bool) -> None:
self.addCampaignBtn.setEnabled(enabled)
def set_add_round_enabled(self, enabled: bool) -> None:
self.addRoundBtn.setEnabled(enabled)
def _on_wars_tree_context_menu(self, pos: QPoint) -> None:
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(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.warsTree.viewport()
assert viewport is not None
action = menu.exec(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 display_wars_tree(self, wars: List[WarDTO]) -> None:
tree = self.warsTree
tree.blockSignals(True)
tree.clear()
tree.setColumnCount(1)
tree.setHeaderLabels(["Wars"])
hourglass = Icons.get(IconName.ONGOING)
check = Icons.get(IconName.DONE)
for war in wars:
war_item = QTreeWidgetItem([format_war_label(war)])
war_item.setIcon(0, check if war.is_over else hourglass)
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([format_campaign_label(camp)])
camp_item.setIcon(0, check if camp.is_over else hourglass)
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([format_round_label(index)])
rnd_item.setIcon(0, check if rnd.is_over else hourglass)
rnd_item.setData(0, ROLE_TYPE, ItemType.ROUND)
rnd_item.setData(0, ROLE_ID, rnd.id)
camp_item.addChild(rnd_item)
tree.expandAll()
tree.blockSignals(False)
def select_tree_item(self, *, item_type: ItemType, item_id: str) -> None:
def walk(item: QTreeWidgetItem) -> bool:
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)):
ytem = item.child(i)
if ytem is not None and walk(ytem):
return True
return False
for i in range(self.warsTree.topLevelItemCount()):
# if walk(self.warsTree.topLevelItem(i)):
item = self.warsTree.topLevelItem(i)
if item is not None and walk(item):
return
def get_selected_tree_item(self) -> Dict[str, str] | None:
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) -> 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: QPoint) -> None:
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(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.objectivesTable.viewport()
assert viewport is not None
action = menu.exec(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: QPoint) -> None:
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(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.warParticipantsTable.viewport()
assert viewport is not None
action = menu.exec(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) -> None:
self.warName.setText(name)
self.warYear.setText(str(year))
def display_war_objectives(self, objectives: List[ObjectiveDTO]) -> None:
table = self.objectivesTable
table.setSortingEnabled(False)
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.setSortingEnabled(True)
table.resizeColumnsToContents()
def display_war_participants(
self,
participants: List[WarParticipantScoreDTO],
objectives: List[ObjectiveDTO],
) -> None:
table = self.warParticipantsTable
table.setSortingEnabled(False)
table.clearContents()
base_cols = ["Player", "Faction", "Victory pts"]
headers = (
base_cols + [str(obj.name + " pts") for obj in objectives] + ["Tokens"]
)
table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants))
table.setIconSize(QSize(48, 16))
for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(part.player_name)
if part.rank_icon:
name_item.setIcon(part.rank_icon)
faction_item = QtWidgets.QTableWidgetItem(part.faction)
VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points))
name_item.setData(Qt.ItemDataRole.UserRole, part.war_participant_id)
token_item = QtWidgets.QTableWidgetItem(str(part.tokens))
table.setItem(row, 0, name_item)
table.setItem(row, 1, faction_item)
table.setItem(row, 2, VP_item)
col = 3
for obj in objectives:
value = part.narrative_points.get(obj.id, 0)
NP_item = QtWidgets.QTableWidgetItem(str(value))
table.setItem(row, col, NP_item)
col += 1
table.setItem(row, col, token_item)
table.setSortingEnabled(True)
table.sortItems(2, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
def _on_major_changed(self, value: int) -> None:
self.minorValue.setMaximum(value)
def _on_minor_changed(self, value: int) -> None:
self.majorValue.setMinimum(value)
def set_war_objective_values(self, major: int, minor: int, influence: bool) -> None:
self.majorValue.blockSignals(True)
self.minorValue.blockSignals(True)
self.influenceToken.blockSignals(True)
self.majorValue.setValue(major)
self.minorValue.setValue(minor)
self.influenceToken.setChecked(influence)
self.minorValue.setMaximum(major)
self.majorValue.setMinimum(minor)
self.majorValue.blockSignals(False)
self.minorValue.blockSignals(False)
self.influenceToken.blockSignals(False)
# Campaign page
def _on_sectors_table_context_menu(self, pos: QPoint) -> None:
item = self.sectorsTable.itemAt(pos)
if not item:
return
row = item.row()
name_item = self.sectorsTable.item(row, 0)
if not name_item:
return
sector_id = name_item.data(Qt.ItemDataRole.UserRole)
menu = QMenu(self)
edit_action = menu.addAction(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.sectorsTable.viewport()
assert viewport is not None
action = menu.exec(viewport.mapToGlobal(pos))
if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.SECTOR, sector_id)
elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.SECTOR, sector_id)
def _on_campaign_participants_table_context_menu(self, pos: QPoint) -> None:
item = self.campaignParticipantsTable.itemAt(pos)
if not item:
return
row = item.row()
name_item = self.campaignParticipantsTable.item(row, 0)
if not name_item:
return
participant_id = name_item.data(Qt.ItemDataRole.UserRole)
menu = QMenu(self)
edit_action = menu.addAction(Icons.get(IconName.EDIT), "Edit")
delete_action = menu.addAction(Icons.get(IconName.DELETE), "Delete")
viewport = self.campaignParticipantsTable.viewport()
assert viewport is not None
action = menu.exec(viewport.mapToGlobal(pos))
if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id)
elif action == delete_action and self.on_delete_item:
self.on_delete_item(ItemType.CAMPAIGN_PARTICIPANT, participant_id)
def show_campaign_details(self, *, name: str, month: int) -> None:
self.campaignName.setText(name)
self.campaignMonth.setText(calendar.month_name[month])
def display_campaign_sectors(self, sectors: List[SectorDTO]) -> None:
table = self.sectorsTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(sectors))
for row, sect in enumerate(sectors):
name_item = QtWidgets.QTableWidgetItem(sect.name)
round_item = QtWidgets.QTableWidgetItem(
format_round_label(sect.round_index)
)
mission_item = QtWidgets.QTableWidgetItem(sect.mission)
major_item = QtWidgets.QTableWidgetItem(sect.major)
minor_item = QtWidgets.QTableWidgetItem(sect.minor)
influence_item = QtWidgets.QTableWidgetItem(sect.influence)
description_item = QtWidgets.QTableWidgetItem(sect.description)
name_item.setData(Qt.ItemDataRole.UserRole, sect.id)
table.setItem(row, 0, name_item)
table.setItem(row, 1, round_item)
table.setItem(row, 2, mission_item)
table.setItem(row, 3, major_item)
table.setItem(row, 4, minor_item)
table.setItem(row, 5, influence_item)
table.setItem(row, 6, description_item)
table.setSortingEnabled(True)
table.sortItems(1, Qt.SortOrder.AscendingOrder)
table.resizeColumnsToContents()
def display_campaign_participants(
self,
participants: List[CampaignParticipantScoreDTO],
objectives: List[ObjectiveDTO],
) -> None:
table = self.campaignParticipantsTable
table.setSortingEnabled(False)
table.clearContents()
base_cols = ["Player", "Leader", "Theme", "Victory pts"]
headers = (
base_cols + [str(obj.name + " pts") for obj in objectives] + ["Tokens"]
)
table.setColumnCount(len(headers))
table.setHorizontalHeaderLabels(headers)
table.setRowCount(len(participants))
table.setIconSize(QSize(48, 16))
for row, part in enumerate(participants):
name_item = QtWidgets.QTableWidgetItem(part.player_name)
if part.rank_icon:
name_item.setIcon(part.rank_icon)
lead_item = QtWidgets.QTableWidgetItem(part.leader)
theme_item = QtWidgets.QTableWidgetItem(part.theme)
VP_item = QtWidgets.QTableWidgetItem(str(part.victory_points))
token_item = QtWidgets.QTableWidgetItem(str(part.tokens))
name_item.setData(Qt.ItemDataRole.UserRole, part.campaign_participant_id)
table.setItem(row, 0, name_item)
table.setItem(row, 1, lead_item)
table.setItem(row, 2, theme_item)
table.setItem(row, 3, VP_item)
col = 4
for obj in objectives:
value = part.narrative_points.get(obj.id, 0)
NP_item = QtWidgets.QTableWidgetItem(str(value))
table.setItem(row, col, NP_item)
col += 1
table.setItem(row, col, token_item)
table.setSortingEnabled(True)
table.sortItems(3, Qt.SortOrder.DescendingOrder)
table.resizeColumnsToContents()
# Round page
def _on_choices_table_context_menu(self, pos: QPoint) -> None:
item = self.choicesTable.itemAt(pos)
if not item:
return
row = item.row()
name_item = self.choicesTable.item(row, 0)
if not name_item:
return
choice_id = name_item.data(Qt.ItemDataRole.UserRole)
if choice_id is None:
return
menu = QMenu(self)
edit_action = menu.addAction(Icons.get(IconName.EDIT), "Edit")
viewport = self.choicesTable.viewport()
assert viewport is not None
action = menu.exec(viewport.mapToGlobal(pos))
if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.CHOICE, choice_id)
def _on_battles_table_context_menu(self, pos: QPoint) -> None:
item = self.battlesTable.itemAt(pos)
if not item:
return
row = item.row()
name_item = self.battlesTable.item(row, 0)
if not name_item:
return
battle_id = name_item.data(Qt.ItemDataRole.UserRole)
if battle_id is None:
return
menu = QMenu(self)
edit_action = menu.addAction(Icons.get(IconName.EDIT), "Edit")
viewport = self.battlesTable.viewport()
assert viewport is not None
action = menu.exec(viewport.mapToGlobal(pos))
if action == edit_action and self.on_edit_item:
self.on_edit_item(ItemType.BATTLE, battle_id)
def show_round_details(self, *, index: int | None) -> None:
self.roundNb.setText(f"Round {index}")
def display_round_choices(self, participants: List[ChoiceDTO]) -> None:
table = self.choicesTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(participants))
for row, choice in enumerate(participants):
participant_item = QtWidgets.QTableWidgetItem(choice.participant_name)
priority_item = QtWidgets.QTableWidgetItem(choice.priority_sector)
secondary_item = QtWidgets.QTableWidgetItem(choice.secondary_sector)
participant_item.setData(Qt.ItemDataRole.UserRole, choice.id)
table.setItem(row, 0, participant_item)
table.setItem(row, 1, priority_item)
table.setItem(row, 2, secondary_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()
def display_round_battles(self, sectors: List[BattleDTO]) -> None:
table = self.battlesTable
table.setSortingEnabled(False)
table.clearContents()
table.setRowCount(len(sectors))
table.setIconSize(QSize(32, 16))
for row, battle in enumerate(sectors):
sector_item = QtWidgets.QTableWidgetItem(battle.sector_name)
if battle.state_icon:
sector_item.setIcon(battle.state_icon)
player_1_item = QtWidgets.QTableWidgetItem(battle.player_1)
if battle.player1_icon:
player_1_item.setIcon(battle.player1_icon)
player_2_item = QtWidgets.QTableWidgetItem(battle.player_2)
if battle.player2_icon:
player_2_item.setIcon(battle.player2_icon)
score_item = QtWidgets.QTableWidgetItem(battle.score)
vp_item = QtWidgets.QTableWidgetItem(battle.victory_condition)
comment_item = QtWidgets.QTableWidgetItem(battle.comment)
sector_item.setData(Qt.ItemDataRole.UserRole, battle.id)
table.setItem(row, 0, sector_item)
table.setItem(row, 1, player_1_item)
table.setItem(row, 2, player_2_item)
table.setItem(row, 3, vp_item)
table.setItem(row, 4, score_item)
table.setItem(row, 5, comment_item)
table.setSortingEnabled(True)
table.resizeColumnsToContents()