OffroadHome: separate the update and alert buttons (#21326)

* refactor

* better loop

* remove stretch

* only refreshed by timer

* fix wrong bg color

* descending sort alert labels  by severity

* remove duplicate stylesheet

* Update selfdrive/ui/qt/widgets/offroad_alerts.cc

* rebase master

* qt best pratice

* split offroadAlerts

* only show first time

* short variable name

* space

* don't do refresh in every showEvent

* Revert "don't do refresh in every showEvent"

This reverts commit 2b0032fb0af7138e9d9df1ad052c589fd96d2e05.

* cleanup

rename name to key

cleanup

* add stretch back

* hide alert widgets when not alerts

* rebase master

* fix flash on startup

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
pull/21528/head
Dean Lee 4 years ago committed by GitHub
parent cb7e96f99d
commit e836ed7f6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 108
      selfdrive/ui/qt/home.cc
  2. 15
      selfdrive/ui/qt/home.h
  3. 110
      selfdrive/ui/qt/widgets/offroad_alerts.cc
  4. 44
      selfdrive/ui/qt/widgets/offroad_alerts.h

@ -6,9 +6,6 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include "selfdrive/common/params.h" #include "selfdrive/common/params.h"
#include "selfdrive/common/swaglog.h"
#include "selfdrive/common/timing.h"
#include "selfdrive/common/util.h"
#include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/util.h"
#include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/drive_stats.h"
#include "selfdrive/ui/qt/widgets/setup.h" #include "selfdrive/ui/qt/widgets/setup.h"
@ -94,18 +91,24 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
// top header // top header
QHBoxLayout* header_layout = new QHBoxLayout(); QHBoxLayout* header_layout = new QHBoxLayout();
header_layout->setSpacing(16);
date = new QLabel(); date = new QLabel();
header_layout->addWidget(date, 0, Qt::AlignHCenter | Qt::AlignLeft); header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft);
alert_notification = new QPushButton(); update_notif = new QPushButton("UPDATE");
alert_notification->setObjectName("alert_notification"); update_notif->setVisible(false);
alert_notification->setVisible(false); update_notif->setStyleSheet("background-color: #364DEF;");
QObject::connect(alert_notification, &QPushButton::released, this, &OffroadHome::openAlerts); QObject::connect(update_notif, &QPushButton::released, [=]() { center_layout->setCurrentIndex(1); });
header_layout->addWidget(alert_notification, 0, Qt::AlignHCenter | Qt::AlignRight); header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignRight);
QLabel* version = new QLabel(getBrandVersion()); alert_notif = new QPushButton();
header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); alert_notif->setVisible(false);
alert_notif->setStyleSheet("background-color: #E22C2C;");
QObject::connect(alert_notif, &QPushButton::released, [=] { center_layout->setCurrentIndex(2); });
header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignRight);
header_layout->addWidget(new QLabel(getBrandVersion()), 0, Qt::AlignHCenter | Qt::AlignRight);
main_layout->addLayout(header_layout); main_layout->addLayout(header_layout);
@ -113,32 +116,29 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
main_layout->addSpacing(25); main_layout->addSpacing(25);
center_layout = new QStackedLayout(); center_layout = new QStackedLayout();
QHBoxLayout* statsAndSetup = new QHBoxLayout(); QWidget* statsAndSetupWidget = new QWidget(this);
QHBoxLayout* statsAndSetup = new QHBoxLayout(statsAndSetupWidget);
statsAndSetup->setMargin(0); statsAndSetup->setMargin(0);
DriveStats* drive = new DriveStats();
DriveStats* drive = new DriveStats;
drive->setFixedSize(800, 800); drive->setFixedSize(800, 800);
statsAndSetup->addWidget(drive); statsAndSetup->addWidget(drive);
statsAndSetup->addWidget(new SetupWidget);
SetupWidget* setup = new SetupWidget;
statsAndSetup->addWidget(setup);
QWidget* statsAndSetupWidget = new QWidget();
statsAndSetupWidget->setLayout(statsAndSetup);
center_layout->addWidget(statsAndSetupWidget); center_layout->addWidget(statsAndSetupWidget);
// add update & alerts widgets
update_widget = new UpdateAlert();
QObject::connect(update_widget, &UpdateAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(update_widget);
alerts_widget = new OffroadAlert(); alerts_widget = new OffroadAlert();
QObject::connect(alerts_widget, &OffroadAlert::closeAlerts, this, &OffroadHome::closeAlerts); QObject::connect(alerts_widget, &OffroadAlert::dismiss, [=]() { center_layout->setCurrentIndex(0); });
center_layout->addWidget(alerts_widget); center_layout->addWidget(alerts_widget);
center_layout->setAlignment(alerts_widget, Qt::AlignCenter);
main_layout->addLayout(center_layout, 1); main_layout->addLayout(center_layout, 1);
// set up refresh timer // set up refresh timer
timer = new QTimer(this); timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, this, &OffroadHome::refresh); timer->callOnTimeout(this, &OffroadHome::refresh);
timer->start(10 * 1000);
setStyleSheet(R"( setStyleSheet(R"(
* { * {
@ -147,61 +147,47 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
OffroadHome { OffroadHome {
background-color: black; background-color: black;
} }
#alert_notification { OffroadHome > QPushButton {
padding: 15px; padding: 15px 30px;
padding-left: 30px;
padding-right: 30px;
border: 1px solid;
border-radius: 5px; border-radius: 5px;
font-size: 40px; font-size: 40px;
font-weight: 500; font-weight: 500;
} }
OffroadHome>QLabel { OffroadHome > QLabel {
font-size: 55px; font-size: 55px;
} }
)"); )");
}
void OffroadHome::showEvent(QShowEvent *event) {
refresh(); refresh();
} }
void OffroadHome::openAlerts() { void OffroadHome::showEvent(QShowEvent *event) {
center_layout->setCurrentIndex(1); timer->start(10 * 1000);
} }
void OffroadHome::closeAlerts() { void OffroadHome::hideEvent(QHideEvent *event) {
center_layout->setCurrentIndex(0); timer->stop();
} }
void OffroadHome::refresh() { void OffroadHome::refresh() {
bool first_refresh = !date->text().size();
if (!isVisible() && !first_refresh) {
return;
}
date->setText(QDateTime::currentDateTime().toString("dddd, MMMM d")); date->setText(QDateTime::currentDateTime().toString("dddd, MMMM d"));
// update alerts bool updateAvailable = update_widget->refresh();
int alerts = alerts_widget->refresh();
alerts_widget->refresh();
if (!alerts_widget->alertCount && !alerts_widget->updateAvailable) { // pop-up new notification
closeAlerts(); int idx = center_layout->currentIndex();
alert_notification->setVisible(false); if (!updateAvailable && !alerts) {
return; idx = 0;
} } else if (updateAvailable && (!update_notif->isVisible() || (!alerts && idx == 2))) {
idx = 1;
if (alerts_widget->updateAvailable) { } else if (alerts && (!alert_notif->isVisible() || (!updateAvailable && idx == 1))) {
alert_notification->setText("UPDATE"); idx = 2;
} else {
int alerts = alerts_widget->alertCount;
alert_notification->setText(QString::number(alerts) + " ALERT" + (alerts == 1 ? "" : "S"));
} }
center_layout->setCurrentIndex(idx);
if (!alert_notification->isVisible() && !first_refresh) { update_notif->setVisible(updateAvailable);
openAlerts(); alert_notif->setVisible(alerts);
if (alerts) {
alert_notif->setText(QString::number(alerts) + " ALERT" + (alerts > 1 ? "S" : ""));
} }
alert_notification->setVisible(true);
// Red background for alerts, blue for update available
alert_notification->setStyleSheet(alerts_widget->updateAvailable ? "background-color: #364DEF" : "background-color: #E22C2C");
} }

