mirror of
https://github.com/dragonpilot/dragonpilot.git
synced 2026-08-22 00:33:48 +08:00
Merge branch 'min-feat/dev/model-selector' into full
This commit is contained in:
@@ -26,6 +26,7 @@ Export('widgets')
|
||||
qt_libs = [widgets, qt_util] + base_libs
|
||||
|
||||
qt_src = ["main.cc", "ui.cc", "qt/sidebar.cc", "qt/body.cc",
|
||||
"qt/offroad/model_selector.cc",
|
||||
"qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc",
|
||||
"qt/offroad/software_settings.cc", "qt/offroad/developer_panel.cc", "qt/offroad/onboarding.cc", "qt/offroad/dp_panel.cc",
|
||||
"qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc", "qt/offroad/firehose.cc",
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
// MIT Non-Commercial License
|
||||
//
|
||||
// Copyright (c) 2019, dragonpilot
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, for non-commercial purposes only, subject to the following conditions:
|
||||
//
|
||||
// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
// - Commercial use (e.g., use in a product, service, or activity intended to generate revenue) is prohibited without explicit written permission from dragonpilot. Contact ricklan@gmail.com for inquiries.
|
||||
// - Any project that uses the Software must visibly mention the following acknowledgment: "This project uses software from dragonpilot and is licensed under a custom license requiring permission for use."
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#include "selfdrive/ui/qt/offroad/model_selector.h"
|
||||
|
||||
// Define style constants to improve maintainability
|
||||
namespace {
|
||||
const QString SELECTOR_BTN_STYLE = "background-color: #00309a; font-size: 48px;";
|
||||
const QString MODEL_LIST_STYLE = "font-size: 64px;";
|
||||
const QString SCROLLBAR_STYLE = "width: 96px;";
|
||||
const QString GROUP_HEADER_BG_COLOR = "#c8c8c8"; // Light gray
|
||||
const QString GROUP_HEADER_TEXT_COLOR = "#000000"; // Black
|
||||
|
||||
// Role for storing the actual model name without indentation
|
||||
const int ModelNameRole = Qt::UserRole;
|
||||
}
|
||||
|
||||
ModelSelector::ModelSelector(QWidget *parent) : QWidget(parent) {
|
||||
setupUI();
|
||||
setupModelListPanel();
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
QWidget* ModelSelector::setupUI() {
|
||||
QVBoxLayout* main_layout = new QVBoxLayout(this);
|
||||
main_layout->addSpacing(10);
|
||||
|
||||
// Selector button
|
||||
QWidget* model_selector_btn_widget = new QWidget;
|
||||
QHBoxLayout* model_selector_btn_layout = new QHBoxLayout();
|
||||
QLabel* vehicle_model_label = new QLabel(tr("Vehicle Model:"));
|
||||
vehicle_model_label->setStyleSheet("margin-right: 2px; font-size: 48px;");
|
||||
model_selector_btn_layout->addWidget(vehicle_model_label);
|
||||
|
||||
QString model_selected = QString::fromUtf8(Params().get("dp_device_model_selected").c_str());
|
||||
model_selector_btn = new QPushButton(model_selected.isEmpty() ? tr("[AUTO DETECT]") : model_selected);
|
||||
model_selector_btn->setObjectName("ModelSelectorBtn");
|
||||
model_selector_btn->setStyleSheet(SELECTOR_BTN_STYLE);
|
||||
model_selector_btn_layout->addWidget(model_selector_btn);
|
||||
model_selector_btn_layout->setAlignment(Qt::AlignCenter);
|
||||
model_selector_btn_layout->setStretch(1, 1);
|
||||
|
||||
model_selector_btn_widget->setLayout(model_selector_btn_layout);
|
||||
main_layout->addWidget(model_selector_btn_widget);
|
||||
main_layout->addSpacing(10);
|
||||
main_layout->addStretch(); // Add stretch to push everything to the top
|
||||
|
||||
setLayout(main_layout);
|
||||
return model_selector_btn_widget;
|
||||
}
|
||||
|
||||
void ModelSelector::setupModelListPanel() {
|
||||
// Create model list panel
|
||||
model_list_panel = new QWidget();
|
||||
QVBoxLayout* model_list_layout = new QVBoxLayout(model_list_panel);
|
||||
model_list_layout->setContentsMargins(50, 25, 50, 25);
|
||||
|
||||
model_list = new QListWidget(model_list_panel);
|
||||
|
||||
// Set styles using the constants
|
||||
QString listStyle = QString("QListWidget { %1 } QScrollBar:vertical { %2 }")
|
||||
.arg(MODEL_LIST_STYLE)
|
||||
.arg(SCROLLBAR_STYLE);
|
||||
model_list->setStyleSheet(listStyle);
|
||||
|
||||
model_list->setFixedHeight(750);
|
||||
model_list_layout->addWidget(model_list);
|
||||
|
||||
model_list_frame = new ScrollView(model_list_panel, nullptr);
|
||||
}
|
||||
|
||||
void ModelSelector::loadModelList() {
|
||||
if (model_list->count() > 0) {
|
||||
// If list is already populated, just update the selection
|
||||
updateCurrentSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add auto-detect option
|
||||
QListWidgetItem* autoDetectItem = new QListWidgetItem(tr("[AUTO DETECT]"));
|
||||
autoDetectItem->setData(ModelNameRole, tr("[AUTO DETECT]"));
|
||||
model_list->addItem(autoDetectItem);
|
||||
|
||||
Params params;
|
||||
QString model_list_str = QString::fromStdString(params.get("dp_device_model_list"));
|
||||
|
||||
QJsonDocument document = QJsonDocument::fromJson(model_list_str.toUtf8());
|
||||
if (document.isArray()) {
|
||||
QJsonArray models = document.array();
|
||||
|
||||
for (const auto& groupValue : models) {
|
||||
QJsonObject group = groupValue.toObject();
|
||||
QString groupName = group["group"].toString();
|
||||
|
||||
// Add group header item
|
||||
QListWidgetItem* groupHeader = new QListWidgetItem(groupName);
|
||||
groupHeader->setFlags(Qt::NoItemFlags); // Make non-selectable
|
||||
groupHeader->setBackground(QColor(GROUP_HEADER_BG_COLOR));
|
||||
groupHeader->setForeground(QColor(GROUP_HEADER_TEXT_COLOR));
|
||||
groupHeader->setTextAlignment(Qt::AlignCenter);
|
||||
model_list->addItem(groupHeader);
|
||||
|
||||
// Add models in this group
|
||||
QJsonArray groupModels = group["models"].toArray();
|
||||
for (const auto& model : groupModels) {
|
||||
QString modelName = model.toString();
|
||||
// Create item with visual indentation
|
||||
QListWidgetItem* modelItem = new QListWidgetItem(" " + modelName);
|
||||
// Store actual model name without indentation in user role
|
||||
modelItem->setData(ModelNameRole, modelName);
|
||||
model_list->addItem(modelItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the current selection after loading
|
||||
updateCurrentSelection();
|
||||
}
|
||||
|
||||
// New helper method to update the selection
|
||||
void ModelSelector::updateCurrentSelection() {
|
||||
// Get the currently selected model from params
|
||||
Params params;
|
||||
QString currentModel = QString::fromStdString(params.get("dp_device_model_selected"));
|
||||
|
||||
// If empty, select the AUTO DETECT option
|
||||
if (currentModel.isEmpty()) {
|
||||
model_list->setCurrentRow(0); // AUTO DETECT is the first item
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, find and select the matching model
|
||||
for (int i = 0; i < model_list->count(); i++) {
|
||||
QListWidgetItem* item = model_list->item(i);
|
||||
// Only check selectable items (not group headers)
|
||||
if (item->flags() & Qt::ItemIsSelectable) {
|
||||
QString modelName = item->data(ModelNameRole).toString();
|
||||
if (modelName == currentModel) {
|
||||
model_list->setCurrentItem(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelSelector::clearModelList() {
|
||||
model_list->clear();
|
||||
}
|
||||
|
||||
void ModelSelector::updateButtonText(const QString& text) {
|
||||
model_selector_btn->setText(text);
|
||||
}
|
||||
|
||||
QWidget* ModelSelector::getModelListPanel() {
|
||||
return model_list_frame;
|
||||
}
|
||||
|
||||
void ModelSelector::setPanelWidget(QStackedWidget* panel) {
|
||||
panel_widget = panel;
|
||||
if (panel_widget && model_list_frame->parent() != panel_widget) {
|
||||
model_list_frame->setParent(panel_widget);
|
||||
panel_widget->addWidget(model_list_frame);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelSelector::setNavButtonGroup(QButtonGroup* buttons) {
|
||||
nav_btns = buttons;
|
||||
}
|
||||
|
||||
void ModelSelector::connectSignals() {
|
||||
connect(model_selector_btn, &QPushButton::clicked, [this]() {
|
||||
if (panel_widget) {
|
||||
// Load the model list when needed
|
||||
loadModelList();
|
||||
panel_widget->setCurrentWidget(model_list_frame);
|
||||
}
|
||||
emit buttonClicked();
|
||||
});
|
||||
|
||||
connect(model_list, &QListWidget::itemClicked, [this](QListWidgetItem* item) {
|
||||
// Only process clicks on selectable items (not group headers)
|
||||
if (item->flags() & Qt::ItemIsSelectable) {
|
||||
// Get model name from the data role rather than trimming text
|
||||
QString model_name = item->data(ModelNameRole).toString();
|
||||
QString param_value = (model_name == tr("[AUTO DETECT]")) ? QString() : model_name;
|
||||
|
||||
// Update param and button text
|
||||
Params().put("dp_device_model_selected", param_value.toStdString());
|
||||
updateButtonText(param_value.isEmpty() ? tr("[AUTO DETECT]") : model_name);
|
||||
|
||||
// Emit signal that model was selected
|
||||
emit modelSelected(model_name);
|
||||
|
||||
// Go back to the previous panel
|
||||
if (nav_btns && nav_btns->checkedButton()) {
|
||||
nav_btns->checkedButton()->click();
|
||||
} else if (nav_btns && nav_btns->buttons().size() > 0) {
|
||||
// Default to first panel if none selected
|
||||
nav_btns->buttons().first()->click();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (model_list_frame) {
|
||||
model_list_frame->installEventFilter(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool ModelSelector::eventFilter(QObject *obj, QEvent *event) {
|
||||
if (obj == model_list_frame) {
|
||||
if (event->type() == QEvent::Hide) {
|
||||
clearModelList();
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// MIT Non-Commercial License
|
||||
//
|
||||
// Copyright (c) 2019, dragonpilot
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, for non-commercial purposes only, subject to the following conditions:
|
||||
//
|
||||
// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
// - Commercial use (e.g., use in a product, service, or activity intended to generate revenue) is prohibited without explicit written permission from dragonpilot. Contact ricklan@gmail.com for inquiries.
|
||||
// - Any project that uses the Software must visibly mention the following acknowledgment: "This project uses software from dragonpilot and is licensed under a custom license requiring permission for use."
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QListWidgetItem>
|
||||
#include <QDebug>
|
||||
#include <QStackedWidget>
|
||||
#include <QButtonGroup> // Add this include for QButtonGroup
|
||||
#include <QEvent>
|
||||
#include "common/params.h"
|
||||
#include "selfdrive/ui/qt/widgets/scrollview.h"
|
||||
|
||||
class ModelSelector : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ModelSelector(QWidget *parent = nullptr);
|
||||
|
||||
// Get the model list panel widget
|
||||
QWidget* getModelListPanel();
|
||||
|
||||
// Set the panel widget to switch to when selecting models
|
||||
void setPanelWidget(QStackedWidget* panel);
|
||||
|
||||
// Set the button group to return to after model selection
|
||||
void setNavButtonGroup(QButtonGroup* nav_btns);
|
||||
|
||||
signals:
|
||||
void buttonClicked();
|
||||
void modelSelected(const QString& model_name);
|
||||
|
||||
private:
|
||||
QPushButton* model_selector_btn;
|
||||
QListWidget* model_list;
|
||||
QWidget* model_list_panel;
|
||||
ScrollView* model_list_frame;
|
||||
QStackedWidget* panel_widget = nullptr;
|
||||
QButtonGroup* nav_btns = nullptr;
|
||||
|
||||
QWidget* setupUI();
|
||||
void setupModelListPanel();
|
||||
void loadModelList();
|
||||
void updateCurrentSelection();
|
||||
void clearModelList();
|
||||
void connectSignals();
|
||||
void updateButtonText(const QString& text);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
};
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "selfdrive/ui/qt/offroad/developer_panel.h"
|
||||
#include "selfdrive/ui/qt/offroad/firehose.h"
|
||||
#include "selfdrive/ui/qt/offroad/dp_panel.h"
|
||||
#include "selfdrive/ui/qt/offroad/model_selector.h"
|
||||
|
||||
TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
// param, title, desc, icon, restart needed
|
||||
@@ -482,7 +483,26 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
|
||||
|
||||
sidebar_widget->setFixedWidth(500);
|
||||
main_layout->addWidget(sidebar_widget);
|
||||
main_layout->addWidget(panel_widget);
|
||||
|
||||
// Create right column with model selector on top and panel_widget below
|
||||
QWidget* right_column = new QWidget(this);
|
||||
QVBoxLayout* right_layout = new QVBoxLayout(right_column);
|
||||
right_layout->setContentsMargins(0, 0, 0, 0);
|
||||
right_layout->setSpacing(20); // Space between model selector and panel
|
||||
|
||||
// Create the ModelSelector button at the top of right column
|
||||
ModelSelector* model_selector = new ModelSelector(this);
|
||||
right_layout->addWidget(model_selector);
|
||||
|
||||
// Set up panel widget and nav button references
|
||||
model_selector->setPanelWidget(panel_widget);
|
||||
model_selector->setNavButtonGroup(nav_btns);
|
||||
|
||||
// Add panel_widget below the model selector
|
||||
right_layout->addWidget(panel_widget, 1); // Give panel_widget stretch priority
|
||||
|
||||
// Add right column to main layout
|
||||
main_layout->addWidget(right_column);
|
||||
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
|
||||
Reference in New Issue
Block a user