Replace the project-owned 2026 copyright owner in src headers from Switchroot to NiklasCFW while leaving vendored third-party notices unchanged.
266 lines
8.0 KiB
C++
266 lines
8.0 KiB
C++
/*
|
|
SWR INI Tool - Switchroot INI Configuration Editor
|
|
Copyright (C) 2026 NiklasCFW
|
|
*/
|
|
|
|
#include "os_config_tab.h"
|
|
#include "i18n.h"
|
|
#include "oc_defs.h"
|
|
|
|
#include <algorithm>
|
|
|
|
OsConfigTab::OsConfigTab(const std::string& osName, const std::string& iniPath)
|
|
: osName(osName), iniPath(iniPath)
|
|
{
|
|
if (!this->ini.load(iniPath))
|
|
{
|
|
buildErrorUI(I18n::errorListItemTitleIni(), I18n::iniLoadErrorBody(iniPath));
|
|
return;
|
|
}
|
|
|
|
this->osSection = this->ini.findOsSection();
|
|
if (this->osSection.empty())
|
|
{
|
|
buildErrorUI(I18n::errorListItemTitleIni(), I18n::iniSectionErrorBody(iniPath, osName));
|
|
return;
|
|
}
|
|
|
|
buildUI();
|
|
}
|
|
|
|
void OsConfigTab::reload(const std::string& newIniPath)
|
|
{
|
|
this->iniPath = newIniPath;
|
|
this->osSection.clear();
|
|
|
|
// Remove all child views
|
|
this->clear();
|
|
|
|
if (!this->ini.load(iniPath))
|
|
{
|
|
buildErrorUI(I18n::errorListItemTitleIni(), I18n::iniLoadErrorBody(iniPath));
|
|
return;
|
|
}
|
|
|
|
this->osSection = this->ini.findOsSection();
|
|
if (this->osSection.empty())
|
|
{
|
|
buildErrorUI(I18n::errorListItemTitleIni(), I18n::iniSectionErrorBody(iniPath, osName));
|
|
return;
|
|
}
|
|
|
|
buildUI();
|
|
}
|
|
|
|
void OsConfigTab::buildErrorUI(const std::string& title, const std::string& message)
|
|
{
|
|
this->setSpacing(15);
|
|
|
|
// Use a ListItem (focusable) so borealis doesn't crash on controller nav
|
|
brls::ListItem *errorItem = new brls::ListItem(title, message);
|
|
this->addView(errorItem);
|
|
}
|
|
|
|
void OsConfigTab::buildUI()
|
|
{
|
|
this->setSpacing(2);
|
|
this->setMarginBottom(20);
|
|
|
|
// Section name info
|
|
brls::Label *sectionInfo = new brls::Label(
|
|
brls::LabelStyle::DESCRIPTION,
|
|
I18n::sectionEditLine(this->osSection, this->iniPath),
|
|
true
|
|
);
|
|
this->addView(sectionInfo);
|
|
|
|
// ── Toggle Options ──
|
|
this->addView(new brls::Header(I18n::headerToggleOptions()));
|
|
|
|
for (const auto& def : OC_BOOL_KEYS)
|
|
{
|
|
bool val = this->ini.getBool(this->osSection, def.key, false);
|
|
addBooleanToggle(I18n::ocBoolLabel(def.key), "", def.key, val);
|
|
}
|
|
|
|
// ── Frequency Settings ──
|
|
brls::Rectangle* spacer1 = new brls::Rectangle(nvgRGBA(0, 0, 0, 0));
|
|
spacer1->setHeight(30);
|
|
this->addView(spacer1);
|
|
this->addView(new brls::Header(I18n::headerFrequencySettings()));
|
|
|
|
for (const auto& def : OC_FREQ_KEYS)
|
|
{
|
|
int defVal = def.options.empty() ? 0 : (int)def.options.front();
|
|
uint32_t val = (uint32_t)this->ini.getInt(this->osSection, def.key, defVal);
|
|
addFreqDropdown(I18n::ocFreqLabel(def.key), "", def.key, val, def.options);
|
|
}
|
|
|
|
// ── Voltage Settings ──
|
|
brls::Rectangle* spacer2 = new brls::Rectangle(nvgRGBA(0, 0, 0, 0));
|
|
spacer2->setHeight(30);
|
|
this->addView(spacer2);
|
|
this->addView(new brls::Header(I18n::headerVoltageSettings()));
|
|
|
|
for (const auto& def : OC_VOLTAGE_KEYS)
|
|
{
|
|
int defVal = def.options.empty() ? 0 : (int)def.options.front();
|
|
uint32_t val = (uint32_t)this->ini.getInt(this->osSection, def.key, defVal);
|
|
addVoltageDropdown(I18n::ocVoltageLabel(def.key), "", def.key, val, def.options);
|
|
}
|
|
|
|
// ── Other Keys (read-only info) ──
|
|
// Show any keys that aren't covered by the known definitions above
|
|
auto allKeys = this->ini.getKeys(this->osSection);
|
|
bool hasOtherKeys = false;
|
|
|
|
for (const auto& kv : allKeys)
|
|
{
|
|
bool isKnown = false;
|
|
|
|
for (const auto& def : OC_BOOL_KEYS)
|
|
if (def.key == kv.first) { isKnown = true; break; }
|
|
if (!isKnown)
|
|
for (const auto& def : OC_FREQ_KEYS)
|
|
if (def.key == kv.first) { isKnown = true; break; }
|
|
if (!isKnown)
|
|
for (const auto& def : OC_VOLTAGE_KEYS)
|
|
if (def.key == kv.first) { isKnown = true; break; }
|
|
|
|
if (!isKnown)
|
|
{
|
|
if (!hasOtherKeys)
|
|
{
|
|
brls::Rectangle* spacerOther = new brls::Rectangle(nvgRGBA(0, 0, 0, 0));
|
|
spacerOther->setHeight(30);
|
|
this->addView(spacerOther);
|
|
this->addView(new brls::Header(I18n::headerOther()));
|
|
hasOtherKeys = true;
|
|
}
|
|
|
|
brls::ListItem *item = new brls::ListItem(kv.first);
|
|
item->setValue(kv.second);
|
|
this->addView(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
void OsConfigTab::saveAndNotify()
|
|
{
|
|
if (this->ini.save())
|
|
{
|
|
brls::Application::notify(I18n::notifyConfigSaved());
|
|
}
|
|
else
|
|
{
|
|
brls::Application::notify(I18n::notifyConfigSaveError());
|
|
}
|
|
}
|
|
|
|
void OsConfigTab::addBooleanToggle(const std::string& label, const std::string& description,
|
|
const std::string& key, bool currentValue)
|
|
{
|
|
brls::ToggleListItem *toggle = new brls::ToggleListItem(
|
|
label, currentValue, description, I18n::wordOn(), I18n::wordOff()
|
|
);
|
|
|
|
std::string keyCopy = key;
|
|
toggle->getClickEvent()->subscribe([this, toggle, keyCopy](brls::View* view) {
|
|
bool newVal = toggle->getToggleState();
|
|
this->ini.setBool(this->osSection, keyCopy, newVal);
|
|
this->saveAndNotify();
|
|
});
|
|
|
|
this->addView(toggle);
|
|
}
|
|
|
|
void OsConfigTab::addFreqDropdown(const std::string& label, const std::string& description,
|
|
const std::string& key, uint32_t currentValue,
|
|
const std::vector<uint32_t>& options)
|
|
{
|
|
// Build option list, including the current value if not in the predefined list
|
|
std::vector<uint32_t> allOptions = options;
|
|
|
|
bool currentFound = false;
|
|
for (uint32_t opt : allOptions)
|
|
{
|
|
if (opt == currentValue) { currentFound = true; break; }
|
|
}
|
|
|
|
if (!currentFound && currentValue > 0)
|
|
{
|
|
allOptions.push_back(currentValue);
|
|
std::sort(allOptions.begin(), allOptions.end());
|
|
}
|
|
|
|
// Build display strings
|
|
std::vector<std::string> displayOptions;
|
|
size_t selectedIdx = 0;
|
|
|
|
for (size_t i = 0; i < allOptions.size(); i++)
|
|
{
|
|
displayOptions.push_back(formatFreqKHz(allOptions[i]));
|
|
if (allOptions[i] == currentValue)
|
|
selectedIdx = i;
|
|
}
|
|
|
|
brls::SelectListItem *item = new brls::SelectListItem(label, displayOptions, selectedIdx);
|
|
|
|
std::string keyCopy = key;
|
|
std::vector<uint32_t> optionsCopy = allOptions;
|
|
item->getValueSelectedEvent()->subscribe([this, keyCopy, optionsCopy](int result) {
|
|
if (result >= 0 && result < (int)optionsCopy.size())
|
|
{
|
|
this->ini.setInt(this->osSection, keyCopy, (int)optionsCopy[result]);
|
|
this->saveAndNotify();
|
|
}
|
|
});
|
|
|
|
this->addView(item);
|
|
}
|
|
|
|
void OsConfigTab::addVoltageDropdown(const std::string& label, const std::string& description,
|
|
const std::string& key, uint32_t currentValue,
|
|
const std::vector<uint32_t>& options)
|
|
{
|
|
// Build option list, including the current value if not in the predefined list
|
|
std::vector<uint32_t> allOptions = options;
|
|
|
|
bool currentFound = false;
|
|
for (uint32_t opt : allOptions)
|
|
{
|
|
if (opt == currentValue) { currentFound = true; break; }
|
|
}
|
|
|
|
if (!currentFound && currentValue > 0)
|
|
{
|
|
allOptions.push_back(currentValue);
|
|
std::sort(allOptions.begin(), allOptions.end());
|
|
}
|
|
|
|
// Build display strings
|
|
std::vector<std::string> displayOptions;
|
|
size_t selectedIdx = 0;
|
|
|
|
for (size_t i = 0; i < allOptions.size(); i++)
|
|
{
|
|
displayOptions.push_back(formatVoltageMv(allOptions[i]));
|
|
if (allOptions[i] == currentValue)
|
|
selectedIdx = i;
|
|
}
|
|
|
|
brls::SelectListItem *item = new brls::SelectListItem(label, displayOptions, selectedIdx);
|
|
|
|
std::string keyCopy = key;
|
|
std::vector<uint32_t> optionsCopy = allOptions;
|
|
item->getValueSelectedEvent()->subscribe([this, keyCopy, optionsCopy](int result) {
|
|
if (result >= 0 && result < (int)optionsCopy.size())
|
|
{
|
|
this->ini.setInt(this->osSection, keyCopy, (int)optionsCopy[result]);
|
|
this->saveAndNotify();
|
|
}
|
|
});
|
|
|
|
this->addView(item);
|
|
}
|