mirror of
https://github.com/dcs-liberation/dcs_liberation.git
synced 2025-11-10 14:22:26 +00:00
This appears to be incompatible with pyinstaller. I get the following when trying to run the executable generated with pyside6: ``` Traceback (most recent call last): File "qt_ui\main.py", line 29, in <module> File "PyInstaller\loader\pyimod03_importers.py", line 476, in exec_module File "qt_ui\windows\QLiberationWindow.py", line 28, in <module> File "PyInstaller\loader\pyimod03_importers.py", line 476, in exec_module File "qt_ui\widgets\map\QLiberationMap.py", line 11, in <module> ImportError: could not import module 'PySide6.QtPrintSupport' ```
363 lines
12 KiB
Python
363 lines
12 KiB
Python
import logging
|
|
import textwrap
|
|
from typing import Callable
|
|
|
|
from PySide2.QtCore import QItemSelectionModel, QPoint, QSize, Qt
|
|
from PySide2.QtGui import QStandardItem, QStandardItemModel
|
|
from PySide2.QtWidgets import (
|
|
QAbstractItemView,
|
|
QCheckBox,
|
|
QComboBox,
|
|
QDialog,
|
|
QGridLayout,
|
|
QGroupBox,
|
|
QLabel,
|
|
QListView,
|
|
QPushButton,
|
|
QSpinBox,
|
|
QStackedLayout,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
|
|
import qt_ui.uiconstants as CONST
|
|
from game.game import Game
|
|
from game.settings import (
|
|
BooleanOption,
|
|
BoundedFloatOption,
|
|
BoundedIntOption,
|
|
ChoicesOption,
|
|
MinutesOption,
|
|
OptionDescription,
|
|
Settings,
|
|
)
|
|
from qt_ui.widgets.QLabeledWidget import QLabeledWidget
|
|
from qt_ui.widgets.spinsliders import FloatSpinSlider, TimeInputs
|
|
from qt_ui.windows.GameUpdateSignal import GameUpdateSignal
|
|
from qt_ui.windows.settings.plugins import PluginOptionsPage, PluginsPage
|
|
|
|
|
|
class CheatSettingsBox(QGroupBox):
|
|
def __init__(self, game: Game, apply_settings: Callable[[], None]) -> None:
|
|
super().__init__("Cheat Settings")
|
|
self.main_layout = QVBoxLayout()
|
|
self.setLayout(self.main_layout)
|
|
|
|
self.red_ato_checkbox = QCheckBox()
|
|
self.red_ato_checkbox.setChecked(game.settings.show_red_ato)
|
|
self.red_ato_checkbox.toggled.connect(apply_settings)
|
|
|
|
self.frontline_cheat_checkbox = QCheckBox()
|
|
self.frontline_cheat_checkbox.setChecked(game.settings.enable_frontline_cheats)
|
|
self.frontline_cheat_checkbox.toggled.connect(apply_settings)
|
|
|
|
self.base_capture_cheat_checkbox = QCheckBox()
|
|
self.base_capture_cheat_checkbox.setChecked(
|
|
game.settings.enable_base_capture_cheat
|
|
)
|
|
self.base_capture_cheat_checkbox.toggled.connect(apply_settings)
|
|
|
|
self.red_ato = QLabeledWidget("Show Red ATO:", self.red_ato_checkbox)
|
|
self.main_layout.addLayout(self.red_ato)
|
|
self.frontline_cheat = QLabeledWidget(
|
|
"Enable Frontline Cheats:", self.frontline_cheat_checkbox
|
|
)
|
|
self.main_layout.addLayout(self.frontline_cheat)
|
|
self.base_capture_cheat = QLabeledWidget(
|
|
"Enable Base Capture Cheat:", self.base_capture_cheat_checkbox
|
|
)
|
|
self.main_layout.addLayout(self.base_capture_cheat)
|
|
|
|
@property
|
|
def show_red_ato(self) -> bool:
|
|
return self.red_ato_checkbox.isChecked()
|
|
|
|
@property
|
|
def show_frontline_cheat(self) -> bool:
|
|
return self.frontline_cheat_checkbox.isChecked()
|
|
|
|
@property
|
|
def show_base_capture_cheat(self) -> bool:
|
|
return self.base_capture_cheat_checkbox.isChecked()
|
|
|
|
|
|
class AutoSettingsLayout(QGridLayout):
|
|
def __init__(
|
|
self,
|
|
page: str,
|
|
section: str,
|
|
settings: Settings,
|
|
write_full_settings: Callable[[], None],
|
|
) -> None:
|
|
super().__init__()
|
|
self.settings = settings
|
|
self.write_full_settings = write_full_settings
|
|
|
|
for row, (name, description) in enumerate(Settings.fields(page, section)):
|
|
self.add_label(row, description)
|
|
if isinstance(description, BooleanOption):
|
|
self.add_checkbox_for(row, name, description)
|
|
elif isinstance(description, ChoicesOption):
|
|
self.add_combobox_for(row, name, description)
|
|
elif isinstance(description, BoundedFloatOption):
|
|
self.add_float_spin_slider_for(row, name, description)
|
|
elif isinstance(description, BoundedIntOption):
|
|
self.add_spinner_for(row, name, description)
|
|
elif isinstance(description, MinutesOption):
|
|
self.add_duration_controls_for(row, name, description)
|
|
else:
|
|
raise TypeError(f"Unhandled option type: {description}")
|
|
|
|
def add_label(self, row: int, description: OptionDescription) -> None:
|
|
text = f"<strong>{description.text}</strong>"
|
|
if description.detail is not None:
|
|
wrapped = "<br />".join(textwrap.wrap(description.detail, width=55))
|
|
text += f"<br />{wrapped}"
|
|
label = QLabel(text)
|
|
if description.tooltip is not None:
|
|
label.setToolTip(description.tooltip)
|
|
self.addWidget(label, row, 0)
|
|
|
|
def add_checkbox_for(self, row: int, name: str, description: BooleanOption) -> None:
|
|
def on_toggle(value: bool) -> None:
|
|
if description.invert:
|
|
value = not value
|
|
self.settings.__dict__[name] = value
|
|
if description.causes_expensive_game_update:
|
|
self.write_full_settings()
|
|
|
|
checkbox = QCheckBox()
|
|
value = self.settings.__dict__[name]
|
|
if description.invert:
|
|
value = not value
|
|
checkbox.setChecked(value)
|
|
checkbox.toggled.connect(on_toggle)
|
|
self.addWidget(checkbox, row, 1, Qt.AlignRight)
|
|
|
|
def add_combobox_for(self, row: int, name: str, description: ChoicesOption) -> None:
|
|
combobox = QComboBox()
|
|
|
|
def on_changed(index: int) -> None:
|
|
self.settings.__dict__[name] = combobox.itemData(index)
|
|
|
|
for text, value in description.choices.items():
|
|
combobox.addItem(text, value)
|
|
combobox.setCurrentText(
|
|
description.text_for_value(self.settings.__dict__[name])
|
|
)
|
|
combobox.currentIndexChanged.connect(on_changed)
|
|
self.addWidget(combobox, row, 1, Qt.AlignRight)
|
|
|
|
def add_float_spin_slider_for(
|
|
self, row: int, name: str, description: BoundedFloatOption
|
|
) -> None:
|
|
spinner = FloatSpinSlider(
|
|
description.min,
|
|
description.max,
|
|
self.settings.__dict__[name],
|
|
divisor=description.divisor,
|
|
)
|
|
|
|
def on_changed() -> None:
|
|
self.settings.__dict__[name] = spinner.value
|
|
|
|
spinner.spinner.valueChanged.connect(on_changed)
|
|
self.addLayout(spinner, row, 1, Qt.AlignRight)
|
|
|
|
def add_spinner_for(
|
|
self, row: int, name: str, description: BoundedIntOption
|
|
) -> None:
|
|
def on_changed(value: int) -> None:
|
|
self.settings.__dict__[name] = value
|
|
|
|
spinner = QSpinBox()
|
|
spinner.setMinimum(description.min)
|
|
spinner.setMaximum(description.max)
|
|
spinner.setValue(self.settings.__dict__[name])
|
|
|
|
spinner.valueChanged.connect(on_changed)
|
|
self.addWidget(spinner, row, 1, Qt.AlignRight)
|
|
|
|
def add_duration_controls_for(
|
|
self, row: int, name: str, description: MinutesOption
|
|
) -> None:
|
|
inputs = TimeInputs(
|
|
self.settings.__dict__[name], description.min, description.max
|
|
)
|
|
|
|
def on_changed() -> None:
|
|
self.settings.__dict__[name] = inputs.value
|
|
|
|
inputs.spinner.valueChanged.connect(on_changed)
|
|
self.addLayout(inputs, row, 1, Qt.AlignRight)
|
|
|
|
|
|
class AutoSettingsGroup(QGroupBox):
|
|
def __init__(
|
|
self,
|
|
page: str,
|
|
section: str,
|
|
settings: Settings,
|
|
write_full_settings: Callable[[], None],
|
|
) -> None:
|
|
super().__init__(section)
|
|
self.setLayout(AutoSettingsLayout(page, section, settings, write_full_settings))
|
|
|
|
|
|
class AutoSettingsPageLayout(QVBoxLayout):
|
|
def __init__(
|
|
self,
|
|
page: str,
|
|
settings: Settings,
|
|
write_full_settings: Callable[[], None],
|
|
) -> None:
|
|
super().__init__()
|
|
self.setAlignment(Qt.AlignTop)
|
|
|
|
for section in Settings.sections(page):
|
|
self.addWidget(
|
|
AutoSettingsGroup(page, section, settings, write_full_settings)
|
|
)
|
|
|
|
|
|
class AutoSettingsPage(QWidget):
|
|
def __init__(
|
|
self,
|
|
page: str,
|
|
settings: Settings,
|
|
write_full_settings: Callable[[], None],
|
|
) -> None:
|
|
super().__init__()
|
|
self.setLayout(AutoSettingsPageLayout(page, settings, write_full_settings))
|
|
|
|
|
|
class QSettingsWindow(QDialog):
|
|
def __init__(self, game: Game):
|
|
super().__init__()
|
|
|
|
self.game = game
|
|
self.pluginsPage = None
|
|
self.pluginsOptionsPage = None
|
|
|
|
self.pages: dict[str, AutoSettingsPage] = {}
|
|
for page in Settings.pages():
|
|
self.pages[page] = AutoSettingsPage(page, game.settings, self.applySettings)
|
|
|
|
self.setModal(True)
|
|
self.setWindowTitle("Settings")
|
|
self.setWindowIcon(CONST.ICONS["Settings"])
|
|
self.setMinimumSize(600, 250)
|
|
|
|
self.initUi()
|
|
|
|
def initUi(self):
|
|
self.layout = QGridLayout()
|
|
|
|
self.categoryList = QListView()
|
|
self.right_layout = QStackedLayout()
|
|
|
|
self.categoryList.setMaximumWidth(175)
|
|
|
|
self.categoryModel = QStandardItemModel(self.categoryList)
|
|
|
|
self.categoryList.setIconSize(QSize(32, 32))
|
|
|
|
for name, page in self.pages.items():
|
|
page_item = QStandardItem(name)
|
|
if name in CONST.ICONS:
|
|
page_item.setIcon(CONST.ICONS[name])
|
|
else:
|
|
page_item.setIcon(CONST.ICONS["Generator"])
|
|
page_item.setEditable(False)
|
|
page_item.setSelectable(True)
|
|
self.categoryModel.appendRow(page_item)
|
|
self.right_layout.addWidget(page)
|
|
|
|
self.initCheatLayout()
|
|
cheat = QStandardItem("Cheat Menu")
|
|
cheat.setIcon(CONST.ICONS["Cheat"])
|
|
cheat.setEditable(False)
|
|
cheat.setSelectable(True)
|
|
self.categoryModel.appendRow(cheat)
|
|
self.right_layout.addWidget(self.cheatPage)
|
|
|
|
self.pluginsPage = PluginsPage()
|
|
plugins = QStandardItem("LUA Plugins")
|
|
plugins.setIcon(CONST.ICONS["Plugins"])
|
|
plugins.setEditable(False)
|
|
plugins.setSelectable(True)
|
|
self.categoryModel.appendRow(plugins)
|
|
self.right_layout.addWidget(self.pluginsPage)
|
|
|
|
self.pluginsOptionsPage = PluginOptionsPage()
|
|
pluginsOptions = QStandardItem("LUA Plugins Options")
|
|
pluginsOptions.setIcon(CONST.ICONS["PluginsOptions"])
|
|
pluginsOptions.setEditable(False)
|
|
pluginsOptions.setSelectable(True)
|
|
self.categoryModel.appendRow(pluginsOptions)
|
|
self.right_layout.addWidget(self.pluginsOptionsPage)
|
|
|
|
self.categoryList.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
self.categoryList.setModel(self.categoryModel)
|
|
self.categoryList.selectionModel().setCurrentIndex(
|
|
self.categoryList.indexAt(QPoint(1, 1)), QItemSelectionModel.Select
|
|
)
|
|
self.categoryList.selectionModel().selectionChanged.connect(
|
|
self.onSelectionChanged
|
|
)
|
|
|
|
self.layout.addWidget(self.categoryList, 0, 0, 1, 1)
|
|
self.layout.addLayout(self.right_layout, 0, 1, 5, 1)
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
def initCheatLayout(self):
|
|
|
|
self.cheatPage = QWidget()
|
|
self.cheatLayout = QVBoxLayout()
|
|
self.cheatPage.setLayout(self.cheatLayout)
|
|
|
|
self.cheat_options = CheatSettingsBox(self.game, self.applySettings)
|
|
self.cheatLayout.addWidget(self.cheat_options)
|
|
|
|
self.moneyCheatBox = QGroupBox("Money Cheat")
|
|
self.moneyCheatBox.setAlignment(Qt.AlignTop)
|
|
self.moneyCheatBoxLayout = QGridLayout()
|
|
self.moneyCheatBox.setLayout(self.moneyCheatBoxLayout)
|
|
|
|
cheats_amounts = [25, 50, 100, 200, 500, 1000, -25, -50, -100, -200]
|
|
for i, amount in enumerate(cheats_amounts):
|
|
if amount > 0:
|
|
btn = QPushButton("Cheat +" + str(amount) + "M")
|
|
btn.setProperty("style", "btn-success")
|
|
else:
|
|
btn = QPushButton("Cheat " + str(amount) + "M")
|
|
btn.setProperty("style", "btn-danger")
|
|
btn.clicked.connect(self.cheatLambda(amount))
|
|
self.moneyCheatBoxLayout.addWidget(btn, i / 2, i % 2)
|
|
self.cheatLayout.addWidget(self.moneyCheatBox, stretch=1)
|
|
|
|
def cheatLambda(self, amount):
|
|
return lambda: self.cheatMoney(amount)
|
|
|
|
def cheatMoney(self, amount):
|
|
logging.info("CHEATING FOR AMOUNT : " + str(amount) + "M")
|
|
self.game.blue.budget += amount
|
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
|
|
|
def applySettings(self):
|
|
self.game.settings.show_red_ato = self.cheat_options.show_red_ato
|
|
self.game.settings.enable_frontline_cheats = (
|
|
self.cheat_options.show_frontline_cheat
|
|
)
|
|
self.game.settings.enable_base_capture_cheat = (
|
|
self.cheat_options.show_base_capture_cheat
|
|
)
|
|
|
|
self.game.compute_conflicts_position()
|
|
GameUpdateSignal.get_instance().updateGame(self.game)
|
|
|
|
def onSelectionChanged(self):
|
|
index = self.categoryList.selectionModel().currentIndex().row()
|
|
self.right_layout.setCurrentIndex(index)
|