diff options
Diffstat (limited to 'libs/installer_bain/src')
| -rw-r--r-- | libs/installer_bain/src/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.cpp | 121 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.h | 94 | ||||
| -rw-r--r-- | libs/installer_bain/src/baincomplexinstallerdialog.ui | 126 | ||||
| -rw-r--r-- | libs/installer_bain/src/installer_bain_en.ts | 83 | ||||
| -rw-r--r-- | libs/installer_bain/src/installerbain.cpp | 222 | ||||
| -rw-r--r-- | libs/installer_bain/src/installerbain.h | 80 |
7 files changed, 739 insertions, 0 deletions
diff --git a/libs/installer_bain/src/CMakeLists.txt b/libs/installer_bain/src/CMakeLists.txt new file mode 100644 index 0000000..de57063 --- /dev/null +++ b/libs/installer_bain/src/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) + +file(GLOB installer_bain_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.h + ${CMAKE_CURRENT_SOURCE_DIR}/*.ui + ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc +) + +add_library(installer_bain SHARED ${installer_bain_SOURCES}) +mo2_configure_plugin(installer_bain NO_SOURCES WARNINGS OFF) +target_link_libraries(installer_bain PRIVATE mo2::uibase) +mo2_install_plugin(installer_bain) diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.cpp b/libs/installer_bain/src/baincomplexinstallerdialog.cpp new file mode 100644 index 0000000..2c25d76 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.cpp @@ -0,0 +1,121 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "ui_baincomplexinstallerdialog.h" + +#include <QCompleter> + +#include <uibase/textviewer.h> + +#include "baincomplexinstallerdialog.h" + +using namespace MOBase; + +BainComplexInstallerDialog::BainComplexInstallerDialog( + const std::vector<std::shared_ptr<const MOBase::FileTreeEntry>>& subpackages, + const GuessedValue<QString>& modName, const QStringList& defaultOptions, + const QString& packageTXT, QWidget* parent) + : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), + m_Manual(false), m_PackageTXT(packageTXT) +{ + ui->setupUi(this); + + for (auto iter = modName.variants().begin(); iter != modName.variants().end(); + ++iter) { + ui->nameCombo->addItem(*iter); + } + + ui->nameCombo->setCurrentIndex(ui->nameCombo->findText(modName)); + + for (const auto& subpackage : subpackages) { + + auto name = subpackage->name(); + + QListWidgetItem* item = new QListWidgetItem(name, ui->optionsList); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + + if (name.mid(0, 2) == "00" || defaultOptions.contains(name, Qt::CaseInsensitive)) { + item->setCheckState(Qt::Checked); + } else { + item->setCheckState(Qt::Unchecked); + } + + ui->optionsList->addItem(item); + } + + ui->packageBtn->setEnabled(!packageTXT.isEmpty()); + ui->nameCombo->completer()->setCaseSensitivity(Qt::CaseSensitive); +} + +BainComplexInstallerDialog::~BainComplexInstallerDialog() +{ + delete ui; +} + +QString BainComplexInstallerDialog::getName() const +{ + return ui->nameCombo->currentText(); +} + +QStringList BainComplexInstallerDialog::updateTree(std::shared_ptr<IFileTree>& tree) +{ + // Retrieve the list of selected names: + std::set<QString, FileNameComparator> selectedNames; + for (int i = 0; i < ui->optionsList->count(); ++i) { + QListWidgetItem* item = ui->optionsList->item(i); + if (item->checkState() == Qt::Checked) { + selectedNames.insert(item->text()); + } + } + + // Create a new empty tree and merge all the selected folder in it: + auto newTree = tree->createOrphanTree(); + for (auto& entry : *tree) { + if (entry->isDir() && selectedNames.count(entry->name()) > 0) { + newTree->merge(entry->astree()); + } + } + + tree = newTree; + + return QStringList(selectedNames.begin(), selectedNames.end()); +} + +void BainComplexInstallerDialog::on_okBtn_clicked() +{ + this->accept(); +} + +void BainComplexInstallerDialog::on_cancelBtn_clicked() +{ + this->reject(); +} + +void BainComplexInstallerDialog::on_manualBtn_clicked() +{ + m_Manual = true; + this->reject(); +} + +void BainComplexInstallerDialog::on_packageBtn_clicked() +{ + TextViewer viewer(m_PackageTXT, this); + viewer.setDescription(""); + viewer.addFile(m_PackageTXT, false); + viewer.exec(); +} diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.h b/libs/installer_bain/src/baincomplexinstallerdialog.h new file mode 100644 index 0000000..f657627 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.h @@ -0,0 +1,94 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef BAINCOMPLEXINSTALLERDIALOG_H +#define BAINCOMPLEXINSTALLERDIALOG_H + +#include <uibase/guessedvalue.h> +#include <uibase/ifiletree.h> +#include <uibase/tutorabledialog.h> + +namespace Ui +{ +class BainComplexInstallerDialog; +} + +/** + * @brief Dialog to choose from options offered by a (complex) bain package + **/ +class BainComplexInstallerDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + +public: + /** + * @brief Constructor + * + * @param tree the directory tree of the archive. The caller is resonsible to verify + *this actually qualifies as a bain installer + * @param modName proposed name for the mod. The dialog allows the user to change this + * @param defaultOptions The default options to select. Can contains options not + *actually present. + * @param packageTXT path to the extracted package.txt file or an empty string if + *there is none + * @param parent parent widget + **/ + explicit BainComplexInstallerDialog( + const std::vector<std::shared_ptr<const MOBase::FileTreeEntry>>& subpackages, + const MOBase::GuessedValue<QString>& modName, const QStringList& defaultOptions, + const QString& packageTXT, QWidget* parent); + ~BainComplexInstallerDialog(); + + /** + * @return bool true if the user requested the manual dialog + **/ + bool manualRequested() const { return m_Manual; } + + /** + * @return QString the (user-modified) name to be used for the mod + **/ + QString getName() const; + + /** + * @brief Remove from the given tree the option not selected by the user. + * + * @param tree The input tree. + * + * @return the list of options selected by the user. + **/ + QStringList updateTree(std::shared_ptr<MOBase::IFileTree>& tree); + +private slots: + + void on_manualBtn_clicked(); + + void on_okBtn_clicked(); + + void on_cancelBtn_clicked(); + + void on_packageBtn_clicked(); + +private: + Ui::BainComplexInstallerDialog* ui; + + bool m_Manual; + QString m_PackageTXT; +}; + +#endif // BAINCOMPLEXINSTALLERDIALOG_H diff --git a/libs/installer_bain/src/baincomplexinstallerdialog.ui b/libs/installer_bain/src/baincomplexinstallerdialog.ui new file mode 100644 index 0000000..0ab5214 --- /dev/null +++ b/libs/installer_bain/src/baincomplexinstallerdialog.ui @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BainComplexInstallerDialog</class> + <widget class="QDialog" name="BainComplexInstallerDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>424</width> + <height>365</height> + </rect> + </property> + <property name="windowTitle"> + <string>BAIN Package Installer</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,2"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Name</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="nameCombo"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>This looks like a Package prepared for Installation through BAIN. You can select options from the list below. If there is a package.txt file, it should contain detailed information about the options.</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="optionsList"> + <property name="toolTip"> + <string>Components of this package.</string> + </property> + <property name="whatsThis"> + <string>Components of this package. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author.</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="manualBtn"> + <property name="toolTip"> + <string>Opens a Dialog that allows custom modifications.</string> + </property> + <property name="whatsThis"> + <string>Opens a Dialog that allows custom modifications.</string> + </property> + <property name="text"> + <string>Manual</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="packageBtn"> + <property name="toolTip"> + <string>The package.txt is often part of BAIN packages and contains details about the options available.</string> + </property> + <property name="whatsThis"> + <string>The package.txt is often part of BAIN packages and contains details about the options available.</string> + </property> + <property name="text"> + <string notr="true">Package.txt</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="okBtn"> + <property name="text"> + <string>Ok</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelBtn"> + <property name="text"> + <string>Cancel</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/libs/installer_bain/src/installer_bain_en.ts b/libs/installer_bain/src/installer_bain_en.ts new file mode 100644 index 0000000..fe78e5d --- /dev/null +++ b/libs/installer_bain/src/installer_bain_en.ts @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="en_US"> +<context> + <name>BainComplexInstallerDialog</name> + <message> + <location filename="baincomplexinstallerdialog.ui" line="14"/> + <source>BAIN Package Installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="22"/> + <source>Name</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="38"/> + <source>This looks like a Package prepared for Installation through BAIN. You can select options from the list below. If there is a package.txt file, it should contain detailed information about the options.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="48"/> + <source>Components of this package.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="51"/> + <source>Components of this package. +If there is a component called "00 Core" it is usually required. Options are ordered by priority as set up by the author.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="61"/> + <location filename="baincomplexinstallerdialog.ui" line="64"/> + <source>Opens a Dialog that allows custom modifications.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="67"/> + <source>Manual</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="77"/> + <location filename="baincomplexinstallerdialog.ui" line="80"/> + <source>The package.txt is often part of BAIN packages and contains details about the options available.</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="106"/> + <source>Ok</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="baincomplexinstallerdialog.ui" line="113"/> + <source>Cancel</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>InstallerBAIN</name> + <message> + <location filename="installerbain.cpp" line="51"/> + <source>BAIN Installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="61"/> + <source>Installer for BAIN archives (originally targeting Wrye Bash)</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="173"/> + <source>May be BAIN installer</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="installerbain.cpp" line="174"/> + <source>This installer looks like it may contain a BAIN installer but I'm not sure. Install as BAIN installer?</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/installer_bain/src/installerbain.cpp b/libs/installer_bain/src/installerbain.cpp new file mode 100644 index 0000000..d39590e --- /dev/null +++ b/libs/installer_bain/src/installerbain.cpp @@ -0,0 +1,222 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "installerbain.h" + +#include <uibase/game_features/igamefeatures.h> +#include <uibase/game_features/moddatachecker.h> +#include <uibase/iinstallationmanager.h> +#include <uibase/iplugingame.h> +#include <uibase/log.h> + +#include <QDir> +#include <QMessageBox> +#include <QtPlugin> + +#include "baincomplexinstallerdialog.h" + +using namespace MOBase; + +InstallerBAIN::InstallerBAIN() : m_MOInfo(nullptr) {} + +bool InstallerBAIN::init(IOrganizer* moInfo) +{ + m_MOInfo = moInfo; + return true; +} + +QString InstallerBAIN::name() const +{ + return "BAIN Installer"; +} + +QString InstallerBAIN::localizedName() const +{ + return tr("BAIN Installer"); +} + +QString InstallerBAIN::author() const +{ + return "Tannin"; +} + +QString InstallerBAIN::description() const +{ + return tr("Installer for BAIN archives (originally targeting Wrye Bash)"); +} + +VersionInfo InstallerBAIN::version() const +{ + return VersionInfo(1, 1, 0, VersionInfo::RELEASE_FINAL); +} + +QList<PluginSetting> InstallerBAIN::settings() const +{ + return QList<PluginSetting>(); +} + +unsigned int InstallerBAIN::priority() const +{ + return 40; +} + +bool InstallerBAIN::isManualInstaller() const +{ + return false; +} + +void InstallerBAIN::onInstallationStart(QString const&, bool, IModInterface* currentMod) +{ + // We reset some field and fetch the previously installed options: + m_InstallerUsed = false; + m_PreviousOptions.clear(); + m_SelectedOptions.clear(); + + if (currentMod) { + auto settings = currentMod->pluginSettings(name()); + for (auto& [name, value] : settings) { + if (name.startsWith("option")) { + m_PreviousOptions.append(value.toString()); + } + } + } +} + +void InstallerBAIN::onInstallationEnd(EInstallResult result, IModInterface* newMod) +{ + if (result == EInstallResult::RESULT_SUCCESS && m_InstallerUsed) { + newMod->clearPluginSettings(name()); + for (auto i = 0; i < m_SelectedOptions.size(); ++i) { + newMod->setPluginSetting(name(), QString("option%1").arg(i), + m_SelectedOptions[i]); + } + } +} + +std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> +InstallerBAIN::findSubpackages(std::shared_ptr<const MOBase::IFileTree> tree, + std::size_t* invalidFolders) const +{ + // Folders that can be present but do not impact the installation: + static const std::set<QString, FileNameComparator> IGNORED_FOLDERS{ + "fomod", "omod conversion data", "images", "screenshots", "docs"}; + + auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>(); + + if (!checker) { + return {}; + } + + // each directory would have to serve as a top-level directory + std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> subpackages; + std::size_t ninvalids = 0; + for (auto entry : *tree) { + + if (!entry->isDir()) { + continue; + } + + // ignore fomod in case of combined fomod/bain packages. + // dirs starting with -- are supposed to be ignored + if (IGNORED_FOLDERS.contains(entry->name()) || entry->name().startsWith("--")) { + continue; + } + + if (checker->dataLooksValid(entry->astree()) == + ModDataChecker::CheckReturn::VALID) { + subpackages.push_back(entry); + } else { + ninvalids++; + } + } + + if (invalidFolders) { + *invalidFolders = ninvalids; + } + + return subpackages; +} + +bool InstallerBAIN::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const +{ + auto checker = m_MOInfo->gameFeatures()->gameFeature<ModDataChecker>(); + + if (!checker) { + return false; + } + + std::size_t numInvalidDirs = 0; + auto subpackages = findSubpackages(tree, &numInvalidDirs); + + if (subpackages.size() < 2) { + // a complex bain package contains at least 2 directories to choose from + return false; + } else if (numInvalidDirs == 0) { + return true; + } else { + return QMessageBox::question(parentWidget(), tr("May be BAIN installer"), + tr("This installer looks like it may contain a BAIN " + "installer but I'm not sure. " + "Install as BAIN installer?"), + QMessageBox::Yes | QMessageBox::No) == + QMessageBox::Yes; + } + + return true; +} + +IPluginInstaller::EInstallResult +InstallerBAIN::install(GuessedValue<QString>& modName, std::shared_ptr<IFileTree>& tree, + QString&, int&) +{ + auto entry = tree->find("package.txt", FileTreeEntry::FILE); + + QString packageTXT; + if (entry != nullptr) { + packageTXT = manager()->extractFile(entry); + } + + auto subpackages = findSubpackages(tree); + + BainComplexInstallerDialog dialog(subpackages, modName, m_PreviousOptions, packageTXT, + parentWidget()); + + int res = dialog.exec(); + + if (res == QDialog::Accepted) { + modName.update(dialog.getName(), GUESS_USER); + + // Update the tree: + m_SelectedOptions = dialog.updateTree(tree); + m_InstallerUsed = true; + + return IPluginInstaller::RESULT_SUCCESS; + } else { + if (dialog.manualRequested()) { + modName.update(dialog.getName(), GUESS_USER); + return IPluginInstaller::RESULT_MANUALREQUESTED; + } else { + return IPluginInstaller::RESULT_CANCELED; + } + } +} + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) +Q_EXPORT_PLUGIN2(installerBAIN, InstallerBAIN) +#endif diff --git a/libs/installer_bain/src/installerbain.h b/libs/installer_bain/src/installerbain.h new file mode 100644 index 0000000..535a31a --- /dev/null +++ b/libs/installer_bain/src/installerbain.h @@ -0,0 +1,80 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef INSTALLERBAIN_H +#define INSTALLERBAIN_H + +#include <uibase/iplugininstallersimple.h> + +class InstallerBAIN : public MOBase::IPluginInstallerSimple +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstaller MOBase::IPluginInstallerSimple) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "org.tannin.InstallerBAIN") +#endif + +public: + InstallerBAIN(); + + virtual bool init(MOBase::IOrganizer* moInfo) override; + virtual QString name() const override; + virtual QString localizedName() const override; + virtual QString author() const override; + virtual QString description() const override; + virtual MOBase::VersionInfo version() const override; + virtual QList<MOBase::PluginSetting> settings() const override; + + virtual unsigned int priority() const; + virtual bool isManualInstaller() const; + + virtual void onInstallationStart(QString const& archive, bool reinstallation, + MOBase::IModInterface* currentMod) override; + virtual void onInstallationEnd(EInstallResult result, + MOBase::IModInterface* newMod) override; + + virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const; + virtual EInstallResult install(MOBase::GuessedValue<QString>& modName, + std::shared_ptr<MOBase::IFileTree>& tree, + QString& version, int& modID); + +protected: + /** + * @brief Retrieve the entries corresponding to subpackages in the given tree. + * + * @param tree The tree to check for subpackages. + * @param invalidFolders If not null, will contain the number of invalid folders. + * + * @return the list of entries corresponding to subpackages. + */ + std::vector<std::shared_ptr<const MOBase::FileTreeEntry>> + findSubpackages(std::shared_ptr<const MOBase::IFileTree> tree, + std::size_t* invalidFolders = nullptr) const; + +private: + const MOBase::IOrganizer* m_MOInfo; + + // Indicates if the installer was used: + bool m_InstallerUsed; + + // List of options currently installed and being installed: + QStringList m_PreviousOptions, m_SelectedOptions; +}; + +#endif // INSTALLERBAIN_H |
