aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/installer
diff options
context:
space:
mode:
Diffstat (limited to 'libs/installer_fomod_plus/installer')
-rw-r--r--libs/installer_fomod_plus/installer/CMakeLists.txt53
-rw-r--r--libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp842
-rw-r--r--libs/installer_fomod_plus/installer/FomodInstallerWindow.h214
-rw-r--r--libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp510
-rw-r--r--libs/installer_fomod_plus/installer/FomodPlusInstaller.h115
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_de.ts4
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_en.ts4
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_installer_de.ts75
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_installer_en.ts79
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_installer_zh_CN.ts75
-rw-r--r--libs/installer_fomod_plus/installer/fomod_plus_zh_CN.ts4
-rw-r--r--libs/installer_fomod_plus/installer/fomodplus.json4
-rw-r--r--libs/installer_fomod_plus/installer/integration/FomodDataContent.cpp50
-rw-r--r--libs/installer_fomod_plus/installer/integration/FomodDataContent.h21
-rw-r--r--libs/installer_fomod_plus/installer/lib/ConditionTester.cpp176
-rw-r--r--libs/installer_fomod_plus/installer/lib/ConditionTester.h42
-rw-r--r--libs/installer_fomod_plus/installer/lib/CrashHandler.cpp122
-rw-r--r--libs/installer_fomod_plus/installer/lib/CrashHandler.h20
-rw-r--r--libs/installer_fomod_plus/installer/lib/FileInstaller.cpp274
-rw-r--r--libs/installer_fomod_plus/installer/lib/FileInstaller.h94
-rw-r--r--libs/installer_fomod_plus/installer/lib/FlagMap.h113
-rw-r--r--libs/installer_fomod_plus/installer/lib/Logger.h116
-rw-r--r--libs/installer_fomod_plus/installer/lib/ViewModels.h120
-rw-r--r--libs/installer_fomod_plus/installer/resources.qrc14
-rw-r--r--libs/installer_fomod_plus/installer/resources/delete.pngbin2125 -> 0 bytes
-rw-r--r--libs/installer_fomod_plus/installer/resources/fomod_icon.pngbin1782 -> 0 bytes
-rw-r--r--libs/installer_fomod_plus/installer/resources/image_icon.pngbin727 -> 0 bytes
-rw-r--r--libs/installer_fomod_plus/installer/resources/left-chevron.pngbin1732 -> 0 bytes
-rw-r--r--libs/installer_fomod_plus/installer/resources/right-chevron.pngbin1808 -> 0 bytes
-rw-r--r--libs/installer_fomod_plus/installer/ui/ClickableWidget.h24
-rw-r--r--libs/installer_fomod_plus/installer/ui/Colors.h155
-rw-r--r--libs/installer_fomod_plus/installer/ui/FomodImageViewer.cpp284
-rw-r--r--libs/installer_fomod_plus/installer/ui/FomodImageViewer.h92
-rw-r--r--libs/installer_fomod_plus/installer/ui/FomodViewModel.cpp746
-rw-r--r--libs/installer_fomod_plus/installer/ui/FomodViewModel.h154
-rw-r--r--libs/installer_fomod_plus/installer/ui/ScaleLabel.cpp120
-rw-r--r--libs/installer_fomod_plus/installer/ui/ScaleLabel.h45
-rw-r--r--libs/installer_fomod_plus/installer/ui/UIHelper.cpp97
-rw-r--r--libs/installer_fomod_plus/installer/ui/UIHelper.h80
39 files changed, 0 insertions, 4938 deletions
diff --git a/libs/installer_fomod_plus/installer/CMakeLists.txt b/libs/installer_fomod_plus/installer/CMakeLists.txt
deleted file mode 100644
index 07a670b..0000000
--- a/libs/installer_fomod_plus/installer/CMakeLists.txt
+++ /dev/null
@@ -1,53 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-include(FetchContent)
-
-file(GLOB_RECURSE INSTALLER_SOURCES CONFIGURE_DEPENDS
- ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/*.h
- ${CMAKE_CURRENT_SOURCE_DIR}/*.ui
- ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc
-)
-file(GLOB SHARE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../share/**/*.cpp")
-
-add_library(fomod_plus_installer SHARED ${INSTALLER_SOURCES} ${SHARE_SOURCES})
-
-target_include_directories(
- fomod_plus_installer
- PUBLIC
- ${CMAKE_CURRENT_SOURCE_DIR}/../share
- ${MO2_UIBASE_INCLUDE_DIRS}
- ${MO2_ARCHIVE_INCLUDE_DIRS}
- ${CMAKE_CURRENT_SOURCE_DIR}
-)
-
-FetchContent_Declare(
- pugixml
- GIT_REPOSITORY https://github.com/zeux/pugixml
- GIT_TAG v1.14
- PATCH_COMMAND ${CMAKE_COMMAND}
- -DPUGIXML_CMAKELISTS=<SOURCE_DIR>/CMakeLists.txt
- -P ${CMAKE_CURRENT_LIST_DIR}/../cmake/patch_pugixml.cmake
-)
-
-FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE)
-FetchContent_MakeAvailable(pugixml json)
-
-set(project_type plugin)
-project(fomod_plus_installer)
-
-target_link_libraries(fomod_plus_installer PRIVATE mo2::uibase pugixml nlohmann_json::nlohmann_json)
-if (WIN32)
- target_link_libraries(fomod_plus_installer PRIVATE dbghelp)
-endif ()
-mo2_configure_plugin(fomod_plus_installer NO_SOURCES WARNINGS OFF PRIVATE_DEPENDS)
-
-if (MSVC)
- target_compile_options(fomod_plus_installer PRIVATE $<$<CONFIG:RelWithDebInfo>:/Zi>)
- target_link_options(fomod_plus_installer PRIVATE $<$<CONFIG:RelWithDebInfo>:/DEBUG>)
- install(FILES $<TARGET_PDB_FILE:fomod_plus_installer>
- DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO}
- CONFIGURATIONS RelWithDebInfo
- OPTIONAL)
-endif ()
-
-mo2_install_target(fomod_plus_installer)
diff --git a/libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp b/libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp
deleted file mode 100644
index bd1c0bc..0000000
--- a/libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp
+++ /dev/null
@@ -1,842 +0,0 @@
-#include "FomodInstallerWindow.h"
-
-#include "ui/FomodImageViewer.h"
-
-#include "ui/ScaleLabel.h"
-#include "ui/UIHelper.h"
-#include "xml/ModuleConfiguration.h"
-
-#include <QButtonGroup>
-#include <QCheckBox>
-#include <QComboBox>
-#include <QCompleter>
-#include <QGroupBox>
-#include <QLabel>
-#include <QRadioButton>
-#include <QScrollArea>
-#include <QScrollBar>
-#include <QSettings>
-#include <QSizePolicy>
-#include <QStandardPaths>
-#include <QSplitter>
-#include <QVBoxLayout>
-#include <utility>
-
-#include "ui/FomodViewModel.h"
-
-#include <unordered_set>
-
-/**
- *
- * @param installer
- * @param modName
- * @param tree
- * @param fomodPath
- * @param viewModel
- * @param fomodJson
- * @param parent
- */
-FomodInstallerWindow::FomodInstallerWindow(
- FomodPlusInstaller* installer,
- GuessedValue<QString>& modName,
- const std::shared_ptr<IFileTree>& tree,
- QString fomodPath,
- const std::shared_ptr<FomodViewModel>& viewModel,
- const nlohmann::json& fomodJson,
- QWidget* parent): QDialog(parent),
- mInstaller(installer),
- mFomodPath(std::move(fomodPath)),
- mModName(modName),
- mTree(tree),
- mViewModel(viewModel),
- mFomodJson(fomodJson)
-{
- setupUi();
-
- mInstallStepStack = new QStackedWidget(this);
-
- // Handle legacy FOMODs with no steps
- if (mViewModel->getSteps().empty()) {
- // Create a simple "Install" widget for legacy FOMODs
- auto* legacyWidget = new QWidget(this);
- auto* layout = new QVBoxLayout(legacyWidget);
- auto* label = new QLabel("This mod will install all files automatically.", legacyWidget);
- layout->addWidget(label);
- mInstallStepStack->addWidget(legacyWidget);
- } else {
- updateInstallStepStack();
- stylePreviouslySelectedOptions();
- stylePreviouslyDeselectedOptions();
- }
-
- const auto containerLayout = createContainerLayout();
- setLayout(containerLayout);
-
- updateButtons();
- restoreGeometryAndState();
-
- if (!mViewModel->getSteps().empty()) {
- populatePluginMap();
- if (mInstaller->shouldAutoRestoreChoices()) {
- onSelectPreviousClicked();
- }
- } else {
- // For empty FOMODs, set default description
- mDescriptionBox->setText("This mod will install all files automatically.");
- }
-}
-
-void FomodInstallerWindow::closeEvent(QCloseEvent* event)
-{
- saveGeometryAndState();
- QDialog::closeEvent(event);
-}
-
-void FomodInstallerWindow::saveGeometryAndState() const
-{
- const auto configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
- QDir().mkpath(configDir);
- QSettings settings(configDir + "/fomod-plus-settings.ini", QSettings::IniFormat);
- settings.setValue("windowGeometry", saveGeometry());
- settings.setValue("centerSplitState", mCenterRow->saveState());
- settings.setValue("leftSplitState", mLeftPane->saveState());
-}
-
-void FomodInstallerWindow::restoreGeometryAndState()
-{
- const auto configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
- const QSettings settings(configDir + "/fomod-plus-settings.ini", QSettings::IniFormat);
- restoreGeometry(settings.value("windowGeometry").toByteArray());
- mCenterRow->restoreState(settings.value("centerSplitState").toByteArray());
- mLeftPane->restoreState(settings.value("leftSplitState").toByteArray());
-}
-
-void FomodInstallerWindow::populatePluginMap()
-{
- const auto checkboxes = findChildren<QCheckBox*>();
- const auto radioButtons = findChildren<QRadioButton*>();
-
- for (const auto& step : mViewModel->getSteps()) {
- for (const auto& group : step->getGroups()) {
- for (const auto& plugin : group->getPlugins()) {
-
- const auto name = createObjectName(plugin, group);
-
- for (auto* checkbox : checkboxes) {
- if (checkbox->objectName() == name) {
- mPluginMap.insert({ name, { plugin, checkbox } });
- }
- }
- for (auto* radioButton : radioButtons) {
- if (radioButton->objectName() == name) {
- mPluginMap.insert({ name, { plugin, radioButton } });
- }
- }
- }
- }
- }
-}
-
-void FomodInstallerWindow::onNextClicked()
-{
- // For legacy FOMODs with no steps, always install
- if (mViewModel->getSteps().empty()) {
- onInstallClicked();
- return;
- }
-
- if (!mViewModel->isLastVisibleStep()) {
- mViewModel->stepForward();
- mInstallStepStack->setCurrentIndex(mViewModel->getCurrentStepIndex());
- updateButtons();
- updateDisplayForActivePlugin();
- } else {
- onInstallClicked();
- }
-}
-
-void FomodInstallerWindow::updateCheckboxStates() const
-{
- // PluginMap presumed to be populated
- for (const auto& [objectName, pluginData] : mPluginMap) {
- if (pluginData.plugin->isSelected() != pluginData.uiElement->isChecked()) {
- const auto widgetType = pluginData.uiElement->metaObject()->className();
- if (objectName != nullptr) {
- logMessage(DEBUG,
- "Updating " + objectName.toStdString() + " to state: " + (pluginData.plugin->isSelected()
- ? "TRUE"
- : "FALSE") + " because " + widgetType +
- " selection state is " + (pluginData.uiElement->isChecked() ? "TRUE" : "FALSE"));
- }
- pluginData.uiElement->setChecked(pluginData.plugin->isSelected());
- }
-
- if (pluginData.plugin->isEnabled() != pluginData.uiElement->isEnabled()) {
- logMessage(DEBUG, "[WINDOW] Changing enabled state of element " + objectName.toStdString() + " to " +
- (pluginData.plugin->isEnabled() ? "TRUE" : "FALSE"));
- pluginData.uiElement->setEnabled(pluginData.plugin->isEnabled());
- }
- }
-}
-
-void FomodInstallerWindow::onPluginToggled(const bool selected, const std::shared_ptr<GroupViewModel>& group,
- const std::shared_ptr<PluginViewModel>& plugin) const
-{
- logMessage(INFO,
- "onPluginToggled called with " + plugin->getName() + " in " + group->getName() + ": " +
- std::to_string(selected));
- if (mViewModel->togglePlugin(group, plugin, selected)) {
- updateCheckboxStates();
- }
- if (mNextInstallButton != nullptr) {
- updateButtons();
- }
-}
-
-void FomodInstallerWindow::onPluginManuallyUnchecked(const std::shared_ptr<PluginViewModel>& plugin) const
-{
- logMessage(INFO, "onPluginManuallyUnchecked called with " + plugin->getName());
- mViewModel->markManuallySet(plugin);
-}
-
-void FomodInstallerWindow::onPluginHovered(const std::shared_ptr<PluginViewModel>& plugin) const
-{
- mViewModel->setActivePlugin(plugin);
- updateDisplayForActivePlugin();
-}
-
-void FomodInstallerWindow::onBackClicked() const
-{
- mViewModel->stepBack();
- mInstallStepStack->setCurrentIndex(mViewModel->getCurrentStepIndex());
- updateButtons();
- updateDisplayForActivePlugin();
-}
-
-void FomodInstallerWindow::onInstallClicked()
-{
- saveGeometryAndState();
-
- logMessage(DEBUG, "Installing mod: " + mModName->toStdString());
- mModName.update(mModNameInput->currentText(), GUESS_USER);
- mViewModel->preinstall(mTree, mFomodPath);
- // now the installer is available in the outer scope
- // the outer scope should call getFileInstaller() and install there.
- this->accept();
-}
-
-void FomodInstallerWindow::updateButtons() const
-{
- // For legacy FOMODs with no steps, always show Install
- if (mViewModel->getSteps().empty()) {
- mBackButton->setEnabled(false);
- mNextInstallButton->setText(tr("Install"));
- return;
- }
-
- if (mViewModel->isFirstVisibleStep()) {
- mBackButton->setEnabled(false);
- } else {
- mBackButton->setEnabled(true);
- }
-
- if (mViewModel->isLastVisibleStep()) {
- mNextInstallButton->setText(tr("Install"));
- } else {
- mNextInstallButton->setText(tr("Next"));
- }
-}
-
-void FomodInstallerWindow::setupUi()
-{
- setWindowIcon(QIcon(":/fomod/hat"));
- setWindowFlags(Qt::Window); // Allows OS-controlled resizing, including snapping
- setMinimumSize(UiConstants::WINDOW_MIN_WIDTH, UiConstants::WINDOW_MIN_HEIGHT);
- setWindowTitle(mModName);
- setWindowModality(Qt::NonModal); // To allow scrolling modlist without closing the window
-}
-
-// mInstallStepStack must be initialized before calling this
-void FomodInstallerWindow::updateInstallStepStack()
-{
- if (!mInstallStepStack) {
- logMessage(ERR, "updateInstallStepStack called with no initialized mInstallStepStack. tf?");
- return;
- }
- // Create the widgets for each step. Not sure if we need these as member variables. Try it like this for now.
- for (const auto& steps = mViewModel->getSteps(); const auto& installStep : steps) {
- mInstallStepStack->addWidget(createStepWidget(installStep));
- }
- mInstallStepStack->setCurrentIndex(mViewModel->getCurrentStepIndex());
-}
-
-/*
-+-------------------------------------------------------------------+
-| +----------------------------------------------------------------+|
-| | ||
-| | Metadata and Name Input ||
-| | ||
-| +----------------------------------------------------------------+|
-| +------------------------------++--------------------------------+|
-| | || ||
-| | || ||
-| | Description || ||
-| | || ||
-| | || Step/Group/Plugins ||
-| | || ||
-| +------------------------------+| ||
-| +------------------------------+| ||
-| | || ||
-| | || ||
-| | Image || ||
-| | || ||
-| | || ||
-| | || ||
-| +----------------------------------------------------------------+|
-| | ||
-| | Bottom Bar ||
-| +----------------------------------------------------------------+|
-+-------------------------------------------------------------------+
-*/
-QBoxLayout* FomodInstallerWindow::createContainerLayout()
-{
- const auto layout = new QVBoxLayout(this);
-
- mTopRow = createTopRow();
- mCenterRow = createCenterRow();
- mBottomRow = createBottomRow();
-
- layout->addWidget(mTopRow);
- layout->addWidget(mCenterRow, 1); // stretch 1 here so the others are static size
- layout->addWidget(mBottomRow);
-
- if (mInstaller->shouldShowNotifications()) {
- mNotificationsPanel = createNotificationPanel();
- layout->addWidget(mNotificationsPanel);
- // Set a default welcome message
- addNotification("FOMOD Plus notification panel initialized :)", INFO);
- }
- return layout;
-}
-
-QSplitter* FomodInstallerWindow::createCenterRow()
-{
- mLeftPane = createLeftPane(); // Instance var to persist the geometry
- const auto centerRow = new QSplitter(Qt::Horizontal, this);
- const auto rightPane = createRightPane();
- centerRow->addWidget(mLeftPane);
- centerRow->addWidget(rightPane);
- centerRow->setSizes({ width() / 2, width() / 2 });
- return centerRow;
-}
-
-QWidget* FomodInstallerWindow::createTopRow()
-{
- const auto topRow = new QWidget(this);
-
- auto* mainHLayout = new QHBoxLayout(topRow);
-
- // Holds the name (label), author, version, and website
- auto* metadataLayout = new QHBoxLayout();
-
- // left side metadata. just the titles of the metadata
- auto* labelsColumn = new QVBoxLayout();
- QLabel* nameLabel = UIHelper::createLabel(tr("Name:"), topRow);
- QLabel* authorLabel = UIHelper::createLabel(tr("Author:"), topRow);
- QLabel* versionLabel = UIHelper::createLabel(tr("Version:"), topRow);
- QLabel* websiteLabel = UIHelper::createLabel(tr("Website:"), topRow);
-
- labelsColumn->addWidget(nameLabel);
- labelsColumn->addWidget(authorLabel);
- labelsColumn->addWidget(versionLabel);
- labelsColumn->addWidget(websiteLabel);
-
- UIHelper::reduceLabelPadding(labelsColumn);
- UIHelper::setGlobalAlignment(labelsColumn, Qt::AlignTop);
-
- // the values of the metadata MINUS the search box
- auto* valuesColumn = new QVBoxLayout();
- QLabel* emptyLabel = UIHelper::createLabel("", topRow);
- QLabel* authorValueLabel = UIHelper::createLabel(mViewModel->getInfoViewModel()->getAuthor().c_str(), topRow);
- QLabel* versionValueLabel = UIHelper::createLabel(mViewModel->getInfoViewModel()->getVersion().c_str(), topRow);
- QLabel* websiteValueLabel = UIHelper::createHyperlink(mViewModel->getInfoViewModel()->getWebsite().c_str(), topRow);
-
- valuesColumn->addWidget(emptyLabel);
- valuesColumn->addWidget(authorValueLabel);
- valuesColumn->addWidget(versionValueLabel);
- valuesColumn->addWidget(websiteValueLabel);
-
- // We want these cleanup fns to be at the layout level directly containing the labels.
- // Since we aren't recursing down the UI forever we can't just call it for mainHLayout.
- UIHelper::reduceLabelPadding(valuesColumn);
- UIHelper::setGlobalAlignment(valuesColumn, Qt::AlignTop);
-
- metadataLayout->addLayout(labelsColumn);
- metadataLayout->addLayout(valuesColumn, 1); // To push the right column close to the edge of the left.
-
- mainHLayout->addLayout(metadataLayout, 1);
-
- // Now make the search bar layout
- auto* modNameComboBox = createModNameComboBox();
- mainHLayout->addWidget(modNameComboBox, 4);
- UIHelper::setGlobalAlignment(mainHLayout, Qt::AlignTop);
-
- // Extra stuff
- topRow->setLayout(mainHLayout);
- topRow->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
- return topRow;
-}
-
-QComboBox* FomodInstallerWindow::createModNameComboBox()
-{
- mModNameInput = new QComboBox(this);
- mModNameInput->setEditable(true);
-
- mModNameInput->addItem(mModName);
-
- for (const auto& variant : mModName.variants()) {
- if (variant.toStdString() != mModName->toStdString()) {
- mModNameInput->addItem(variant);
- }
- }
- mModNameInput->completer()->setCaseSensitivity(Qt::CaseSensitive);
- return mModNameInput;
-}
-
-QWidget* FomodInstallerWindow::createBottomRow()
-{
- // In vanilla FOMOD installer, left has the Manual button, right has back, next/install, and cancel buttons
- const auto bottomRow = new QWidget(this);
- auto* layout = new QHBoxLayout(bottomRow);
-
- // Manual on far left
- mManualButton = UIHelper::createButton(tr("Manual"), bottomRow);
- mSelectPreviousButton = UIHelper::createButton(tr("Restore Previous Choices"), bottomRow);
- mResetChoicesButton = UIHelper::createButton(tr("Reset Choices"), bottomRow);
-
- layout->addWidget(mManualButton);
- layout->addWidget(mSelectPreviousButton);
- layout->addWidget(mResetChoicesButton);
-
- // Space to push remaining buttons right
- layout->addStretch();
-
- mBackButton = UIHelper::createButton(tr("Back"), bottomRow);
- mNextInstallButton = UIHelper::createButton(tr("Next"), bottomRow);
- mCancelButton = UIHelper::createButton(tr("Cancel"), bottomRow);
-
- mNextInstallButton->setDefault(true);
- mNextInstallButton->setAutoDefault(true);
-
- connect(mManualButton, SIGNAL(clicked()), this, SLOT(onManualClicked()));
- connect(mNextInstallButton, SIGNAL(clicked()), this, SLOT(onNextClicked()));
- connect(mBackButton, SIGNAL(clicked()), this, SLOT(onBackClicked()));
- connect(mCancelButton, SIGNAL(clicked()), this, SLOT(onCancelClicked()));
- connect(mSelectPreviousButton, SIGNAL(clicked()), this, SLOT(onSelectPreviousClicked()));
- connect(mResetChoicesButton, SIGNAL(clicked()), this, SLOT(onResetChoicesClicked()));
- // connect(mHideImagesButton, SIGNAL(clicked()), this, SLOT(toggleImagesShown()));
-
- layout->addWidget(mBackButton);
- layout->addWidget(mNextInstallButton);
- layout->addWidget(mCancelButton);
-
- bottomRow->setLayout(layout);
- return bottomRow;
-}
-
-QSplitter* FomodInstallerWindow::createLeftPane()
-{
- const auto leftPane = new QSplitter(Qt::Vertical, this);
-
- // Add description box
- // Initialize with defaults (the first plugin's description (which defaults to the module image otherwise))
- auto* scrollArea = new QScrollArea(leftPane);
- scrollArea->setWidgetResizable(true);
-
- mDescriptionBox = new QLabel("", leftPane);
- mDescriptionBox->setTextFormat(Qt::RichText);
- mDescriptionBox->setTextInteractionFlags(Qt::TextBrowserInteraction);
- mDescriptionBox->setOpenExternalLinks(true);
- mDescriptionBox->setWordWrap(true);
- mDescriptionBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
- mDescriptionBox->setAlignment(Qt::AlignTop | Qt::AlignLeft);
-
- scrollArea->setWidget(mDescriptionBox);
- leftPane->addWidget(scrollArea);
-
- // Add image
- // Initialize with defaults (the first plugin's image)
- mImageLabel = new ScaleLabel(leftPane);
- mImageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
-
- connect(mImageLabel, &ScaleLabel::clicked, this, [this] {
- if (!mImageLabel->hasResource()) {
- return;
- }
- const auto viewer = new FomodImageViewer(this, mFomodPath, mViewModel->getActiveStep(),
- mViewModel->getActivePlugin());
- viewer->showMaximized();
- });
-
- if (!mInstaller->shouldShowImages()) {
- mImageLabel->hide();
- }
-
- leftPane->addWidget(mImageLabel);
- leftPane->setSizes({ height() / 2, height() / 2 });
-
- updateDisplayForActivePlugin();
-
- return leftPane;
-}
-
-QWidget* FomodInstallerWindow::createRightPane()
-{
- const auto rightPane = new QWidget(this);
- auto* layout = new QVBoxLayout(rightPane);
-
- layout->addWidget(mInstallStepStack);
-
- return rightPane;
-}
-
-QTextEdit* FomodInstallerWindow::createNotificationPanel()
-{
- auto* panel = new QTextEdit(this);
- panel->setReadOnly(true);
- panel->setMaximumHeight(100); // Limit height
- panel->setStyleSheet("font-family: monospace; font-size: 9pt;");
-
- return panel;
-}
-
-QWidget* FomodInstallerWindow::createStepWidget(const std::shared_ptr<StepViewModel>& installStep)
-{
- const auto stepBox = new QGroupBox(QString::fromStdString(installStep->getName()), this);
- const auto stepBoxLayout = new QVBoxLayout(stepBox);
-
- auto* scrollArea = new QScrollArea(stepBox);
- scrollArea->setWidgetResizable(true);
-
- const auto scrollAreaContent = new QWidget(scrollArea);
- auto* scrollAreaLayout = new QVBoxLayout(scrollAreaContent);
-
- for (const auto& group : installStep->getGroups()) {
- const auto groupSection = renderGroup(group);
- scrollAreaLayout->addWidget(groupSection);
- }
-
- scrollAreaContent->setLayout(scrollAreaLayout);
- scrollArea->setWidget(scrollAreaContent);
-
- stepBoxLayout->addWidget(scrollArea);
- stepBox->setLayout(stepBoxLayout);
- return stepBox;
-}
-
-QWidget* FomodInstallerWindow::renderGroup(const std::shared_ptr<GroupViewModel>& group)
-{
- const auto groupBox = new QGroupBox(QString::fromStdString(group->getName()), this);
- const auto groupBoxLayout = new QVBoxLayout(groupBox);
-
- switch (group->getType()) {
- case SelectAtLeastOne:
- case SelectAny:
- case SelectAll:
- renderCheckboxGroup(groupBox, groupBoxLayout, group);
- break;
- case SelectExactlyOne:
- case SelectAtMostOne:
- renderSelectExactlyOne(groupBox, groupBoxLayout, group);
- break;
- default: ;
- }
-
- groupBox->setLayout(groupBoxLayout);
- return groupBox;
-}
-
-QString FomodInstallerWindow::createObjectName(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group)
-{
- const std::string objectName = std::format("[{}:{}] {}-{}",
- group->getStepIndex(), group->getOwnIndex(),
- group->getName(), plugin->getName());
-
- return QString::fromStdString(objectName);
-}
-
-QRadioButton* FomodInstallerWindow::createPluginRadioButton(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group,
- QWidget* parent)
-{
- auto* radioButton = new QRadioButton(QString::fromStdString(plugin->getName()), parent);
- radioButton->setObjectName(createObjectName(plugin, group));
- auto* hoverFilter = new HoverEventFilter(plugin, this);
- radioButton->installEventFilter(hoverFilter);
- connect(hoverFilter, &HoverEventFilter::hovered, this, &FomodInstallerWindow::onPluginHovered);
-
- connect(radioButton, &QRadioButton::toggled, this, [this, radioButton, group, plugin](const bool checked) {
- logMessage(INFO,
- "Received toggled signal for radio: " + plugin->getName() + ": " + (checked ? "TRUE" : "FALSE") +
- " Radio is now: " + (radioButton->isChecked() ? "TRUE" : "FALSE"));
- onPluginToggled(checked, group, plugin);
- });
-
- radioButton->setEnabled(plugin->isEnabled());
- radioButton->setChecked(plugin->isSelected());
- return radioButton;
-}
-
-QCheckBox* FomodInstallerWindow::createPluginCheckBox(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group, QWidget* parent)
-{
- auto* checkBox = new QCheckBox(QString::fromStdString(plugin->getName()), parent);
- checkBox->setObjectName(createObjectName(plugin, group));
-
- // Make the hover stuff work
- auto* hoverFilter = new HoverEventFilter(plugin, this);
- checkBox->installEventFilter(hoverFilter);
- connect(hoverFilter, &HoverEventFilter::hovered, this, &FomodInstallerWindow::onPluginHovered);
-
- // Install Ctrl+click event filter
- auto* ctrlClickFilter = new CtrlClickEventFilter(plugin, group, this);
- checkBox->installEventFilter(ctrlClickFilter);
-
- checkBox->setEnabled(plugin->isEnabled());
- checkBox->setChecked(plugin->isSelected());
- connect(checkBox, &QCheckBox::clicked, this, [this, plugin](const bool checked) {
- // Send a message to viewModel saying the user deactivated the plugin manually.
- // This may get overridden later by automatic checking, but we'll reconcile that at JSON serialization time.
- if (!checked) {
- onPluginManuallyUnchecked(plugin);
- }
- });
- connect(checkBox, &QCheckBox::toggled, this, [this, checkBox, group, plugin](const bool checked) {
- logMessage(INFO,
- "Received toggled signal for checkbox: " + plugin->getName() + ": " + (checked ? "true" : "false") +
- " Checkbox was previously " + (checkBox->isChecked() ? "true" : "false"));
- onPluginToggled(checked, group, plugin);
- });
- return checkBox;
-}
-
-void FomodInstallerWindow::renderSelectExactlyOne(QWidget* parent, QLayout* parentLayout,
- const std::shared_ptr<GroupViewModel>& group)
-{
- // This is for parity with the legacy installer. Both styles are functionally equivalent
- // for a group size of 1, but they chose checkbox.
- if (group->getPlugins().size() == 1) {
- renderCheckboxGroup(parent, parentLayout, group);
- } else {
- renderRadioGroup(parent, parentLayout, group);
- }
-}
-
-void FomodInstallerWindow::renderCheckboxGroup(QWidget* parent, QLayout* parentLayout,
- const std::shared_ptr<GroupViewModel>& group)
-{
- for (const auto& plugin : group->getPlugins()) {
- auto* checkbox = createPluginCheckBox(plugin, group, parent);
- parentLayout->addWidget(checkbox);
- }
-}
-
-QButtonGroup* FomodInstallerWindow::renderRadioGroup(QWidget* parent, QLayout* parentLayout,
- const std::shared_ptr<GroupViewModel>& group)
-{
- auto* buttonGroup = new QButtonGroup(parent);
- buttonGroup->setExclusive(true); // Ensure only one button can be selected
-
- for (const auto& plugin : group->getPlugins()) {
- auto* radioButton = createPluginRadioButton(plugin, group, parent);
- buttonGroup->addButton(radioButton);
- parentLayout->addWidget(radioButton);
- }
- return buttonGroup;
-}
-
-void FomodInstallerWindow::addNotification(const QString& message, const LogLevel level) const
-{
- if (!mNotificationsPanel) {
- return;
- }
-
- const QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss");
- const QString formattedMsg = QString("<span>[%2] [%3] %4</span>")
- .arg(timestamp).arg(logLevelToString(level)).arg(message);
-
- mNotificationsPanel->append(formattedMsg);
-
- // Auto-scroll to bottom
- QScrollBar* scrollbar = mNotificationsPanel->verticalScrollBar();
- scrollbar->setValue(scrollbar->maximum());
-}
-
-[[deprecated]]
-void FomodInstallerWindow::toggleImagesShown() const
-{
- logMessage(DEBUG, "Toggling image visibility");
- mInstaller->toggleShouldShowImages();
- if (mInstaller->shouldShowImages()) {
- logMessage(DEBUG, "Turning images ON");
- mImageLabel->show();
- mHideImagesButton->setText(tr("Hide Images"));
- } else {
- logMessage(DEBUG, "Turning images OFF");
- mImageLabel->hide();
- mHideImagesButton->setText(tr("Show Images"));
- }
-}
-
-
-// Updates the image and description field for a given plugin. Also use this on initialization of those widgets.
-void FomodInstallerWindow::updateDisplayForActivePlugin() const
-{
- // Skip if no steps (legacy FOMOD)
- if (mViewModel->getSteps().empty()) {
- return;
- }
-
- auto plugin = mViewModel->getActivePlugin();
- if (!plugin) {
- const auto activeStep = mViewModel->getActiveStep();
- if (!activeStep || activeStep->getGroups().empty() || activeStep->getGroups().front()->getPlugins().empty()) {
- mDescriptionBox->setText(tr("Select a plugin to see its description."));
- mImageLabel->clear();
- return;
- }
- // Fall back to the first plugin in the active step when no active plugin is set.
- plugin = activeStep->getGroups().front()->getPlugins().front();
- mViewModel->setActivePlugin(plugin);
- }
-
- const QString description = formatPluginDescription(QString::fromStdString(plugin->getDescription()));
- mDescriptionBox->setText(description);
-
- const auto image = mViewModel->getDisplayImage();
- if (image.empty()) {
- mImageLabel->clear();
- return;
- }
-
- const auto imagePath = UIHelper::getFullImagePath(mFomodPath, QString::fromStdString(image));
- mImageLabel->setScalableResource(imagePath);
-}
-
-/**
- *
- * @param pluginSelector For now either 'plugins', or 'deselected'. The key of the member of 'groups' to iterate over.
- * @param fn The callback for each plugin in the chosen group member.
- */
-void FomodInstallerWindow::applyFnFromJson(const std::string& pluginSelector,
- const std::function<void(QAbstractButton*)>& fn)
-{
- if (mFomodJson.empty()) {
- return;
- }
-
- const auto jsonSteps = mFomodJson["steps"];
- // for each step in JSON, create a <group>-<plugin> string out of the { groups: [ { plugins... } ] } array
- vector<std::string> selectedPlugins;
-
- // TODO: Can groups have the same name within a step, or across steps? How do we account for that?
- for (int stepIndex = 0; stepIndex < jsonSteps.size(); ++stepIndex) {
- const auto& step = jsonSteps[stepIndex];
- for (int groupIndex = 0; groupIndex < step["groups"].size(); ++groupIndex) {
- const auto& group = step["groups"][groupIndex];
-
- if (!group.contains(pluginSelector)) {
- continue;
- }
-
- for (int pluginIndex = 0; pluginIndex < group[pluginSelector].size(); ++pluginIndex) {
- const auto& plugin = group[pluginSelector][pluginIndex];
-
- std::string name = std::format("[{}:{}] {}-{}",
- stepIndex, groupIndex, group["name"].get<std::string>(), plugin.get<std::string>());
- selectedPlugins.push_back(name);
- }
- }
- }
-
- const auto checkboxes = findChildren<QCheckBox*>();
- const auto radioButtons = findChildren<QRadioButton*>();
-
- for (auto* checkbox : checkboxes) {
- for (const auto& selectedPlugin : selectedPlugins) {
- if (checkbox->objectName().toStdString() == selectedPlugin) {
- fn(checkbox);
- }
- }
- }
- for (auto* radio : radioButtons) {
- for (const auto& selectedPlugin : selectedPlugins) {
- if (radio->objectName().toStdString() == selectedPlugin) {
- fn(radio);
- }
- }
- }
-}
-
-void FomodInstallerWindow::stylePreviouslySelectedOptions()
-{
- const auto stylesheet = getColorStyle(UiColors::ColorApplication::BACKGROUND);
-
- const auto tooltip = "You previously selected this plugin when installing this mod.";
-
- logMessage(INFO, "Styling previously selected choices with stylesheet " + stylesheet.toStdString(), true);
- applyFnFromJson("plugins", [stylesheet, tooltip](QAbstractButton* button) {
- button->setStyleSheet(stylesheet);
- button->setToolTip(tooltip);
- });
-}
-
-void FomodInstallerWindow::stylePreviouslyDeselectedOptions()
-{
- const auto stylesheet = getColorStyle(UiColors::ColorApplication::BORDER);
- const auto tooltip = "You previously unchecked this plugin when installing this mod.";
- applyFnFromJson("deselected", [stylesheet, tooltip](QAbstractButton* button) {
- button->setStyleSheet(stylesheet);
- button->setToolTip(tooltip);
- });
-}
-
-void FomodInstallerWindow::selectPreviouslySelectedOptions() const
-{
- logMessage(INFO, "Selecting previously selected choices", true);
- logMessage(INFO, "Existing JSON provided: " + mFomodJson.dump(4));
- if (mFomodJson.empty()) {
- return;
- }
- try {
- mViewModel->selectFromJson(mFomodJson);
- } catch (Exception& e) {
- logMessage(ERR, std::string("Error selecting previously selected options: ") + e.what(), true);
- }
- updateCheckboxStates();
-}
-
-void FomodInstallerWindow::onResetChoicesClicked()
-{
- logMessage(INFO, "Resetting choices to author defaults", true);
- try {
- mViewModel->resetToDefaults();
- } catch (Exception& e) {
- logMessage(ERR, std::string("Error resetting choices: ") + e.what(), true);
- return;
- }
-
- // Reset the UI to show the first step
- mInstallStepStack->setCurrentIndex(mViewModel->getCurrentStepIndex());
- updateCheckboxStates();
- updateButtons();
- updateDisplayForActivePlugin();
-}
-
-QString FomodInstallerWindow::getColorStyle(const UiColors::ColorApplication color_application) const
-{
- const auto selectedColor = mInstaller->getSelectedColor();
- logMessage(DEBUG, "Selected color: " + selectedColor.toStdString());
- return getStyle(selectedColor, color_application);
-}
diff --git a/libs/installer_fomod_plus/installer/FomodInstallerWindow.h b/libs/installer_fomod_plus/installer/FomodInstallerWindow.h
deleted file mode 100644
index bca2215..0000000
--- a/libs/installer_fomod_plus/installer/FomodInstallerWindow.h
+++ /dev/null
@@ -1,214 +0,0 @@
-#ifndef FOMODINSTALLERWINDOW_H
-#define FOMODINSTALLERWINDOW_H
-
-#include <qboxlayout.h>
-#include <qbuttongroup.h>
-#include <qcheckbox.h>
-#include <qcombobox.h>
-#include <qtextedit.h>
-
-#include "FomodPlusInstaller.h"
-#include "xml/ModuleConfiguration.h"
-
-#include <QDialog>
-#include <QStackedWidget>
-#include <qradiobutton.h>
-#include <ui/ScaleLabel.h>
-
-#include "FomodInstallerWindow.h"
-#include "lib/FileInstaller.h"
-#include "ui/FomodViewModel.h"
-#include "ui/Colors.h"
-
-#include <QSplitter>
-
-using namespace MOBase;
-
-struct PluginData {
- std::shared_ptr<PluginViewModel> plugin;
- QAbstractButton* uiElement;
-};
-
-
-class FomodPlusInstaller;
-/**
- * @class FomodInstallerWindow
- * @brief This class represents a window for the FOMOD installer.
- *
- * The FomodInstallerWindow class is designed to handle and manage the FOMOD installation
- * process. It integrates functionalities specific to FOMOD package installations.
- * By inheriting from QObject, it supports signal-slot mechanisms, enabling interaction
- * and communication with other components in the application.
- *
- * This class is primarily intended to provide a user interface and functionality
- * to process and run the FOMOD installer in a structured manner.
- */
-class FomodInstallerWindow final : public QDialog {
- Q_OBJECT
-
-public:
- FomodInstallerWindow(FomodPlusInstaller* installer,
- GuessedValue<QString>& modName,
- const std::shared_ptr<IFileTree>& tree,
- QString fomodPath,
- const std::shared_ptr<FomodViewModel>& viewModel,
- const nlohmann::json& fomodJson,
- QWidget* parent = nullptr);
-
- void closeEvent(QCloseEvent* event) override;
-
- void saveGeometryAndState() const;
-
- void restoreGeometryAndState();
-
- void populatePluginMap();
-
-
- // So FomodPlusInstaller can check if the user wants to manually install
- [[nodiscard]] bool isManualInstall() const
- {
- return mIsManualInstall;
- }
-
- [[nodiscard]] std::shared_ptr<FileInstaller> getFileInstaller() const { return mViewModel->getFileInstaller(); }
-
-private slots:
- void onNextClicked();
-
- void updateCheckboxStates() const;
-
- void onPluginToggled(bool selected, const std::shared_ptr<GroupViewModel>& group,
- const std::shared_ptr<PluginViewModel>& plugin) const;
-
- void onPluginManuallyUnchecked(const std::shared_ptr<PluginViewModel>& plugin) const;
-
- void onPluginHovered(const std::shared_ptr<PluginViewModel>& plugin) const;
-
- void onSelectPreviousClicked() const { this->selectPreviouslySelectedOptions(); }
-
- void onResetChoicesClicked();
-
- void onBackClicked() const;
-
- void onCancelClicked()
- {
- this->saveGeometryAndState();
- this->reject();
- }
-
- void onManualClicked()
- {
- mIsManualInstall = true;
- this->saveGeometryAndState();
- this->reject();
- }
-
- void onInstallClicked();
-
- [[deprecated]] void toggleImagesShown() const;
-
-private:
- Logger& log = Logger::getInstance();
- FomodPlusInstaller* mInstaller;
- QString mFomodPath;
- GuessedValue<QString>& mModName;
- std::shared_ptr<IFileTree> mTree;
- std::shared_ptr<FomodViewModel> mViewModel;
- bool mInitialized{ false };
- std::unordered_map<QString, PluginData> mPluginMap;
-
-
- // Meta
- bool mIsManualInstall{};
- nlohmann::json mFomodJson;
-
- // Buttons
- QPushButton* mNextInstallButton{};
- QPushButton* mBackButton{};
- QPushButton* mCancelButton{};
- QPushButton* mManualButton{};
- QPushButton* mSelectPreviousButton{};
- QPushButton* mResetChoicesButton{};
- QPushButton* mHideImagesButton{};
-
- // Widgets
- QComboBox* mModNameInput{};
- QLabel* mDescriptionBox{};
- QSplitter* mCenterRow{};
- QSplitter* mLeftPane{};
- QStackedWidget* mInstallStepStack{};
- QTextEdit* mNotificationsPanel{};
- QWidget* mBottomRow{};
- QWidget* mTopRow{};
- ScaleLabel* mImageLabel{};
-
- // Fn
- void setupUi();
-
- void updateButtons() const;
-
- void updateInstallStepStack();
-
- void updateDisplayForActivePlugin() const;
-
- void applyFnFromJson(const std::string& pluginSelector, const std::function<void(QAbstractButton*)> &fn);
-
- void stylePreviouslySelectedOptions();
-
- void stylePreviouslyDeselectedOptions();
-
- void selectPreviouslySelectedOptions() const;
-
- [[nodiscard]] QString getColorStyle(UiColors::ColorApplication color_application) const;
-
- [[nodiscard]] QBoxLayout* createContainerLayout();
-
- [[nodiscard]] QSplitter* createCenterRow();
-
- [[nodiscard]] QWidget* createTopRow();
-
- [[nodiscard]] QComboBox* createModNameComboBox();
-
- [[nodiscard]] QWidget* createBottomRow();
-
- [[nodiscard]] QSplitter* createLeftPane();
-
- [[nodiscard]] QWidget* createRightPane();
-
- [[nodiscard]] QTextEdit* createNotificationPanel();
-
- [[nodiscard]] QWidget* createStepWidget(const std::shared_ptr<StepViewModel>& installStep);
-
- [[nodiscard]] QWidget* renderGroup(const std::shared_ptr<GroupViewModel>& group);
-
- static QString createObjectName(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group);
-
- QRadioButton* createPluginRadioButton(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group, QWidget* parent);
-
- QCheckBox* createPluginCheckBox(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group, QWidget* parent);
-
- void renderSelectExactlyOne(QWidget* parent, QLayout* parentLayout, const std::shared_ptr<GroupViewModel>& group);
-
- void renderCheckboxGroup(QWidget* parent, QLayout* parentLayout,
- const std::shared_ptr<GroupViewModel>& group);
-
- QButtonGroup* renderRadioGroup(QWidget* parent, QLayout* parentLayout,
- const std::shared_ptr<GroupViewModel>& group);
-
- void addNotification(const QString& message, LogLevel level) const;
-
- void logMessage(const LogLevel level, const std::string& message, const bool asNotification = true) const
- {
- log.logMessage(level, "[WINDOW] " + message);
- if (asNotification) {
- addNotification(QString::fromStdString(message), level);
- }
- }
-
-};
-
-
-#endif //FOMODINSTALLERWINDOW_H
diff --git a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp b/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
deleted file mode 100644
index c748495..0000000
--- a/libs/installer_fomod_plus/installer/FomodPlusInstaller.cpp
+++ /dev/null
@@ -1,510 +0,0 @@
-#include "FomodPlusInstaller.h"
-
-#include <igamefeatures.h>
-#include <iinstallationmanager.h>
-#include <iplugingame.h>
-#include <QEventLoop>
-#include <QTreeWidget>
-#include <xml/FomodInfoFile.h>
-#include <xml/ModuleConfiguration.h>
-#include <xml/XmlParseException.h>
-
-#include "FomodInstallerWindow.h"
-#include "stringutil.h"
-#include "integration/FomodDataContent.h"
-#include "ui/Colors.h"
-#include "ui/FomodViewModel.h"
-#include "lib/CrashHandler.h"
-
-#include <QMessageBox>
-#include <QSettings>
-
-using namespace Qt::Literals::StringLiterals;
-
-/*
---------------------------------------------------------------------------------
- Init
---------------------------------------------------------------------------------
-*/
-#pragma region Initialization
-
-bool FomodPlusInstaller::init(IOrganizer* organizer)
-{
- CrashHandler::initialize();
- mOrganizer = organizer;
- mFomodContent = make_shared<FomodDataContent>(organizer);
- log.setLogFilePath(QDir::currentPath().toStdString() + "/logs/fomodplus.log");
-
- // REMEMBER: This mFomodDB persists beyond the scope of an individual install. Do not do anything nasty to it.
- mFomodDb = std::make_unique<FomodDB>(mOrganizer->basePath().toStdString());
- setupUiInjection();
- return true;
-}
-
-void FomodPlusInstaller::setupUiInjection() const
-{
- if (shouldShowSidebarFilter()) {
- mOrganizer->gameFeatures()->registerFeature(mFomodContent, 9999, false);
- }
-
- mOrganizer->onPluginSettingChanged([this](const QString& pluginName, const QString& key, const QVariant& oldValue, const QVariant& newValue) {
- if (pluginName == name() && key == "show_fomod_filter") {
- toggleFeature(newValue.toBool());
- }
- });
-}
-
-void FomodPlusInstaller::toggleFeature(const bool enabled) const
-{
- if (enabled) {
- mOrganizer->gameFeatures()->registerFeature(mFomodContent, 9999, false);
- } else {
- mOrganizer->gameFeatures()->unregisterFeature(mFomodContent);
- }
- mOrganizer->refresh();
-}
-
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Settings
---------------------------------------------------------------------------------
-*/
-#pragma region Settings
-bool FomodPlusInstaller::shouldFallbackToLegacyInstaller() const
-{
- return mOrganizer->pluginSetting(name(), "fallback_to_legacy").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowImages() const
-{
- return mOrganizer->pluginSetting(name(), "show_images").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowNotifications() const
-{
- return mOrganizer->pluginSetting(name(), "show_notifications").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldShowSidebarFilter() const
-{
- return mOrganizer->pluginSetting(name(), "show_fomod_filter").value<bool>();
-}
-
-bool FomodPlusInstaller::shouldAutoRestoreChoices() const
-{
- return mOrganizer->pluginSetting(name(), "always_restore_choices").value<bool>();
-}
-
-bool FomodPlusInstaller::isWizardIntegrated() const
-{
- return mOrganizer->pluginSetting(name(), "wizard_integration").value<bool>();
-}
-
-void FomodPlusInstaller::toggleShouldShowImages() const
-{
- const bool showImages = shouldShowImages();
- mOrganizer->setPluginSetting(name(), "show_images", !showImages);
-}
-
-QString FomodPlusInstaller::getSelectedColor() const
-{
- const auto colorName = mOrganizer->pluginSetting(name(), "color_theme").toString();
- const auto it = UiColors::colorStyles.find(colorName);
- return it != UiColors::colorStyles.end() ? it->first : "Blue";
-}
-
-std::vector<std::shared_ptr<const IPluginRequirement> > FomodPlusInstaller::requirements() const
-{
- return { Requirements::gameDependency(
- { u"Morrowind"_s, u"Oblivion"_s, u"Fallout 3"_s, u"New Vegas"_s, u"Skyrim"_s, u"Enderal"_s,
- u"Fallout 4"_s, u"Skyrim Special Edition"_s, u"Enderal Special Edition"_s,
- u"Skyrim VR"_s, u"Fallout 4 VR"_s, u"Starfield"_s }) };
-}
-
-QList<PluginSetting> FomodPlusInstaller::settings() const
-{
- return {
- { u"fallback_to_legacy"_s, u"When hitting cancel, fall back to the legacy FOMOD installer."_s, false },
- { u"always_restore_choices"_s, u"Restore previous choices without clicking the magic button"_s, false },
- { u"show_images"_s, u"Show image previews and the image carousel in installer windows."_s, true },
- { u"color_theme"_s, u"Select the color theme for the installer"_s, QString("Blue") }, // Default color name
- { u"show_notifications"_s, u"Show the notifications panel"_s, false }, //WIP
- { u"wizard_integration"_s, u"Integrate the installer with patch wizard."_s, true }, //WIP
- { u"show_fomod_filter"_s, u"Show the filter in the sidebar (may break other content filters)"_s, true }
- };
-}
-
-#pragma endregion
-
-bool FomodPlusInstaller::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const
-{
- tree = findFomodDirectory(tree);
- if (tree != nullptr) {
- return tree->exists(StringConstants::FomodFiles::MODULE_CONFIG.data(), FileTreeEntry::FILE);
- }
- return false;
-}
-
-
-std::pair<nlohmann::json, IModInterface*> FomodPlusInstaller::getExistingFomodJson(const GuessedValue<QString>& modName,
- const int& nexusId,
- const int& stepsInCurrentFomod) const
-{
- logMessage(DEBUG, "FomodPlusInstaller::getExistingFomodJson - modName: " + modName->toStdString() +
- " nexusId: " + std::to_string(nexusId));
-
- // Need to have better mod matching based on presence of FOMOD plugin data.
- struct ModMatch {
- MOBase::IModInterface* mod;
- bool hasFomodData;
- int stepCount;
- nlohmann::json fomodJson;
- };
- std::vector<ModMatch> matches;
-
- auto parseStepCount = [](const QVariant& fomodData) -> std::pair<bool, int> {
- try {
- if (fomodData.isValid() && !fomodData.isNull()) {
- if (auto json = nlohmann::json::parse(fomodData.toString().toStdString()); json.contains("steps") && json["steps"].is_array()) {
- return { true, static_cast<int>(json["steps"].size()) };
- }
- }
- } catch (...) {
- // Ignore parsing errors
- }
- return { false, 0 };
- };
-
- // Check exact match first
- const auto modList = mOrganizer->modList();
- if (modList == nullptr) {
- return {};
- }
-
- if (const auto exactMod = modList->getMod(modName)) {
- const auto fomodData = exactMod->pluginSetting(name(), "fomod", 0);
- if (auto [valid, stepCount] = parseStepCount(fomodData); valid) {
- matches.push_back({
- exactMod,
- true,
- stepCount,
- nlohmann::json::parse(fomodData.toString().toStdString())
- });
- } else {
- matches.push_back({
- exactMod,
- false,
- 0,
- nlohmann::json()
- });
- }
- }
-
- // Check all variants
- for (const auto& variant : modName.variants()) {
- if (const auto variantMod = modList->getMod(variant)) {
- const auto fomodData = variantMod->pluginSetting(name(), "fomod", 0);
- if (auto [valid, stepCount] = parseStepCount(fomodData); valid) {
- matches.push_back({
- variantMod,
- true,
- stepCount,
- nlohmann::json::parse(fomodData.toString().toStdString())
- });
- } else {
- matches.push_back({
- variantMod,
- false,
- 0,
- nlohmann::json()
- });
- }
- }
- }
-
- // No matches found
- if (matches.empty()) {
- return {};
- }
-
- // First try to find exact step count match
- const auto exactMatch = ranges::find_if(matches,
- [stepsInCurrentFomod](const ModMatch& match) {
- return match.hasFomodData && match.stepCount == stepsInCurrentFomod;
- });
-
- if (exactMatch != matches.end()) {
- logMessage(DEBUG, "Found exact step count match in mod: " +
- exactMatch->mod->name().toStdString() +
- " with " + std::to_string(exactMatch->stepCount) + " steps");
- return std::make_pair(exactMatch->fomodJson, exactMatch->mod);
- }
-
- // Find the closest step count among mods with FOMOD data
- const auto closestMatch = ranges::min_element(matches,
- [stepsInCurrentFomod](const ModMatch& a, const ModMatch& b) {
- if (!a.hasFomodData || !b.hasFomodData)
- return false;
- // The min difference between the step counts
- return std::abs(a.stepCount - stepsInCurrentFomod) <
- std::abs(b.stepCount - stepsInCurrentFomod);
- });
-
- if (closestMatch != matches.end() && closestMatch->hasFomodData) {
- logMessage(DEBUG, "Using closest step count match from mod: " +
- closestMatch->mod->name().toStdString() +
- " with " + std::to_string(closestMatch->stepCount) + " steps");
- return std::make_pair(closestMatch->fomodJson, closestMatch->mod);
- }
-
- // Fallback to first mod with any FOMOD data
- const auto anyFomod = ranges::find_if(matches,
- [](const ModMatch& match) { return match.hasFomodData; });
-
- if (anyFomod != matches.end()) {
- logMessage(DEBUG, "Using first available FOMOD data from mod: " +
- anyFomod->mod->name().toStdString());
- return std::make_pair(anyFomod->fomodJson, anyFomod->mod);
- }
-
- logMessage(DEBUG, "No matching FOMOD data found");
- return {};
-}
-
-void FomodPlusInstaller::clearPriorInstallData()
-{
- mInstallerUsed = false;
- mFomodJson = nullptr;
- mFomodPath = "";
-}
-
-/**
- *
- * @param modName
- * @param tree
- * @param version
- * @param nexusID
- * @return
- */
-IPluginInstaller::EInstallResult FomodPlusInstaller::install(GuessedValue<QString>& modName,
- std::shared_ptr<IFileTree>& tree, QString& version,
- int& nexusID)
-{
- clearPriorInstallData();
-
- logMessage(INFO, std::format("FomodPlusInstaller::install - modName: {}, version: {}, nexusID: {}",
- modName->toStdString(),
- version.toStdString(),
- nexusID
- ));
- logMessage(INFO, std::format("FomodPlusInstaller::install - tree size: {}", tree->size()));
-
- auto [infoFile, moduleConfigFile, filePaths] = parseFomodFiles(tree);
-
- if (infoFile == nullptr || moduleConfigFile == nullptr) {
- return RESULT_FAILED;
- }
-
- std::vector<QString> pluginPaths = {};
- for (const auto& filePath : filePaths) {
- if (isPluginFile(filePath)) {
- pluginPaths.emplace_back(filePath);
- }
- }
-
- const auto dbEntry = mFomodDb->getEntryFromFomod(moduleConfigFile.get(), pluginPaths, nexusID);
-
- // create ui & pass xml classes to ui
- auto [json, matchMod] = getExistingFomodJson(modName, nexusID, static_cast<int>(moduleConfigFile->installSteps.installSteps.size()));
- if (matchMod != nullptr) {
- modName.update(matchMod->name(), GUESS_USER);
- }
- auto fomodViewModel = FomodViewModel::create(mOrganizer, std::move(moduleConfigFile), std::move(infoFile));
- const auto window = std::make_shared<FomodInstallerWindow>(this, modName, tree, mFomodPath, fomodViewModel, json);
-
- // ReSharper disable once CppTooWideScopeInitStatement
- const QDialog::DialogCode result = showInstallerWindow(window);
- if (result == QDialog::Accepted) {
- // modname was updated in window
- mInstallerUsed = true;
- const std::shared_ptr<IFileTree> installTree = window->getFileInstaller()->install();
- tree = installTree;
- mFomodJson = std::make_shared<nlohmann::json>(window->getFileInstaller()->generateFomodJson());
-
- try {
- mFomodDb->addEntry(dbEntry);
- mFomodDb->saveToFile();
- } catch ([[maybe_unused]] Exception& e) {
- logMessage(ERR, "Failed to add FomodDB entries.");
- logMessage(ERR, e.what());
- }
- return RESULT_SUCCESS;
- }
- if (window->isManualInstall()) {
- return RESULT_MANUALREQUESTED;
- }
- if (shouldFallbackToLegacyInstaller()) {
- return RESULT_NOTATTEMPTED;
- }
- return RESULT_CANCELED;
-}
-
-
-/**
- *
- * @param tree
- * @return
- */
-ParsedFilesTuple FomodPlusInstaller::parseFomodFiles(const std::shared_ptr<IFileTree>& tree)
-{
- const auto emptyResult = std::make_tuple(nullptr, nullptr, QStringList());
-
- const auto fomodDir = findFomodDirectory(tree);
- if (fomodDir == nullptr) {
- logMessage(ERR, "FomodPlusInstaller::install - fomod directory not found");
- return emptyResult;
- }
-
- // This is a strange place to set this value but okay for now.
- mFomodPath = fomodDir->parent()->path();
-
- const auto infoXML = fomodDir->find(
- StringConstants::FomodFiles::INFO_XML.data(),
- FileTreeEntry::FILE
- );
- const auto moduleConfig = fomodDir->find(
- StringConstants::FomodFiles::MODULE_CONFIG.data(),
- FileTreeEntry::FILE
- );
-
- // Extract files first.
- vector<std::shared_ptr<const FileTreeEntry> > toExtract = {};
- if (moduleConfig) {
- toExtract.push_back(moduleConfig);
- } else {
- logMessage(ERR, "FomodPlusInstaller::install - error parsing moduleConfig.xml: Not Present");
- return emptyResult;
- }
- if (infoXML) {
- toExtract.push_back(infoXML);
- }
- appendImageFiles(toExtract, tree);
- appendPluginFiles(toExtract, tree); // For patch wizard data collection
- auto paths = manager()->extractFiles(toExtract);
- // Normalize backslash separators from MO2's internal file tree to forward slashes for Linux
- for (auto& p : paths) {
- p.replace('\\', '/');
- }
-
- auto moduleConfiguration = std::make_unique<ModuleConfiguration>();
- try {
- moduleConfiguration->deserialize(paths.at(0));
- } catch (XmlParseException& e) {
- logMessage(ERR, std::format("FomodPlusInstaller::install - error parsing moduleConfig.xml: {}", e.what()));
- return emptyResult;
- }
-
- auto infoFile = std::make_unique<FomodInfoFile>();
- if (infoXML) {
- try {
- infoFile->deserialize(paths.at(1));
- } catch (XmlParseException& e) {
- logMessage(ERR, std::format("FomodPlusInstaller::install - error parsing info.xml: {}", e.what()));
- }
- }
-
- return std::make_tuple(std::move(infoFile), std::move(moduleConfiguration), paths);
-}
-
-// Taken from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/installerfomod.cpp#L123
-void FomodPlusInstaller::appendImageFiles(vector<shared_ptr<const FileTreeEntry> >& entries,
- const shared_ptr<const IFileTree>& tree)
-{
- static std::set<QString, FileNameComparator> imageSuffixes{ "png", "jpg", "jpeg", "gif", "bmp" };
- for (auto entry : *tree) {
- if (entry->isDir()) {
- appendImageFiles(entries, entry->astree());
- } else if (imageSuffixes.contains(entry->suffix())) {
- entries.push_back(entry);
- }
- }
-}
-
-void FomodPlusInstaller::appendPluginFiles(vector<shared_ptr<const FileTreeEntry> >& entries,
- const shared_ptr<const IFileTree>& tree)
-{
- // Don't bother with this stuff if the user doesn't care about the wizard.
- // It may slightly bloat the temp file directory if not needed.
- if (isWizardIntegrated()) {
- for (auto entry : *tree) {
- if (entry->isDir()) {
- appendPluginFiles(entries, entry->astree());
- } else if (isPluginFile(entry->suffix())) {
- entries.push_back(entry);
- }
- }
- }
-}
-
-void FomodPlusInstaller::onInstallationStart(QString const& archive, const bool reinstallation,
- IModInterface* currentMod)
-{
- IPluginInstallerSimple::onInstallationStart(archive, reinstallation, currentMod);
-}
-
-void FomodPlusInstaller::onInstallationEnd(const EInstallResult result, IModInterface* newMod)
-{
- IPluginInstallerSimple::onInstallationEnd(result, newMod);
-
- // Update the meta.ini file with the fomod information
- if (mFomodJson != nullptr && result == RESULT_SUCCESS && newMod != nullptr && mInstallerUsed) {
- newMod->setPluginSetting(this->name(), "fomod", mFomodJson->dump().c_str());
- mOrganizer->refresh();
- }
- clearPriorInstallData();
-}
-
-// Borrowed from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/installerfomod.cpp
-std::shared_ptr<const IFileTree> FomodPlusInstaller::findFomodDirectory(const std::shared_ptr<const IFileTree>& tree)
-{
- // ReSharper disable once CppTooWideScopeInitStatement
- const auto entry = tree->find(StringConstants::FomodFiles::FOMOD_DIR.data(), FileTreeEntry::DIRECTORY);
-
- if (entry != nullptr) {
- return entry->astree();
- }
-
- if (tree->size() == 1 && tree->at(0)->isDir()) {
- return findFomodDirectory(tree->at(0)->astree());
- }
- return nullptr;
-}
-
-QDialog::DialogCode FomodPlusInstaller::showInstallerWindow(const std::shared_ptr<FomodInstallerWindow>& window)
-{
- QEventLoop loop;
- connect(window.get(), SIGNAL(accepted()), &loop, SLOT(quit()));
- connect(window.get(), SIGNAL(rejected()), &loop, SLOT(quit()));
- window->show();
- loop.exec();
- return static_cast<QDialog::DialogCode>(window->result());
-}
-
-void showError(const Exception& e)
-{
- const QString errorText = "Mod Name: \n" "Nexus ID: " "\n" "Exception: " + QString(e.what()) + "\n";
-
- QMessageBox msgBox;
- msgBox.setWindowTitle("FOMOD Plus Error :(");
- msgBox.setTextFormat(Qt::RichText);
- msgBox.setText("Sorry this happened. Please copy the following error and report it to me on Nexus or GitHub.\n"
- "<pre style='background-color: #f0f0f0; padding: 10px;'>"
- + errorText +
- "</pre>"
- );
- msgBox.setIcon(QMessageBox::Critical);
- msgBox.exec();
-
-}
diff --git a/libs/installer_fomod_plus/installer/FomodPlusInstaller.h b/libs/installer_fomod_plus/installer/FomodPlusInstaller.h
deleted file mode 100644
index 494ad8e..0000000
--- a/libs/installer_fomod_plus/installer/FomodPlusInstaller.h
+++ /dev/null
@@ -1,115 +0,0 @@
-#pragma once
-
-#include "stringutil.h"
-
-#include <iplugin.h>
-#include <iplugininstaller.h>
-#include <iplugininstallersimple.h>
-
-#include <nlohmann/json.hpp>
-#include "FomodInstallerWindow.h"
-#include "lib/Logger.h"
-#include "xml/FomodInfoFile.h"
-#include "xml/ModuleConfiguration.h"
-
-#include <QDialog>
-#include <integration/FomodDataContent.h>
-#include <FOMODData/FomodDB.h>
-
-class FomodInstallerWindow;
-
-using namespace MOBase;
-using namespace std;
-
-using ParsedFilesTuple = std::tuple<std::unique_ptr<FomodInfoFile>, std::unique_ptr<ModuleConfiguration>, QStringList>;
-
-class FomodPlusInstaller final : public IPluginInstallerSimple {
- Q_OBJECT
- Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstaller)
- Q_PLUGIN_METADATA(IID "io.clearing.FomodPlus" FILE "fomodplus.json")
-
-public:
- bool init(IOrganizer* organizer) override;
-
- // constant values
- [[nodiscard]] QString name() const override { return StringConstants::Plugin::NAME.data(); }
- [[nodiscard]] QString author() const override { return StringConstants::Plugin::AUTHOR.data(); }
- [[nodiscard]] QString description() const override { return StringConstants::Plugin::DESCRIPTION.data(); }
- [[nodiscard]] VersionInfo version() const override { return { 1, 0, 0, VersionInfo::RELEASE_FINAL }; }
-
- [[nodiscard]] unsigned int priority() const override
- {
- return 999; /* Above installer_fomod's highest priority. */
- }
-
- [[nodiscard]] std::vector<std::shared_ptr<const IPluginRequirement> > requirements() const override;
-
- [[nodiscard]] bool isManualInstaller() const override { return false; }
-
- [[nodiscard]] bool isArchiveSupported(std::shared_ptr<const IFileTree> tree) const override;
-
- [[nodiscard]] QList<PluginSetting> settings() const override;
-
- std::pair<nlohmann::json, IModInterface*> getExistingFomodJson(const GuessedValue<QString>& modName,
- const int& nexusId, const int& stepsInCurrentFomod) const;
-
- void clearPriorInstallData();
-
- EInstallResult install(GuessedValue<QString>& modName, std::shared_ptr<IFileTree>& tree, QString& version,
- int& nexusID) override;
-
- void onInstallationStart(QString const& archive, bool reinstallation, IModInterface* currentMod) override;
-
- void onInstallationEnd(EInstallResult result, IModInterface* newMod) override;
-
- [[nodiscard]] bool shouldShowImages() const;
-
- [[nodiscard]] bool shouldShowNotifications() const;
- bool shouldShowSidebarFilter() const;
-
- [[nodiscard]] bool shouldAutoRestoreChoices() const;
-
- [[nodiscard]] bool isWizardIntegrated() const;
-
- void toggleShouldShowImages() const;
-
- QString getSelectedColor() const;
-
-private:
- Logger& log = Logger::getInstance();
- IOrganizer* mOrganizer = nullptr;
- QString mFomodPath{};
- std::shared_ptr<nlohmann::json> mFomodJson{ nullptr };
- bool mInstallerUsed{ false };
- std::shared_ptr<FomodDataContent> mFomodContent{ nullptr };
- std::unique_ptr<FomodDB> mFomodDb;
-
- /**
- * @brief Retrieve the tree entry corresponding to the fomod directory.
- *
- * @param tree Tree to look-up the directory in.
- *
- * @return the entry corresponding to the fomod directory in the tree, or a null
- * pointer if the entry was not found.
- */
- [[nodiscard]] static shared_ptr<const IFileTree> findFomodDirectory(const shared_ptr<const IFileTree>& tree);
-
- [[nodiscard]] static QDialog::DialogCode showInstallerWindow(const shared_ptr<FomodInstallerWindow>& window);
-
- [[nodiscard]] ParsedFilesTuple parseFomodFiles(const shared_ptr<IFileTree>& tree);
-
- static void appendImageFiles(vector<shared_ptr<const FileTreeEntry> >& entries,
- const shared_ptr<const IFileTree>& tree);
-
- void appendPluginFiles(vector<shared_ptr<const FileTreeEntry> >& entries, const shared_ptr<const IFileTree>& tree);
-
- void setupUiInjection() const;
- void toggleFeature(bool enabled) const;
-
- [[nodiscard]] bool shouldFallbackToLegacyInstaller() const;
-
- void logMessage(const LogLevel level, const std::string& message) const
- {
- log.logMessage(level, "[INSTALLER] " + message);
- }
-};
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_de.ts b/libs/installer_fomod_plus/installer/fomod_plus_de.ts
deleted file mode 100644
index 1552582..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_de.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="de_DE">
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_en.ts b/libs/installer_fomod_plus/installer/fomod_plus_en.ts
deleted file mode 100644
index bc6d6e7..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_en.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="en_US">
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_installer_de.ts b/libs/installer_fomod_plus/installer/fomod_plus_installer_de.ts
deleted file mode 100644
index ad7bfed..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_installer_de.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="de_DE">
-<context>
- <name>FomodInstallerWindow</name>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="194"/>
- <source>Installieren</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="196"/>
- <location filename="FomodInstallerWindow.cpp" line="370"/>
- <source>Weiter</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="288"/>
- <source>Name:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="289"/>
- <source>Autor:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="290"/>
- <source>Version:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="291"/>
- <source>Webseite:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="357"/>
- <source>Manuell</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="358"/>
- <source>Alle Vorherige Optionen Ausw&auml;hlen</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="359"/>
- <source>Reset Choices</source>
- <translation>Optionen Zur&uuml;cksetzen</translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="361"/>
- <location filename="FomodInstallerWindow.cpp" line="586"/>
- <source>Bilder Ausblenden</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="369"/>
- <source>Zur&uuml;ck</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="371"/>
- <source>Abbrechen</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="360"/>
- <location filename="FomodInstallerWindow.cpp" line="590"/>
- <source>Bilder Einblenden</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_installer_en.ts b/libs/installer_fomod_plus/installer/fomod_plus_installer_en.ts
deleted file mode 100644
index 5cbe0a7..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_installer_en.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="en_US">
-<context>
- <name>FomodInstallerWindow</name>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="231"/>
- <location filename="FomodInstallerWindow.cpp" line="242"/>
- <source>Install</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="244"/>
- <location filename="FomodInstallerWindow.cpp" line="423"/>
- <source>Next</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="342"/>
- <source>Name:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="343"/>
- <source>Author:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="344"/>
- <source>Version:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="345"/>
- <source>Website:</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="411"/>
- <source>Manual</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="412"/>
- <source>Restore Previous Choices</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="413"/>
- <source>Reset Choices</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="422"/>
- <source>Back</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="424"/>
- <source>Cancel</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="682"/>
- <source>Hide Images</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="686"/>
- <source>Show Images</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="703"/>
- <source>Select a plugin to see its description.</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_installer_zh_CN.ts b/libs/installer_fomod_plus/installer/fomod_plus_installer_zh_CN.ts
deleted file mode 100644
index f2769e6..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_installer_zh_CN.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="zh_CN">
-<context>
- <name>FomodInstallerWindow</name>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="194"/>
- <source>安装</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="196"/>
- <location filename="FomodInstallerWindow.cpp" line="370"/>
- <source>下一步</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="288"/>
- <source>名称</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="289"/>
- <source>作者</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="290"/>
- <source>版本</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="291"/>
- <source>网址</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="357"/>
- <source>手动</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="358"/>
- <source>选中所有上次安装的选择</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="359"/>
- <source>Reset Choices</source>
- <translation>重置选择</translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="361"/>
- <location filename="FomodInstallerWindow.cpp" line="586"/>
- <source>隐藏图片</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="369"/>
- <source>上一步</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="371"/>
- <source>取消</source>
- <translation type="unfinished"></translation>
- </message>
- <message>
- <location filename="FomodInstallerWindow.cpp" line="360"/>
- <location filename="FomodInstallerWindow.cpp" line="590"/>
- <source>显示图片</source>
- <translation type="unfinished"></translation>
- </message>
-</context>
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomod_plus_zh_CN.ts b/libs/installer_fomod_plus/installer/fomod_plus_zh_CN.ts
deleted file mode 100644
index e5ca8aa..0000000
--- a/libs/installer_fomod_plus/installer/fomod_plus_zh_CN.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE TS>
-<TS version="2.1" language="zh_CN">
-</TS>
diff --git a/libs/installer_fomod_plus/installer/fomodplus.json b/libs/installer_fomod_plus/installer/fomodplus.json
deleted file mode 100644
index dde5a05..0000000
--- a/libs/installer_fomod_plus/installer/fomodplus.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "Id": "FOMODPlus",
- "Version" : "1.0.0"
-} \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/integration/FomodDataContent.cpp b/libs/installer_fomod_plus/installer/integration/FomodDataContent.cpp
deleted file mode 100644
index ac4508d..0000000
--- a/libs/installer_fomod_plus/installer/integration/FomodDataContent.cpp
+++ /dev/null
@@ -1,50 +0,0 @@
-#include "FomodDataContent.h"
-
-#include <ifiletree.h>
-
-#include "stringutil.h"
-
-#include <iplugingame.h>
-
-FomodDataContent::FomodDataContent(MOBase::IOrganizer* organizer) : mOrganizer(organizer)
-{
-}
-
-std::vector<MOBase::ModDataContent::Content> FomodDataContent::getAllContents() const
-{
- static const std::vector<Content> contents = {
- {FomodDataContentConstants::FOMOD_CONTENT_ID, "FOMOD", ":/fomod/hat", false}
- };
-
- return contents;
-}
-
-// Confirmed working, no need to update
-std::vector<int> FomodDataContent::getContentsFor(const std::shared_ptr<const MOBase::IFileTree> fileTree) const
-{
- std::vector<int> contents;
- if (!mOrganizer || !fileTree) {
- return contents;
- }
-
- const auto modList = mOrganizer->modList();
- if (!modList) {
- return contents;
- }
-
- const auto mod = modList->getMod(fileTree->name());
- if (modHasFomodContent(mod)) {
- contents.emplace_back(FomodDataContentConstants::FOMOD_CONTENT_ID);
- }
- return contents;
-}
-
-bool FomodDataContent::modHasFomodContent(const MOBase::IModInterface* mod)
-{
- if (!mod) {
- return false;
- }
- const auto pluginName = QString::fromStdString(StringConstants::Plugin::NAME.data());
- const auto fomodMeta = mod->pluginSetting(pluginName, "fomod", 0);
- return fomodMeta != 0;
-}
diff --git a/libs/installer_fomod_plus/installer/integration/FomodDataContent.h b/libs/installer_fomod_plus/installer/integration/FomodDataContent.h
deleted file mode 100644
index 4ffa3d7..0000000
--- a/libs/installer_fomod_plus/installer/integration/FomodDataContent.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-
-#include <imoinfo.h>
-#include <moddatacontent.h>
-
-namespace FomodDataContentConstants {
-constexpr int FOMOD_CONTENT_ID = 400400;
-}
-
-class FomodDataContent final : public MOBase::ModDataContent {
-public:
- explicit FomodDataContent(MOBase::IOrganizer* organizer);
-
- [[nodiscard]] std::vector<Content> getAllContents() const override;
-
- [[nodiscard]] std::vector<int> getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const override;
-
-private:
- MOBase::IOrganizer* mOrganizer;
- static bool modHasFomodContent(const MOBase::IModInterface* mod);
-};
diff --git a/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp b/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
deleted file mode 100644
index c35bf47..0000000
--- a/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-#include "ConditionTester.h"
-
-#include "ui/FomodViewModel.h"
-
-#include <iplugingame.h>
-#include <ipluginlist.h>
-
-std::string setToString(const std::set<int> &set)
-{
- std::string str;
- for (const auto& i : set) {
- str += std::to_string(i) + ", ";
- }
- return str;
-}
-
-bool ConditionTester::isStepVisible(const std::shared_ptr<FlagMap>& flags,
- const CompositeDependency& compositeDependency,
- const int stepIndex,
- const std::vector<std::shared_ptr<StepViewModel>>& steps) const
-{
-
- // first things first: is it visible?
- if (!testCompositeDependency(flags, compositeDependency)) {
- return false;
- }
-
- const auto flagDependencies = compositeDependency.flagDependencies;
- if (flagDependencies.empty()) {
- return true;
- }
-
- std::set<int> stepsThatSetThisFlag;
-
- for (const auto& flagDependency : flagDependencies) {
- // for this flag, find the plugins that set it
- for (int i = stepIndex - 1; i >= 0; --i) {
- for (const auto& group : steps[i]->getGroups()) {
- for (const auto& plugin : group->getPlugins()) {
- if (std::ranges::any_of(plugin->getPlugin()->conditionFlags.flags,
- [&flagDependency](const ConditionFlag& flag) {
- return flag.name == flagDependency.flag && flag.value == flagDependency.value;
- })) {
- stepsThatSetThisFlag.insert(i);
- }
- }
- }
- }
- }
- const auto anyVisible = std::ranges::any_of(stepsThatSetThisFlag, [this, &steps, &flags](const int index) {
- return isStepVisible(flags, steps[index]->getVisibilityConditions(), index, steps);
- });
- if (!anyVisible) {
- log.logMessage(DEBUG, "Step " + steps[stepIndex]->getName() + " has no dependent steps that are visible.");
- log.logMessage(DEBUG, "Steps that set this flag: " + setToString(stepsThatSetThisFlag));
- }
- return anyVisible;
-
-}
-
-bool ConditionTester::testCompositeDependency(const std::shared_ptr<FlagMap>& flags,
- const CompositeDependency& compositeDependency) const
-{
- const auto fileDependencies = compositeDependency.fileDependencies;
- const auto flagDependencies = compositeDependency.flagDependencies;
- const auto gameDependencies = compositeDependency.gameDependencies;
- const auto nestedDependencies = compositeDependency.nestedDependencies;
- const auto globalOperatorType = compositeDependency.operatorType;
-
- // For the globalOperatorType
- // Evaluate all conditions and store the results in a vector<bool>, then return based on operator.
- // These aren't expensive to calculate so rather than do some fancy logic to short-circuit, just calculate all of 'em.
- std::vector<bool> results;
- for (const auto& fileDependency : fileDependencies) {
- results.emplace_back(testFileDependency(fileDependency));
- }
- for (const auto& flagDependency : flagDependencies) {
- results.emplace_back(testFlagDependency(flags, flagDependency));
- }
- for (const auto& gameDependency : gameDependencies) {
- results.emplace_back(testGameDependency(gameDependency));
- }
- for (const auto& nestedDependency : nestedDependencies) {
- results.emplace_back(testCompositeDependency(flags, nestedDependency));
- }
-
- if (globalOperatorType == OperatorTypeEnum::AND) {
- return std::ranges::all_of(results, [](const bool result) { return result; });
- }
- return std::ranges::any_of(results, [](const bool result) { return result; });
-}
-
-
-bool ConditionTester::testFlagDependency(const std::shared_ptr<FlagMap>& flags, const FlagDependency& flagDependency)
-{
- // Every instance of this flag being set in the map.
- const auto flagList = flags->getFlagsByKey(flagDependency.flag);
-
- // Find the first instance of this flag being set (in the order specified by getFlagsByKey)
- if (flagList.empty()) {
- // If the dependency value is an empty string, it means this flag should be unset.
- // So if we don't have any value for this flag, the result is true.
- return flagDependency.value.empty();
- }
-
- return flagList.front().second == flagDependency.value;
-}
-
-bool ConditionTester::testFileDependency(const FileDependency& fileDependency) const
-{
- const std::string& pluginName = fileDependency.file;
- const auto pluginState = getFileDependencyStateForPlugin(pluginName);
- return pluginState == fileDependency.state;
-}
-
-bool ConditionTester::testGameDependency(const GameDependency& gameDependency) const
-{
- const auto gameVersion = mOrganizer->managedGame()->gameVersion().toStdString();
- log.logMessage(DEBUG, "Comparing condition version " + gameDependency.version + " against " + gameVersion);
- if ( gameDependency.version <= gameVersion) {
- log.logMessage(DEBUG, "Version matches!");
- }
- return gameDependency.version <= gameVersion;
-}
-
-FileDependencyTypeEnum ConditionTester::getFileDependencyStateForPlugin(const std::string& pluginName) const
-{
- if (const auto it = pluginStateCache.find(pluginName); it != pluginStateCache.end()) {
- return it->second;
- }
-
- const QFlags<MOBase::IPluginList::PluginState> pluginState = mOrganizer->pluginList()->state(
- QString::fromStdString(pluginName));
-
- FileDependencyTypeEnum state;
-
- if (pluginState == MOBase::IPluginList::STATE_MISSING) {
- state = FileDependencyTypeEnum::Missing;
- } else if (pluginState == MOBase::IPluginList::STATE_INACTIVE) {
- state = FileDependencyTypeEnum::Inactive;
- } else if (pluginState == MOBase::IPluginList::STATE_ACTIVE) {
- state = FileDependencyTypeEnum::Active;
- } else {
- state = FileDependencyTypeEnum::UNKNOWN_STATE;
- }
-
- pluginStateCache[pluginName] = state;
- return state;
-}
-
-PluginTypeEnum ConditionTester::getPluginTypeDescriptorState(const std::shared_ptr<Plugin>& plugin,
- const std::shared_ptr<FlagMap>& flags) const
-{
- // NOTE: A plugin's ConditionFlags aren't the same thing as a step visibility one.
- // A plugin's ConditionFlags are toggled based on the selection state of the plugin
- // We only evaluate the typeDescriptor here.
-
- // We will return the 'winning' type or the default. If multiple conditions are met,
- // ...well, I'm not sure.
- // ReSharper disable once CppTooWideScopeInitStatement
- const auto& dependencyType = plugin->typeDescriptor.dependencyType;
- for (const auto& pattern : dependencyType.patterns.patterns) {
- if (testCompositeDependency(flags, pattern.dependencies)) {
- return pattern.type;
- }
- }
-
- // Sometimes authors do this.
- if (plugin->typeDescriptor.type != PluginTypeEnum::Optional) {
- return plugin->typeDescriptor.type;
- }
- if (plugin->typeDescriptor.dependencyType.defaultType.has_value()) {
- return plugin->typeDescriptor.dependencyType.defaultType.value();
- }
- return PluginTypeEnum::Optional;
-} \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/lib/ConditionTester.h b/libs/installer_fomod_plus/installer/lib/ConditionTester.h
deleted file mode 100644
index 09aa26e..0000000
--- a/libs/installer_fomod_plus/installer/lib/ConditionTester.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#pragma once
-
-#include <imoinfo.h>
-
-#include "FlagMap.h"
-#include "Logger.h"
-#include "xml/ModuleConfiguration.h"
-
-class CompositeDependency;
-class StepViewModel;
-
-class ConditionTester {
-public:
- explicit ConditionTester(MOBase::IOrganizer* organizer) : mOrganizer(organizer) {}
-
- bool isStepVisible(const std::shared_ptr<FlagMap>& flags, const CompositeDependency& compositeDependency,
- int stepIndex,
- const std::vector<std::shared_ptr<StepViewModel>>& steps) const;
-
- bool testCompositeDependency(const std::shared_ptr<FlagMap>& flags,
- const CompositeDependency& compositeDependency) const;
-
- static bool testFlagDependency(const std::shared_ptr<FlagMap>& flags, const FlagDependency& flagDependency);
-
- [[nodiscard]] bool testFileDependency(const FileDependency& fileDependency) const;
-
- bool testGameDependency(const GameDependency& gameDependency) const;
-
-private:
- Logger& log = Logger::getInstance();
- MOBase::IOrganizer* mOrganizer;
-
- friend class FomodViewModel;
-
- [[nodiscard]] FileDependencyTypeEnum getFileDependencyStateForPlugin(const std::string& pluginName) const;
-
- PluginTypeEnum getPluginTypeDescriptorState(const std::shared_ptr<Plugin>& plugin,
- const std::shared_ptr<FlagMap>& flags) const;
-
- mutable std::unordered_map<std::string, FileDependencyTypeEnum> pluginStateCache;
-
-};
diff --git a/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp b/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp
deleted file mode 100644
index 98ed1f3..0000000
--- a/libs/installer_fomod_plus/installer/lib/CrashHandler.cpp
+++ /dev/null
@@ -1,122 +0,0 @@
-#include "CrashHandler.h"
-#include <iostream>
-
-#ifdef _WIN32
-#include <windows.h>
-#include <dbghelp.h>
-#include <sstream>
-#include <fstream>
-
-LPTOP_LEVEL_EXCEPTION_FILTER CrashHandler::previousFilter = nullptr;
-PVOID CrashHandler::vectoredHandler = nullptr;
-#endif
-
-void CrashHandler::initialize() {
-#ifdef _WIN32
- std::cout << "CrashHandler initializing..." << std::endl;
- vectoredHandler = AddVectoredExceptionHandler(1, vectoredExceptionHandler);
- previousFilter = SetUnhandledExceptionFilter(unhandledExceptionFilter);
- std::cout << "CrashHandler initialized successfully" << std::endl;
-#else
- // No-op on Linux — Windows crash handler not needed.
-#endif
-}
-
-void CrashHandler::cleanup() {
-#ifdef _WIN32
- if (vectoredHandler) {
- RemoveVectoredExceptionHandler(vectoredHandler);
- vectoredHandler = nullptr;
- }
- if (previousFilter) {
- SetUnhandledExceptionFilter(previousFilter);
- previousFilter = nullptr;
- }
-#endif
-}
-
-#ifdef _WIN32
-LONG WINAPI CrashHandler::vectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo) {
- // Log all exceptions but only create dumps for serious ones
- std::cout << "Exception caught: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl;
-
- // Only create dumps for serious crashes, not benign exceptions
- if (exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
- exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW ||
- exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
-
- // For stack overflow, we need to handle it on a different thread with its own stack
- if (exceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) {
- // Create a new thread to handle the crash dump since our stack is corrupted
- HANDLE dumpThread = CreateThread(nullptr, 0, [](LPVOID param) -> DWORD {
- auto* exceptionInfo = static_cast<EXCEPTION_POINTERS*>(param);
- logCrashInfo(exceptionInfo);
-
- HANDLE file = CreateFileA("fomod_plus_stack_overflow.dmp", GENERIC_WRITE, 0, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
-
- if (file != INVALID_HANDLE_VALUE) {
- MINIDUMP_EXCEPTION_INFORMATION dumpInfo = {0};
- dumpInfo.ThreadId = GetCurrentThreadId();
- dumpInfo.ExceptionPointers = exceptionInfo;
- dumpInfo.ClientPointers = FALSE;
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file,
- MiniDumpNormal, &dumpInfo, nullptr, nullptr);
- CloseHandle(file);
-
- std::cout << "Stack overflow crash dump written to fomod_plus_stack_overflow.dmp" << std::endl;
- }
- return 0;
- }, exceptionInfo, 0, nullptr);
-
- if (dumpThread) {
- WaitForSingleObject(dumpThread, 5000); // Wait up to 5 seconds
- CloseHandle(dumpThread);
- }
- } else {
- logCrashInfo(exceptionInfo);
-
- // Generate minidump
- HANDLE file = CreateFileA("fomod_plus_crash.dmp", GENERIC_WRITE, 0, nullptr,
- CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
-
- if (file != INVALID_HANDLE_VALUE) {
- MINIDUMP_EXCEPTION_INFORMATION dumpInfo = {0};
- dumpInfo.ThreadId = GetCurrentThreadId();
- dumpInfo.ExceptionPointers = exceptionInfo;
- dumpInfo.ClientPointers = FALSE;
-
- MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file,
- MiniDumpNormal, &dumpInfo, nullptr, nullptr);
- CloseHandle(file);
-
- std::cout << "Crash dump written to fomod_plus_crash.dmp" << std::endl;
- }
- }
- }
-
- return EXCEPTION_CONTINUE_SEARCH; // Let other handlers process it
-}
-
-LONG WINAPI CrashHandler::unhandledExceptionFilter(EXCEPTION_POINTERS* exceptionInfo) {
- logCrashInfo(exceptionInfo);
- return EXCEPTION_EXECUTE_HANDLER;
-}
-
-void CrashHandler::logCrashInfo(const EXCEPTION_POINTERS* exceptionInfo) {
- std::ostringstream oss;
- oss << "FOMOD PLUS CRASH DETECTED!" << std::endl;
- oss << "Exception Code: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionCode << std::endl;
- oss << "Exception Address: 0x" << std::hex << exceptionInfo->ExceptionRecord->ExceptionAddress << std::endl;
-
- // Write to stdout immediately
- std::cout << oss.str() << std::endl;
-
- // Also write to crash log file
- std::ofstream crashLog("fomod_plus_crash.log", std::ios::app);
- if (crashLog.is_open()) {
- crashLog << oss.str() << std::endl;
- }
-}
-#endif
diff --git a/libs/installer_fomod_plus/installer/lib/CrashHandler.h b/libs/installer_fomod_plus/installer/lib/CrashHandler.h
deleted file mode 100644
index 78c8552..0000000
--- a/libs/installer_fomod_plus/installer/lib/CrashHandler.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#ifdef _WIN32
-#include <windows.h>
-#endif
-
-class CrashHandler {
-public:
- static void initialize();
- static void cleanup();
-
-private:
-#ifdef _WIN32
- static LONG WINAPI unhandledExceptionFilter(EXCEPTION_POINTERS* exceptionInfo);
- static LONG WINAPI vectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo);
- static void logCrashInfo(const EXCEPTION_POINTERS* exceptionInfo);
- static LPTOP_LEVEL_EXCEPTION_FILTER previousFilter;
- static PVOID vectoredHandler;
-#endif
-};
diff --git a/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp b/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp
deleted file mode 100644
index e47e088..0000000
--- a/libs/installer_fomod_plus/installer/lib/FileInstaller.cpp
+++ /dev/null
@@ -1,274 +0,0 @@
-#include "FileInstaller.h"
-
-#include <utility>
-
-#include "ui/FomodViewModel.h"
-
-using namespace MOBase;
-
-FileInstaller::FileInstaller(
- IOrganizer* organizer,
- QString fomodPath,
- const std::shared_ptr<IFileTree>& fileTree,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- const std::shared_ptr<FlagMap>& flagMap,
- const std::vector<std::shared_ptr<StepViewModel> >& steps) : mOrganizer(organizer),
- mFomodPath(std::move(fomodPath)),
- mFileTree(fileTree),
- mFomodFile(std::move(fomodFile)),
- mFlagMap(flagMap),
- mConditionTester(organizer), mSteps(steps)
-{
- const auto requiredCount =
- (mFomodFile != nullptr) ? mFomodFile->requiredInstallFiles.files.size() : 0;
- const auto stepCount = mSteps.size();
- logMessage(DEBUG,
- "FileInstaller constructed. steps=" + std::to_string(stepCount) + ", required files=" +
- std::to_string(requiredCount) + ", fomodPath=" + mFomodPath.toStdString());
-}
-
-std::shared_ptr<IFileTree> FileInstaller::install() const
-{
- logMessage(DEBUG, "Starting FileInstaller::install()");
- const auto filesToInstall = collectFilesToInstall();
- logMessage(INFO, "Installing " + std::to_string(filesToInstall.size()) + " files");
- logMessage(INFO, "FlagMap");
- logMessage(INFO, mFlagMap->toString());
-
- // update the file tree with the new files
- const std::shared_ptr<IFileTree> installTree = mFileTree->createOrphanTree();
-
- for (const auto& file : filesToInstall) {
-
- logMessage(DEBUG,
- "Processing install entry source=" + file.source + ", destination=" +
- (file.destination.has_value() ? file.destination.value() : "<default>") +
- ", priority=" + std::to_string(file.priority));
- const auto sourcePath = getQualifiedFilePath(file.source);
- const auto sourceNode = mFileTree->find(QString::fromStdString(sourcePath));
- if (sourceNode == nullptr) {
- logMessage(ERR, "Could not find source: " + file.source);
- continue;
- }
- const auto targetPath = file.destination.has_value()
- ? QString::fromStdString(file.destination.value())
- : QString::fromStdString(sourcePath);
-
- // If it's a folder, copy the contents of the folder, not the folder itself.
- if (sourceNode->isDir()) {
- logMessage(DEBUG,
- "Copying directory '" + file.source + "' into target '" + targetPath.toStdString() +
- "'");
- // TODO: Check if target path is literally undefined/null
- const auto& tree = sourceNode->astree();
- for (auto it = tree->begin(); it != tree->end(); ++it) {
- const auto& entry = *it;
- const auto path = targetPath.isEmpty() ? entry->name() : targetPath + "/" + entry->name();
- logMessage(DEBUG,
- " Copying entry '" + entry->name().toStdString() + "' to '" +
- path.toStdString() + "'");
- installTree->copy(entry, path, IFileTree::InsertPolicy::MERGE);
- }
- } else {
- logMessage(DEBUG,
- "Copying file '" + file.source + "' to '" + targetPath.toStdString() + "'");
- installTree->copy(sourceNode, targetPath, IFileTree::InsertPolicy::MERGE);
- }
- }
-
- // This file will be written by the InstallationManager later.
- const auto jsonFilePath = "fomod.json";
- installTree->addFile(QString::fromStdString(jsonFilePath), true);
- logMessage(DEBUG, "Added fomod.json placeholder file to install tree.");
-
- logMessage(DEBUG, "FileInstaller::install completed.");
- return installTree;
-}
-
-nlohmann::json FileInstaller::generateFomodJson() const
-{
- nlohmann::json fomodJson;
- logMessage(DEBUG, "Generating fomod.json representation for " + std::to_string(mSteps.size()) +
- " steps.");
-
- fomodJson["steps"] = nlohmann::json::array();
- for (const auto& stepViewModel : mSteps) {
- auto stepJson = nlohmann::json::object();
- stepJson["name"] = stepViewModel->getName();
- stepJson["groups"] = nlohmann::json::array();
- logMessage(DEBUG, "Serializing step '" + stepViewModel->getName() + "'");
-
- for (const auto& groupViewModel : stepViewModel->getGroups()) {
- auto groupJson = nlohmann::json::object();
- groupJson["name"] = groupViewModel->getName();
- auto pluginArray = nlohmann::json::array();
- auto deselectedArray = nlohmann::json::array();
- logMessage(DEBUG,
- " Serializing group '" + groupViewModel->getName() +
- "' with " + std::to_string(groupViewModel->getPlugins().size()) + " plugins");
-
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- logMessage(DEBUG,
- " Plugin '" + pluginViewModel->getName() + "' selected=" +
- (pluginViewModel->isSelected() ? "true" : "false") + ", manually-set=" +
- (pluginViewModel->wasManuallySet() ? "true" : "false"));
- if (pluginViewModel->isSelected()) {
- pluginArray.emplace_back(pluginViewModel->getName());
- }
- // Add deselected plugins here. (TODO: This will be replaced with an embedded db.)
- if (!pluginViewModel->isSelected() && pluginViewModel->wasManuallySet()) {
- deselectedArray.emplace_back(pluginViewModel->getName());
- }
- }
- groupJson["plugins"] = pluginArray;
- groupJson["deselected"] = deselectedArray;
- stepJson["groups"].emplace_back(groupJson);
- }
- fomodJson["steps"].emplace_back(stepJson);
- }
- return fomodJson;
-}
-
-std::string FileInstaller::getQualifiedFilePath(const std::string& treePath) const
-{
- // We need to prepend the fomod path to whatever source we reference. Guess we're passing that path around.
- const auto qualifiedPath = mFomodPath.toStdString() + "/" + treePath;
- logMessage(DEBUG, "Qualifying tree path '" + treePath + "' to '" + qualifiedPath + "'");
- return qualifiedPath;
-}
-
-std::vector<std::string> FileInstaller::collectPositiveFileNamesFromDependencyPatterns(
- const std::vector<DependencyPattern>& patterns)
-{
- std::vector<std::string> usableFileDependencyPluginNames = {};
- logMessage(DEBUG,
- "Collecting positive file names from " + std::to_string(patterns.size()) + " patterns.");
-
- for (const auto& pattern : patterns) {
- if (pattern.type == PluginTypeEnum::NotUsable) {
- logMessage(DEBUG, "Skipping pattern marked as NotUsable.");
- continue;
- }
-
- if (pattern.dependencies.fileDependencies.empty() && pattern.dependencies.nestedDependencies.empty()) {
- logMessage(DEBUG, "Skipping pattern with no dependencies.");
- continue;
- }
-
- const auto fileDependencies = pattern.dependencies.fileDependencies;
- const auto nestedDependencies = pattern.dependencies.nestedDependencies;
-
- for (const auto& fileDependency : fileDependencies) {
- if (fileDependency.state != FileDependencyTypeEnum::Active) {
- continue;
- }
- usableFileDependencyPluginNames.emplace_back(fileDependency.file);
- logMessage(DEBUG, "Adding active file dependency: " + fileDependency.file);
- }
-
- for (const auto& nestedDependency : nestedDependencies) {
- for (const auto& fileDependency : nestedDependency.fileDependencies) {
- if (fileDependency.state != FileDependencyTypeEnum::Active) {
- continue;
- }
- usableFileDependencyPluginNames.emplace_back(fileDependency.file);
- logMessage(DEBUG, "Adding nested active file dependency: " + fileDependency.file);
- }
- }
- // Not handling twice-nested dependencies now. IDK if that's even feasible.
- }
-
- logMessage(DEBUG,
- "CollectPositiveFileNamesFromDependencyPatterns found " +
- std::to_string(usableFileDependencyPluginNames.size()) + " entries.");
- return usableFileDependencyPluginNames;
-}
-
-// Generic vector appender
-void FileInstaller::addFiles(std::vector<File>& main, std::vector<File> toAdd) const
-{
- for (const auto& add : toAdd) {
- logMessage(INFO, "Adding file with source: " + add.source);
- }
- main.insert(main.end(), toAdd.begin(), toAdd.end());
- logMessage(DEBUG,
- "addFiles merged " + std::to_string(toAdd.size()) + " files. Total is now " +
- std::to_string(main.size()));
-}
-
-// TODO: Unclear if we're copying. oh well.
-// TODO: Rebuild flagmap and step indeces before installing
-std::vector<File> FileInstaller::collectFilesToInstall() const
-{
- logMessage(DEBUG, "collectFilesToInstall started.");
- std::vector<File> allFiles;
-
- // Required files from FOMOD
- const FileList requiredInstallFiles = mFomodFile->requiredInstallFiles;
- logMessage(DEBUG,
- "Adding " + std::to_string(requiredInstallFiles.files.size()) +
- " required install files from fomod.");
- addFiles(allFiles, requiredInstallFiles.files);
-
- // Selected files from visible steps
- for (const auto& stepViewModel : mSteps) {
- if (!mConditionTester.testCompositeDependency(mFlagMap, stepViewModel->getVisibilityConditions())) {
- logMessage(DEBUG,
- "Skipping invisible step '" + stepViewModel->getName() + "'");
- continue;
- }
- logMessage(DEBUG,
- "Processing visible step '" + stepViewModel->getName() + "' with " +
- std::to_string(stepViewModel->getGroups().size()) + " groups.");
- for (const auto& groupViewModel : stepViewModel->getGroups()) {
- logMessage(DEBUG,
- " Processing group '" + groupViewModel->getName() + "' with " +
- std::to_string(groupViewModel->getPlugins().size()) + " plugins.");
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- if (pluginViewModel->isSelected()) {
- logMessage(DEBUG,
- " Adding selected plugin '" + pluginViewModel->getName() +
- "' with " +
- std::to_string(pluginViewModel->getPlugin()->files.files.size()) +
- " files.");
- addFiles(allFiles, pluginViewModel->getPlugin()->files.files);
- } else {
- logMessage(DEBUG,
- " Skipping plugin '" + pluginViewModel->getName() +
- "' (not selected).");
- }
- }
- }
- }
-
- // ConditionalInstall files
- for (const auto conditionals = mFomodFile->conditionalFileInstalls; const auto& pattern : conditionals.patterns) {
- //<folder source="CR\Dagi-Raht LL\VLrn_Custom Race - Dagi-Raht LL" destination="" priority="2" />
-
- if (mConditionTester.testCompositeDependency(mFlagMap, pattern.dependencies)) {
- // also check if the plugins setting these flags are visible. at least one
-
- addFiles(allFiles, pattern.files.files);
- logMessage(DEBUG,
- "Conditional install pattern matched; added " +
- std::to_string(pattern.files.files.size()) + " files.");
- } else {
- logMessage(DEBUG, "Conditional install pattern did not match.");
- }
- }
-
- // Files will all have a default priority of 0 if not specified, so the order should also be informed by the
- // order they appear within XML. That's why we put conditionalFileInstalls after.
- logMessage(DEBUG, "Sorting " + std::to_string(allFiles.size()) + " files by priority.");
- std::ranges::sort(allFiles, [](const auto& a, const auto& b) {
- return a.priority < b.priority;
- });
-
- for (const auto& toInstall : allFiles) {
- logMessage(DEBUG, "File to install: " + toInstall.source);
- }
- logMessage(DEBUG,
- "collectFilesToInstall completed with " + std::to_string(allFiles.size()) + " files.");
-
- return allFiles;
-}
diff --git a/libs/installer_fomod_plus/installer/lib/FileInstaller.h b/libs/installer_fomod_plus/installer/lib/FileInstaller.h
deleted file mode 100644
index 1c122a7..0000000
--- a/libs/installer_fomod_plus/installer/lib/FileInstaller.h
+++ /dev/null
@@ -1,94 +0,0 @@
-#pragma once
-
-#include <ifiletree.h>
-#include <nlohmann/json.hpp>
-
-#include "ConditionTester.h"
-#include "FlagMap.h"
-#include "Logger.h"
-#include "xml/ModuleConfiguration.h"
-
-
-/* This is what legacy fomodInstaller does:
- modName.update(dialog.getName(), GUESS_USER);
- return dialog.updateTree(tree);
-
- This is a link to the legacy updateTree method:
- https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/fc263f2d923c704b4853c11ed4f8b8cf3920f30d/src/fomodinstallerdialog.cpp#L546
-
-*/
-
-// Things this should do:
-// - Copy all requiredInstall files
-// - Copy all conditionalInstall files
-// - Copy all selected files from steps that were visible to the user at the point of install
-
-using namespace MOBase;
-
-using FileGlobalIndex = int;
-using FileDescriptor = std::pair<File, FileGlobalIndex>;
-
-class StepViewModel;
-
-class FileInstaller {
-public:
- FileInstaller(
- IOrganizer* organizer,
- QString fomodPath,
- const std::shared_ptr<IFileTree>& fileTree,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- const std::shared_ptr<FlagMap>& flagMap,
- const std::vector<std::shared_ptr<StepViewModel> >& steps);
-
- std::shared_ptr<IFileTree> install() const;
-
- /**
- * @brief Create a 'fomod.json' file to add to the base of the installTree. Functionally similar to MO2's meta.ini.
- *
- * Until there's more utility in the JSON structure itself, it will simply be of this format:
- * @code
- * {
- * "steps": [
- * {
- * "name": "Step 1",
- * "groups": [
- * {
- * "name": "Group 1",
- * "plugins: [
- * "Plugin1.esp",
- * "Plugin2.esp"
- * ]
- * }
- * ]
- * }
- * ]
- * }
- * @endcode
- *
- * @return nhlohmann::json
- */
- nlohmann::json generateFomodJson() const;
-
- std::string getQualifiedFilePath(const std::string& treePath) const;
-
- std::vector<std::string> collectPositiveFileNamesFromDependencyPatterns(const std::vector<DependencyPattern> &patterns);
-
- void addFiles(std::vector<File>& main, std::vector<File> toAdd) const;
-
-private:
- IOrganizer* mOrganizer;
- Logger& log = Logger::getInstance();
- QString mFomodPath;
- std::shared_ptr<IFileTree> mFileTree;
- std::unique_ptr<ModuleConfiguration> mFomodFile;
- std::shared_ptr<FlagMap> mFlagMap;
- ConditionTester mConditionTester;
- std::vector<std::shared_ptr<StepViewModel> > mSteps; // TODO: Maybe this is nasty. Idk.
-
- std::vector<File> collectFilesToInstall() const;
-
- void logMessage(LogLevel level, const std::string& message) const
- {
- log.logMessage(level, "[INSTALLER] " + message);
- }
-};
diff --git a/libs/installer_fomod_plus/installer/lib/FlagMap.h b/libs/installer_fomod_plus/installer/lib/FlagMap.h
deleted file mode 100644
index 4959cec..0000000
--- a/libs/installer_fomod_plus/installer/lib/FlagMap.h
+++ /dev/null
@@ -1,113 +0,0 @@
-#pragma once
-
-#include "ViewModels.h"
-#include "stringutil.h"
-
-#include <ranges>
-#include <string>
-#include <unordered_map>
-
-using Flag = std::pair<std::string, std::string>;
-using FlagList = std::vector<Flag>;
-
-class FlagMap {
-public:
-
- [[nodiscard]] std::vector<std::shared_ptr<PluginViewModel>> getPluginsSettingFlag(const std::string& key, const std::string& value) const
- {
- std::vector<std::shared_ptr<PluginViewModel>> result;
- for (const auto& [plugin, theseFlags] : flags) {
- for (const auto& [fst, snd] : theseFlags) {
- if (toLower(fst) == toLower(key) && snd == value) {
- result.emplace_back(plugin);
- }
- }
- }
- return result;
- }
-
- /**
- *
- * @param key The flag key
- * @return A list of flags currently set in this map with the given key. The list is ordered by step descending, then plugin ascending.
- * So if steps 1, 2, and 3 set flag X in their first two plugins, it'll be ordered [3:1, 3:2, 2:1, 2:2, 1:2, 1:1]
- */
- [[nodiscard]] FlagList getFlagsByKey(const std::string& key) const
- {
- FlagList result;
- std::vector<std::pair<int, std::shared_ptr<PluginViewModel>>> orderedPlugins;
-
- // Collect all plugins with their stepIndex and ownIndex
- for (const auto& plugin : flags | std::views::keys) {
- orderedPlugins.emplace_back(plugin->getStepIndex(), plugin);
- }
-
- // Sort plugins by stepIndex and ownIndex, stepIndex descending and ownIndex ascending
- // TODO: Might need group sorting too. How can I just do the natural order??
- std::ranges::sort(orderedPlugins, [](const auto& a, const auto& b) {
- return a.first > b.first || (a.first == b.first && a.second->getOwnIndex() < b.second->getOwnIndex());
- });
-
- // Collect flags in the sorted order
- for (const auto& plugin : orderedPlugins | std::views::values) {
- for (const auto& flag : flags.at(plugin)) {
- if (toLower(flag.first) == toLower(key)) {
- result.emplace_back(flag);
- }
- }
- }
- return result;
- }
-
- void setFlagsForPlugin(PluginRef plugin)
- {
- // Don't clutter the map with empty key-vals
- if (plugin->getConditionFlags().empty()) {
- return;
- }
- unsetFlagsForPlugin(plugin);
-
- FlagList flagList;
- for (const auto& conditionFlag : plugin->getConditionFlags()) {
- flagList.emplace_back(toLower(conditionFlag.name), conditionFlag.value);
- }
- flags[plugin] = flagList;
- }
-
- void unsetFlagsForPlugin(PluginRef plugin)
- {
- if (flags.contains(plugin)) {
- flags.erase(plugin);
- }
- }
-
- std::string toString()
- {
- auto result = std::string();
- result += "FlagMap:\n";
-
- for (const auto& [plugin, theseFlags] : flags) {
- result += plugin->getName() + " [";
- for (const auto& [fst, snd] : theseFlags) {
- result += fst + ": " + snd + ", ";
- }
- result.erase(result.size() - 2);
- result += "]\n";
- }
- return result;
- }
-
- void clearAll()
- {
- flags.clear();
- }
-
- [[nodiscard]] size_t getFlagCount() const
- {
- return flags.size();
- }
-
-
-private:
- std::unordered_map<std::shared_ptr<PluginViewModel>, FlagList> flags;
-};
diff --git a/libs/installer_fomod_plus/installer/lib/Logger.h b/libs/installer_fomod_plus/installer/lib/Logger.h
deleted file mode 100644
index 53fb7d9..0000000
--- a/libs/installer_fomod_plus/installer/lib/Logger.h
+++ /dev/null
@@ -1,116 +0,0 @@
-#pragma once
-#include <fstream>
-#include <iostream>
-#include <mutex>
-#include <regex>
-
-
-// Log levels
-enum LogLevel {
- DEBUG = 0,
- INFO = 1,
- WARN = 2,
- ERR = 3
-};
-
-// Convert LogLevel to string
-inline const char* logLevelToString(const LogLevel level) {
- switch (level) {
- case DEBUG: return "DEBUG";
- case INFO: return "INFO";
- case WARN: return "WARN";
- case ERR: return "ERROR";
- default: return "UNKNOWN";
- }
-}
-
-
-class Logger {
-public:
- static Logger& getInstance()
- {
- static Logger instance;
- return instance;
- }
-
- void setLogFilePath(const std::string& filePath)
- {
- std::lock_guard lock(mMutex);
- if (mLogFile.is_open()) {
- mLogFile.close();
- }
- mLogFile.open(filePath, std::ios::out);
- // std::ios::app is an option for appending but dont wanna grow it forever.
- }
-
- void setDebugMode(bool debug)
- {
- std::lock_guard lock(mMutex);
- mDebugMode = debug;
- }
-
- void logMessage(const LogLevel level, const std::string& message)
- {
-
-#if defined(__GNUC__) || defined(__clang__)
- std::string functionName = __PRETTY_FUNCTION__;
-#elif defined(_MSC_VER)
- std::string functionName = __FUNCSIG__;
-#else
- std::string functionName = "UnknownFunction";
-#endif
-
- std::regex classNameRegex(R"((\w+)::\w+\()");
- std::smatch match;
- std::string className = "UnknownClass";
-
- if (std::regex_search(functionName, match, classNameRegex) && match.size() > 1) {
- className = match.str(1);
- }
-
- std::lock_guard lock(mMutex);
-
- auto writeLog = [&](std::ostream& stream) {
- switch (level) {
- case DEBUG:
- stream << "[DEBUG] " << message << std::endl;
- break;
- case INFO:
- stream << "[INFO] " << message << std::endl;
- break;
- case WARN:
- stream << "[WARN] " << message << std::endl;
- break;
- case ERR:
- stream << "[ERROR] " << message << std::endl;
- break;
- }
- };
-
- if (mLogFile.is_open()) {
- writeLog(mLogFile);
- }
- }
-
- Logger& operator=(const Logger&) = delete;
-
-private:
- Logger() = default;
-
- ~Logger()
- {
- if (mLogFile.is_open()) {
- mLogFile.close();
- }
- }
-
- Logger(const Logger&) = delete;
-
- std::ofstream mLogFile;
- std::mutex mMutex;
-#if !defined(NDEBUG) || defined(CMAKE_BUILD_TYPE_RELWITHDEBINFO)
- bool mDebugMode = true; // Auto-enable in debug/RelWithDebInfo builds
-#else
- bool mDebugMode = false; // Disable in release builds
-#endif
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/lib/ViewModels.h b/libs/installer_fomod_plus/installer/lib/ViewModels.h
deleted file mode 100644
index 062445a..0000000
--- a/libs/installer_fomod_plus/installer/lib/ViewModels.h
+++ /dev/null
@@ -1,120 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "xml/ModuleConfiguration.h"
-
-
-template <typename T>
-using shared_ptr_list = std::vector<std::shared_ptr<T> >;
-
-
-/*
---------------------------------------------------------------------------------
- Plugins
---------------------------------------------------------------------------------
-*/
-class PluginViewModel {
-public:
- PluginViewModel(const std::shared_ptr<Plugin>& plugin_, const bool selected, bool, const int index)
- : ownIndex(index), selected(selected), enabled(true), manuallySet(false), plugin(plugin_) {}
-
- void setSelected(const bool selected) { this->selected = selected; }
- void setEnabled(const bool enabled) { this->enabled = enabled; }
- [[nodiscard]] std::string getName() const { return plugin ? plugin->name : std::string(); }
- [[nodiscard]] std::string getDescription() const { return plugin ? plugin->description : std::string(); }
- [[nodiscard]] std::string getImagePath() const { return plugin ? plugin->image.path : std::string(); }
- [[nodiscard]] bool isSelected() const { return selected; }
- [[nodiscard]] bool isEnabled() const { return enabled; }
- [[nodiscard]] int getOwnIndex() const { return ownIndex; }
- [[nodiscard]] std::vector<ConditionFlag> getConditionFlags() const { return plugin->conditionFlags.flags; }
- [[nodiscard]] PluginTypeEnum getCurrentPluginType() const { return currentPluginType; }
- [[nodiscard]] bool wasManuallySet() const { return manuallySet; }
-
- void setCurrentPluginType(const PluginTypeEnum type) { currentPluginType = type; }
- void setStepIndex(const int stepIndex) { this->stepIndex = stepIndex; }
- void setGroupIndex(const int groupIndex) { this->groupIndex = groupIndex; }
-
- [[nodiscard]] int getStepIndex() const { return stepIndex; }
- [[nodiscard]] int getGroupIndex() const { return groupIndex; }
-
- friend class FomodViewModel;
- friend class FileInstaller;
- friend class ConditionTester;
-
-protected:
- [[nodiscard]] std::shared_ptr<Plugin> getPlugin() const { return plugin; }
-
-private:
- int ownIndex;
- bool selected;
- bool enabled;
- bool manuallySet;
- PluginTypeEnum currentPluginType = PluginTypeEnum::UNKNOWN;
- std::shared_ptr<Plugin> plugin;
-
- int stepIndex{ -1 };
- int groupIndex{ -1 };
-};
-
-/*
---------------------------------------------------------------------------------
- Groups
---------------------------------------------------------------------------------
-*/
-class GroupViewModel {
-public:
- GroupViewModel(const std::shared_ptr<Group>& group_, const shared_ptr_list<PluginViewModel>& plugins,
- const int index, const int stepIndex)
- : plugins(plugins), group(group_), ownIndex(index), stepIndex(stepIndex) {}
-
- void addPlugin(const std::shared_ptr<PluginViewModel>& plugin) { plugins.emplace_back(plugin); }
-
- [[nodiscard]] std::string getName() const { return group->name; }
- [[nodiscard]] GroupTypeEnum getType() const { return group->type; }
- [[nodiscard]] const shared_ptr_list<PluginViewModel>& getPlugins() const { return plugins; }
- [[nodiscard]] int getOwnIndex() const { return ownIndex; }
- [[nodiscard]] int getStepIndex() const { return stepIndex; }
-
-private:
- shared_ptr_list<PluginViewModel> plugins;
- std::shared_ptr<Group> group;
- int ownIndex;
- int stepIndex;
-};
-
-/*
---------------------------------------------------------------------------------
- Steps
---------------------------------------------------------------------------------
-*/
-class StepViewModel {
-public:
- StepViewModel(const std::shared_ptr<InstallStep>& installStep_, const shared_ptr_list<GroupViewModel>& groups,
- const int index)
- : installStep(installStep_), groups(groups), ownIndex(index) {}
-
- [[nodiscard]] CompositeDependency& getVisibilityConditions() const { return installStep->visible; }
- [[nodiscard]] std::string getName() const { return installStep->name; }
- [[nodiscard]] const shared_ptr_list<GroupViewModel>& getGroups() const { return groups; }
- [[nodiscard]] int getOwnIndex() const { return ownIndex; }
- [[nodiscard]] bool getHasVisited() const { return visited; }
- void setVisited(const bool visited) { this->visited = visited; }
-
-private:
- bool visited{ false };
- std::shared_ptr<InstallStep> installStep;
- shared_ptr_list<GroupViewModel> groups;
- int ownIndex;
-};
-
-
-/*
---------------------------------------------------------------------------------
- Outbound Types
---------------------------------------------------------------------------------
-*/
-using StepRef = const std::shared_ptr<StepViewModel>&;
-using GroupRef = const std::shared_ptr<GroupViewModel>&;
-using PluginRef = const std::shared_ptr<PluginViewModel>&;
diff --git a/libs/installer_fomod_plus/installer/resources.qrc b/libs/installer_fomod_plus/installer/resources.qrc
deleted file mode 100644
index 6c583a7..0000000
--- a/libs/installer_fomod_plus/installer/resources.qrc
+++ /dev/null
@@ -1,14 +0,0 @@
-<RCC>
- <qresource prefix="/fomod">
- <file alias="hat">resources/fomod_icon.png</file>
- </qresource>
- <qresource prefix="/fomod">
- <file alias="previous">resources/left-chevron.png</file>
- </qresource>
- <qresource prefix="/fomod">
- <file alias="next">resources/right-chevron.png</file>
- </qresource>
- <qresource prefix="/fomod">
- <file alias="close">resources/delete.png</file>
- </qresource>
-</RCC> \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/resources/delete.png b/libs/installer_fomod_plus/installer/resources/delete.png
deleted file mode 100644
index f6502b1..0000000
--- a/libs/installer_fomod_plus/installer/resources/delete.png
+++ /dev/null
Binary files differ
diff --git a/libs/installer_fomod_plus/installer/resources/fomod_icon.png b/libs/installer_fomod_plus/installer/resources/fomod_icon.png
deleted file mode 100644
index e044052..0000000
--- a/libs/installer_fomod_plus/installer/resources/fomod_icon.png
+++ /dev/null
Binary files differ
diff --git a/libs/installer_fomod_plus/installer/resources/image_icon.png b/libs/installer_fomod_plus/installer/resources/image_icon.png
deleted file mode 100644
index b300751..0000000
--- a/libs/installer_fomod_plus/installer/resources/image_icon.png
+++ /dev/null
Binary files differ
diff --git a/libs/installer_fomod_plus/installer/resources/left-chevron.png b/libs/installer_fomod_plus/installer/resources/left-chevron.png
deleted file mode 100644
index 28222a1..0000000
--- a/libs/installer_fomod_plus/installer/resources/left-chevron.png
+++ /dev/null
Binary files differ
diff --git a/libs/installer_fomod_plus/installer/resources/right-chevron.png b/libs/installer_fomod_plus/installer/resources/right-chevron.png
deleted file mode 100644
index 1a0af49..0000000
--- a/libs/installer_fomod_plus/installer/resources/right-chevron.png
+++ /dev/null
Binary files differ
diff --git a/libs/installer_fomod_plus/installer/ui/ClickableWidget.h b/libs/installer_fomod_plus/installer/ui/ClickableWidget.h
deleted file mode 100644
index 881600f..0000000
--- a/libs/installer_fomod_plus/installer/ui/ClickableWidget.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include <QMouseEvent>
-#include <QWidget>
-
-class ClickableWidget final : public QWidget {
- Q_OBJECT
-
-public:
- explicit ClickableWidget(QWidget* parent = nullptr) : QWidget(parent) {
- setCursor(Qt::PointingHandCursor);
- }
-
- signals:
- void clicked();
-
-protected:
- void mousePressEvent(QMouseEvent* event) override {
- if (event->button() == Qt::LeftButton) {
- emit clicked();
- }
- QWidget::mousePressEvent(event);
- }
-};
diff --git a/libs/installer_fomod_plus/installer/ui/Colors.h b/libs/installer_fomod_plus/installer/ui/Colors.h
deleted file mode 100644
index c3262eb..0000000
--- a/libs/installer_fomod_plus/installer/ui/Colors.h
+++ /dev/null
@@ -1,155 +0,0 @@
-#pragma once
-
-#include <QString>
-#include <map>
-
-namespace UiColors {
-
-enum class ColorApplication {
- BACKGROUND,
- BORDER,
- TEXT,
- ALL
-};
-
-// Color values
-namespace Colors {
- // Light
- const QString Light0 = "251, 241, 199";
- const QString Light1 = "235, 219, 178";
- const QString Light2 = "213, 196, 161";
- const QString Light3 = "189, 174, 147";
-
- // Dark
- const QString Dark0 = "40, 40, 40";
- const QString Dark1 = "60, 56, 54";
- const QString Dark2 = "80, 73, 69";
- const QString Dark3 = "102, 92, 84";
-
- // Red
- const QString Red = "204, 36, 29";
- const QString RedBright = "251, 73, 52";
-
- // Green
- const QString Green = "152, 151, 26";
- const QString GreenBright = "184, 187, 38";
-
- // Yellow
- const QString Yellow = "215, 153, 33";
- const QString YellowBright = "250, 189, 47";
-
- // Blue
- const QString Blue = "69, 133, 136";
- const QString BlueBright = "131, 165, 152";
-
- // Purple
- const QString Purple = "177, 98, 134";
- const QString PurpleBright = "211, 134, 155";
-
- // Aqua
- const QString Aqua = "104, 157, 106";
- const QString AquaBright = "142, 192, 124";
-
- // Orange
- const QString Orange = "214, 93, 14";
- const QString OrangeBright = "254, 128, 25";
-}
-
-// Helper function to generate style strings based on color and application
-inline QString generateStyle(const QString& color, const ColorApplication application, const float opacity = 0.4,
- const int borderWidth = 1)
-{
- QString style;
-
- switch (application) {
- case ColorApplication::BACKGROUND:
- style = QString("QCheckBox { background-color: rgba(%1, %2); } "
- "QRadioButton { background-color: rgba(%1, %2); }")
- .arg(color).arg(opacity);
- break;
-
- case ColorApplication::BORDER:
- style = QString("QCheckBox { border: %1px dashed rgb(%2); } "
- "QRadioButton { border: %1px dashed rgb(%2); }")
- .arg(borderWidth).arg(color);
- break;
-
- case ColorApplication::TEXT:
- style = QString("QCheckBox { color: rgb(%1); } "
- "QRadioButton { color: rgb(%1); }")
- .arg(color);
- break;
-
- case ColorApplication::ALL:
- style = QString("QCheckBox { background-color: rgba(%1, %2); border: %3px solid rgb(%1); color: rgb(%1); } "
- "QRadioButton { background-color: rgba(%1, %2); border: %3px solid rgb(%1); color: rgb(%1); }")
- .arg(color).arg(opacity).arg(borderWidth);
- break;
- }
-
- return style;
-}
-
-// Main function to get style for a color name and application
-inline QString getStyle(const QString& colorName, const ColorApplication application = ColorApplication::BACKGROUND,
- const float opacity = 0.4, const int borderWidth = 1)
-{
- static const std::map<QString, QString> colorValues = {
- { "Light0", Colors::Light0 },
- { "Light1", Colors::Light1 },
- { "Light2", Colors::Light2 },
- { "Light3", Colors::Light3 },
- { "Dark0", Colors::Dark0 },
- { "Dark1", Colors::Dark1 },
- { "Dark2", Colors::Dark2 },
- { "Dark3", Colors::Dark3 },
- { "Red", Colors::Red },
- { "Red Bright", Colors::RedBright },
- { "Green", Colors::Green },
- { "Green Bright", Colors::GreenBright },
- { "Yellow", Colors::Yellow },
- { "Yellow Bright", Colors::YellowBright },
- { "Blue", Colors::Blue },
- { "Blue Bright", Colors::BlueBright },
- { "Purple", Colors::Purple },
- { "Purple Bright", Colors::PurpleBright },
- { "Aqua", Colors::Aqua },
- { "Aqua Bright", Colors::AquaBright },
- { "Orange", Colors::Orange },
- { "Orange Bright", Colors::OrangeBright }
- };
-
- if (const auto it = colorValues.find(colorName); it != colorValues.end()) {
- return generateStyle(it->second, application, opacity, borderWidth);
- }
-
- return {};
-}
-
-// For backward compatibility
-const static std::map<QString, QString> colorStyles = {
- { "Light0", getStyle("Light0", ColorApplication::BACKGROUND) },
- { "Light1", getStyle("Light1", ColorApplication::BACKGROUND) },
- { "Light2", getStyle("Light2", ColorApplication::BACKGROUND) },
- { "Light3", getStyle("Light3", ColorApplication::BACKGROUND) },
- { "Dark0", getStyle("Dark0", ColorApplication::BACKGROUND) },
- { "Dark1", getStyle("Dark1", ColorApplication::BACKGROUND) },
- { "Dark2", getStyle("Dark2", ColorApplication::BACKGROUND) },
- { "Dark3", getStyle("Dark3", ColorApplication::BACKGROUND) },
- { "Red", getStyle("Red", ColorApplication::BACKGROUND) },
- { "Red Bright", getStyle("Red Bright", ColorApplication::BACKGROUND) },
- { "Green", getStyle("Green", ColorApplication::BACKGROUND) },
- { "Green Bright", getStyle("Green Bright", ColorApplication::BACKGROUND) },
- { "Yellow", getStyle("Yellow", ColorApplication::BACKGROUND) },
- { "Yellow Bright", getStyle("Yellow Bright", ColorApplication::BACKGROUND) },
- { "Blue", getStyle("Blue", ColorApplication::BACKGROUND) },
- { "Blue Bright", getStyle("Blue Bright", ColorApplication::BACKGROUND) },
- { "Purple", getStyle("Purple", ColorApplication::BACKGROUND) },
- { "Purple Bright", getStyle("Purple Bright", ColorApplication::BACKGROUND) },
- { "Aqua", getStyle("Aqua", ColorApplication::BACKGROUND) },
- { "Aqua Bright", getStyle("Aqua Bright", ColorApplication::BACKGROUND) },
- { "Orange", getStyle("Orange", ColorApplication::BACKGROUND) },
- { "Orange Bright", getStyle("Orange Bright", ColorApplication::BACKGROUND) }
-};
-
-}
diff --git a/libs/installer_fomod_plus/installer/ui/FomodImageViewer.cpp b/libs/installer_fomod_plus/installer/ui/FomodImageViewer.cpp
deleted file mode 100644
index cb99d27..0000000
--- a/libs/installer_fomod_plus/installer/ui/FomodImageViewer.cpp
+++ /dev/null
@@ -1,284 +0,0 @@
-#include "FomodImageViewer.h"
-
-#include "ScaleLabel.h"
-#include "UIHelper.h"
-
-#include <QLabel>
-#include <QScrollArea>
-#include <QtConcurrent/QtConcurrent>
-
-constexpr int PREVIEW_IMAGE_WIDTH = 160;
-constexpr int PREVIEW_IMAGE_HEIGHT = 90;
-
-/*
-+----------------------------------------------------------+
-|n/N X |
-+---+--------------------------------------------------+---+
-| | | |
-| | | |
-| | | |
-| | | |
-| | | |
-| | | |
-| < | Image | > |
-| | | |
-| | | |
-| | | |
-| | | |
-| | label | |
-+------+------+------+---------------------------------+---+
-| | | | ...previews |
-| | | | |
-+------+------+------+-------------------------------------+
-*/
-constexpr auto BUTTON_STYLE =
- "font-size: 16px; font-weight: bold; color: white; background-color: black; padding: 5px; border-radius: 1px solid black;";
-
-FomodImageViewer::FomodImageViewer(QWidget* parent,
- const QString& fomodPath,
- const std::shared_ptr<StepViewModel>& activeStep,
- const std::shared_ptr<PluginViewModel>& activePlugin) : QDialog(parent), mFomodPath(fomodPath),
- mActiveStep(activeStep),
- mActivePlugin(activePlugin)
-{
-
- setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
- setAttribute(Qt::WA_TranslucentBackground);
- setStyleSheet("background-color: rgba(0, 0, 0, 150);");
-
- const QScreen* screen = this->screen();
- const QRect availableGeometry = screen->availableGeometry();
- setFixedSize(availableGeometry.width(), availableGeometry.height());
- move(availableGeometry.x(), availableGeometry.y());
-
- collectImages();
- mMainImageWrapper = createSinglePhotoPane(this);
- mTopBar = createTopBar(this);
- mPreviewImages = createPreviewImages(this);
- mCenterRow = createCenterRow(this);
-
- const auto layout = new QVBoxLayout(this);
- layout->setContentsMargins(0, 0, 0, 0); // Remove margins
- layout->setSpacing(0); // Remove spacing between widgets
- layout->addWidget(mTopBar);
- layout->addWidget(mCenterRow, 1);
- layout->addWidget(mPreviewImages);
- setLayout(layout);
-
- select(mCurrentIndex);
-
- setFocusPolicy(Qt::StrongFocus); // so we can receive key events
-}
-
-void FomodImageViewer::collectImages()
-{
- mLabelsAndImages.clear();
- for (const auto& groupViewModel : mActiveStep->getGroups()) {
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- if (pluginViewModel->getImagePath().empty()) {
- continue;
- }
- QString imagePath = UIHelper::getFullImagePath(mFomodPath,
- QString::fromStdString(pluginViewModel->getImagePath()));
- mLabelsAndImages.emplace_back(QString::fromStdString(pluginViewModel->getName()), imagePath);
-
- if (pluginViewModel == mActivePlugin) {
- mCurrentIndex = static_cast<int>(mLabelsAndImages.size()) - 1;
- }
- }
- }
-}
-
-QWidget* FomodImageViewer::createCenterRow(QWidget* parent)
-{
- const auto centerRow = new QWidget(parent);
- const auto layout = new QHBoxLayout(centerRow);
-
- mBackButton = createBackButton(centerRow);
- mForwardButton = createForwardButton(centerRow);
-
- mBackButton->setFocusPolicy(Qt::NoFocus);
- mForwardButton->setFocusPolicy(Qt::NoFocus);
-
- layout->addWidget(mBackButton);
- layout->addWidget(mMainImageWrapper, 1);
- layout->addWidget(mForwardButton);
-
- return centerRow;
-}
-
-// ReSharper disable once CppMemberFunctionMayBeStatic
-QWidget* FomodImageViewer::createSinglePhotoPane(QWidget* parent)
-{
- const auto singlePhotoPane = new QWidget(parent);
- const auto layout = new QVBoxLayout(singlePhotoPane);
-
- // const auto [labelText, imagePath] = pair;
-
- mMainImage = new ScaleLabel(singlePhotoPane);
- mMainImage->setAlignment(Qt::AlignCenter);
- layout->addWidget(mMainImage, 1);
-
- mLabel = new QLabel(singlePhotoPane);
- // mLabel->setText(labelText);
- mLabel->setAlignment(Qt::AlignCenter);
- mLabel->setStyleSheet("color: white; font-size: 20px;");
- layout->addWidget(mLabel);
-
- return singlePhotoPane;
-}
-
-QScrollArea* FomodImageViewer::createPreviewImages(QWidget* parent)
-{
- mImagePanes.clear();
- const auto previewImages = new QScrollArea(parent);
- const auto widget = new QWidget(previewImages);
- const auto layout = new QHBoxLayout(previewImages);
-
- layout->setContentsMargins(0, 0, 0, 0);
- layout->setSpacing(0);
-
- for (int i = 0; i < mLabelsAndImages.size(); i++) {
- const auto imageLabel = new ScaleLabel(previewImages);
- imageLabel->setAlignment(Qt::AlignCenter);
- imageLabel->setFixedSize(PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT);
- imageLabel->setFocusPolicy(Qt::NoFocus);
- connect(imageLabel, &ScaleLabel::clicked, this, [this, i] {
- select(i);
- });
- layout->addWidget(imageLabel);
- mImagePanes.emplace_back(imageLabel);
-
- // imageLabel->setScalableResource(mLabelsAndImages[i].second);
- const auto imagePath = mLabelsAndImages[i].second;
-
- QThreadPool::globalInstance()->start([imageLabel, imagePath]() {
- QMetaObject::invokeMethod(imageLabel, [imageLabel, imagePath]() {
- imageLabel->setScalableResource(imagePath);
- }, Qt::QueuedConnection);
- });
- }
-
- widget->setLayout(layout);
- previewImages->setFixedHeight(PREVIEW_IMAGE_HEIGHT + 10);
- previewImages->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- previewImages->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
- previewImages->setFocusPolicy(Qt::NoFocus);
-
- previewImages->setWidget(widget);
- previewImages->setStyleSheet("QScrollArea { border: none; }");
-
- return previewImages;
-}
-
-QPushButton* FomodImageViewer::createBackButton(QWidget* parent) const
-{
- const auto backButton = new QPushButton(parent);
- backButton->setText("<");
- backButton->setStyleSheet(BUTTON_STYLE);
- connect(backButton, &QPushButton::clicked, this, &FomodImageViewer::goBack);
- return backButton;
-}
-
-QPushButton* FomodImageViewer::createForwardButton(QWidget* parent) const
-{
- const auto forwardButton = new QPushButton(parent);
- forwardButton->setText(">");
- forwardButton->
- setStyleSheet(BUTTON_STYLE);
- connect(forwardButton, &QPushButton::clicked, this, &FomodImageViewer::goForward);
- return forwardButton;
-}
-
-QWidget* FomodImageViewer::createTopBar(QWidget* parent)
-{
- const auto topBar = new QWidget(parent);
- const auto layout = new QHBoxLayout(topBar);
-
- // counter, spacer, close button
- mCounter = new QLabel(topBar);
- mCounter->setStyleSheet(BUTTON_STYLE);
- layout->addWidget(mCounter);
-
- layout->addStretch();
-
- mCloseButton = createCloseButton(topBar);
- layout->addWidget(mCloseButton);
- return topBar;
-}
-
-QPushButton* FomodImageViewer::createCloseButton(QWidget* parent)
-{
- const auto closeButton = new QPushButton(parent);
- // const QIcon icon(":/fomod/close");
- // closeButton->setIcon(icon);
- closeButton->setText("X");
- closeButton->setStyleSheet("color: white; background-color: black; padding: 5px; border-radius: 1px solid black;");
- connect(closeButton, &QPushButton::clicked, this, &FomodImageViewer::close);
- return closeButton;
-}
-
-void FomodImageViewer::updateCounterText() const
-{
- mCounter->setText(QString::number(mCurrentIndex + 1) + "/" + QString::number(mLabelsAndImages.size()));
-}
-
-void FomodImageViewer::goBack()
-{
- if (mCurrentIndex == 0) {
- return;
- }
- select(--mCurrentIndex);
-}
-
-void FomodImageViewer::goForward()
-{
- if (mCurrentIndex == mLabelsAndImages.size() - 1) {
- return;
- }
- select(++mCurrentIndex);
-}
-
-void FomodImageViewer::select(const int index)
-{
- if (index < 0 || index >= mLabelsAndImages.size()) {
- return;
- }
-
- // Remove border from previously selected image
- mCurrentIndex = index; // check for bounds?
- updateCounterText();
-
- for (int i = 0; i < mImagePanes.size(); i++) {
- if (i == mCurrentIndex) {
- mImagePanes[i]->setStyleSheet("border: 2px solid white;");
- } else {
- mImagePanes[i]->setStyleSheet("");
- }
- }
-
- const auto& imagePath = mLabelsAndImages[index].second;
- const auto& labelText = mLabelsAndImages[index].first;
- mLabel->setText(labelText);
- mMainImage->setScalableResource(imagePath);
-}
-
-void FomodImageViewer::keyPressEvent(QKeyEvent* event)
-{
- switch (event->key()) {
- case Qt::Key_Left:
- goBack();
- break;
- case Qt::Key_Right:
- goForward();
- break;
- default:
- QDialog::keyPressEvent(event);
- }
-}
-
-void FomodImageViewer::showEvent(QShowEvent* event)
-{
- QDialog::showEvent(event);
- setFocus();
-} \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/ui/FomodImageViewer.h b/libs/installer_fomod_plus/installer/ui/FomodImageViewer.h
deleted file mode 100644
index 73a06f7..0000000
--- a/libs/installer_fomod_plus/installer/ui/FomodImageViewer.h
+++ /dev/null
@@ -1,92 +0,0 @@
-#pragma once
-
-#include "FomodViewModel.h"
-
-#include <QDialog>
-#include <QKeyEvent>
-#include <QLabel>
-#include <QScrollArea>
-
-class ScaleLabel;
-using LabelImagePair = std::pair<QString, QString>;
-
-
-/*
-+----------------------------------------------------------+
-|n/N X |
-+---+--------------------------------------------------+---+
-| | | |
-| | | |
-| | | |
-| | | |
-| | | |
-| | | |
-| < | Image | > |
-| | | |
-| | | |
-| | | |
-| | | |
-| | label | |
-+------+------+------+---------------------------------+---+
-| | | | ...previews |
-| | | | |
-+------+------+------+-------------------------------------+
-*/
-
-class FomodImageViewer final : public QDialog {
- Q_OBJECT
-
-public:
- explicit FomodImageViewer(QWidget* parent,
- const QString& fomodPath,
- const std::shared_ptr<StepViewModel>& activeStep,
- const std::shared_ptr<PluginViewModel>& activePlugin);
-
-private:
- void collectImages();
-
- QWidget* createCenterRow(QWidget* parent);
-
- QWidget* createSinglePhotoPane(QWidget* parent);
-
- QScrollArea* createPreviewImages(QWidget* parent);
-
- QPushButton* createBackButton(QWidget* parent) const;
-
- QPushButton* createForwardButton(QWidget* parent) const;
-
- QWidget* createTopBar(QWidget* parent);
-
- QPushButton* createCloseButton(QWidget* parent);
-
- void updateCounterText() const;
-
- void goBack();
-
- void goForward();
-
- void select(int index);
-
- void keyPressEvent(QKeyEvent* event) override;
-
- void showEvent(QShowEvent* event) override;
-
- std::vector<LabelImagePair> mLabelsAndImages;
- std::vector<QWidget*> mImagePanes{};
- int mCurrentIndex{ 0 };
-
- QString mFomodPath;
- const std::shared_ptr<StepViewModel>& mActiveStep;
- const std::shared_ptr<PluginViewModel>& mActivePlugin;
-
- QWidget* mCenterRow{ nullptr };
- QWidget* mTopBar{ nullptr };
- QWidget* mCloseButton{ nullptr };
- QPushButton* mBackButton{ nullptr };
- QPushButton* mForwardButton{ nullptr };
- QLabel* mCounter{ nullptr };
- QWidget* mMainImageWrapper{ nullptr };
- ScaleLabel* mMainImage{ nullptr };
- QLabel* mLabel{ nullptr };
- QScrollArea* mPreviewImages{ nullptr };
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/ui/FomodViewModel.cpp b/libs/installer_fomod_plus/installer/ui/FomodViewModel.cpp
deleted file mode 100644
index b11674c..0000000
--- a/libs/installer_fomod_plus/installer/ui/FomodViewModel.cpp
+++ /dev/null
@@ -1,746 +0,0 @@
-#include "FomodViewModel.h"
-#include "xml/ModuleConfiguration.h"
-#include "lib/Logger.h"
-
-using GroupCallback = std::function<void(GroupRef)>;
-using PluginCallback = std::function<void(GroupRef, PluginRef)>;
-
-
-/*
---------------------------------------------------------------------------------
- Helpers
---------------------------------------------------------------------------------
-*/
-#pragma region Helpers
-
-bool isRadioLike(GroupRef group)
-{
- return group->getType() == SelectExactlyOne
- || (group->getType() == SelectAtMostOne && group->getPlugins().size() > 1);
-}
-
-bool moreThanOneSelected(GroupRef group)
-{
- auto selectedPlugins = group->getPlugins() | std::views::filter([](const auto& plugin) {
- return plugin->isSelected();
- });
- return std::ranges::distance(selectedPlugins) > 1;
-}
-
-bool anySelected(GroupRef group)
-{
- return std::ranges::any_of(group->getPlugins(), [](const auto& plugin) { return plugin->isSelected(); });
-}
-
-std::string pluginTypeEnumToString(const PluginTypeEnum type)
-{
- switch (type) {
- case PluginTypeEnum::Recommended:
- return "Recommended";
- case PluginTypeEnum::Required:
- return "Required";
- case PluginTypeEnum::Optional:
- return "Optional";
- case PluginTypeEnum::NotUsable:
- return "NotUsable";
- case PluginTypeEnum::CouldBeUsable:
- return "CouldBeUsable";
- default:
- return "Unknown";
- }
-}
-
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Lifecycle
---------------------------------------------------------------------------------
-*/
-#pragma region ViewModel Lifecycle
-
-/**
- * @brief FomodViewModel constructor
- *
- * @note DO NOT USE DIRECTLY. We should only use FomodViewModel::create() to create a new instance.
- * @see FomodViewModel::create
- *
- * @param organizer The organizer instance passed from the IInstaller
- * @param fomodFile The ModuleConfiguration instance created from the raw ModuleConfiguration.xml file
- * @param infoFile The FomodInfoFile instance created from the raw info.xml file
- *
- * @return A FomodViewModel instance
- */
-FomodViewModel::FomodViewModel(MOBase::IOrganizer* organizer,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- std::unique_ptr<FomodInfoFile> infoFile)
- : mOrganizer(organizer), mFomodFile(std::move(fomodFile)), mInfoFile(std::move(infoFile)),
- mConditionTester(organizer),
- mInfoViewModel(std::make_shared<InfoViewModel>(mInfoFile))
-{
- mFlags = std::make_shared<FlagMap>();
-}
-
-/**
- *
- * @param organizer The organizer instance passed from the IInstaller
- * @param fomodFile The ModuleConfiguration instance created from the raw ModuleConfiguration.xml file
- * @param infoFile The FomodInfoFile instance created from the raw info.xml file
- * @return A shared pointer to the FomodViewModel instance
- */
-std::shared_ptr<FomodViewModel> FomodViewModel::create(MOBase::IOrganizer* organizer,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- std::unique_ptr<FomodInfoFile> infoFile)
-{
- auto viewModel = std::make_shared<FomodViewModel>(organizer, std::move(fomodFile), std::move(infoFile));
- if (viewModel->mFlags == nullptr) {
- viewModel->mFlags = std::make_shared<FlagMap>();
- }
- viewModel->createStepViewModels();
-
- // Handle FOMODs with no steps
- if (viewModel->mSteps.empty()) {
- viewModel->mInitialized = true;
- viewModel->logMessage(INFO, "FOMOD with no steps - initialization complete");
- return viewModel;
- }
-
- viewModel->processPluginConditions(-1); // please dont judge me. ill fix this someday.
- viewModel->enforceGroupConstraints();
- viewModel->updateVisibleSteps();
- viewModel->mInitialized = true;
- viewModel->mCurrentStepIndex = viewModel->mVisibleStepIndices.front();
- viewModel->mActiveStep = viewModel->mSteps.at(viewModel->mVisibleStepIndices.front());
- viewModel->mActivePlugin = viewModel->getFirstPluginForActiveStep();
- viewModel->getActiveStep()->setVisited(true);
- viewModel->logMessage(DEBUG, "VIEWMODEL INITIALIZED");
- viewModel->logMessage(DEBUG, viewModel->toString());
- return viewModel;
-}
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Traversal Functions
---------------------------------------------------------------------------------
-*/
-#pragma region Traversal Functions
-
-void FomodViewModel::forEachGroup(const GroupCallback& callback) const
-{
- for (const auto& stepViewModel : mSteps) {
- for (const auto& groupViewModel : stepViewModel->getGroups()) {
- callback(groupViewModel);
- }
- }
-}
-
-void FomodViewModel::forEachPlugin(const PluginCallback& callback) const
-{
- for (const auto& stepViewModel : mSteps) {
- for (const auto& groupViewModel : stepViewModel->getGroups()) {
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- callback(groupViewModel, pluginViewModel);
- }
- }
- }
-}
-
-void FomodViewModel::forEachFuturePlugin(const int fromStepIndex, const PluginCallback& callback) const
-{
- for (int i = fromStepIndex + 1; i < mSteps.size(); ++i) {
- for (const auto& groupViewModel : mSteps[i]->getGroups()) {
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- callback(groupViewModel, pluginViewModel);
- }
- }
- }
-}
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Initialization
---------------------------------------------------------------------------------
-*/
-#pragma region Initializers
-void FomodViewModel::createStepViewModels()
-{
- shared_ptr_list<StepViewModel> stepViewModels;
-
- // Handle legacy FOMODs with no install steps
- if (mFomodFile->installSteps.installSteps.empty()) {
- logMessage(INFO, "No install steps found - creating default step for legacy FOMOD");
- return;
- }
-
- for (int stepIndex = 0; stepIndex < mFomodFile->installSteps.installSteps.size(); ++stepIndex) {
- const auto& installStep = mFomodFile->installSteps.installSteps[stepIndex];
- shared_ptr_list<GroupViewModel> groupViewModels;
-
- for (int groupIndex = 0; groupIndex < installStep.optionalFileGroups.groups.size(); ++groupIndex) {
- const auto& group = installStep.optionalFileGroups.groups[groupIndex];
- shared_ptr_list<PluginViewModel> pluginViewModels;
-
- for (int pluginIndex = 0; pluginIndex < group.plugins.plugins.size(); ++pluginIndex) {
- const auto& plugin = group.plugins.plugins[pluginIndex];
- auto pluginViewModel = std::make_shared<PluginViewModel>(std::make_shared<Plugin>(plugin), false, true,
- pluginIndex);
-
- pluginViewModel->setStepIndex(stepIndex);
- pluginViewModel->setGroupIndex(groupIndex);
- pluginViewModels.emplace_back(pluginViewModel); // Assuming default values for selected and enabled
- }
- auto groupViewModel = std::make_shared<GroupViewModel>(std::make_shared<Group>(group), pluginViewModels,
- groupIndex, stepIndex);
- if (groupViewModel->getType() == SelectAtMostOne && groupViewModel->getPlugins().size() > 1) {
- createNonePluginForGroup(groupViewModel);
- }
- groupViewModels.emplace_back(groupViewModel);
- }
- auto stepViewModel = std::make_shared<StepViewModel>(std::make_shared<InstallStep>(installStep),
- std::move(groupViewModels), stepIndex);
- stepViewModels.emplace_back(stepViewModel);
-
- }
- mSteps = std::move(stepViewModels);
-}
-
-void FomodViewModel::createNonePluginForGroup(GroupRef group)
-{
- const auto nonePlugin = std::make_shared<Plugin>();
- nonePlugin->name = "None";
- nonePlugin->typeDescriptor.type = PluginTypeEnum::Optional;
- const int newIndex = static_cast<int>(group->getPlugins().size());
- const auto nonePluginViewModel = std::make_shared<PluginViewModel>(nonePlugin, true, true, newIndex);
- group->addPlugin(nonePluginViewModel);
-}
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Group Constraints
---------------------------------------------------------------------------------
-*/
-#pragma region Group Constraints
-
-void FomodViewModel::enforceRadioGroupConstraints(GroupRef group) const
-{
- if (!isRadioLike(group)) {
- return;
- }
-
- logMessage(INFO, "Enforcing group constraints for group " + group->getName());
-
- if (group->getType() == SelectExactlyOne && group->getPlugins().size() == 1) {
- logMessage(INFO,
- "Disabling " + group->getPlugins().at(0)->getName() + " because it's the only plugin.");
- group->getPlugins().at(0)->setEnabled(false);
- }
-
- if (moreThanOneSelected(group)) {
- logMessage(ERR, "More than one plugin is selected in a SelectExactlyOne group. Deselecting all.");
- for (const auto& plugin : group->getPlugins()) {
- plugin->setSelected(false); // don't call toggle here, that'll do the radio stuff.
- }
- }
-
- if (anySelected(group)) {
- logMessage(INFO, "At least one plugin is selected. Nothing to enforce.");
- return;
- }
-
- // First, try to select the first Recommended plugin
- for (const auto& plugin : group->getPlugins()) {
- if (mConditionTester.getPluginTypeDescriptorState(plugin->getPlugin(), mFlags) == PluginTypeEnum::Recommended) {
- logMessage(INFO, "Selecting " + plugin->getName() + " because it's the first recommended plugin.");
- togglePlugin(group, plugin, true);
- return;
- }
- }
-
- // If no Recommended plugin is found, select the first one that isn't NotUsable
- for (const auto& plugin : group->getPlugins()) {
- if (mConditionTester.getPluginTypeDescriptorState(plugin->getPlugin(), mFlags) != PluginTypeEnum::NotUsable) {
- logMessage(INFO, "Selecting " + plugin->getName() + " because it's the first usable plugin.");
- togglePlugin(group, plugin, true);
- return;
- }
- }
-}
-
-void FomodViewModel::enforceSelectAllConstraint(GroupRef groupViewModel) const
-{
- if (groupViewModel->getType() != SelectAll) {
- return;
- }
-
- for (const auto& pluginViewModel : groupViewModel->getPlugins()) {
- togglePlugin(groupViewModel, pluginViewModel, true);
- pluginViewModel->setEnabled(false);
- }
-
-}
-
-void FomodViewModel::enforceSelectAtLeastOneConstraint(GroupRef group) const
-{
- if (group->getType() != SelectAtLeastOne || group->getPlugins().size() != 1) {
- return;
- }
-
- const auto plugin = group->getPlugins().front();
- if (mConditionTester.getPluginTypeDescriptorState(plugin->getPlugin(), mFlags) != PluginTypeEnum::NotUsable) {
- logMessage(DEBUG, "Selecting " + plugin->getName() + " because it's the only plugin in a SelectAtLeastOne.");
- togglePlugin(group, plugin, true);
- plugin->setEnabled(false);
- }
-}
-
-void FomodViewModel::enforceGroupConstraints() const
-{
- forEachGroup([this](const auto& groupViewModel) {
- enforceRadioGroupConstraints(groupViewModel);
- enforceSelectAllConstraint(groupViewModel);
- enforceSelectAtLeastOneConstraint(groupViewModel);
- });
-}
-
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Plugin Constraints
---------------------------------------------------------------------------------
-*/
-#pragma region Plugin Constraints
-
-void FomodViewModel::processPlugin(GroupRef group, PluginRef plugin) const
-{
- if (group->getType() == SelectAll) {
- return;
- }
- const auto typeDescriptor = mConditionTester.getPluginTypeDescriptorState(plugin->plugin, mFlags);
-
- if (typeDescriptor == plugin->getCurrentPluginType()) {
- return;
- }
-
- logMessage(DEBUG,
- "Plugin " + plugin->getName() + " in group " + std::to_string(group->getOwnIndex()) + " has changed type from "
- + pluginTypeEnumToString(plugin->getCurrentPluginType()) + " to " + pluginTypeEnumToString(typeDescriptor));
-
- plugin->setCurrentPluginType(typeDescriptor);
-
- const bool isOnlyPlugin = group->getPlugins().size() == 1
- && (group->getType() == SelectExactlyOne || group->getType() == SelectAtLeastOne);
-
- // check if step hasVisited, if it hasn't been, set it to unchecked if it's optional.
- const auto stepNotVisitedYet = !mSteps[group->getStepIndex()]->getHasVisited();
-
- switch (typeDescriptor) {
- case PluginTypeEnum::Recommended:
- plugin->setEnabled(true);
- if (!plugin->isSelected()) {
- togglePlugin(group, plugin, true);
- }
- break;
- case PluginTypeEnum::Required:
- plugin->setEnabled(false);
- if (!plugin->isSelected()) {
- togglePlugin(group, plugin, true);
- }
- break;
- case PluginTypeEnum::Optional:
- if (!isOnlyPlugin) {
- plugin->setEnabled(true);
- }
- // In the case where we're changing flags to make something optional from Recommended, set it back to unchecked.
- if (plugin->isSelected() & stepNotVisitedYet && group->getType() == SelectAny) {
- togglePlugin(group, plugin, false);
- }
- break;
- case PluginTypeEnum::NotUsable:
- plugin->setEnabled(false);
- if (plugin->isSelected()) {
- togglePlugin(group, plugin, false);
- }
- break;
- case PluginTypeEnum::CouldBeUsable:
- plugin->setEnabled(true);
- break;
- default: ;
- }
-}
-
-void FomodViewModel::processPluginConditions(const int fromStepIndex) const
-{
- // We only want to update plugins that haven't been seen yet. Otherwise, we could undo manual selections by the user.
- if (fromStepIndex >= 0) {
- logMessage(DEBUG, "Processing plugins from step " + std::to_string(fromStepIndex));
- forEachFuturePlugin(fromStepIndex, [this](const auto& groupViewModel, const auto& pluginViewModel) {
- processPlugin(groupViewModel, pluginViewModel);
- });
- } else {
- forEachPlugin([this](const auto& groupViewModel, const auto& pluginViewModel) {
- processPlugin(groupViewModel, pluginViewModel);
- });
- }
-}
-
-void FomodViewModel::setFlagForPluginState(PluginRef plugin) const
-{
- if (plugin->isSelected()) {
- mFlags->setFlagsForPlugin(plugin);
- } else {
- mFlags->unsetFlagsForPlugin(plugin);
- }
-}
-
-/*
- * In an exclusive group, this gets called for the deselected plugin and then the selected plugin.
- * So if we're unselecting modB to select modA, we will get calls like
- * togglePlugin(group, modB, false)
- * togglePlugin(group, modA, true)
- */
-bool FomodViewModel::togglePlugin(GroupRef group, PluginRef plugin, const bool selected) const
-{
- if (plugin->isSelected() == selected) {
- logMessage(DEBUG, "Plugin " + plugin->getName() + " is already " + (selected ? "selected" : "deselected"));
- return false;
- }
-
- // Disable other radio options first.
- if (selected && isRadioLike(group)) {
- for (const auto& otherPlugin : group->getPlugins()) {
- if (otherPlugin != plugin && plugin->isSelected()) {
- logMessage(DEBUG,
- "Deselecting " + otherPlugin->getName() + " because " + plugin->getName() + " was selected.");
- otherPlugin->setSelected(false);
- setFlagForPluginState(otherPlugin);
- }
- }
- }
-
- const auto stepIndex = group->getStepIndex();
-
- logMessage(INFO, "Toggling " + plugin->getName() + " to " + (selected ? "true" : "false"));
- plugin->setSelected(selected);
- setFlagForPluginState(plugin);
-
- if (mInitialized) {
- mActivePlugin = plugin;
- }
- processPluginConditions(stepIndex);
- updateVisibleSteps();
- return true;
-}
-
-void FomodViewModel::markManuallySet(PluginRef plugin)
-{
- plugin->manuallySet = true;
-}
-
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Step Constraints
---------------------------------------------------------------------------------
-*/
-#pragma region Step Constraints
-
-void FomodViewModel::updateVisibleSteps() const
-{
- mVisibleStepIndices.clear();
- mFlags->clearAll();
-
- for (int i = 0; i < mSteps.size(); ++i) {
- if (i == 0) {
- rebuildConditionFlagsForStep(i);
- }
-
- // This also depends on previous flags that may have set this particular flag.
- if (mConditionTester.isStepVisible(mFlags, mSteps[i]->getVisibilityConditions(), i, mSteps)) {
- mVisibleStepIndices.push_back(i);
- rebuildConditionFlagsForStep(i);
- }
- }
- if (mFlags->getFlagCount() > 0) {
- logMessage(DEBUG, mFlags->toString());
- }
-}
-
-void FomodViewModel::rebuildConditionFlagsForStep(const int stepIndex) const
-{
- for (const auto& group : mSteps[stepIndex]->getGroups()) {
- for (const auto& plugin : group->getPlugins()) {
- setFlagForPluginState(plugin);
- }
- }
-}
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Navigation/UI
---------------------------------------------------------------------------------
-*/
-#pragma region Navigation/UI
-
-void FomodViewModel::stepBack()
-{
- if (mSteps.empty()) {
- return; // No steps to move back to
- }
-
- logMessage(DEBUG, "Stepping back from step " + std::to_string(mCurrentStepIndex));
- const auto it = std::ranges::find(mVisibleStepIndices, mCurrentStepIndex);
- if (it != mVisibleStepIndices.end() && it != mVisibleStepIndices.begin()) {
- mCurrentStepIndex = *std::prev(it);
- mActiveStep = mSteps[mCurrentStepIndex];
- mActivePlugin = getFirstPluginForActiveStep();
- }
- logMessage(DEBUG, "Stepped back to step " + std::to_string(mCurrentStepIndex));
-}
-
-void FomodViewModel::stepForward()
-{
- if (mSteps.empty()) {
- return; // No steps to move forward to
- }
-
- logMessage(DEBUG, "Stepping forward from step " + std::to_string(mCurrentStepIndex));
- const auto it = std::ranges::find(mVisibleStepIndices, mCurrentStepIndex);
- if (it != mVisibleStepIndices.end() && std::next(it) != mVisibleStepIndices.end()) {
- mCurrentStepIndex = *std::next(it);
- mActiveStep = mSteps[mCurrentStepIndex];
- mActivePlugin = getFirstPluginForActiveStep();
- }
- mActiveStep->setVisited(true);
- logMessage(DEBUG, "Stepped forward to step " + std::to_string(mCurrentStepIndex));
-}
-
-bool FomodViewModel::isLastVisibleStep() const
-{
- if (mSteps.empty()) {
- return true; // Legacy FOMODs are always "last step"
- }
- return !mVisibleStepIndices.empty() && mCurrentStepIndex == mVisibleStepIndices.back();
-}
-
-bool FomodViewModel::isFirstVisibleStep() const
-{
- if (mSteps.empty()) {
- return true; // Legacy FOMODs are always "first step"
- }
- return !mVisibleStepIndices.empty() && mCurrentStepIndex == mVisibleStepIndices.front();
-}
-
-void FomodViewModel::preinstall(const std::shared_ptr<MOBase::IFileTree>& tree, const QString& fomodPath)
-{
- mFileInstaller = std::make_shared<
- FileInstaller>(mOrganizer, fomodPath, tree, std::move(mFomodFile), mFlags, mSteps);
-}
-
-
-std::string FomodViewModel::getDisplayImage() const
-{
- // if the active plugin has an image, return it
- if (mActivePlugin && !mActivePlugin->getImagePath().empty()) {
- return mActivePlugin->getImagePath();
- }
- return mCurrentStepIndex == 0 ? mFomodFile->moduleImage.path : "";
-}
-
-std::shared_ptr<PluginViewModel> FomodViewModel::getFirstPluginForActiveStep() const
-{
- if (!mActiveStep) {
- return nullptr;
- }
-
- const auto& groups = mActiveStep->getGroups();
- if (groups.empty()) {
- return nullptr;
- }
-
- const auto& plugins = groups.front()->getPlugins();
- if (plugins.empty()) {
- return nullptr;
- }
-
- return plugins.front();
-}
-#pragma endregion
-
-/*
---------------------------------------------------------------------------------
- Utility
---------------------------------------------------------------------------------
-*/
-#pragma region Utility
-std::string FomodViewModel::toString() const
-{
- std::string viewModel = "\n";
- for (const auto& step : mSteps) {
-
- const auto isVisible = std::ranges::find(mVisibleStepIndices, step->getOwnIndex()) != mVisibleStepIndices.end();
- viewModel += "Step " + std::to_string(step->getOwnIndex()) + ": " + step->getName() + "[Visible: " +
- std::to_string(isVisible) + "]\n";
-
- for (const auto& group : step->getGroups()) {
-
- viewModel += "\tGroup " + std::to_string(group->getOwnIndex()) + ": " + group->getName() + "\n";
-
- for (const auto& plugin : group->getPlugins()) {
- viewModel += "\t\tPlugin: " + plugin->getName() + "[Selected: " + (plugin->isSelected()
- ? "TRUE"
- : "FALSE") + "]\n";
- }
- }
- }
- viewModel += "\n";
- std::ostringstream oss;
- std::ranges::transform(mVisibleStepIndices,
- std::ostream_iterator<std::string>(oss, ", "),
- [](const int i) { return std::to_string(i); });
-
- std::string stepList = oss.str();
- stepList.erase(stepList.length() - 2);
-
- viewModel += "Visible Steps: [" + stepList + "]\n";
- viewModel += mFlags->toString();
- return viewModel;
-}
-
-
-void FomodViewModel::resetToDefaults()
-{
- logMessage(INFO, "Resetting all choices to author defaults");
-
- // Clear all flags first
- mFlags->clearAll();
-
- // Reset all plugins to deselected and clear visited states
- for (const auto& step : mSteps) {
- step->setVisited(false);
- for (const auto& group : step->getGroups()) {
- for (const auto& plugin : group->getPlugins()) {
- plugin->setSelected(false);
- plugin->setEnabled(true);
- plugin->manuallySet = false;
- plugin->setCurrentPluginType(PluginTypeEnum::UNKNOWN);
- }
- }
- }
-
- // Re-run the initial constraint enforcement to restore author defaults
- processPluginConditions(-1);
- enforceGroupConstraints();
- updateVisibleSteps();
-
- // Reset to first step
- mCurrentStepIndex = mVisibleStepIndices.empty() ? 0 : mVisibleStepIndices.front();
- mActiveStep = mSteps.empty() ? nullptr : mSteps.at(mCurrentStepIndex);
- mActivePlugin = getFirstPluginForActiveStep();
- if (mActiveStep) {
- mActiveStep->setVisited(true);
- }
-
- logMessage(DEBUG, "Reset complete. Current state:\n" + toString());
-}
-
-void FomodViewModel::selectFromJson(nlohmann::json json) const
-{
- const auto jsonSteps = json["steps"];
- const auto stepCount = jsonSteps.size();
-
- for (int stepIndex = 0; stepIndex < stepCount; ++stepIndex) {
-
- if (stepIndex > mSteps.size() - 1) {
- logMessage(ERR, "Step index " + std::to_string(stepIndex) + " is out of bounds.");
- continue;
- }
-
- const auto currentStep = mSteps[stepIndex];
- const auto step = jsonSteps[stepIndex];
- const auto groupCount = step["groups"].size();
-
- logMessage(DEBUG, "Selecting plugins for step " + std::to_string(stepIndex));
- logMessage(DEBUG, "There are " + std::to_string(groupCount) + " groups.");
-
- for (int groupIndex = 0; groupIndex < groupCount; ++groupIndex) {
- if (groupIndex > currentStep->getGroups().size() - 1) {
- logMessage(ERR, "Group index " + std::to_string(groupIndex) + " is out of bounds.");
- continue;
- }
-
- const auto group = step["groups"][groupIndex];
- const auto currentGroup = currentStep->getGroups()[groupIndex];
-
- for (const auto jsonPlugin : group["plugins"]) {
-
- const auto& allPlugins = currentGroup->getPlugins();
- const auto searchName = jsonPlugin.get<std::string>();
-
- logMessage(DEBUG, "Looking for plugin " + searchName);
-
- const auto currentPlugin = std::ranges::find_if(allPlugins,
- [searchName](PluginRef p) {
- return p->getName() == searchName;
- });
-
- if (currentPlugin == allPlugins.end()) {
- logMessage(DEBUG, "Plugin " + searchName + " not found in group " + currentGroup->getName());
- continue;
- }
-
- if ((*currentPlugin)->isSelected()) {
- logMessage(DEBUG, "Plugin " + searchName + " is already selected.");
- continue;
- }
- logMessage(DEBUG, "Toggle plugin " + searchName + " to selected.");
- if (!(*currentPlugin)->isEnabled()) {
- logMessage(DEBUG, "Plugin " + searchName + " is not enabled.");
- continue;
- }
- togglePlugin(currentGroup, *currentPlugin, true);
- }
-
- if (!group.contains("deselected")) {
- continue;
- }
-
- // Do the opposite of the above. For unchecked plugins, disable them.
- for (const auto jsonPlugin : group["deselected"]) {
-
- const auto& allPlugins = currentGroup->getPlugins();
- const auto searchName = jsonPlugin.get<std::string>();
-
- logMessage(DEBUG, "Looking for plugin to disable: " + searchName);
-
- const auto currentPlugin = std::ranges::find_if(allPlugins,
- [searchName](PluginRef p) {
- return p->getName() == searchName;
- });
-
- if (currentPlugin == allPlugins.end()) {
- logMessage(DEBUG, "Plugin " + searchName + " not found in group " + currentGroup->getName());
- continue;
- }
-
- if (!(*currentPlugin)->isSelected()) {
- logMessage(DEBUG, "Plugin " + searchName + " is already deselected.");
- continue;
- }
- logMessage(DEBUG, "Toggle plugin " + searchName + " to deselected.");
- if (!(*currentPlugin)->isEnabled()) {
- logMessage(DEBUG, "Plugin " + searchName + " is not enabled.");
- continue;
- }
- togglePlugin(currentGroup, *currentPlugin, false);
- (*currentPlugin)->manuallySet = true; // To preserve this state when serializing JSON.
- }
- }
- }
-}
-#pragma endregion
diff --git a/libs/installer_fomod_plus/installer/ui/FomodViewModel.h b/libs/installer_fomod_plus/installer/ui/FomodViewModel.h
deleted file mode 100644
index f0ce070..0000000
--- a/libs/installer_fomod_plus/installer/ui/FomodViewModel.h
+++ /dev/null
@@ -1,154 +0,0 @@
-#pragma once
-
-#include <imoinfo.h>
-#include <string>
-
-#include "lib/ConditionTester.h"
-#include "lib/FlagMap.h"
-#include "lib/FileInstaller.h"
-#include "lib/ViewModels.h"
-#include "xml/FomodInfoFile.h"
-
-/*
---------------------------------------------------------------------------------
- Info
---------------------------------------------------------------------------------
-*/
-class InfoViewModel {
-public:
- explicit InfoViewModel(const std::unique_ptr<FomodInfoFile>& infoFile)
- {
- if (infoFile) {
- // Copy the necessary members from FomodInfoFile to InfoViewModel
- mName = infoFile->getName();
- mVersion = infoFile->getVersion();
- mAuthor = infoFile->getAuthor();
- mWebsite = infoFile->getWebsite();
- }
- }
-
- // Accessor methods
- [[nodiscard]] std::string getName() const { return mName; }
- [[nodiscard]] std::string getVersion() const { return mVersion; }
- [[nodiscard]] std::string getAuthor() const { return mAuthor; }
- [[nodiscard]] std::string getWebsite() const { return mWebsite; }
-
-private:
- std::string mName;
- std::string mVersion;
- std::string mAuthor;
- std::string mWebsite;
-};
-
-/*
---------------------------------------------------------------------------------
- View Model
---------------------------------------------------------------------------------
-*/
-class FomodViewModel {
-public:
- FomodViewModel(
- MOBase::IOrganizer* organizer,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- std::unique_ptr<FomodInfoFile> infoFile);
-
- static std::shared_ptr<FomodViewModel> create(
- MOBase::IOrganizer* organizer,
- std::unique_ptr<ModuleConfiguration> fomodFile,
- std::unique_ptr<FomodInfoFile> infoFile);
-
- void forEachGroup(const std::function<void(GroupRef)>& callback) const;
-
- void forEachPlugin(const std::function<void(GroupRef, PluginRef)>& callback) const;
-
- void forEachFuturePlugin(int fromStepIndex, const std::function<void(GroupRef, PluginRef)>& callback) const;
-
- void selectFromJson(nlohmann::json json) const;
-
- void resetToDefaults();
-
- [[nodiscard]] std::shared_ptr<PluginViewModel> getFirstPluginForActiveStep() const;
-
- // Steps
- [[nodiscard]] shared_ptr_list<StepViewModel> getSteps() const { return mSteps; }
- [[nodiscard]] StepRef getActiveStep() const { return mActiveStep; }
- [[nodiscard]] int getCurrentStepIndex() const { return mCurrentStepIndex; }
- [[deprecated]] void setCurrentStepIndex(const int index) { mCurrentStepIndex = index; }
-
- void updateVisibleSteps() const;
-
- void rebuildConditionFlagsForStep(int stepIndex) const;
-
- void preinstall(const std::shared_ptr<MOBase::IFileTree>& tree, const QString& fomodPath);
-
- std::shared_ptr<FileInstaller> getFileInstaller() { return mFileInstaller; }
-
- std::string getDisplayImage() const;
-
- // Plugins
- [[nodiscard]] PluginRef getActivePlugin() const { return mActivePlugin; }
-
- // Info
- [[nodiscard]] std::shared_ptr<InfoViewModel> getInfoViewModel() const { return mInfoViewModel; }
-
- // Interactions
- void stepBack();
-
- void stepForward();
-
- bool isLastVisibleStep() const;
-
- bool isFirstVisibleStep() const;
-
- bool togglePlugin(const GroupRef, const PluginRef, bool selected) const;
-
- bool ctrlTogglePlugin(const GroupRef, const PluginRef, bool selected) const;
-
- void setActivePlugin(const PluginRef plugin) const { mActivePlugin = plugin; }
-
- static void markManuallySet(PluginRef plugin);
-
-private:
- Logger& log = Logger::getInstance();
- MOBase::IOrganizer* mOrganizer = nullptr;
- std::unique_ptr<ModuleConfiguration> mFomodFile;
- std::unique_ptr<FomodInfoFile> mInfoFile;
- std::shared_ptr<FlagMap> mFlags{ nullptr };
- ConditionTester mConditionTester;
- std::shared_ptr<InfoViewModel> mInfoViewModel;
- std::vector<std::shared_ptr<StepViewModel> > mSteps;
- mutable std::shared_ptr<PluginViewModel> mActivePlugin{ nullptr };
- mutable std::shared_ptr<StepViewModel> mActiveStep{ nullptr };
- mutable std::vector<int> mVisibleStepIndices;
- std::shared_ptr<FileInstaller> mFileInstaller{ nullptr };
- bool mInitialized{ false };
-
- void createStepViewModels();
-
- void setFlagForPluginState(const std::shared_ptr<PluginViewModel>& plugin) const;
-
- static void createNonePluginForGroup(const std::shared_ptr<GroupViewModel>& group);
-
- void processPlugin(const std::shared_ptr<GroupViewModel>& group,
- const std::shared_ptr<PluginViewModel>& plugin) const;
-
- void enforceRadioGroupConstraints(const std::shared_ptr<GroupViewModel>& group) const;
-
- void enforceSelectAllConstraint(const std::shared_ptr<GroupViewModel>& groupViewModel) const;
-
- void enforceSelectAtLeastOneConstraint(const std::shared_ptr<GroupViewModel>& group) const;
-
- void enforceGroupConstraints() const;
-
- void processPluginConditions(int fromStepIndex) const;
-
- // Indices
- int mCurrentStepIndex{ 0 };
-
- void logMessage(const LogLevel level, const std::string& message) const
- {
- log.logMessage(level, "[VIEWMODEL] " + message);
- }
-
- std::string toString() const;
-};
diff --git a/libs/installer_fomod_plus/installer/ui/ScaleLabel.cpp b/libs/installer_fomod_plus/installer/ui/ScaleLabel.cpp
deleted file mode 100644
index 3a33640..0000000
--- a/libs/installer_fomod_plus/installer/ui/ScaleLabel.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-#include "ScaleLabel.h"
-#include <QResizeEvent>
-#include <iostream>
-
-// Taken from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/scalelabel.h
-static bool isResourceMovie(const QString& path)
-{
- const auto formats = QMovie::supportedFormats();
- return std::ranges::any_of(formats, [&path](const QByteArray& format) {
- return path.endsWith("." + QString::fromUtf8(format));
- });
-}
-
-void ScaleLabel::setScalableResource(const QString& path)
-{
- if (const auto m = movie()) {
- setMovie(nullptr);
- delete m;
- mOriginalMovieSize = QSize();
- }
- if (!pixmap().isNull()) {
- setPixmap(QPixmap());
- mUnscaledImage = QImage();
- }
-
- if (path.isEmpty()) {
- return;
- }
-
- if (isResourceMovie(path)) {
- setScalableMovie(path);
- } else {
- setScalableImage(path);
- }
-}
-
-void ScaleLabel::setStatic(const bool isStatic)
-{
- misStatic = isStatic;
-
- if (const auto m = movie()) {
- if (isStatic) {
- m->stop();
- } else {
- m->start();
- }
- }
-}
-
-void ScaleLabel::setScalableMovie(const QString& path)
-{
- const auto m = new QMovie(path);
- if (!m->isValid()) {
- qWarning(">%s< is an invalid movie. Reason: %s", qUtf8Printable(path),
- m->lastErrorString().toStdString().c_str());
- delete m;
- return;
- }
-
- m->setParent(this);
- setMovie(m);
- m->start();
- m->stop();
- mOriginalMovieSize = m->currentImage().size();
-
- m->setScaledSize(mOriginalMovieSize.scaled(size(), Qt::KeepAspectRatio));
- if (!misStatic) {
- m->start();
- }
- mHasResource = true;
-}
-
-void ScaleLabel::setScalableImage(const QString& path)
-{
- if (const QImage image(path); image.isNull()) {
- qWarning(">%s< is a null image", qUtf8Printable(path));
- } else {
- mUnscaledImage = image;
- setPixmap(QPixmap::fromImage(image).scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- mHasResource = true;
- }
-}
-
-void ScaleLabel::resizeEvent(QResizeEvent* event)
-{
- if (const auto m = movie()) {
- m->stop();
- m->setScaledSize(mOriginalMovieSize.scaled(event->size(), Qt::KeepAspectRatio));
- m->start();
-
- // We can't just skip the start() above since that is what triggers the label to
- // resize the movie The only way to resize the movie but keep it paused is to start
- // and then re-stop it
- if (misStatic) {
- m->stop();
- }
- }
- if (const auto p = pixmap(); !p.isNull()) {
- setPixmap(
- QPixmap::fromImage(mUnscaledImage).scaled(event->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- }
-}
-
-void ScaleLabel::showEvent(QShowEvent* event)
-{
- QLabel::showEvent(event);
-
- if (const auto m = movie()) {
- m->stop();
- m->setScaledSize(mOriginalMovieSize.scaled(size(), Qt::KeepAspectRatio));
- m->start();
-
- if (misStatic) {
- m->stop();
- }
- }
- if (const auto p = pixmap(); !p.isNull()) {
- setPixmap(QPixmap::fromImage(mUnscaledImage).scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
- }
-} \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/ui/ScaleLabel.h b/libs/installer_fomod_plus/installer/ui/ScaleLabel.h
deleted file mode 100644
index f4fb5f2..0000000
--- a/libs/installer_fomod_plus/installer/ui/ScaleLabel.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-// Taken from https://github.com/ModOrganizer2/modorganizer-installer_fomod/blob/master/src/scalelabel.h
-#include <QLabel>
-#include <QMouseEvent>
-#include <QMovie>
-
-
-class ScaleLabel final : public QLabel {
- Q_OBJECT
-
-public:
- explicit ScaleLabel(QWidget* parent = nullptr) : QLabel(parent)
- {
- setCursor(Qt::PointingHandCursor);
- }
-
- void setScalableResource(const QString& path);
- void setStatic(bool isStatic);
- [[nodiscard]] bool hasResource() const { return mHasResource; }
-
-signals:
- void clicked();
-
-protected:
- void mousePressEvent(QMouseEvent* event) override
- {
- if (event->button() == Qt::LeftButton) {
- emit clicked();
- }
- QLabel::mousePressEvent(event);
- }
-
- void resizeEvent(QResizeEvent* event) override;
- void showEvent(QShowEvent* event) override;
-
-private:
- void setScalableMovie(const QString& path);
- void setScalableImage(const QString& path);
-
- QImage mUnscaledImage;
- QSize mOriginalMovieSize;
- bool mHasResource = false;
- bool misStatic = false;
-}; \ No newline at end of file
diff --git a/libs/installer_fomod_plus/installer/ui/UIHelper.cpp b/libs/installer_fomod_plus/installer/ui/UIHelper.cpp
deleted file mode 100644
index 5f34e5d..0000000
--- a/libs/installer_fomod_plus/installer/ui/UIHelper.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#include "UIHelper.h"
-
-#include <qcoreevent.h>
-#include <QDir>
-#include <QMouseEvent>
-
-HoverEventFilter::HoverEventFilter(const std::shared_ptr<PluginViewModel>& plugin, QObject* parent)
- : QObject(parent), mPlugin(plugin) {}
-
-bool HoverEventFilter::eventFilter(QObject* obj, QEvent* event)
-{
- if (event->type() == QEvent::HoverEnter) {
- emit hovered(mPlugin);
- return true;
- }
- return QObject::eventFilter(obj, event);
-}
-
-CtrlClickEventFilter::CtrlClickEventFilter(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group, QObject* parent)
- : QObject(parent), mPlugin(plugin), mGroup(group) {}
-
-bool CtrlClickEventFilter::eventFilter(QObject* obj, QEvent* event)
-{
- if (event->type() == QEvent::MouseButtonPress) {
- const QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
- if (mouseEvent->button() == Qt::LeftButton &&
- mouseEvent->modifiers() & Qt::ControlModifier) {
- // TODO: Add Ctrl+click handler logic here
- // For now, just fall through to default behavior
- }
- }
- return QObject::eventFilter(obj, event);
-}
-
-QPushButton* UIHelper::createButton(const QString& text, QWidget* parent = nullptr)
-{
- const auto button = new QPushButton(text, parent);
- return button;
-}
-
-QLabel* UIHelper::createLabel(const QString& text, QWidget* parent = nullptr)
-{
- const auto label = new QLabel(text, parent);
- return label;
-}
-
-QLabel* UIHelper::createHyperlink(const QString& url, QWidget* parent = nullptr)
-{
- if (url.isEmpty() || !QUrl(url).isValid()) {
- return createLabel(url, parent);
- }
- const auto label = new QLabel(url, parent);
- const QString hyperlink = QString("<a href=\"%1\">%2</a>").arg(url, "Link");
- label->setText(hyperlink);
- label->setOpenExternalLinks(true);
- label->setTextFormat(Qt::RichText);
- return label;
-}
-
-QString UIHelper::getFullImagePath(const QString& fomodPath, const QString& imagePath)
-{
- // Fomod XMLs may use Windows backslashes in image paths (e.g. "fomod\MCM.png")
- QString normalized = imagePath;
- normalized.replace('\\', '/');
- return QDir::tempPath() + "/" + fomodPath + "/" + normalized;
-}
-
-void UIHelper::setGlobalAlignment(QBoxLayout* layout, const Qt::Alignment alignment)
-{
- for (int i = 0; i < layout->count(); ++i) {
- if (const QLayoutItem* item = layout->itemAt(i); item->widget()) {
- layout->setAlignment(item->widget(), alignment);
- }
- }
-}
-
-void UIHelper::setDebugBorders(QWidget* widget)
-{
- widget->setStyleSheet("border: 1px solid red;");
- for (auto* child : widget->findChildren<QWidget*>()) {
- child->setStyleSheet("border: 1px solid red;");
- }
-}
-
-void UIHelper::reduceLabelPadding(const QLayout* layout)
-{
- for (int i = 0; i < layout->count(); ++i) {
- const QLayoutItem* item = layout->itemAt(i);
- if (QWidget* widget = item->widget()) {
- if (const auto label = qobject_cast<QLabel*>(widget)) {
- label->setContentsMargins(0, 0, 0, 0);
- label->setStyleSheet("padding: 0px; margin: 0px;");
- }
- }
- }
-}
diff --git a/libs/installer_fomod_plus/installer/ui/UIHelper.h b/libs/installer_fomod_plus/installer/ui/UIHelper.h
deleted file mode 100644
index 1007e7c..0000000
--- a/libs/installer_fomod_plus/installer/ui/UIHelper.h
+++ /dev/null
@@ -1,80 +0,0 @@
-#pragma once
-
-#include <QPushButton>
-#include <QVBoxLayout>
-#include <QLabel>
-
-#include "FomodViewModel.h"
-
-class HoverEventFilter final : public QObject {
- Q_OBJECT
-
-public:
- explicit HoverEventFilter(const std::shared_ptr<PluginViewModel>& plugin, QObject* parent = nullptr);
-
-signals:
- void hovered(const std::shared_ptr<PluginViewModel>& plugin);
-
-protected:
- bool eventFilter(QObject* obj, QEvent* event) override;
-
-private:
- std::shared_ptr<PluginViewModel> mPlugin;
-};
-
-class CtrlClickEventFilter final : public QObject {
- Q_OBJECT
-
-public:
- explicit CtrlClickEventFilter(const std::shared_ptr<PluginViewModel>& plugin,
- const std::shared_ptr<GroupViewModel>& group, QObject* parent = nullptr);
-
-signals:
- void ctrlClicked(bool selected, const std::shared_ptr<GroupViewModel>& group,
- const std::shared_ptr<PluginViewModel>& plugin);
-
-protected:
- bool eventFilter(QObject* obj, QEvent* event) override;
-
-private:
- std::shared_ptr<PluginViewModel> mPlugin;
- std::shared_ptr<GroupViewModel> mGroup;
-};
-
-
-namespace UiConstants {
-constexpr int WINDOW_MIN_WIDTH = 900;
-constexpr int WINDOW_MIN_HEIGHT = 600;
-}
-
-class UIHelper {
-public:
- /*
- --------------------------------------------------------------------------------
- Widgets & Events
- --------------------------------------------------------------------------------
- */
- static QPushButton* createButton(const QString& text, QWidget* parent);
-
- static QLabel* createLabel(const QString& text, QWidget* parent);
-
- static QLabel* createHyperlink(const QString& url, QWidget* parent);
-
- /*
- --------------------------------------------------------------------------------
- Helpers
- --------------------------------------------------------------------------------
- */
- static QString getFullImagePath(const QString& fomodPath, const QString& imagePath);
-
- static void setGlobalAlignment(QBoxLayout* layout, Qt::Alignment alignment);
-
- static void reduceLabelPadding(const QLayout* layout);
-
- /*
- --------------------------------------------------------------------------------
- Development
- --------------------------------------------------------------------------------
- */
- static void setDebugBorders(QWidget* widget);
-}; \ No newline at end of file