#include "map_settings.h" #include #include "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) { QStackedLayout *stack = new QStackedLayout(this); QWidget *main_widget = new QWidget; QVBoxLayout *main_layout = new QVBoxLayout(main_widget); main_layout->setSpacing(20); // Home & Work layout QHBoxLayout *home_work_layout = new QHBoxLayout; { // Home home_button = new QPushButton; home_button->setIconSize(QSize(MAP_PANEL_ICON_SIZE, MAP_PANEL_ICON_SIZE)); home_address = new QLabel; home_address->setWordWrap(true); QHBoxLayout *home_layout = new QHBoxLayout; home_layout->addWidget(home_button); home_layout->addSpacing(30); home_layout->addWidget(home_address); home_layout->addStretch(); // Work work_button = new QPushButton; work_button->setIconSize(QSize(MAP_PANEL_ICON_SIZE, MAP_PANEL_ICON_SIZE)); work_address = new QLabel; work_address->setWordWrap(true); QHBoxLayout *work_layout = new QHBoxLayout; work_layout->addWidget(work_button); work_layout->addSpacing(30); work_layout->addWidget(work_address); work_layout->addStretch(); 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->addWidget(horizontal_line()); // Current route { current_widget = new QWidget(this); QVBoxLayout *current_layout = new QVBoxLayout(current_widget); QLabel *title = new QLabel(tr("Current Destination")); title->setStyleSheet("font-size: 55px"); current_layout->addWidget(title); current_route = new ButtonControl("", tr("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(tr("Recent Destinations")); recents_title->setStyleSheet("font-size: 55px"); main_layout->addWidget(recents_title); 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(tr("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(tr("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); connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) { stack->setCurrentIndex(prime_type ? 0 : 1); }); 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::requestDone, this, &MapPanel::parseResponse); } // 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::requestDone, [=](const QString &resp, bool success) { if (success && 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(); refresh(); } void MapPanel::clear() { home_button->setIcon(QPixmap("../assets/navigation/home_inactive.png")); home_address->setStyleSheet(R"(font-size: 50px; color: grey;)"); home_address->setText(tr("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(tr("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, bool success) { if (!success) return; cur_destinations = response; if (isVisible()) { refresh(); } } void MapPanel::refresh() { if (cur_destinations == prev_destinations) return; QJsonDocument doc = QJsonDocument::fromJson(cur_destinations.trimmed().toUtf8()); if (doc.isNull()) { qDebug() << "JSON Parse failed on navigation locations"; return; } prev_destinations = cur_destinations; clear(); // add favorites before recents bool has_recents = false; for (auto &save_type: {NAV_TYPE_FAVORITE, NAV_TYPE_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 == NAV_TYPE_FAVORITE && label == NAV_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 == NAV_TYPE_FAVORITE && label == NAV_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 == NAV_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(tr("no recent destinations")); no_recents->setStyleSheet(R"(font-size: 50px; color: #9c9c9c)"); recent_layout->addWidget(no_recents); } recent_layout->addStretch(); repaint(); } void MapPanel::navigateTo(const QJsonObject &place) { QJsonDocument doc(place); params.put("NavDestination", doc.toJson().toStdString()); }