#include "map_settings.h" #include #include "selfdrive/common/util.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/qt_window.h" #include "selfdrive/ui/qt/request_repeater.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/scrollview.h" static QString shorten(const QString &str, int max_len) { return str.size() > max_len ? str.left(max_len).trimmed() + "…" : str; } MapPanel::MapPanel(QWidget* parent) : QWidget(parent) { stack = new QStackedWidget; QWidget * main_widget = new QWidget; QVBoxLayout *main_layout = new QVBoxLayout(main_widget); const int icon_size = 200; // Home QHBoxLayout *home_layout = new QHBoxLayout; home_button = new QPushButton; home_button->setIconSize(QSize(icon_size, icon_size)); home_layout->addWidget(home_button); home_address = new QLabel; home_address->setWordWrap(true); home_layout->addSpacing(30); home_layout->addWidget(home_address); home_layout->addStretch(); // Work QHBoxLayout *work_layout = new QHBoxLayout; work_button = new QPushButton; work_button->setIconSize(QSize(icon_size, icon_size)); work_layout->addWidget(work_button); work_address = new QLabel; work_address->setWordWrap(true); work_layout->addSpacing(30); work_layout->addWidget(work_address); work_layout->addStretch(); // Home & Work layout QHBoxLayout *home_work_layout = new QHBoxLayout; home_work_layout->addLayout(home_layout, 1); home_work_layout->addSpacing(50); home_work_layout->addLayout(work_layout, 1); main_layout->addLayout(home_work_layout); main_layout->addSpacing(20); main_layout->addWidget(horizontal_line()); main_layout->addSpacing(20); // Recents recent_layout = new QVBoxLayout; QWidget *recent_widget = new LayoutWidget(recent_layout, this); ScrollView *recent_scroller = new ScrollView(recent_widget, this); main_layout->addWidget(recent_scroller, 1); QWidget * no_prime_widget = new QWidget; QVBoxLayout *no_prime_layout = new QVBoxLayout(no_prime_widget); QLabel *signup_header = new QLabel("Try the Navigation Beta"); signup_header->setStyleSheet(R"(font-size: 75px; color: white; font-weight:600;)"); signup_header->setAlignment(Qt::AlignCenter); no_prime_layout->addWidget(signup_header); no_prime_layout->addSpacing(50); QLabel *screenshot = new QLabel; QPixmap pm = QPixmap("../assets/navigation/screenshot.png"); screenshot->setPixmap(pm.scaledToWidth(vwp_w * 0.5, Qt::SmoothTransformation)); no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); QLabel *signup = new QLabel("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai"); signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); signup->setAlignment(Qt::AlignCenter); no_prime_layout->addSpacing(50); no_prime_layout->addWidget(signup); no_prime_layout->addStretch(); stack->addWidget(main_widget); stack->addWidget(no_prime_widget); stack->setCurrentIndex(1); QVBoxLayout *wrapper = new QVBoxLayout(this); wrapper->addWidget(stack); clear(); if (auto dongle_id = getDongleId()) { // Fetch favorite and recent locations { QString url = "https://api.commadotai.com/v1/navigation/" + *dongle_id + "/locations"; RequestRepeater* repeater = new RequestRepeater(this, url, "ApiCache_NavDestinations", 30, true); QObject::connect(repeater, &RequestRepeater::receivedResponse, this, &MapPanel::parseResponse); QObject::connect(repeater, &RequestRepeater::failedResponse, this, &MapPanel::failedResponse); } // Destination set while offline { QString url = "https://api.commadotai.com/v1/navigation/" + *dongle_id + "/next"; RequestRepeater* repeater = new RequestRepeater(this, url, "", 10, true); HttpRequest* deleter = new HttpRequest(this); QObject::connect(repeater, &RequestRepeater::receivedResponse, [=](QString resp) { auto params = Params(); if (resp != "null") { if (params.get("NavDestination").empty()) { qWarning() << "Setting NavDestination from /next" << resp; params.put("NavDestination", resp.toStdString()); } else { qWarning() << "Got location from /next, but NavDestination already set"; } // Send DELETE to clear destination server side deleter->sendRequest(url, HttpRequest::Method::DELETE); } }); } } } void MapPanel::clear() { home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); home_address->setText("No home\nlocation set"); home_button->disconnect(); work_button->setIcon(QPixmap("../assets/navigation/work_inactive.png")); work_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); work_address->setText("No work\nlocation set"); work_button->disconnect(); clearLayout(recent_layout); } void MapPanel::parseResponse(const QString &response) { QJsonDocument doc = QJsonDocument::fromJson(response.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on navigation locations"; return; } clear(); bool has_recents = false; for (auto &save_type: {"favorite", "recent"}) { for (auto location : doc.array()) { auto obj = location.toObject(); auto type = obj["save_type"].toString(); auto label = obj["label"].toString(); auto name = obj["place_name"].toString(); auto details = obj["place_details"].toString(); if (type != save_type) continue; if (type == "favorite" && label == "home") { home_address->setText(name); home_address->setStyleSheet(R"(font-size: 50px; color: white;)"); home_button->setIcon(QPixmap("../assets/navigation/home.png")); QObject::connect(home_button, &QPushButton::clicked, [=]() { navigateTo(obj); emit closeSettings(); }); } else if (type == "favorite" && label == "work") { work_address->setText(name); work_address->setStyleSheet(R"(font-size: 50px; color: white;)"); work_button->setIcon(QPixmap("../assets/navigation/work.png")); QObject::connect(work_button, &QPushButton::clicked, [=]() { navigateTo(obj); emit closeSettings(); }); } else { ClickableWidget *widget = new ClickableWidget; QHBoxLayout *layout = new QHBoxLayout(widget); layout->setContentsMargins(15, 14, 40, 14); QLabel *star = new QLabel("★"); auto sp = star->sizePolicy(); sp.setRetainSizeWhenHidden(true); star->setSizePolicy(sp); star->setVisible(type == "favorite"); star->setStyleSheet(R"(font-size: 60px;)"); layout->addWidget(star); layout->addSpacing(10); QLabel *recent_label = new QLabel(shorten(name + " " + details, 45)); recent_label->setStyleSheet(R"(font-size: 50px;)"); layout->addWidget(recent_label); layout->addStretch(); QLabel *arrow = new QLabel("→"); arrow->setStyleSheet(R"(font-size: 60px;)"); layout->addWidget(arrow); widget->setStyleSheet(R"( .ClickableWidget { border-radius: 10px; border-width: 1px; border-style: solid; border-color: gray; } QWidget { background-color: #393939; color: #9c9c9c; } )"); QObject::connect(widget, &ClickableWidget::clicked, [=]() { navigateTo(obj); emit closeSettings(); }); recent_layout->addWidget(widget); recent_layout->addSpacing(10); has_recents = true; } } } if (!has_recents) { QLabel *no_recents = new QLabel("no recent destinations"); no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); recent_layout->addWidget(no_recents); } recent_layout->addStretch(); stack->setCurrentIndex(0); repaint(); } void MapPanel::failedResponse(const QString &response) { stack->setCurrentIndex(1); } void MapPanel::navigateTo(const QJsonObject &place) { QJsonDocument doc(place); params.put("NavDestination", doc.toJson().toStdString()); }