diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 102f0ce493..d91db27ebd 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -74,7 +74,7 @@ else: qt_env.Library("qt_widgets", ["qt/qt_window.cc", "qt/qt_sound.cc", "qt/widgets/keyboard.cc", "qt/widgets/input_field.cc", - "qt/offroad/wifi.cc", "qt/offroad/wifiManager.cc", "qt/widgets/toggle.cc"], + "qt/offroad/wifi.cc", "qt/offroad/wifiManager.cc", "qt/widgets/toggle.cc", "qt/widgets/offroad_alerts.cc"], LIBS=qt_libs) qt_libs.append("qt_widgets") diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 8d5424df08..c836c6067c 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -13,6 +13,7 @@ #include "wifi.hpp" #include "settings.hpp" #include "widgets/toggle.hpp" +#include "widgets/offroad_alerts.hpp" #include "common/params.h" #include "common/utilpp.h" @@ -224,7 +225,6 @@ void SettingsWindow::setActivePanel() { } SettingsWindow::SettingsWindow(QWidget *parent) : QWidget(parent) { - // sidebar QVBoxLayout *sidebar_layout = new QVBoxLayout(); panel_layout = new QStackedLayout(); @@ -241,12 +241,21 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QWidget(parent) { sidebar_layout->addWidget(close_button); QObject::connect(close_button, SIGNAL(released()), this, SIGNAL(closeSettings())); + // offroad alerts + alerts_widget = new OffroadAlert(); + QObject::connect(alerts_widget, SIGNAL(closeAlerts()), this, SLOT(closeAlerts())); + panel_layout->addWidget(alerts_widget); + + sidebar_alert_widget = new QPushButton("");//Should get text when it is visible + QObject::connect(sidebar_alert_widget, SIGNAL(released()), this, SLOT(openAlerts())); + sidebar_layout->addWidget(sidebar_alert_widget); + // setup panels panels = { - {"device", device_panel()}, - {"toggles", toggles_panel()}, {"developer", developer_panel()}, + {"device", device_panel()}, {"network", network_panel(this)}, + {"toggles", toggles_panel()}, }; for (auto &panel : panels) { @@ -267,10 +276,14 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QWidget(parent) { panel_layout->addWidget(panel.second); QObject::connect(btn, SIGNAL(released()), this, SLOT(setActivePanel())); } + + // We either show the alerts, or the developer panel + if (alerts_widget->show_alert){ + panel_layout->setCurrentWidget(alerts_widget); + } + QHBoxLayout *settings_layout = new QHBoxLayout(); settings_layout->addSpacing(45); - - // settings_layout->addLayout(sidebar_layout); sidebar_widget = new QWidget; sidebar_widget->setLayout(sidebar_layout); sidebar_widget->setFixedWidth(SIDEBAR_WIDTH); @@ -280,7 +293,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QWidget(parent) { settings_layout->addLayout(panel_layout); settings_layout->addSpacing(45); setLayout(settings_layout); - + setStyleSheet(R"( * { color: white; @@ -289,6 +302,51 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QWidget(parent) { )"); } +// Refreshes the offroad alerts from the params folder and sets up the sidebar alerts widget. +// The function gets called every time a user opens the settings page +void SettingsWindow::refreshParams(){ + alerts_widget->refresh(); + if (!alerts_widget->show_alert){ + sidebar_alert_widget->setFixedHeight(0); + panel_layout->setCurrentIndex(1); + return; + } + + // Panel 0 contains the alerts or release notes. + panel_layout->setCurrentIndex(0); + sidebar_alert_widget->setFixedHeight(100); + sidebar_alert_widget->setStyleSheet(R"( + background-color: #114267; + )"); // light blue + + // Check for alerts + int alerts = alerts_widget->alerts.size(); + if (!alerts){ + //There is a new release + sidebar_alert_widget->setText("UPDATE"); + return; + } + //Check if there is an important alert + bool existsImportantAlert = false; + for (auto alert : alerts_widget->alerts){ + if (alert.severity){ + existsImportantAlert = true; + } + } + + sidebar_alert_widget->setText(QString::number(alerts) + " ALERT" + (alerts == 1 ? "" : "S")); + if (existsImportantAlert){ + sidebar_alert_widget->setStyleSheet(R"( + background-color: #661111; + )"); //dark red + } +} +void SettingsWindow::closeAlerts(){ + panel_layout->setCurrentIndex(1); +} +void SettingsWindow::openAlerts(){ + panel_layout->setCurrentIndex(0); +} void SettingsWindow::closeSidebar(){ sidebar_widget->setFixedWidth(0); } diff --git a/selfdrive/ui/qt/offroad/settings.hpp b/selfdrive/ui/qt/offroad/settings.hpp index 81d127aea1..a34f6bec75 100644 --- a/selfdrive/ui/qt/offroad/settings.hpp +++ b/selfdrive/ui/qt/offroad/settings.hpp @@ -6,9 +6,10 @@ #include #include #include +#include #include "wifi.hpp" - +#include "widgets/offroad_alerts.hpp" class ParamsToggle : public QFrame { Q_OBJECT @@ -29,17 +30,23 @@ class SettingsWindow : public QWidget { public: explicit SettingsWindow(QWidget *parent = 0); + void refreshParams(); signals: void closeSettings(); private: + QPushButton *sidebar_alert_widget; QWidget *sidebar_widget; + OffroadAlert *alerts_widget; std::map panels; QStackedLayout *panel_layout; -private slots: + +public slots: void setActivePanel(); + void closeAlerts(); + void openAlerts(); void closeSidebar(); void openSidebar(); }; diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.cc b/selfdrive/ui/qt/widgets/offroad_alerts.cc new file mode 100644 index 0000000000..925c6fcf2f --- /dev/null +++ b/selfdrive/ui/qt/widgets/offroad_alerts.cc @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include + +#include "offroad_alerts.hpp" + +#include "common/params.h" + + +void cleanLayout(QLayout* layout) { + while (QLayoutItem* item = layout->takeAt(0)) { + if (QWidget* widget = item->widget()){ + widget->deleteLater(); + } + if (QLayout* childLayout = item->layout()) { + cleanLayout(childLayout); + } + delete item; + } +} + +QString vectorToQString(std::vector v){ + return QString::fromStdString(std::string(v.begin(), v.end())); +} + +OffroadAlert::OffroadAlert(QWidget* parent){ + vlayout = new QVBoxLayout; + refresh(); + setLayout(vlayout); +} + +void OffroadAlert::refresh(){ + cleanLayout(vlayout); + parse_alerts(); + + bool updateAvailable = false; + std::vector bytes = Params().read_db_bytes("UpdateAvailable"); + if (bytes.size() && bytes[0] == '1'){ + updateAvailable = true; + } + show_alert = updateAvailable || alerts.size() ; + + if (updateAvailable){ + //If there is update available, don't show alerts + alerts.clear(); + + QFrame *f = new QFrame(); + + QVBoxLayout *update_layout = new QVBoxLayout; + update_layout->addWidget(new QLabel("Update available")); + + std::vector release_notes_bytes = Params().read_db_bytes("ReleaseNotes"); + QString releaseNotes = vectorToQString(release_notes_bytes); + QLabel *notes_label = new QLabel(releaseNotes); + notes_label->setWordWrap(true); + update_layout->addSpacing(20); + update_layout->addWidget(notes_label); + update_layout->addSpacing(20); + + QPushButton *update_button = new QPushButton("Reboot and Update"); + update_layout->addWidget(update_button); + update_layout->setMargin(10); +#ifdef __aarch64__ + QObject::connect(update_button, &QPushButton::released,[=]() {std::system("sudo reboot");}); +#endif + + f->setLayout(update_layout); + f->setStyleSheet(R"( + .QFrame{ + border-radius: 30px; + border: 2px solid white; + background-color: #114267; + } + QLabel{ + font-size: 60px; + background-color: #114267; + } + )"); + + vlayout->addWidget(f); + vlayout->addSpacing(60); + }else{ + vlayout->addSpacing(60); + + for (auto alert : alerts){ + QLabel *l = new QLabel(alert.text); + l->setWordWrap(true); + l->setMargin(60); + + if (alert.severity){ + l->setStyleSheet(R"( + QLabel { + font-size: 40px; + font-weight: bold; + border-radius: 30px; + background-color: #971b1c; + border-style: solid; + border-width: 2px; + border-color: white; + } + )");//red rounded rectange with white surround + }else{ + l->setStyleSheet(R"( + QLabel { + font-size: 40px; + font-weight: bold; + border-radius: 30px; + background-color: #114267; + border-style: solid; + border-width: 2px; + border-color: white; + } + )");//blue rounded rectange with white surround + } + + vlayout->addWidget(l); + vlayout->addSpacing(20); + } + + //Pad the vlayout + for (int i = alerts.size(); i < 4; i++){ + QWidget *w = new QWidget(); + vlayout->addWidget(w); + vlayout->addSpacing(50); + } + } + + QPushButton *hide_alerts_button = new QPushButton(updateAvailable ? "Later" : "Hide alerts"); + vlayout->addWidget(hide_alerts_button); + QObject::connect(hide_alerts_button, SIGNAL(released()), this, SIGNAL(closeAlerts())); +} + +void OffroadAlert::parse_alerts(){ + alerts.clear(); + //We launch in selfdrive/ui + QFile inFile("../controls/lib/alerts_offroad.json"); + inFile.open(QIODevice::ReadOnly | QIODevice::Text); + QByteArray data = inFile.readAll(); + inFile.close(); + + QJsonDocument doc = QJsonDocument::fromJson(data); + if (doc.isNull()) { + qDebug() << "Parse failed"; + } + + QJsonObject json = doc.object(); + for (const QString& key : json.keys()) { + std::vector bytes = Params().read_db_bytes(key.toStdString().c_str()); + + if (bytes.size()){ + QJsonDocument doc_par = QJsonDocument::fromJson(QByteArray(bytes.data(), bytes.size())); + QJsonObject obj = doc_par.object(); + Alert alert = {obj.value("text").toString(), obj.value("severity").toInt()}; + alerts.push_back(alert); + } + } +} diff --git a/selfdrive/ui/qt/widgets/offroad_alerts.hpp b/selfdrive/ui/qt/widgets/offroad_alerts.hpp new file mode 100644 index 0000000000..e7347c4f41 --- /dev/null +++ b/selfdrive/ui/qt/widgets/offroad_alerts.hpp @@ -0,0 +1,34 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include + +struct Alert{ + QString text; + int severity; +}; + +class OffroadAlert : public QWidget{ + Q_OBJECT + +public: + explicit OffroadAlert(QWidget *parent = 0); + bool show_alert; + QVector alerts; + +private: + QVBoxLayout *vlayout; + + void parse_alerts(); + +signals: + void closeAlerts(); + +public slots: + void refresh(); +}; diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 4a47e70c4a..c3b1993ea9 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -79,6 +79,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { void MainWindow::openSettings() { main_layout->setCurrentIndex(1); + settingsWindow->refreshParams(); } void MainWindow::closeSettings() {