#include "map_settings.h" #include #include "selfdrive/common/util.h" #include "selfdrive/ui/qt/util.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); // Current route { current_widget = new QWidget(this); QVBoxLayout *current_layout = new QVBoxLayout(current_widget); QLabel *title = new QLabel("Current Destination"); title->setStyleSheet("font-size: 55px"); current_layout->addWidget(title); current_route = new ButtonControl("", "CLEAR"); current_route->setStyleSheet("padding-left: 40px;"); current_layout->addWidget(current_route); QObject::connect(current_route, &ButtonControl::clicked, [=]() { params.remove("NavDestination"); updateCurrentRoute(); }); current_layout->addSpacing(10); current_layout->addWidget(horizontal_line()); current_layout->addSpacing(20); } main_layout->addWidget(current_widget); // Recents QLabel *recents_title = new QLabel("Recent Destinations"); recents_title->setStyleSheet("font-size: 55px"); main_layout->addWidget(recents_title); main_layout->addSpacing(20); 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); // No prime upsell 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(1080, 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(20); 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 = CommaApi::BASE_URL + "/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 = CommaApi::BASE_URL + "/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) { 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::showEvent(QShowEvent *event) { updateCurrentRoute(); } 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::updateCurrentRoute() { auto dest = QString::fromStdString(params.get("NavDestination")); QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8()); if (dest.size() && !doc.isNull()) { auto name = doc["place_name"].toString(); auto details = doc["place_details"].toString(); current_route->setTitle(shorten(name + " " + details, 42)); } current_widget->setVisible(dest.size() && !doc.isNull()); } 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()); }