diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index a3faab0a20..79b40133a5 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -196,6 +196,14 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { close_btn_proxy->setWidget(remove_btn); close_btn_proxy->setZValue(chart->zValue() + 11); + QToolButton *manage_btn = new QToolButton(); + manage_btn->setText("🔧"); + manage_btn->setAutoRaise(true); + manage_btn->setToolTip(tr("Manage series")); + manage_btn_proxy = new QGraphicsProxyWidget(chart); + manage_btn_proxy->setWidget(manage_btn); + manage_btn_proxy->setZValue(chart->zValue() + 11); + setChart(chart); setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::HorizontalRubberBand); @@ -207,6 +215,7 @@ ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); + QObject::connect(manage_btn, &QToolButton::clicked, this, &ChartView::manageSeries); } ChartView::~ChartView() { @@ -278,9 +287,41 @@ void ChartView::msgRemoved(uint32_t address) { } } +void ChartView::manageSeries() { + SeriesSelector dlg(this); + for (auto &s : sigs) { + dlg.addSeries(s.msg_id, msgName(s.msg_id), QString::fromStdString(s.sig->name)); + } + + int ret = dlg.exec(); + if (ret == QDialog::Accepted) { + QList series_list = dlg.series(); + if (series_list.isEmpty()) { + emit remove(); + } else { + for (auto &s : series_list) { + if (auto m = dbc()->msg(s[0])) { + auto it = m->sigs.find(s[2]); + if (it != m->sigs.end() && !hasSeries(s[0], &(it->second))) { + addSeries(s[0], &(it->second)); + } + } + } + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + bool exists = std::any_of(series_list.cbegin(), series_list.cend(), [&](auto &s) { + return s[0] == it->msg_id && s[2] == it->sig->name.c_str(); + }); + it = exists ? ++it : removeSeries(it); + } + } + } +} + void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); - close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); + int x = event->size().width() - close_btn_proxy->size().width() - 11; + close_btn_proxy->setPos(x, 8); + manage_btn_proxy->setPos(x - manage_btn_proxy->size().width() - 5, 8); } void ChartView::updateTitle() { @@ -462,3 +503,93 @@ void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { painter->setPen(QPen(chart()->titleBrush().color(), 2)); painter->drawLine(QPointF{x, chart()->plotArea().top() - 2}, QPointF{x, chart()->plotArea().bottom() + 2}); } + +// SeriesSelector + +SeriesSelector::SeriesSelector(QWidget *parent) { + setWindowTitle(tr("Manage Chart Series")); + QHBoxLayout *contents_layout = new QHBoxLayout(); + + QVBoxLayout *left_layout = new QVBoxLayout(); + left_layout->addWidget(new QLabel(tr("Select Signals:"))); + msgs_combo = new QComboBox(this); + left_layout->addWidget(msgs_combo); + sig_list = new QListWidget(this); + sig_list->setSortingEnabled(true); + sig_list->setToolTip(tr("Double click on an item to add signal to chart")); + left_layout->addWidget(sig_list); + + QVBoxLayout *right_layout = new QVBoxLayout(); + right_layout->addWidget(new QLabel(tr("Chart Signals:"))); + chart_series = new QListWidget(this); + chart_series->setSortingEnabled(true); + chart_series->setToolTip(tr("Double click on an item to remove signal from chart")); + right_layout->addWidget(chart_series); + contents_layout->addLayout(left_layout); + contents_layout->addLayout(right_layout); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addLayout(contents_layout); + main_layout->addWidget(buttonBox); + + for (auto it = can->can_msgs.cbegin(); it != can->can_msgs.cend(); ++it) { + if (auto m = dbc()->msg(it.key())) { + msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key()), it.key()); + } + } + msgs_combo->model()->sort(0); + + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + QObject::connect(msgs_combo, SIGNAL(currentIndexChanged(int)), SLOT(msgSelected(int))); + QObject::connect(sig_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::addSignal); + QObject::connect(chart_series, &QListWidget::itemDoubleClicked, [](QListWidgetItem *item) { delete item; }); + + if (int index = msgs_combo->currentIndex(); index >= 0) { + msgSelected(index); + } +} + +void SeriesSelector::msgSelected(int index) { + QString msg_id = msgs_combo->itemData(index).toString(); + sig_list->clear(); + if (auto m = dbc()->msg(msg_id)) { + for (auto &[name, s] : m->sigs) { + QStringList data({msg_id, m->name, name}); + QListWidgetItem *item = new QListWidgetItem(name, sig_list); + item->setData(Qt::UserRole, data); + sig_list->addItem(item); + } + } +} + +void SeriesSelector::addSignal(QListWidgetItem *item) { + QStringList data = item->data(Qt::UserRole).toStringList(); + addSeries(data[0], data[1], data[2]); +} + +void SeriesSelector::addSeries(const QString &id, const QString& msg_name, const QString &sig_name) { + QStringList data({id, msg_name, sig_name}); + for (int i = 0; i < chart_series->count(); ++i) { + if (chart_series->item(i)->data(Qt::UserRole).toStringList() == data) { + return; + } + } + QListWidgetItem *new_item = new QListWidgetItem(chart_series); + new_item->setData(Qt::UserRole, data); + chart_series->addItem(new_item); + QLabel *label = new QLabel(QString("%0 %1 %2").arg(data[2]).arg(data[1]).arg(data[0]), chart_series); + label->setContentsMargins(5, 0, 5, 0); + new_item->setSizeHint(label->sizeHint()); + chart_series->setItemWidget(new_item, label); +} + +QList SeriesSelector::series() { + QList ret; + for (int i = 0; i < chart_series->count(); ++i) { + ret.push_back(chart_series->item(i)->data(Qt::UserRole).toStringList()); + } + return ret; +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index c3fa931e6e..3e3277c5b8 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,6 +1,9 @@ #pragma once +#include +#include #include +#include #include #include #include @@ -50,6 +53,7 @@ private slots: void msgUpdated(uint32_t address); void signalUpdated(const Signal *sig); void signalRemoved(const Signal *sig); + void manageSeries(); private: QList::iterator removeSeries(const QList::iterator &it); @@ -70,6 +74,7 @@ private: QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; + QGraphicsProxyWidget *manage_btn_proxy; std::pair events_range = {0, 0}; QList sigs; }; @@ -114,3 +119,21 @@ private: std::pair zoomed_range; bool use_dark_theme = false; }; + +class SeriesSelector : public QDialog { + Q_OBJECT + +public: + SeriesSelector(QWidget *parent); + void addSeries(const QString &id, const QString& msg_name, const QString &sig_name); + QList series(); + +private slots: + void msgSelected(int index); + void addSignal(QListWidgetItem *item); + +private: + QComboBox *msgs_combo; + QListWidget *sig_list; + QListWidget *chart_series; +}; diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 4e0bc91069..c7675121bb 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -28,6 +28,7 @@ public: static std::pair parseId(const QString &id); inline static std::vector allDBCNames() { return get_dbc_names(); } + inline std::map &allMsgs() { return msgs; } inline QString name() const { return dbc ? dbc->name.c_str() : ""; } void updateMsg(const QString &id, const QString &name, uint32_t size);