@ -19,21 +19,18 @@ class OffroadHome : public QFrame {
public: public:
explicit OffroadHome(QWidget* parent = 0); explicit OffroadHome(QWidget* parent = 0);
protected: private:
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void hideEvent(QHideEvent *event) override;
void refresh();
private:
QTimer* timer; QTimer* timer;
QLabel* date; QLabel* date;
QStackedLayout* center_layout; QStackedLayout* center_layout;
UpdateAlert *update_widget;
OffroadAlert* alerts_widget; OffroadAlert* alerts_widget;
QPushButton* alert_notification; QPushButton* alert_notif;
QPushButton* update_notif;
public slots:
void closeAlerts();
void openAlerts();
void refresh();
}; };
class HomeWindow : public QWidget { class HomeWindow : public QWidget {

@ -3,33 +3,21 @@
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QVBoxLayout> #include <QPushButton>
#include "selfdrive/common/util.h" #include "selfdrive/common/util.h"
#include "selfdrive/hardware/hw.h" #include "selfdrive/hardware/hw.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
OffroadAlert::OffroadAlert(QWidget* parent) : QFrame(parent) { AbstractAlert::AbstractAlert(bool hasRebootBtn, QWidget *parent) : QFrame(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this); QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setMargin(50); main_layout->setMargin(50);
main_layout->setSpacing(30); main_layout->setSpacing(30);
QWidget *alerts_widget = new QWidget(this); QWidget *widget = new QWidget;
alerts_layout = new QVBoxLayout(alerts_widget); scrollable_layout = new QVBoxLayout(widget);
alerts_layout->setMargin(0); widget->setStyleSheet("background-color: transparent;");
alerts_layout->setSpacing(30); main_layout->addWidget(new ScrollView(widget));
alerts_widget->setStyleSheet("background-color: transparent;");
// release notes
releaseNotes.setWordWrap(true);
releaseNotes.setVisible(false);
releaseNotes.setStyleSheet("font-size: 48px;");
releaseNotes.setAlignment(Qt::AlignTop);
releaseNotesScroll = new ScrollView(&releaseNotes, this);
main_layout->addWidget(releaseNotesScroll);
alertsScroll = new ScrollView(alerts_widget, this);
main_layout->addWidget(alertsScroll);
// bottom footer, dismiss + reboot buttons // bottom footer, dismiss + reboot buttons
QHBoxLayout *footer_layout = new QHBoxLayout(); QHBoxLayout *footer_layout = new QHBoxLayout();
@ -38,14 +26,14 @@ OffroadAlert::OffroadAlert(QWidget* parent) : QFrame(parent) {
QPushButton *dismiss_btn = new QPushButton("Dismiss"); QPushButton *dismiss_btn = new QPushButton("Dismiss");
dismiss_btn->setFixedSize(400, 125); dismiss_btn->setFixedSize(400, 125);
footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft); footer_layout->addWidget(dismiss_btn, 0, Qt::AlignBottom | Qt::AlignLeft);
QObject::connect(dismiss_btn, &QPushButton::released, this, &OffroadAlert::closeAlerts); QObject::connect(dismiss_btn, &QPushButton::released, this, &AbstractAlert::dismiss);
rebootBtn.setText("Reboot and Update");
rebootBtn.setFixedSize(600, 125);
rebootBtn.setVisible(false);
footer_layout->addWidget(&rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(&rebootBtn, &QPushButton::released, [=]() { Hardware::reboot(); });
if (hasRebootBtn) {
QPushButton *rebootBtn = new QPushButton("Reboot and Update");
rebootBtn->setFixedSize(600, 125);
footer_layout->addWidget(rebootBtn, 0, Qt::AlignBottom | Qt::AlignRight);
QObject::connect(rebootBtn, &QPushButton::released, [=]() { Hardware::reboot(); });
}
setStyleSheet(R"( setStyleSheet(R"(
* { * {
font-size: 48px; font-size: 48px;
@ -64,49 +52,55 @@ OffroadAlert::OffroadAlert(QWidget* parent) : QFrame(parent) {
)"); )");
} }
void OffroadAlert::refresh() { int OffroadAlert::refresh() {
if (alerts.empty()) { if (alerts.empty()) {
// setup labels for each alert // setup labels for each alert
QString json = QString::fromStdString(util::read_file("../controls/lib/alerts_offroad.json")); QString json = util::read_file("../controls/lib/alerts_offroad.json").c_str();
QJsonObject obj = QJsonDocument::fromJson(json.toUtf8()).object(); QJsonObject obj = QJsonDocument::fromJson(json.toUtf8()).object();
for (auto &k : obj.keys()) { // descending sort labels by severity
QLabel *l = new QLabel(this); std::vector<std::pair<std::string, int>> sorted;
alerts[k.toStdString()] = l; for (auto it = obj.constBegin(); it != obj.constEnd(); ++it) {
int severity = obj[k].toObject()["severity"].toInt(); sorted.push_back({it.key().toStdString(), it.value()["severity"].toInt()});
}
std::sort(sorted.begin(), sorted.end(), [=](auto &l, auto &r) { return l.second > r.second; });
for (auto &[key, severity] : sorted) {
QLabel *l = new QLabel(this);
alerts[key] = l;
l->setMargin(60); l->setMargin(60);
l->setWordWrap(true); l->setWordWrap(true);
l->setStyleSheet("background-color: " + QString(severity ? "#E22C2C" : "#292929")); l->setStyleSheet(QString("background-color: %1").arg(severity ? "#E22C2C" : "#292929"));
l->setVisible(false); scrollable_layout->addWidget(l);
alerts_layout->addWidget(l);
} }
alerts_layout->addStretch(1); scrollable_layout->addStretch(1);
} }
updateAlerts(); int alertCount = 0;
for (const auto &[key, label] : alerts) {
rebootBtn.setVisible(updateAvailable); QString text;
releaseNotesScroll->setVisible(updateAvailable); std::string bytes = params.get(key);
releaseNotes.setText(QString::fromStdString(params.get("ReleaseNotes"))); if (bytes.size()) {
auto doc_par = QJsonDocument::fromJson(bytes.c_str());
alertsScroll->setVisible(!updateAvailable); text = doc_par["text"].toString();
for (const auto& [k, label] : alerts) { }
label->setVisible(!label->text().isEmpty()); label->setText(text);
label->setVisible(!text.isEmpty());
alertCount += !text.isEmpty();
} }
return alertCount;
} }
void OffroadAlert::updateAlerts() { UpdateAlert::UpdateAlert(QWidget *parent) : AbstractAlert(true, parent) {
alertCount = 0; releaseNotes = new QLabel(this);
for (const auto& [key, label] : alerts) { releaseNotes->setWordWrap(true);
auto bytes = params.get(key.c_str()); releaseNotes->setAlignment(Qt::AlignTop);
if (bytes.size()) { scrollable_layout->addWidget(releaseNotes);
QJsonDocument doc_par = QJsonDocument::fromJson(QByteArray(bytes.data(), bytes.size())); }
QJsonObject obj = doc_par.object();
label->setText(obj.value("text").toString()); bool UpdateAlert::refresh() {
alertCount++; bool updateAvailable = params.getBool("UpdateAvailable");
} else { if (updateAvailable) {
label->setText(""); releaseNotes->setText(params.get("ReleaseNotes").c_str());
}
} }
updateAvailable = params.getBool("UpdateAvailable") && alertCount < 1; return updateAvailable;
} }

@ -2,37 +2,41 @@
#include <map> #include <map>
#include <QFrame>
#include <QLabel> #include <QLabel>
#include <QPushButton>
#include <QVBoxLayout> #include <QVBoxLayout>
#include "selfdrive/common/params.h" #include "selfdrive/common/params.h"
#include "selfdrive/ui/qt/widgets/scrollview.h"
class OffroadAlert : public QFrame { class AbstractAlert : public QFrame {
Q_OBJECT
protected:
AbstractAlert(bool hasRebootBtn, QWidget *parent = nullptr);
QVBoxLayout *scrollable_layout;
Params params;
signals:
void dismiss();
};
class UpdateAlert : public AbstractAlert {
Q_OBJECT Q_OBJECT
public: public:
explicit OffroadAlert(QWidget *parent = 0); UpdateAlert(QWidget *parent = 0);
int alertCount = 0; bool refresh();
bool updateAvailable;
private: private:
void updateAlerts(); QLabel *releaseNotes = nullptr;
};
Params params;
std::map<std::string, QLabel*> alerts;
QLabel releaseNotes; class OffroadAlert : public AbstractAlert {
QPushButton rebootBtn; Q_OBJECT
ScrollView *alertsScroll;
ScrollView *releaseNotesScroll;
QVBoxLayout *alerts_layout;
signals: public:
void closeAlerts(); explicit OffroadAlert(QWidget *parent = 0) : AbstractAlert(false, parent) {}
int refresh();
public slots: private:
void refresh(); std::map<std::string, QLabel*> alerts;
}; };

Loading…
Cancel
Save