openpilot/frogpilot/ui/qt/offroad/maps_settings.cc
2025-11-01 12:00:00 -07:00

314 lines
12 KiB
C++

#include <regex>
#include <QtConcurrent>
#include "frogpilot/ui/qt/offroad/maps_settings.h"
FrogPilotMapsPanel::FrogPilotMapsPanel(FrogPilotSettingsWindow *parent) : FrogPilotListWidget(parent), parent(parent) {
QJsonObject shownDescriptions = QJsonDocument::fromJson(QString::fromStdString(params.get("ShownToggleDescriptions")).toUtf8()).object();
QString className = this->metaObject()->className();
if (!shownDescriptions.value(className).toBool(false)) {
forceOpenDescriptions = true;
shownDescriptions.insert(className, true);
params.put("ShownToggleDescriptions", QJsonDocument(shownDescriptions).toJson(QJsonDocument::Compact).toStdString());
}
QStackedLayout *mapsLayout = new QStackedLayout();
addItem(mapsLayout);
FrogPilotListWidget *settingsList = new FrogPilotListWidget(this);
std::vector<QString> scheduleOptions{tr("Manually"), tr("Weekly"), tr("Monthly")};
preferredSchedule = new ButtonParamControl("PreferredSchedule", tr("Automatically Update Maps"),
tr("<b>How often maps update</b> from \"OpenStreetMap (OSM)\" with the latest speed limit information. "
"Weekly updates run every Sunday; monthly updates run on the 1st."),
"",
scheduleOptions);
settingsList->addItem(preferredSchedule);
downloadMapsButton = new ButtonControl(tr("Download Maps"), tr("DOWNLOAD"), tr("<b>Manually update your selected map sources</b> so \"Speed Limit Controller\" has the latest speed limit information."));
QObject::connect(downloadMapsButton, &ButtonControl::clicked, [this] {
if (downloadMapsButton->text() == tr("CANCEL")) {
if (FrogPilotConfirmationDialog::yesorno(tr("Cancel the download?"), this)) {
cancelDownload();
}
} else {
startDownload();
}
});
settingsList->addItem(downloadMapsButton);
settingsList->addItem(lastMapsDownload = new LabelControl(tr("Last Updated"), params.get("LastMapsUpdate").empty() ? "Never" : QString::fromStdString(params.get("LastMapsUpdate"))));
selectMaps = new FrogPilotButtonsControl(tr("Map Sources"),
tr("<b>Select the countries or U.S. states to use with \"Speed Limit Controller\".</b>") ,
"", {tr("COUNTRIES"), tr("STATES")});
QObject::connect(selectMaps, &FrogPilotButtonsControl::buttonClicked, [mapsLayout, this](int id) {
mapsLayout->setCurrentIndex(id + 1);
openSubPanel();
});
settingsList->addItem(selectMaps);
settingsList->addItem(downloadStatus = new LabelControl(tr("Progress")));
settingsList->addItem(downloadTimeElapsed = new LabelControl(tr("Time Elapsed")));
settingsList->addItem(downloadETA = new LabelControl(tr("Time Remaining")));
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
removeMapsButton = new ButtonControl(tr("Remove Maps"), tr("REMOVE"), tr("<b>Delete downloaded map data</b> to free up storage space."));
QObject::connect(removeMapsButton, &ButtonControl::clicked, [this] {
if (FrogPilotConfirmationDialog::yesorno(tr("Delete all downloaded maps?"), this)) {
std::thread([this] {
mapsSize->setText(tr("0 MB"));
mapsFolderPath.removeRecursively();
}).detach();
}
});
settingsList->addItem(removeMapsButton);
resetMapdButton = new ButtonControl(tr("Reset Downloader"), tr("RESET"),
tr("<b>Reset the map downloader.</b> Use this if downloads are stuck or failing."));
QObject::connect(resetMapdButton, &ButtonControl::clicked, [parent, this]() {
if (ConfirmationDialog::confirm(tr("Reset the map downloader? Your device will reboot afterward."), tr("Reset"), this)) {
std::thread([parent, this]() {
parent->keepScreenOn = true;
resetMapdButton->setEnabled(false);
resetMapdButton->setValue(tr("Resetting..."));
std::system("pkill mapd");
QDir("/data/media/0/osm").removeRecursively();
resetMapdButton->setValue(tr("Reset!"));
util::sleep_for(2500);
resetMapdButton->setValue(tr("Rebooting..."));
util::sleep_for(2500);
Hardware::reboot();
}).detach();
}
});
settingsList->addItem(resetMapdButton);
settingsList->addItem(mapsSize = new LabelControl(tr("Storage Used"), calculateDirectorySize(mapsFolderPath)));
ScrollView *settingsPanel = new ScrollView(settingsList, this);
mapsLayout->addWidget(settingsPanel);
FrogPilotListWidget *countriesList = new FrogPilotListWidget(this);
std::vector<std::pair<QString, QMap<QString, QString>>> countries = {
{tr("Africa"), africaMap},
{tr("Antarctica"), antarcticaMap},
{tr("Asia"), asiaMap},
{tr("Europe"), europeMap},
{tr("North America"), northAmericaMap},
{tr("Oceania"), oceaniaMap},
{tr("South America"), southAmericaMap}
};
for (std::pair<QString, QMap<QString, QString>> country : countries) {
countriesList->addItem(new LabelControl(country.first, ""));
countriesList->addItem(new MapSelectionControl(country.second, true));
}
ScrollView *countryMapsPanel = new ScrollView(countriesList, this);
mapsLayout->addWidget(countryMapsPanel);
FrogPilotListWidget *statesList = new FrogPilotListWidget(this);
std::vector<std::pair<QString, QMap<QString, QString>>> states = {
{tr("United States - Midwest"), midwestMap},
{tr("United States - Northeast"), northeastMap},
{tr("United States - South"), southMap},
{tr("United States - West"), westMap},
{tr("United States - Territories"), territoriesMap}
};
for (std::pair<QString, QMap<QString, QString>> state : states) {
statesList->addItem(new LabelControl(state.first, ""));
statesList->addItem(new MapSelectionControl(state.second));
}
ScrollView *stateMapsPanel = new ScrollView(statesList, this);
mapsLayout->addWidget(stateMapsPanel);
QObject::connect(parent, &FrogPilotSettingsWindow::closeSubPanel, [mapsLayout, settingsPanel, this] {
if (forceOpenDescriptions) {
downloadMapsButton->showDescription();
preferredSchedule->showDescription();
removeMapsButton->showDescription();
resetMapdButton->showDescription();
selectMaps->showDescription();
}
std::string mapsSelected = params.get("MapsSelected");
hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty();
hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty();
mapsLayout->setCurrentWidget(settingsPanel);
});
QObject::connect(uiState(), &UIState::uiUpdate, this, &FrogPilotMapsPanel::updateState);
}
void FrogPilotMapsPanel::showEvent(QShowEvent *event) {
if (forceOpenDescriptions) {
downloadMapsButton->showDescription();
preferredSchedule->showDescription();
removeMapsButton->showDescription();
resetMapdButton->showDescription();
selectMaps->showDescription();
}
FrogPilotUIState &fs = *frogpilotUIState();
UIState &s = *uiState();
std::string mapsSelected = params.get("MapsSelected");
hasMapsSelected = !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("nations").toArray().isEmpty();
hasMapsSelected |= !QJsonDocument::fromJson(QByteArray::fromStdString(mapsSelected)).object().value("states").toArray().isEmpty();
bool parked = !s.scene.started || fs.frogpilot_scene.parked || fs.frogpilot_toggles.value("frogs_go_moo").toBool();
removeMapsButton->setVisible(mapsFolderPath.exists());
std::string osmDownloadProgress = params.get("OSMDownloadProgress");
if (!osmDownloadProgress.empty()) {
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadStatus->setVisible(true);
lastMapsDownload->setVisible(false);
removeMapsButton->setVisible(false);
resetMapdButton->setVisible(false);
updateDownloadLabels(osmDownloadProgress);
} else {
downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && fs.frogpilot_scene.online && parked);
downloadMapsButton->setValue(fs.frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline..."));
}
}
void FrogPilotMapsPanel::updateState(const UIState &s, const FrogPilotUIState &fs) {
if (!isVisible() || s.sm->frame % (UI_FREQ / 2) != 0) {
return;
}
bool parked = !s.scene.started || fs.frogpilot_scene.parked || fs.frogpilot_toggles.value("frogs_go_moo").toBool();
std::string osmDownloadProgress = params.get("OSMDownloadProgress");
if (!osmDownloadProgress.empty() && !cancellingDownload) {
updateDownloadLabels(osmDownloadProgress);
} else {
downloadMapsButton->setEnabled(!cancellingDownload && hasMapsSelected && fs.frogpilot_scene.online && parked);
downloadMapsButton->setValue(fs.frogpilot_scene.online ? (parked ? "" : tr("Not parked")) : tr("Offline..."));
}
parent->keepScreenOn = !osmDownloadProgress.empty();
}
void FrogPilotMapsPanel::cancelDownload() {
cancellingDownload = true;
downloadMapsButton->setEnabled(false);
downloadETA->setText(tr("Calculating..."));
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadTimeElapsed->setText(tr("Calculating..."));
params.remove("OSMDownloadProgress");
params_memory.remove("OSMDownloadLocations");
std::system("pkill mapd");
QTimer::singleShot(2500, [this]() {
cancellingDownload = false;
downloadMapsButton->setEnabled(true);
downloadMapsButton->setText(tr("DOWNLOAD"));
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
lastMapsDownload->setVisible(true);
removeMapsButton->setVisible(mapsFolderPath.exists());
resetMapdButton->setVisible(true);
update();
});
}
void FrogPilotMapsPanel::startDownload() {
downloadETA->setText(tr("Calculating..."));
downloadMapsButton->setText(tr("CANCEL"));
downloadStatus->setText(tr("Calculating..."));
downloadTimeElapsed->setText(tr("Calculating..."));
downloadETA->setVisible(true);
downloadStatus->setVisible(true);
downloadTimeElapsed->setVisible(true);
lastMapsDownload->setVisible(false);
removeMapsButton->setVisible(false);
resetMapdButton->setVisible(false);
elapsedTime.start();
startTime = QDateTime::currentDateTime();
params_memory.put("OSMDownloadLocations", params.get("MapsSelected"));
}
void FrogPilotMapsPanel::updateDownloadLabels(std::string &osmDownloadProgress) {
static std::regex fileStatusRegex(R"("total_files":(\d+),.*"downloaded_files":(\d+))");
std::smatch match;
if (std::regex_search(osmDownloadProgress, match, fileStatusRegex)) {
int totalFiles = std::stoi(match[1].str());
int downloadedFiles = std::stoi(match[2].str());
if (downloadedFiles == totalFiles) {
downloadMapsButton->setText(tr("DOWNLOAD"));
lastMapsDownload->setText(formatCurrentDate());
downloadETA->setVisible(false);
downloadStatus->setVisible(false);
downloadTimeElapsed->setVisible(false);
lastMapsDownload->setVisible(true);
removeMapsButton->setVisible(true);
resetMapdButton->setVisible(true);
params.put("LastMapsUpdate", formatCurrentDate().toStdString());
params.remove("OSMDownloadProgress");
update();
return;
}
static int previousDownloadedFiles = 0;
if (downloadedFiles != previousDownloadedFiles) {
std::thread([this]() {
mapsSize->setText(calculateDirectorySize(mapsFolderPath));
}).detach();
}
downloadETA->setText(QString("%1").arg(formatETA(elapsedTime.elapsed(), downloadedFiles, previousDownloadedFiles, totalFiles, startTime)));
downloadStatus->setText(QString("%1 / %2 (%3%)").arg(downloadedFiles).arg(totalFiles).arg((downloadedFiles * 100) / totalFiles));
downloadTimeElapsed->setText(formatElapsedTime(elapsedTime.elapsed()));
previousDownloadedFiles = downloadedFiles;
}
}