aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/installer/ui
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-06-17 01:46:09 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-06-17 01:46:09 -0500
commit05aa96d20ca726908a7d8b1943f86f0c4e11bf18 (patch)
treebd45913d1c7a033088f3456e40c8b44c72818ace /libs/installer_fomod_plus/installer/ui
parent30d2c801a01eeb140ddd5adf8676f2626ecff3fa (diff)
Prepare Nexus-safe release cleanup
Diffstat (limited to 'libs/installer_fomod_plus/installer/ui')
-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
10 files changed, 0 insertions, 1797 deletions
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