UI: improved nav destination entry (#28476)
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g fill="#fff" clip-path="url(#a)" transform="matrix(2.3325 0 0 2.3825 -72.89 -63.797)"><path d="M31.25 53.12A15.62 15.62 0 0 1 46.88 37.5h9.37v-9.16a1.56 1.56 0 0 1 2.56-1.2l14.75 12.28c.75.63.75 1.78 0 2.4l-14.75 12.3a1.56 1.56 0 0 1-2.56-1.2v-9.17h-9.38a9.37 9.37 0 0 0-9.37 9.37v15.63h-6.25z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 466 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g fill="#fff" clip-path="url(#a)"><path d="M31.25 53.12A15.62 15.62 0 0 1 46.88 37.5h9.37v-9.16a1.56 1.56 0 0 1 2.56-1.2l14.75 12.28c.75.63.75 1.78 0 2.4l-14.75 12.3a1.56 1.56 0 0 1-2.56-1.2v-9.17h-9.38a9.37 9.37 0 0 0-9.37 9.37v15.63h-6.25V53.12Z"/><path fill-rule="evenodd" d="M43.44 2.72a9.28 9.28 0 0 1 13.12 0l40.72 40.72a9.26 9.26 0 0 1 0 13.12L56.56 97.28a9.26 9.26 0 0 1-13.1 0L2.71 56.56a9.26 9.26 0 0 1 0-13.11L43.44 2.72Zm8.75 4.37a3.1 3.1 0 0 0-4.38 0L7.1 47.81a3.1 3.1 0 0 0 0 4.38L47.8 92.9a3.1 3.1 0 0 0 4.38 0L92.9 52.2a3.1 3.1 0 0 0 0-4.38L52.2 7.1Z" clip-rule="evenodd"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 757 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g clip-path="url(#a)"><path fill="#fff" d="M49.1 0 36.35 31.82H0l27.27 27.27L13.64 100l35.45-23.64L86.36 100 72.73 59.1 100 31.81H63.64L49.09 0Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 315 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g clip-path="url(#a)"><path fill="#fff" d="M7.7 98.08c0 1.06.85 1.92 1.92 1.92h28.84V76.92h23.08V100H90.4c1.06 0 1.92-.86 1.92-1.92V53.85H7.69v44.23ZM99.47 42.9l-7.17-6.3V7.7a7.7 7.7 0 0 0-15.39 0v15.4L51.4.68A1.92 1.92 0 0 0 48.68.6l-.08.08L.52 42.72a1.92 1.92 0 0 0 1.38 3.24l13.49.2h82.68a1.92 1.92 0 0 0 1.41-3.25Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 489 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g fill="#fff" clip-path="url(#a)"><path d="M53.22 6.37a43.75 43.75 0 0 0-3.21-.12V0a50 50 0 0 1 3.68.14l-.47 6.23Zm12.53 2.81c-2-.77-4.06-1.4-6.16-1.87l1.37-6.1c2.4.54 4.75 1.25 7.04 2.14l-2.25 5.83Zm8.56 4.44c-.9-.6-1.8-1.16-2.74-1.69l3.08-5.44a50.15 50.15 0 0 1 6.12 4.09l-3.85 4.93a43.75 43.75 0 0 0-2.61-1.89ZM85.77 24.8a43.7 43.7 0 0 0-4.08-4.97l4.53-4.31a52.14 52.14 0 0 1 4.66 5.68l-5.1 3.6Zm4.65 8.45a36.5 36.5 0 0 0-1.34-2.92l5.59-2.81a48.6 48.6 0 0 1 2.8 6.8l-5.93 1.95a43.9 43.9 0 0 0-1.12-3.02Zm3.31 15.67a43.68 43.68 0 0 0-.62-6.4l6.15-1.07c.42 2.41.67 4.86.73 7.31l-6.25.16Zm-.81 9.61c.2-1.06.37-2.12.5-3.19l6.2.77a49.3 49.3 0 0 1-1.43 7.22l-6.02-1.67c.28-1.03.53-2.07.75-3.13ZM86.97 73.4A44.16 44.16 0 0 0 90 67.72l5.72 2.53c-1 2.25-2.16 4.42-3.47 6.49l-5.28-3.34Zm-6.03 7.53c.76-.77 1.5-1.55 2.19-2.36l4.74 4.08a49.2 49.2 0 0 1-2.51 2.7l-4.42-4.42Z"/><path d="M50 6.25a43.75 43.75 0 1 0 30.94 74.68l4.42 4.42A50 50 0 1 1 50 0v6.25Z"/><path d="M46.88 18.75a3.13 3.13 0 0 1 3.13 3.12v32.56l20.3 11.6a3.12 3.12 0 0 1-3.1 5.42l-21.88-12.5a3.12 3.12 0 0 1-1.57-2.7V21.86a3.12 3.12 0 0 1 3.12-3.12Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" fill="#fff" class="bi bi-gear-fill"><path d="M75.24 8.4c-3.304-11.2-19.176-11.2-22.48 0l-.8 2.72a11.712 11.712 0 0 1-16.84 6.976l-2.48-1.36c-10.264-5.584-21.488 5.64-15.896 15.896l1.352 2.488c3.568 6.56.184 14.728-6.976 16.84l-2.72.8c-11.2 3.304-11.2 19.176 0 22.48l2.72.8a11.712 11.712 0 0 1 6.976 16.84l-1.36 2.48c-5.584 10.264 5.64 21.488 15.896 15.896l2.488-1.352a11.712 11.712 0 0 1 16.84 6.976l.8 2.72c3.304 11.2 19.176 11.2 22.48 0l.8-2.72a11.712 11.712 0 0 1 16.84-6.976l2.48 1.36c10.264 5.584 21.488-5.64 15.896-15.896l-1.352-2.488a11.712 11.712 0 0 1 6.976-16.84l2.72-.8c11.2-3.304 11.2-19.176 0-22.48l-2.72-.8a11.712 11.712 0 0 1-6.976-16.84l1.36-2.48c5.584-10.264-5.64-21.488-15.896-15.896l-2.488 1.352a11.712 11.712 0 0 1-16.84-6.976ZM64 87.44a23.44 23.44 0 1 1 0-46.88 23.432 23.432 0 0 1 0 46.864z" style="stroke-width:8"/></svg>
|
||||
|
After Width: | Height: | Size: 910 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><g clip-path="url(#a)"><path fill="#fff" d="M77.78 22.22V6.42L70.67 0H29.33l-7.1 8.3v13.92H5.55L0 27.78v66.66L5.56 100h88.88l5.56-5.56V27.78l-5.56-5.56H77.78Zm-44.45-11.1h33.34v11.1H33.33v-11.1Z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h100v100H0z"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 1.4 MiB |
@@ -29,7 +29,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/drive_stats.cc", "qt/
|
||||
qt_env['CPPDEFINES'] = []
|
||||
if maps:
|
||||
base_libs += ['qmapboxgl']
|
||||
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc"]
|
||||
widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc"]
|
||||
qt_env['CPPDEFINES'] += ["ENABLE_MAPS"]
|
||||
|
||||
widgets = qt_env.Library("qt_widgets", widgets_src, LIBS=base_libs)
|
||||
|
||||
@@ -7,9 +7,14 @@
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/experimental_mode.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/drive_stats.h"
|
||||
#include "selfdrive/ui/qt/widgets/prime.h"
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
#else
|
||||
#include "selfdrive/ui/qt/widgets/drive_stats.h"
|
||||
#endif
|
||||
|
||||
// HomeWindow: the container for the offroad and onroad UIs
|
||||
|
||||
HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) {
|
||||
@@ -137,10 +142,15 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) {
|
||||
home_layout->setContentsMargins(0, 0, 0, 0);
|
||||
home_layout->setSpacing(30);
|
||||
|
||||
// left: DriveStats/PrimeAdWidget
|
||||
// left: MapSettings/PrimeAdWidget
|
||||
QStackedWidget *left_widget = new QStackedWidget(this);
|
||||
#ifdef ENABLE_MAPS
|
||||
left_widget->addWidget(new MapSettings);
|
||||
#else
|
||||
left_widget->addWidget(new DriveStats);
|
||||
#endif
|
||||
left_widget->addWidget(new PrimeAdWidget);
|
||||
left_widget->setStyleSheet("border-radius: 10px;");
|
||||
|
||||
left_widget->setCurrentIndex(uiState()->primeType() ? 0 : 1);
|
||||
connect(uiState(), &UIState::primeTypeChanged, [=](int prime_type) {
|
||||
|
||||
@@ -44,6 +44,29 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings),
|
||||
map_eta->move(25, 1080 - h - bdr_s*2);
|
||||
map_eta->setVisible(false);
|
||||
|
||||
// Settings button
|
||||
QSize icon_size(120, 120);
|
||||
directions_icon = loadPixmap("../assets/navigation/icon_directions_outlined.svg", icon_size);
|
||||
settings_icon = loadPixmap("../assets/navigation/icon_settings.svg", icon_size);
|
||||
|
||||
settings_btn = new QPushButton(directions_icon, "", this);
|
||||
settings_btn->setIconSize(icon_size);
|
||||
settings_btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
background-color: #96000000;
|
||||
border-radius: 50px;
|
||||
padding: 24px;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #D9000000;
|
||||
}
|
||||
)");
|
||||
settings_btn->show(); // force update
|
||||
settings_btn->move(bdr_s, 1080 - bdr_s*3 - settings_btn->height());
|
||||
QObject::connect(settings_btn, &QPushButton::clicked, [=]() {
|
||||
emit openSettings();
|
||||
});
|
||||
|
||||
auto last_gps_position = coordinate_from_param("LastGPSPosition");
|
||||
if (last_gps_position.has_value()) {
|
||||
last_position = *last_gps_position;
|
||||
@@ -128,7 +151,7 @@ void MapWindow::updateState(const UIState &s) {
|
||||
|
||||
// Only open the map on setting destination the first time
|
||||
if (allow_open) {
|
||||
setVisible(true); // Show map on destination set/change
|
||||
emit requestVisible(true); // Show map on destination set/change
|
||||
allow_open = false;
|
||||
}
|
||||
}
|
||||
@@ -186,6 +209,19 @@ void MapWindow::updateState(const UIState &s) {
|
||||
} else {
|
||||
clearRoute();
|
||||
}
|
||||
|
||||
// TODO: only move if position should change
|
||||
// don't move while map isn't visible
|
||||
if (isVisible()) {
|
||||
auto pos = 1080 - bdr_s*2 - settings_btn->height() - bdr_s;
|
||||
if (map_eta->isVisible()) {
|
||||
settings_btn->move(bdr_s, pos - map_eta->height());
|
||||
settings_btn->setIcon(settings_icon);
|
||||
} else {
|
||||
settings_btn->move(bdr_s, pos);
|
||||
settings_btn->setIcon(directions_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sm.rcv_frame("navRoute") != route_rcv_frame) {
|
||||
@@ -321,7 +357,7 @@ void MapWindow::offroadTransition(bool offroad) {
|
||||
clearRoute();
|
||||
} else {
|
||||
auto dest = coordinate_from_param("NavDestination");
|
||||
setVisible(dest.has_value());
|
||||
emit requestVisible(dest.has_value());
|
||||
}
|
||||
last_bearing = {};
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLWidget>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
@@ -110,6 +111,8 @@ private:
|
||||
|
||||
MapInstructions* map_instructions;
|
||||
MapETA* map_eta;
|
||||
QPushButton *settings_btn;
|
||||
QPixmap directions_icon, settings_icon;
|
||||
|
||||
void clearRoute();
|
||||
void updateDestinationMarker();
|
||||
@@ -125,5 +128,7 @@ signals:
|
||||
void distanceChanged(float distance);
|
||||
void instructionsChanged(cereal::NavInstruction::Reader instruction);
|
||||
void ETAChanged(float seconds, float seconds_typical, float distance);
|
||||
};
|
||||
|
||||
void requestVisible(bool visible);
|
||||
void openSettings();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "selfdrive/ui/qt/maps/map_panel.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include "selfdrive/ui/qt/maps/map.h"
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) {
|
||||
content_stack = new QStackedLayout(this);
|
||||
content_stack->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto map = new MapWindow(mapboxSettings);
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, map, &MapWindow::offroadTransition);
|
||||
QObject::connect(map, &MapWindow::requestVisible, [=](bool visible) {
|
||||
setVisible(visible);
|
||||
});
|
||||
QObject::connect(map, &MapWindow::openSettings, [=]() {
|
||||
content_stack->setCurrentIndex(1);
|
||||
});
|
||||
content_stack->addWidget(map);
|
||||
|
||||
auto settings = new MapSettings(true, parent);
|
||||
QObject::connect(settings, &MapSettings::closeSettings, [=]() {
|
||||
content_stack->setCurrentIndex(0);
|
||||
});
|
||||
content_stack->addWidget(settings);
|
||||
}
|
||||
|
||||
bool MapPanel::isShowingMap() const {
|
||||
return content_stack->currentIndex() == 0;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QMapboxGL>
|
||||
#include <QStackedLayout>
|
||||
|
||||
class MapPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr);
|
||||
|
||||
bool isShowingMap() const;
|
||||
|
||||
private:
|
||||
QStackedLayout *content_stack;
|
||||
};
|
||||
@@ -1,132 +1,95 @@
|
||||
#include "map_settings.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <vector>
|
||||
|
||||
#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);
|
||||
MapSettings::MapSettings(bool closeable, QWidget *parent)
|
||||
: QFrame(parent), current_destination(nullptr) {
|
||||
QSize icon_size(100, 100);
|
||||
close_icon = loadPixmap("../assets/icons/close.svg", icon_size);
|
||||
|
||||
QWidget *main_widget = new QWidget;
|
||||
QVBoxLayout *main_layout = new QVBoxLayout(main_widget);
|
||||
main_layout->setSpacing(20);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
// Home & Work layout
|
||||
QHBoxLayout *home_work_layout = new QHBoxLayout;
|
||||
auto *frame = new QVBoxLayout(this);
|
||||
frame->setContentsMargins(40, 40, 40, 0);
|
||||
frame->setSpacing(0);
|
||||
|
||||
auto *heading_frame = new QHBoxLayout;
|
||||
heading_frame->setContentsMargins(0, 0, 0, 0);
|
||||
heading_frame->setSpacing(32);
|
||||
{
|
||||
// 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);
|
||||
if (closeable) {
|
||||
auto *close_btn = new QPushButton("←");
|
||||
close_btn->setStyleSheet(R"(
|
||||
QPushButton {
|
||||
color: #FFFFFF;
|
||||
font-size: 100px;
|
||||
padding-bottom: 8px;
|
||||
border 1px grey solid;
|
||||
border-radius: 70px;
|
||||
background-color: #292929;
|
||||
font-weight: 500;
|
||||
}
|
||||
QPushButton:pressed {
|
||||
background-color: #3B3B3B;
|
||||
}
|
||||
)");
|
||||
close_btn->setFixedSize(140, 140);
|
||||
QObject::connect(close_btn, &QPushButton::clicked, [=]() { emit closeSettings(); });
|
||||
// TODO: read map_on_left from ui state
|
||||
heading_frame->addWidget(close_btn);
|
||||
}
|
||||
|
||||
QHBoxLayout *home_layout = new QHBoxLayout;
|
||||
home_layout->addWidget(home_button);
|
||||
home_layout->addSpacing(30);
|
||||
home_layout->addWidget(home_address);
|
||||
home_layout->addStretch();
|
||||
auto *heading = new QVBoxLayout;
|
||||
heading->setContentsMargins(0, 0, 0, 0);
|
||||
heading->setSpacing(16);
|
||||
{
|
||||
auto *title = new QLabel(tr("NAVIGATION"), this);
|
||||
title->setStyleSheet("color: #FFFFFF; font-size: 54px; font-weight: 600;");
|
||||
heading->addWidget(title);
|
||||
|
||||
// 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);
|
||||
auto *subtitle = new QLabel(tr("Manage at connect.comma.ai"), this);
|
||||
subtitle->setStyleSheet("color: #A0A0A0; font-size: 40px; font-weight: 300;");
|
||||
heading->addWidget(subtitle);
|
||||
}
|
||||
heading_frame->addLayout(heading, 1);
|
||||
}
|
||||
frame->addLayout(heading_frame);
|
||||
frame->addSpacing(32);
|
||||
|
||||
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);
|
||||
current_widget = new DestinationWidget(this);
|
||||
QObject::connect(current_widget, &DestinationWidget::actionClicked, [=]() {
|
||||
if (!current_destination) return;
|
||||
params.remove("NavDestination");
|
||||
updateCurrentRoute();
|
||||
});
|
||||
frame->addWidget(current_widget);
|
||||
frame->addSpacing(32);
|
||||
frame->addWidget(horizontal_line());
|
||||
|
||||
QWidget *destinations_container = new QWidget(this);
|
||||
destinations_layout = new QVBoxLayout(destinations_container);
|
||||
destinations_layout->setContentsMargins(0, 32, 0, 32);
|
||||
destinations_layout->setSpacing(20);
|
||||
ScrollView *destinations_scroller = new ScrollView(destinations_container, this);
|
||||
frame->addWidget(destinations_scroller);
|
||||
|
||||
clear();
|
||||
setStyleSheet("MapSettings { background-color: #333333; }");
|
||||
|
||||
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);
|
||||
QObject::connect(repeater, &RequestRepeater::requestDone, this, &MapSettings::parseResponse);
|
||||
}
|
||||
|
||||
// Destination set while offline
|
||||
@@ -147,153 +110,236 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
|
||||
// Send DELETE to clear destination server side
|
||||
deleter->sendRequest(url, HttpRequest::Method::DELETE);
|
||||
}
|
||||
|
||||
// Update UI (athena can set destination at any time)
|
||||
updateCurrentRoute();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapPanel::showEvent(QShowEvent *event) {
|
||||
void MapSettings::showEvent(QShowEvent *event) {
|
||||
updateCurrentRoute();
|
||||
}
|
||||
|
||||
void MapSettings::updateCurrentRoute() {
|
||||
auto dest = QString::fromStdString(params.get("NavDestination"));
|
||||
if (dest.size()) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(dest.trimmed().toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qWarning() << "JSON Parse failed on NavDestination" << dest;
|
||||
return;
|
||||
}
|
||||
auto destination = new NavDestination(doc.object());
|
||||
if (current_destination && *destination == *current_destination) return;
|
||||
current_destination = destination;
|
||||
current_widget->set(current_destination, true);
|
||||
} else {
|
||||
current_destination = nullptr;
|
||||
current_widget->unset("", true);
|
||||
}
|
||||
if (isVisible()) refresh();
|
||||
}
|
||||
|
||||
void MapSettings::parseResponse(const QString &response, bool success) {
|
||||
if (!success || response == cur_destinations) return;
|
||||
cur_destinations = response;
|
||||
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();
|
||||
void MapSettings::refresh() {
|
||||
bool has_home = false, has_work = false;
|
||||
auto destinations = std::vector<NavDestination*>();
|
||||
|
||||
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();
|
||||
auto destinations_str = cur_destinations.trimmed();
|
||||
if (!destinations_str.isEmpty()) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(destinations_str.toUtf8());
|
||||
if (doc.isNull()) {
|
||||
qWarning() << "JSON Parse failed on navigation locations" << cur_destinations;
|
||||
return;
|
||||
}
|
||||
|
||||
clearLayout(recent_layout);
|
||||
}
|
||||
for (auto el : doc.array()) {
|
||||
auto destination = new NavDestination(el.toObject());
|
||||
|
||||
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;
|
||||
// add home and work later if they are missing
|
||||
if (destination->isFavorite()) {
|
||||
if (destination->label() == NAV_FAVORITE_LABEL_HOME) has_home = true;
|
||||
else if (destination->label() == NAV_FAVORITE_LABEL_WORK) has_work = true;
|
||||
}
|
||||
|
||||
// skip current destination
|
||||
if (current_destination && *destination == *current_destination) continue;
|
||||
destinations.push_back(destination);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// TODO: should we build a new layout and swap it in?
|
||||
clearLayout(destinations_layout);
|
||||
|
||||
// Sort: HOME, WORK, and then descending-alphabetical FAVORITES, RECENTS
|
||||
std::sort(destinations.begin(), destinations.end(), [](const NavDestination *a, const NavDestination *b) {
|
||||
if (a->isFavorite() && b->isFavorite()) {
|
||||
if (a->label() == NAV_FAVORITE_LABEL_HOME) return true;
|
||||
else if (b->label() == NAV_FAVORITE_LABEL_HOME) return false;
|
||||
else if (a->label() == NAV_FAVORITE_LABEL_WORK) return true;
|
||||
else if (b->label() == NAV_FAVORITE_LABEL_WORK) return false;
|
||||
else if (a->label() != b->label()) return a->label() < b->label();
|
||||
}
|
||||
else if (a->isFavorite()) return true;
|
||||
else if (b->isFavorite()) return false;
|
||||
return a->name() < b->name();
|
||||
});
|
||||
|
||||
for (auto destination : destinations) {
|
||||
auto widget = new DestinationWidget(this);
|
||||
widget->set(destination, false);
|
||||
|
||||
QObject::connect(widget, &QPushButton::clicked, [=]() {
|
||||
navigateTo(destination->toJson());
|
||||
emit closeSettings();
|
||||
});
|
||||
|
||||
destinations_layout->addWidget(widget);
|
||||
}
|
||||
|
||||
recent_layout->addStretch();
|
||||
// add home and work if missing
|
||||
if (!has_home) {
|
||||
auto widget = new DestinationWidget(this);
|
||||
widget->unset(NAV_FAVORITE_LABEL_HOME);
|
||||
destinations_layout->insertWidget(0, widget);
|
||||
}
|
||||
if (!has_work) {
|
||||
auto widget = new DestinationWidget(this);
|
||||
widget->unset(NAV_FAVORITE_LABEL_WORK);
|
||||
// TODO: refactor to remove this hack
|
||||
int index = !has_home || (current_destination && current_destination->isFavorite() && current_destination->label() == NAV_FAVORITE_LABEL_HOME) ? 0 : 1;
|
||||
destinations_layout->insertWidget(index, widget);
|
||||
}
|
||||
|
||||
destinations_layout->addStretch();
|
||||
repaint();
|
||||
}
|
||||
|
||||
void MapPanel::navigateTo(const QJsonObject &place) {
|
||||
void MapSettings::navigateTo(const QJsonObject &place) {
|
||||
QJsonDocument doc(place);
|
||||
params.put("NavDestination", doc.toJson().toStdString());
|
||||
updateCurrentRoute();
|
||||
}
|
||||
|
||||
DestinationWidget::DestinationWidget(QWidget *parent) : QPushButton(parent) {
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto *frame = new QHBoxLayout(this);
|
||||
frame->setContentsMargins(32, 24, 32, 24);
|
||||
frame->setSpacing(32);
|
||||
|
||||
icon = new QLabel(this);
|
||||
icon->setAlignment(Qt::AlignCenter);
|
||||
icon->setFixedSize(96, 96);
|
||||
icon->setObjectName("icon");
|
||||
frame->addWidget(icon);
|
||||
|
||||
auto *inner_frame = new QVBoxLayout;
|
||||
inner_frame->setContentsMargins(0, 0, 0, 0);
|
||||
inner_frame->setSpacing(0);
|
||||
{
|
||||
title = new ElidedLabel(this);
|
||||
title->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
inner_frame->addWidget(title);
|
||||
|
||||
subtitle = new ElidedLabel(this);
|
||||
subtitle->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
subtitle->setObjectName("subtitle");
|
||||
inner_frame->addWidget(subtitle);
|
||||
}
|
||||
frame->addLayout(inner_frame, 1);
|
||||
|
||||
action = new QPushButton(this);
|
||||
action->setFixedSize(96, 96);
|
||||
action->setObjectName("action");
|
||||
action->setStyleSheet("font-size: 65px; font-weight: 600;");
|
||||
QObject::connect(action, &QPushButton::clicked, [=]() { emit clicked(); });
|
||||
QObject::connect(action, &QPushButton::clicked, [=]() { emit actionClicked(); });
|
||||
frame->addWidget(action);
|
||||
|
||||
setFixedHeight(164);
|
||||
setStyleSheet(R"(
|
||||
DestinationWidget { background-color: #202123; border-radius: 10px; }
|
||||
QLabel { color: #FFFFFF; font-size: 48px; font-weight: 400; }
|
||||
#icon { background-color: #3B4356; border-radius: 48px; }
|
||||
#subtitle { color: #9BA0A5; }
|
||||
#action { border: none; border-radius: 48px; color: #FFFFFF; padding-bottom: 4px; }
|
||||
|
||||
/* current destination */
|
||||
[current="true"] { background-color: #E8E8E8; }
|
||||
[current="true"] QLabel { color: #000000; }
|
||||
[current="true"] #icon { background-color: #42906B; }
|
||||
[current="true"] #subtitle { color: #333333; }
|
||||
[current="true"] #action { color: #202123; }
|
||||
|
||||
/* no saved destination */
|
||||
[set="false"] QLabel { color: #9BA0A5; }
|
||||
[current="true"][set="false"] QLabel { color: #A0000000; }
|
||||
|
||||
/* pressed */
|
||||
[current="false"]:pressed { background-color: #18191B; }
|
||||
[current="true"] #action:pressed { background-color: #D6D6D6; }
|
||||
)");
|
||||
}
|
||||
|
||||
void DestinationWidget::set(NavDestination *destination, bool current) {
|
||||
setProperty("current", current);
|
||||
setProperty("set", true);
|
||||
|
||||
auto icon_pixmap = current ? icons().directions : icons().recent;
|
||||
auto title_text = destination->name();
|
||||
auto subtitle_text = destination->details();
|
||||
|
||||
if (destination->isFavorite()) {
|
||||
if (destination->label() == NAV_FAVORITE_LABEL_HOME) {
|
||||
icon_pixmap = icons().home;
|
||||
title_text = tr("Home");
|
||||
subtitle_text = destination->name() + ", " + destination->details();
|
||||
} else if (destination->label() == NAV_FAVORITE_LABEL_WORK) {
|
||||
icon_pixmap = icons().work;
|
||||
title_text = tr("Work");
|
||||
subtitle_text = destination->name() + ", " + destination->details();
|
||||
} else {
|
||||
icon_pixmap = icons().favorite;
|
||||
}
|
||||
}
|
||||
|
||||
icon->setPixmap(icon_pixmap);
|
||||
|
||||
// TODO: onroad and offroad have different dimensions
|
||||
title->setText(shorten(title_text, 26));
|
||||
subtitle->setText(shorten(subtitle_text, 26));
|
||||
subtitle->setVisible(true);
|
||||
|
||||
// TODO: use pixmap
|
||||
action->setAttribute(Qt::WA_TransparentForMouseEvents, !current);
|
||||
action->setText(current ? "×" : "→");
|
||||
action->setVisible(true);
|
||||
|
||||
setStyleSheet(styleSheet());
|
||||
}
|
||||
|
||||
void DestinationWidget::unset(const QString &label, bool current) {
|
||||
setProperty("current", current);
|
||||
setProperty("set", false);
|
||||
|
||||
if (label.isEmpty()) {
|
||||
icon->setPixmap(icons().directions);
|
||||
title->setText(tr("No destination set"));
|
||||
} else {
|
||||
QString title_text = label == NAV_FAVORITE_LABEL_HOME ? tr("home") : tr("work");
|
||||
icon->setPixmap(label == NAV_FAVORITE_LABEL_HOME ? icons().home : icons().work);
|
||||
title->setText(tr("No %1 location set").arg(title_text));
|
||||
}
|
||||
|
||||
subtitle->setVisible(false);
|
||||
action->setVisible(false);
|
||||
|
||||
setStyleSheet(styleSheet());
|
||||
}
|
||||
|
||||
@@ -1,46 +1,120 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "common/params.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/widgets/controls.h"
|
||||
|
||||
const int MAP_PANEL_ICON_SIZE = 200;
|
||||
|
||||
const QString NAV_TYPE_FAVORITE = "favorite";
|
||||
const QString NAV_TYPE_RECENT = "recent";
|
||||
|
||||
const QString NAV_FAVORITE_LABEL_HOME = "home";
|
||||
const QString NAV_FAVORITE_LABEL_WORK = "work";
|
||||
|
||||
class MapPanel : public QWidget {
|
||||
class NavDestination;
|
||||
class DestinationWidget;
|
||||
|
||||
class MapSettings : public QFrame {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MapPanel(QWidget* parent = nullptr);
|
||||
explicit MapSettings(bool closeable = false, QWidget *parent = nullptr);
|
||||
|
||||
void navigateTo(const QJsonObject &place);
|
||||
void parseResponse(const QString &response, bool success);
|
||||
void updateCurrentRoute();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void refresh();
|
||||
|
||||
Params params;
|
||||
QString prev_destinations, cur_destinations;
|
||||
QPushButton *home_button, *work_button;
|
||||
QLabel *home_address, *work_address;
|
||||
QVBoxLayout *recent_layout;
|
||||
QWidget *current_widget;
|
||||
ButtonControl *current_route;
|
||||
QString cur_destinations;
|
||||
QVBoxLayout *destinations_layout;
|
||||
NavDestination *current_destination;
|
||||
DestinationWidget *current_widget;
|
||||
|
||||
QPixmap close_icon;
|
||||
|
||||
signals:
|
||||
void closeSettings();
|
||||
};
|
||||
|
||||
class NavDestination {
|
||||
public:
|
||||
explicit NavDestination(const QJsonObject &place)
|
||||
: type_(place["save_type"].toString()), label_(place["label"].toString()),
|
||||
name_(place["place_name"].toString()), details_(place["place_details"].toString()),
|
||||
latitude_(place["latitude"].toDouble()), longitude_(place["longitude"].toDouble()) {
|
||||
// if details starts with `name, ` remove it
|
||||
if (details_.startsWith(name_ + ", ")) {
|
||||
details_ = details_.mid(name_.length() + 2);
|
||||
}
|
||||
}
|
||||
|
||||
QString type() const { return type_; }
|
||||
QString label() const { return label_; }
|
||||
QString name() const { return name_; }
|
||||
QString details() const { return details_; }
|
||||
|
||||
bool isFavorite() const { return type_ == NAV_TYPE_FAVORITE; }
|
||||
bool isRecent() const { return type_ == NAV_TYPE_RECENT; }
|
||||
|
||||
bool operator==(const NavDestination &rhs) {
|
||||
return type_ == rhs.type_ && label_ == rhs.label_ && name_ == rhs.name_ &&
|
||||
details_ == rhs.details_ && latitude_ == rhs.latitude_ && longitude_ == rhs.longitude_;
|
||||
}
|
||||
|
||||
QJsonObject toJson() const {
|
||||
QJsonObject obj;
|
||||
obj["save_type"] = type_;
|
||||
obj["label"] = label_;
|
||||
obj["place_name"] = name_;
|
||||
obj["place_details"] = details_;
|
||||
obj["latitude"] = latitude_;
|
||||
obj["longitude"] = longitude_;
|
||||
return obj;
|
||||
}
|
||||
|
||||
private:
|
||||
QString type_, label_, name_, details_;
|
||||
double latitude_, longitude_;
|
||||
};
|
||||
|
||||
class DestinationWidget : public QPushButton {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DestinationWidget(QWidget *parent = nullptr);
|
||||
|
||||
void set(NavDestination *, bool current = false);
|
||||
void unset(const QString &label, bool current = false);
|
||||
|
||||
signals:
|
||||
void actionClicked();
|
||||
|
||||
private:
|
||||
struct NavIcons {
|
||||
QPixmap home, work, favorite, recent, directions;
|
||||
};
|
||||
|
||||
static NavIcons icons() {
|
||||
static NavIcons nav_icons {
|
||||
loadPixmap("../assets/navigation/icon_home.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_work.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_favorite.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_recent.svg", {48, 48}),
|
||||
loadPixmap("../assets/navigation/icon_directions.svg", {48, 48}),
|
||||
};
|
||||
return nav_icons;
|
||||
}
|
||||
|
||||
private:
|
||||
QLabel *icon, *title, *subtitle;
|
||||
QPushButton *action;
|
||||
};
|
||||
|
||||
@@ -8,10 +8,6 @@
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/networking.h"
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map_settings.h"
|
||||
#endif
|
||||
|
||||
#include "common/params.h"
|
||||
#include "common/watchdog.h"
|
||||
#include "common/util.h"
|
||||
@@ -385,12 +381,6 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
{tr("Software"), new SoftwarePanel(this)},
|
||||
};
|
||||
|
||||
#ifdef ENABLE_MAPS
|
||||
auto map_panel = new MapPanel(this);
|
||||
panels.push_back({tr("Navigation"), map_panel});
|
||||
QObject::connect(map_panel, &MapPanel::closeSettings, this, &SettingsWindow::closeSettings);
|
||||
#endif
|
||||
|
||||
nav_btns = new QButtonGroup(this);
|
||||
for (auto &[name, panel] : panels) {
|
||||
QPushButton *btn = new QPushButton(name);
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
#include <cmath>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMouseEvent>
|
||||
|
||||
#include "common/timing.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#ifdef ENABLE_MAPS
|
||||
#include "selfdrive/ui/qt/maps/map.h"
|
||||
#include "selfdrive/ui/qt/maps/map_helpers.h"
|
||||
#include "selfdrive/ui/qt/maps/map_panel.h"
|
||||
#endif
|
||||
|
||||
OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) {
|
||||
@@ -78,10 +79,15 @@ void OnroadWindow::updateState(const UIState &s) {
|
||||
}
|
||||
|
||||
void OnroadWindow::mousePressEvent(QMouseEvent* e) {
|
||||
#ifdef ENABLE_MAPS
|
||||
if (map != nullptr) {
|
||||
bool sidebarVisible = geometry().x() > 0;
|
||||
if (map->isVisible() && !((MapPanel *)map)->isShowingMap() && e->windowPos().x() >= 1080) {
|
||||
return;
|
||||
}
|
||||
map->setVisible(!sidebarVisible && !map->isVisible());
|
||||
}
|
||||
#endif
|
||||
// propagation event to parent(HomeWindow)
|
||||
QWidget::mousePressEvent(e);
|
||||
}
|
||||
@@ -90,16 +96,14 @@ void OnroadWindow::offroadTransition(bool offroad) {
|
||||
#ifdef ENABLE_MAPS
|
||||
if (!offroad) {
|
||||
if (map == nullptr && (uiState()->primeType() || !MAPBOX_TOKEN.isEmpty())) {
|
||||
MapWindow * m = new MapWindow(get_mapbox_settings());
|
||||
auto m = new MapPanel(get_mapbox_settings());
|
||||
map = m;
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, m, &MapWindow::offroadTransition);
|
||||
|
||||
m->setFixedWidth(topWidget(this)->width() / 2 - bdr_s);
|
||||
split->insertWidget(0, m);
|
||||
|
||||
// Make map visible after adding to split
|
||||
m->offroadTransition(offroad);
|
||||
// hidden by default, made visible when navRoute is published
|
||||
m->setVisible(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,6 @@ QFrame *horizontal_line(QWidget *parent) {
|
||||
QFrame *line = new QFrame(parent);
|
||||
line->setFrameShape(QFrame::StyledPanel);
|
||||
line->setStyleSheet(R"(
|
||||
margin-left: 40px;
|
||||
margin-right: 40px;
|
||||
border-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-color: gray;
|
||||
@@ -127,19 +125,3 @@ void ElidedLabel::paintEvent(QPaintEvent *event) {
|
||||
opt.initFrom(this);
|
||||
style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole());
|
||||
}
|
||||
|
||||
ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { }
|
||||
|
||||
void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) {
|
||||
if (rect().contains(event->pos())) {
|
||||
emit clicked();
|
||||
}
|
||||
}
|
||||
|
||||
// Fix stylesheets
|
||||
void ClickableWidget::paintEvent(QPaintEvent *) {
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
||||
@@ -298,17 +298,3 @@ public:
|
||||
setLayout(l);
|
||||
}
|
||||
};
|
||||
|
||||
class ClickableWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ClickableWidget(QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
signals:
|
||||
void clicked();
|
||||
};
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>Ablehnen, deinstallieren %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -356,42 +383,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>Aktuelles Ziel</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>LÖSCHEN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>Letzte Ziele</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>Beta Navigation ausprobieren</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>Erhalte echtzeit Wegführung und mehr mit dem comma prime
|
||||
Abonnement. Melde dich jetzt an: https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>Keine Heimadresse gesetzt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>Keine Arbeitsadresse gesetzt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>Keine kürzlich gewählten Ziele</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -676,10 +675,6 @@ This may take up to a minute.</source>
|
||||
<source>Software</source>
|
||||
<translation>Software</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>Navigation</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>拒否して %1 をアンインストール</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -355,44 +382,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>現在の目的地</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>削除</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>最近の目的地</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>β版ナビゲーションを試す</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>より詳細な案内情報を得ることができます。
|
||||
詳しくはこちら:https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>自宅の住所はまだ
|
||||
設定されていません</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>職場の住所はまだ
|
||||
設定されていません</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>最近の目的地履歴がありません</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -674,10 +671,6 @@ This may take up to a minute.</source>
|
||||
<source>Software</source>
|
||||
<translation>ソフトウェア</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>ナビゲーション</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>거절, %1 제거</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -355,44 +382,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>현재 목적지</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>삭제</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>최근 목적지</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>네비게이션(베타)를 사용해보세요</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>자세한 경로안내를 원하시면 comma prime을 구독하세요.
|
||||
등록:https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>집
|
||||
설정되지않음</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>회사
|
||||
설정되지않음</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>최근 목적지 없음</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -675,10 +672,6 @@ This may take up to a minute.</source>
|
||||
<source>Software</source>
|
||||
<translation>소프트웨어</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>네비게이션</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>Rejeitar, desintalar %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -356,44 +383,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>Destino Atual</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>LIMPAR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>Destinos Recentes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>Experimente a Navegação Beta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>Obtenha instruções passo a passo exibidas e muito mais com
|
||||
uma assinatura prime. Inscreva-se agora: https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>Sem local
|
||||
residência definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>Sem local de
|
||||
trabalho definido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>sem destinos recentes</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -679,10 +676,6 @@ Isso pode levar até um minuto.</translation>
|
||||
<source>Software</source>
|
||||
<translation>Software</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>Navegação</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>拒绝并卸载%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -355,42 +382,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>当前目的地</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>清空</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>最近目的地</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>试用导航测试版</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>订阅comma prime以获取导航。
|
||||
立即注册:https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>家:未设定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>工作:未设定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>无最近目的地</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -672,10 +671,6 @@ This may take up to a minute.</source>
|
||||
<source>Software</source>
|
||||
<translation>软件</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>导航</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||
@@ -116,6 +116,33 @@
|
||||
<translation>拒絕並解除安裝 %1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DestinationWidget</name>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No destination set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No %1 location set</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>home</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>work</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
<message>
|
||||
@@ -355,44 +382,14 @@
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MapPanel</name>
|
||||
<name>MapSettings</name>
|
||||
<message>
|
||||
<source>Current Destination</source>
|
||||
<translation>當前目的地</translation>
|
||||
<source>NAVIGATION</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CLEAR</source>
|
||||
<translation>清除</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Recent Destinations</source>
|
||||
<translation>最近目的地</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Try the Navigation Beta</source>
|
||||
<translation>試用導航功能</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Get turn-by-turn directions displayed and more with a comma
|
||||
prime subscription. Sign up now: https://connect.comma.ai</source>
|
||||
<translation>成為 comma 高級會員來使用導航功能
|
||||
立即註冊:https://connect.comma.ai</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No home
|
||||
location set</source>
|
||||
<translation>未設定
|
||||
住家位置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No work
|
||||
location set</source>
|
||||
<translation>未設定
|
||||
工作位置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no recent destinations</source>
|
||||
<translation>沒有最近的導航記錄</translation>
|
||||
<source>Manage at connect.comma.ai</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -674,10 +671,6 @@ This may take up to a minute.</source>
|
||||
<source>Software</source>
|
||||
<translation>軟體</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Navigation</source>
|
||||
<translation>導航</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Setup</name>
|
||||
|
||||