#include "settings_menu.h" #include "choices.h" #include "palette.h" namespace roro { namespace { using Row = SettingsMenu::Row; using Kind = SettingsMenu::Kind; struct RowDef { Row row; Kind kind; const char* label; }; const RowDef kGroups[] = { {Row::GroupDevice, Kind::Group, "This device"}, {Row::GroupDisplay, Kind::Group, "Display"}, {Row::GroupPosition, Kind::Group, "GNSS and radio"}, {Row::GroupNetwork, Kind::Group, "Network"}, {Row::GroupSystem, Kind::Group, "System"}, }; constexpr int kGroupCount = sizeof(kGroups) / sizeof(kGroups[0]); struct Member { int group; // an index into kGroups RowDef def; }; // In the order each group lists them. const Member kRows[] = { {0, {Row::LongName, Kind::Text, "Long name"}}, {0, {Row::ShortName, Kind::Text, "Short name"}}, {0, {Row::Region, Kind::Choice, "Region"}}, {0, {Row::Timezone, Kind::Choice, "Timezone"}}, {0, {Row::Sound, Kind::Toggle, "Sound & LED"}}, {1, {Row::Brightness, Kind::Slider, "Brightness"}}, {1, {Row::DimTimeout, Kind::Choice, "Dim after"}}, {1, {Row::OffTimeout, Kind::Choice, "Screen off after"}}, {1, {Row::Theme, Kind::Choice, "Theme"}}, {1, {Row::ThemeLight, Kind::Toggle, "Light or dark"}}, {1, {Row::Launcher, Kind::Toggle, "Launcher"}}, {2, {Row::Gnss, Kind::Toggle, "GNSS"}}, {2, {Row::GnssQuiet, Kind::Toggle, "Pause GNSS for LoRa"}}, {2, {Row::Coordinates, Kind::Toggle, "Coordinates"}}, {2, {Row::ProbeMacs, Kind::Toggle, "Probe MACs"}}, {3, {Row::Wifi, Kind::Page, "Wi-Fi"}}, {3, {Row::Vpn, Kind::Page, "VPN"}}, {4, {Row::CheckUpdates, Kind::Toggle, "Check for updates"}}, {4, {Row::InstallUpdates, Kind::Toggle, "Install updates"}}, {4, {Row::Firmware, Kind::Page, "Firmware"}}, {4, {Row::DebugConsole, Kind::Page, "Debug Console"}}, {4, {Row::About, Kind::Page, "About"}}, }; // Row i of what is listed: a group, or the i-th row of the open one. const RowDef& at(int group, int i) { if (group < 0) return kGroups[i < 0 || i >= kGroupCount ? 0 : i]; for (const Member& m : kRows) if (m.group == group && i-- == 0) return m.def; return kGroups[group]; // out of range: harmless } const int kDimSeconds[] = {10, 15, 30, 60, 120, 300}; const int kOffSeconds[] = {30, 60, 120, 300, 600, 1800}; std::string formatSeconds(int s) { return s < 60 ? std::to_string(s) + " s" : std::to_string(s / 60) + " min"; } template std::vector labels(const Choice (&table)[N]) { std::vector out; for (auto& c : table) out.push_back(c.label); return out; } template std::vector durations(const int (&seconds)[N]) { std::vector out; for (int s : seconds) out.push_back(formatSeconds(s)); return out; } template int indexOf(const Choice (&table)[N], const std::string& value) { for (size_t i = 0; i < N; i++) if (value == table[i].value) return static_cast(i); return -1; } template int indexOf(const int (&table)[N], int value) { for (size_t i = 0; i < N; i++) if (table[i] == value) return static_cast(i); return -1; } } // namespace bool SettingsMenu::open(int i) { if (group_ >= 0 || i < 0 || i >= kGroupCount) return false; group_ = lastGroup_ = i; return true; } bool SettingsMenu::close() { if (group_ < 0) return false; group_ = -1; return true; } std::string SettingsMenu::title() const { return group_ < 0 ? "" : kGroups[group_].label; } bool SettingsMenu::reveal(Row r, int& index) { int seen[kGroupCount] = {}; for (const Member& m : kRows) { if (m.def.row == r) { group_ = lastGroup_ = m.group; index = seen[m.group]; return true; } seen[m.group]++; } return false; } int SettingsMenu::count() const { if (group_ < 0) return kGroupCount; int n = 0; for (const Member& m : kRows) n += m.group == group_; return n; } SettingsMenu::Row SettingsMenu::row(int i) const { return at(group_, i).row; } SettingsMenu::Kind SettingsMenu::kind(int i) const { return at(group_, i).kind; } std::string SettingsMenu::label(int i) const { return at(group_, i).label; } std::string SettingsMenu::value(int i) const { switch (row(i)) { case Row::LongName: return settings_.getString(Setting::LongName); case Row::ShortName: return settings_.getString(Setting::ShortName); case Row::Region: return settings_.getString(Setting::Region); case Row::Timezone: { int c = currentChoice(i); return c >= 0 ? kTimezones[c].label : "Custom"; } case Row::Brightness: return std::to_string(settings_.getInt(Setting::Brightness)) + "%"; case Row::DimTimeout: return formatSeconds(settings_.getInt(Setting::DimTimeoutS)); case Row::OffTimeout: return formatSeconds(settings_.getInt(Setting::OffTimeoutS)); case Row::Launcher: return settings_.getBool(Setting::LauncherList) ? "List" : "Grid"; case Row::Theme: return ui::themeName(settings_.getInt(Setting::Theme)); case Row::ThemeLight: return settings_.getBool(Setting::ThemeLight) ? "Light" : "Dark"; case Row::Sound: return settings_.getBool(Setting::Sound) ? "On" : "Off"; case Row::Gnss: return settings_.getBool(Setting::GnssEnabled) ? "On" : "Off"; case Row::GnssQuiet: return settings_.getBool(Setting::GnssQuietForLora) ? "On" : "Off"; case Row::CheckUpdates: return settings_.getBool(Setting::CheckUpdates) ? "Daily" : "Off"; case Row::InstallUpdates: return settings_.getBool(Setting::InstallUpdates) ? "By itself" : "When asked"; case Row::Coordinates: return settings_.getBool(Setting::CoordinatesDms) ? "Deg min sec" : "Decimal"; case Row::ProbeMacs: return settings_.getBool(Setting::ProbeMacRaw) ? "Raw" : "Pseudonymised"; case Row::Wifi: return settings_.getBool(Setting::WifiEnabled) ? "On" : "Off"; case Row::Vpn: return settings_.getString(Setting::VpnConfig).empty() ? "Not set" : settings_.getBool(Setting::VpnAuto) ? "With Wi-Fi" : "By hand"; case Row::DebugConsole: return settings_.getBool(Setting::DebugConsole) ? "On" : "Off"; default: return ""; } } std::vector SettingsMenu::choices(int i) const { switch (row(i)) { case Row::Region: return labels(kRegions); case Row::Timezone: return labels(kTimezones); case Row::Theme: { std::vector names; for (int t = 0; t < ui::kThemeCount; ++t) names.push_back(ui::themeName(t)); return names; } case Row::DimTimeout: return durations(kDimSeconds); case Row::OffTimeout: return durations(kOffSeconds); default: return {}; } } int SettingsMenu::currentChoice(int i) const { switch (row(i)) { case Row::Region: return indexOf(kRegions, settings_.getString(Setting::Region)); case Row::Timezone: return indexOf(kTimezones, settings_.getString(Setting::Timezone)); case Row::Theme: return settings_.getInt(Setting::Theme); case Row::DimTimeout: return indexOf(kDimSeconds, settings_.getInt(Setting::DimTimeoutS)); case Row::OffTimeout: return indexOf(kOffSeconds, settings_.getInt(Setting::OffTimeoutS)); default: return -1; } } std::string SettingsMenu::choose(int i, int c) { switch (row(i)) { case Row::Region: settings_.setString(Setting::Region, kRegions[c].value); settings_.setBool(Setting::RegionConfirmed, true); return ""; case Row::Timezone: settings_.setString(Setting::Timezone, kTimezones[c].value); return ""; case Row::Theme: settings_.setInt(Setting::Theme, c); return ""; case Row::DimTimeout: return settings_.setInt(Setting::DimTimeoutS, kDimSeconds[c]) ? "" : "Dimming must happen before screen off"; case Row::OffTimeout: return settings_.setInt(Setting::OffTimeoutS, kOffSeconds[c]) ? "" : "Screen off must come after dimming"; default: return "Not a choice"; } } void SettingsMenu::toggle(int i) { if (row(i) == Row::ThemeLight) settings_.setBool(Setting::ThemeLight, !settings_.getBool(Setting::ThemeLight)); if (row(i) == Row::Launcher) settings_.setBool(Setting::LauncherList, !settings_.getBool(Setting::LauncherList)); if (row(i) == Row::Sound) settings_.setBool(Setting::Sound, !settings_.getBool(Setting::Sound)); if (row(i) == Row::Gnss) settings_.setBool(Setting::GnssEnabled, !settings_.getBool(Setting::GnssEnabled)); if (row(i) == Row::GnssQuiet) settings_.setBool(Setting::GnssQuietForLora, !settings_.getBool(Setting::GnssQuietForLora)); if (row(i) == Row::CheckUpdates) settings_.setBool(Setting::CheckUpdates, !settings_.getBool(Setting::CheckUpdates)); if (row(i) == Row::InstallUpdates) settings_.setBool(Setting::InstallUpdates, !settings_.getBool(Setting::InstallUpdates)); if (row(i) == Row::Coordinates) settings_.setBool(Setting::CoordinatesDms, !settings_.getBool(Setting::CoordinatesDms)); if (row(i) == Row::ProbeMacs) settings_.setBool(Setting::ProbeMacRaw, !settings_.getBool(Setting::ProbeMacRaw)); } void SettingsMenu::adjust(int i, int direction) { if (row(i) != Row::Brightness) return; int next = settings_.getInt(Setting::Brightness) + (direction > 0 ? 10 : -10); if (next < 10) next = 10; if (next > 100) next = 100; settings_.setInt(Setting::Brightness, next); } int SettingsMenu::maxBytes(int i) const { return row(i) == Row::ShortName ? 4 : row(i) == Row::LongName ? 39 : 0; } std::string SettingsMenu::setText(int i, const std::string& text) { Setting s = row(i) == Row::ShortName ? Setting::ShortName : Setting::LongName; if (settings_.setString(s, text)) return ""; return "Must be 1 to " + std::to_string(maxBytes(i)) + " bytes"; } } // namespace roro