import logging import os.path import sys from typing import Dict, List, Tuple from PyQt6 import QtWidgets from PyQt6.QtCore import Qt from PyQt6.QtGui import QStandardItemModel, QStandardItem from window.mod_manager_window_export import Ui_MainWindow class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent, parent_logger: logging.Logger, version: str = ""): super(MainWindow, self).__init__() self.parent = parent self.__logger = logging.getLogger("MainWindow") for handler in parent_logger.handlers: self.__logger.addHandler(handler) self.__logger.setLevel(parent_logger.level) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowTitle(f"LC Mod Manager {version}") self.ui.actionAdd_new_mod.triggered.connect(self.on_action_add_new_mod) self.ui.actionrefresh_mods.triggered.connect(self.on_action_refresh_mods) self.ui.actionCheck_for_Updates.triggered.connect(self.on_action_check_for_updates) self.ui.actionSet_game_folder.triggered.connect(self.on_action_set_game_folder) self.ui.actionRemove_ALL_manager_files.triggered.connect(self.on_action_remove_all_files) self.ui.DeleteModFilesButton.pressed.connect(self.on_pressed_delete_mod_files_button) self.ui.ApplyChangesButton.pressed.connect(self.on_pressed_apply_changes_button) self.ui.DisacrdChangesButton.pressed.connect(self.on_pressed_discard_changes_button) self.ui.TODOButton.hide() self.ui.ApplyChangesButton.hide() self.ui.DisacrdChangesButton.hide() self.ui.DeleteModFilesButton.hide() self.ui.actionCheck_for_Updates.setDisabled(True) def set_available_mods(self, available_mods: Dict[Tuple[str, str], Dict]): """ Sets the given mods as the list of available mods :param available_mods: :return: """ keys = [(name + "|" + version, (name, version)) for name, version in available_mods] keys.sort(key=lambda x: x[0]) item_model = QStandardItemModel(self.ui.AvailableModsList) for key in keys: mod_name, mod_version = key[1][0], key[1][1] item = QStandardItem(mod_name + f" ({mod_version})") item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole) item.setData((mod_name, mod_version), Qt.ItemDataRole.UserRole) item_model.appendRow(item) if self.parent.is_mod_installed(mod_name, mod_version): item.setCheckState(Qt.CheckState.Checked) item_model.itemChanged.connect(self.on_available_mod_item_changed) self.ui.AvailableModsList.setModel(item_model) def set_installed_mods(self, installed_mods: Dict[str, str]): """ Sets the given mods as the list of installed mods :param installed_mods: string list of all the mods as to be written to the list :return: """ keys = [(name + "|" + version, (name, version)) for name, version in installed_mods] keys.sort(key=lambda x: x[0]) item_model = QStandardItemModel(self.ui.InstalledModsListView) for key in keys: mod_name, mod_version = key[1][0], key[1][1] if (mod_name, mod_version) in self.parent.available_mods.keys(): mod_version = self.parent.available_mods[(mod_name, mod_version)]['version'] else: mod_version = "Not Tracked" item = QStandardItem(mod_name + f" ({mod_version})") item.setFlags(Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled) item.setData(Qt.CheckState.Unchecked, Qt.ItemDataRole.CheckStateRole) item.setData((mod_name, mod_version), Qt.ItemDataRole.UserRole) item_model.appendRow(item) item.setCheckState(Qt.CheckState.Checked) item_model.itemChanged.connect(self.on_installed_mod_item_changed) self.ui.InstalledModsListView.setModel(item_model) # UI Callback functions ## Actions def on_action_add_new_mod(self): self.__logger.debug("Action: \"add new mod\" triggered!") dialog = QtWidgets.QFileDialog(self, "Select Lethal Company mod") dialog.setFileMode(QtWidgets.QFileDialog.FileMode.ExistingFiles) result = dialog.getOpenFileName(filter='ZIP (*.zip)') self.__logger.debug(f"user selected \"{result[0]}\"") if result == ('', ''): self.__logger.debug("Action: \"add new mod\" was cancelled!") return if not os.path.isfile(result[0]): dialog = QtWidgets.QMessageBox() dialog.setWindowTitle("Not a file") dialog.setInformativeText(f"The given file \"{result}\" did not look like a file!") dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) dialog.exec() return if not self.parent.is_valid_mod_file(result[0]): dialog = QtWidgets.QMessageBox() dialog.setWindowTitle("Not a valid mod file") dialog.setInformativeText( f"The given file \"{result}\" did not look like a mod file. Is the manifest.json present?") dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) dialog.exec() return self.parent.add_mod_file(result[0]) def on_action_refresh_mods(self): self.__logger.debug("Action: \"refresh mods\" triggered!") self.parent.index_stored_mods() self.parent.index_installed_mods() def on_action_check_for_updates(self): self.__logger.debug("Action: \"check for updates\" triggered!") def on_action_set_game_folder(self): self.__logger.debug("Action: \"set game folder\" triggered!") dialog = QtWidgets.QFileDialog(self, "Select Lethal Company folder") dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory) dialog.setOptions(QtWidgets.QFileDialog.Option.ShowDirsOnly) result = dialog.getExistingDirectory() dir_accepted = self.parent.set_game_folder(result) attempt = 0 while not dir_accepted: attempt += 1 dialog = QtWidgets.QMessageBox() dialog.setWindowTitle("Invalid game path") dialog.setInformativeText(f"The given path \"{result}\" did not look like the Lethal Company game folder!\n" f"If you can't find it. Try using steam and select \"browse local files\"!") dialog.setIcon(QtWidgets.QMessageBox.Icon.Warning) dialog.exec() dialog = QtWidgets.QFileDialog(self, "Select Lethal Company folder") dialog.setFileMode(QtWidgets.QFileDialog.FileMode.Directory) dialog.setOptions(QtWidgets.QFileDialog.Option.ShowDirsOnly) result = dialog.getExistingDirectory() dir_accepted = self.parent.set_game_folder(result) if attempt > 2: return def on_action_remove_all_files(self): self.__logger.debug("Action: \"remove all manager files\" triggered!") self.parent.nuke_manager_files() ## Buttons def on_pressed_delete_mod_files_button(self): self.__logger.debug("Pressed button: \"Delete Mod Files\"") raise NotImplementedError def on_pressed_apply_changes_button(self): self.__logger.debug("Pressed button: \"Apply Changes\"") raise NotImplementedError def on_pressed_discard_changes_button(self): self.__logger.debug("Pressed button: \"Discard Changes\"") raise NotImplementedError def on_available_mod_item_changed(self, item: QStandardItem): self.__logger.debug(f"Available Mod list item \"{item.text()}\" changed to {item.checkState()}") mod_name, mod_version = item.data(Qt.ItemDataRole.UserRole) if item.checkState() == Qt.CheckState.Checked: self.parent.install_mod(mod_name, mod_version) elif item.checkState() == Qt.CheckState.Unchecked: self.parent.uninstall_mod(mod_name, mod_version) def on_installed_mod_item_changed(self, item: QStandardItem): self.__logger.debug(f"Installed Mod list item \"{item.text()}\" changed to {item.checkState()}") mod_name, mod_version = item.data(Qt.ItemDataRole.UserRole) if item.checkState() == Qt.CheckState.Unchecked: # mod should be uninstalled accepted = QtWidgets.QMessageBox.question(self, "Really uninstall mod?", f"Do you really want to uninstall the mod \"{mod_name}\"?\n" "This could lead to permanent data loss if it wasn't tracked!") if accepted: self.parent.uninstall_mod(mod_name, mod_version) else: return