diff options
Diffstat (limited to 'libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp')
| -rw-r--r-- | libs/installer_fomod_plus/installer/FomodInstallerWindow.cpp | 842 |
1 files changed, 0 insertions, 842 deletions
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); -} |
