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

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

@ -2,37 +2,41 @@
#include <map>
#include <QFrame>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#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
public:
explicit OffroadAlert(QWidget *parent = 0);
int alertCount = 0;
bool updateAvailable;
UpdateAlert(QWidget *parent = 0);
bool refresh();
private:
void updateAlerts();
Params params;
std::map<std::string, QLabel*> alerts;
QLabel *releaseNotes = nullptr;
};
QLabel releaseNotes;
QPushButton rebootBtn;
ScrollView *alertsScroll;
ScrollView *releaseNotesScroll;
QVBoxLayout *alerts_layout;
class OffroadAlert : public AbstractAlert {
Q_OBJECT
signals:
void closeAlerts();
public:
explicit OffroadAlert(QWidget *parent = 0) : AbstractAlert(false, parent) {}
int refresh();
public slots:
void refresh();
private:
std::map<std::string, QLabel*> alerts;
};

Loading…
Cancel
Save