Cabana: support for multiple series in chart (#26538)

* customize axis x & y

* new function chartView::addSignal

* support multiple series in chartView

* more

* show legend

* update changed signals only

* fix z value

* cleanup

* limit trake line in plot area

* display signal name in value_text

* &nbsp

* fix axis y

* add spaces

* cache min max value for axis y

* cleanup

* better values text

* remove force_update
pull/26542/head
Dean Lee 2 years ago committed by GitHub
parent 187f8c177a
commit 37ad8f4f3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 316
      tools/cabana/chartswidget.cc
  2. 52
      tools/cabana/chartswidget.h
  3. 7
      tools/cabana/signaledit.cc
  4. 2
      tools/cabana/signaledit.h

@ -4,11 +4,9 @@
#include <QGraphicsLayout>
#include <QRubberBand>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QtConcurrent>
#include <QToolBar>
// ChartsWidget
@ -19,7 +17,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
// toolbar
QToolBar *toolbar = new QToolBar(tr("Charts"), this);
title_label = new QLabel();
title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred);
title_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
toolbar->addWidget(title_label);
toolbar->addWidget(range_label = new QLabel());
reset_zoom_btn = toolbar->addAction("");
@ -42,21 +40,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) {
main_layout->addWidget(charts_scroll);
QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); });
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated);
QObject::connect(dbc(), &DBCManager::msgRemoved, [this](uint32_t address) {
for (auto c : charts.toVector())
if (DBCManager::parseId(c->id).second == address) removeChart(c);
});
QObject::connect(dbc(), &DBCManager::msgUpdated, [this](uint32_t address) {
for (auto c : charts) {
if (DBCManager::parseId(c->id).second == address) c->updateTitle();
}
});
QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll);
QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged);
QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState);
QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); });
QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll);
QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset);
QObject::connect(dock_btn, &QAction::triggered, [this]() {
emit dock(!docking);
@ -81,8 +68,8 @@ void ChartsWidget::zoomIn(double min, double max) {
zoomed_range = {min, max};
is_zoomed = zoomed_range != display_range;
updateToolBar();
emit rangeChanged(min, max, is_zoomed);
updateState();
emit rangeChanged(min, max, is_zoomed);
}
void ChartsWidget::zoomReset() {
@ -108,13 +95,13 @@ void ChartsWidget::updateState() {
if (prev_range != display_range) {
QFutureSynchronizer<void> future_synchronizer;
for (auto c : charts)
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, display_range));
future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range));
}
}
const auto &range = is_zoomed ? zoomed_range : display_range;
for (auto c : charts) {
c->setRange(range.first, range.second);
c->setDisplayRange(range.first, range.second);
c->updateLineMarker(current_sec);
}
}
@ -128,49 +115,45 @@ void ChartsWidget::updateToolBar() {
dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts"));
}
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) {
auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; });
if (it != charts.end()) {
if (!show) removeChart((*it));
} else if (show) {
auto chart = new ChartView(id, sig, this);
chart->updateSeries(display_range);
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
charts_layout->insertWidget(0, chart);
charts.push_back(chart);
emit chartOpened(chart->id, chart->signal);
ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) {
for (auto c : charts)
if (c->hasSeries(id, sig)) return c;
return nullptr;
}
void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) {
if (!show) {
if (ChartView *chart = findChart(id, sig)) {
chart->removeSeries(id, sig);
}
} else {
ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr;
if (!chart) {
chart = new ChartView(this);
chart->setEventsRange(display_range);
QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); });
QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn);
QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset);
QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::chartClosed);
charts_layout->insertWidget(0, chart);
charts.push_back(chart);
}
chart->addSeries(id, sig);
emit chartOpened(id, sig);
updateState();
}
updateToolBar();
}
bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) {
auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; });
return it != charts.end();
}
void ChartsWidget::removeChart(ChartView *chart) {
charts.removeOne(chart);
chart->deleteLater();
updateToolBar();
emit chartClosed(chart->id, chart->signal);
}
void ChartsWidget::removeAll(const Signal *sig) {
void ChartsWidget::removeAll() {
for (auto c : charts.toVector())
if (!sig || c->signal == sig) removeChart(c);
}
void ChartsWidget::signalUpdated(const Signal *sig) {
for (auto c : charts) {
if (c->signal == sig) {
c->updateTitle();
c->updateSeries(display_range);
c->setRange(display_range.first, display_range.second, true);
}
}
removeChart(c);
}
bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
@ -183,14 +166,14 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) {
// ChartView
ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
: id(id), signal(sig), QChartView(nullptr, parent) {
QLineSeries *series = new QLineSeries();
ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) {
QChart *chart = new QChart();
chart->setBackgroundRoundness(0);
chart->addSeries(series);
chart->createDefaultAxes();
chart->legend()->hide();
axis_x = new QValueAxis(this);
axis_y = new QValueAxis(this);
chart->addAxis(axis_x, Qt::AlignBottom);
chart->addAxis(axis_y, Qt::AlignLeft);
chart->legend()->setShowToolTips(true);
chart->layout()->setContentsMargins(0, 0, 0, 0);
// top margin for title
chart->setMargins({0, 11, 0, 0});
@ -213,33 +196,108 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent)
remove_btn->setToolTip(tr("Remove Chart"));
close_btn_proxy = new QGraphicsProxyWidget(chart);
close_btn_proxy->setWidget(remove_btn);
close_btn_proxy->setZValue(chart->zValue() + 11);
setChart(chart);
setRenderHint(QPainter::Antialiasing);
setRubberBand(QChartView::HorizontalRubberBand);
updateFromSettings();
updateTitle();
QTimer *timer = new QTimer(this);
timer->setInterval(100);
timer->setSingleShot(true);
timer->callOnTimeout(this, &ChartView::adjustChartMargins);
QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved);
QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated);
QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved);
QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated);
QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings);
QObject::connect(remove_btn, &QToolButton::clicked, [=]() { emit remove(id, sig); });
QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove);
QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) {
// use a singleshot timer to avoid recursion call.
timer->start();
});
}
ChartView::~ChartView() {
for (auto &s : sigs)
emit seriesRemoved(s.msg_id, s.sig);
}
void ChartView::addSeries(const QString &msg_id, const Signal *sig) {
QLineSeries *series = new QLineSeries(this);
chart()->addSeries(series);
series->attachAxis(axis_x);
series->attachAxis(axis_y);
auto [source, address] = DBCManager::parseId(msg_id);
sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series});
updateTitle();
updateSeries(sig);
}
void ChartView::removeSeries(const QString &msg_id, const Signal *sig) {
auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
if (it != sigs.end()) {
it = removeSeries(it);
}
}
bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const {
auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; });
return it != sigs.end();
}
QList<ChartView::SigItem>::iterator ChartView::removeSeries(const QList<ChartView::SigItem>::iterator &it) {
chart()->removeSeries(it->series);
it->series->deleteLater();
emit seriesRemoved(it->msg_id, it->sig);
auto ret = sigs.erase(it);
if (!sigs.isEmpty()) {
updateAxisY();
} else {
emit remove();
}
return ret;
}
void ChartView::signalUpdated(const Signal *sig) {
auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; });
if (it != sigs.end()) {
updateTitle();
// TODO: don't update series if only name changed.
updateSeries(sig);
}
}
void ChartView::signalRemoved(const Signal *sig) {
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
it = (it->sig == sig) ? removeSeries(it) : ++it;
}
}
void ChartView::msgUpdated(uint32_t address) {
auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.address == address; });
if (it != sigs.end())
updateTitle();
}
void ChartView::msgRemoved(uint32_t address) {
for (auto it = sigs.begin(); it != sigs.end(); /**/) {
it = (it->address == address) ? removeSeries(it) : ++it;
}
}
void ChartView::resizeEvent(QResizeEvent *event) {
QChartView::resizeEvent(event);
close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8);
}
void ChartView::updateTitle() {
chart()->setTitle(tr("<font color=\"gray\" text-align:left>%1 %2</font> <b>%3</b>").arg(dbc()->msg(id)->name).arg(id).arg(signal->name.c_str()));
for (auto &s : sigs) {
s.series->setName(QString("<b>%1</b> <font color=\"gray\">%2 %3</font>").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id));
}
}
void ChartView::updateFromSettings() {
@ -249,9 +307,15 @@ void ChartView::updateFromSettings() {
line_marker->setPen(QPen(color, 2));
}
void ChartView::setRange(double min, double max, bool force_update) {
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
if (force_update || (min != axis_x->min() || max != axis_x->max())) {
void ChartView::setEventsRange(const std::pair<double, double> &range) {
if (range != events_range) {
events_range = range;
updateSeries();
}
}
void ChartView::setDisplayRange(double min, double max) {
if (min != axis_x->min() || max != axis_x->max()) {
axis_x->setRange(min, max);
updateAxisY();
}
@ -268,7 +332,6 @@ void ChartView::adjustChartMargins() {
}
void ChartView::updateLineMarker(double current_sec) {
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
int x = chart()->plotArea().left() +
chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min());
if (int(line_marker->line().x1()) != x) {
@ -276,48 +339,72 @@ void ChartView::updateLineMarker(double current_sec) {
}
}
void ChartView::updateSeries(const std::pair<double, double> range) {
void ChartView::updateSeries(const Signal *sig) {
auto events = can->events();
if (!events) return;
vals.clear();
vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz
auto [bus, address] = DBCManager::parseId(id);
double route_start_time = can->routeStartTime();
Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9);
auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
double end_ns = (route_start_time + range.second) * 1e9;
for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) {
if ((*it)->which == cereal::Event::Which::CAN) {
for (const auto &c : (*it)->event.getCan()) {
if (bus == c.getSrc() && address == c.getAddress()) {
auto dat = c.getDat();
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal);
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
vals.push_back({ts, value});
for (int i = 0; i < sigs.size(); ++i) {
if (auto &s = sigs[i]; !sig || s.sig == sig) {
s.vals.clear();
s.vals.reserve((events_range.second - events_range.first) * 1000); // [n]seconds * 1000hz
s.min_y = std::numeric_limits<double>::max();
s.max_y = std::numeric_limits<double>::lowest();
double route_start_time = can->routeStartTime();
Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + events_range.first) * 1e9);
auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan());
double end_ns = (route_start_time + events_range.second) * 1e9;
for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) {
if ((*it)->which == cereal::Event::Which::CAN) {
for (const auto &c : (*it)->event.getCan()) {
if (s.source == c.getSrc() && s.address == c.getAddress()) {
auto dat = c.getDat();
double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig);
double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds
s.vals.push_back({ts, value});
if (value < s.min_y) s.min_y = value;
if (value > s.max_y) s.max_y = value;
}
}
}
}
QLineSeries *series = (QLineSeries *)chart()->series()[i];
series->replace(s.vals);
}
}
QLineSeries *series = (QLineSeries *)chart()->series()[0];
series->replace(vals);
updateAxisY();
}
// auto zoom on yaxis
void ChartView::updateAxisY() {
const auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
const auto axis_y = dynamic_cast<QValueAxis *>(chart()->axisY());
auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
if (begin == vals.end())
return;
auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); });
const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); });
if (max->y() == min->y()) {
axis_y->setRange(min->y() - 1, max->y() + 1);
double min_y = std::numeric_limits<double>::max();
double max_y = std::numeric_limits<double>::lowest();
if (events_range == std::pair{axis_x->min(), axis_x->max()}) {
for (auto &s : sigs) {
if (s.min_y < min_y) min_y = s.min_y;
if (s.max_y > max_y) max_y = s.max_y;
}
} else {
double range = max->y() - min->y();
axis_y->setRange(min->y() - range * 0.05, max->y() + range * 0.05);
for (auto &s : sigs) {
auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; });
if (begin == s.vals.end())
return;
auto end = std::upper_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); });
const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); });
if (min->y() < min_y) min_y = min->y();
if (max->y() > max_y) max_y = max->y();
}
}
if (max_y == min_y) {
axis_y->setRange(min_y - 1, max_y + 1);
} else {
double range = max_y - min_y;
axis_y->setRange(min_y - range * 0.05, max_y + range * 0.05);
axis_y->applyNiceNumbers();
}
}
@ -333,7 +420,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
rubber->hide();
QRectF rect = rubber->geometry().normalized();
rect.translate(-chart()->plotArea().topLeft());
const auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
double min = axis_x->min() + (rect.left() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min());
double max = axis_x->min() + (rect.right() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min());
if (rubber->width() <= 0) {
@ -357,26 +443,40 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
void ChartView::mouseMoveEvent(QMouseEvent *ev) {
auto rubber = findChild<QRubberBand *>();
bool is_zooming = rubber && rubber->isVisible();
if (!is_zooming) {
const auto plot_area = chart()->plotArea();
auto axis_x = dynamic_cast<QValueAxis *>(chart()->axisX());
const auto plot_area = chart()->plotArea();
if (!is_zooming && plot_area.contains(ev->pos())) {
double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1);
double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width();
auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); });
if (value != vals.end()) {
QPointF pos = chart()->mapToPosition((*value));
QStringList text_list;
QPointF pos = plot_area.bottomRight();
double tm = 0.0;
for (auto &s : sigs) {
auto value = std::upper_bound(s.vals.begin(), s.vals.end(), sec, [](double x, auto &p) { return x < p.x(); });
if (value != s.vals.end()) {
text_list.push_back(QString("&nbsp;%1 : %2&nbsp;").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value->y()));
tm = value->x();
auto y_pos = chart()->mapToPosition(*value);
if (y_pos.y() < pos.y()) pos = y_pos;
}
}
if (!text_list.isEmpty()) {
value_text->setHtml("<div style=\"background-color: darkGray;color: white;\">&nbsp;Time: " +
QString::number(tm, 'f', 3) + "&nbsp;<br />" + text_list.join("<br />") + "</div>");
track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom());
track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10);
value_text->setHtml(tr("<div style='background-color:darkGray'><font color='white'>%1, %2)</font></div>")
.arg(value->x(), 0, 'f', 3).arg(value->y()));
int text_x = pos.x() + 8;
if ((text_x + value_text->boundingRect().width()) > plot_area.right()) {
text_x = pos.x() - value_text->boundingRect().width() - 8;
QRectF text_rect = value_text->boundingRect();
if ((text_x + text_rect.width()) > plot_area.right()) {
text_x = pos.x() - text_rect.width() - 8;
}
value_text->setPos(text_x, pos.y() - 10);
value_text->setPos(text_x, pos.y() - text_rect.height() / 2);
track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10);
}
item_group->setVisible(value != vals.end());
item_group->setVisible(!text_list.isEmpty());
} else {
item_group->setVisible(false);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
}
QChartView::mouseMoveEvent(ev);

@ -8,6 +8,8 @@
#include <QPushButton>
#include <QVBoxLayout>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include "tools/cabana/canmessages.h"
#include "tools/cabana/dbcmanager.h"
@ -18,35 +20,59 @@ class ChartView : public QChartView {
Q_OBJECT
public:
ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr);
void updateSeries(const std::pair<double, double> range);
void setRange(double min, double max, bool force_update = false);
ChartView(QWidget *parent = nullptr);
~ChartView();
void addSeries(const QString &msg_id, const Signal *sig);
void removeSeries(const QString &msg_id, const Signal *sig);
bool hasSeries(const QString &msg_id, const Signal *sig) const;
void updateSeries(const Signal *sig = nullptr);
void setEventsRange(const std::pair<double, double> &range);
void setDisplayRange(double min, double max);
void updateLineMarker(double current_sec);
void updateFromSettings();
void updateTitle();
QString id;
const Signal *signal;
struct SigItem {
QString msg_id;
uint8_t source = 0;
uint32_t address = 0;
const Signal *sig = nullptr;
QLineSeries *series = nullptr;
double min_y = 0;
double max_y = 0;
QVector<QPointF> vals;
};
signals:
void seriesRemoved(const QString &id, const Signal *sig);
void zoomIn(double min, double max);
void zoomReset();
void remove(const QString &msg_id, const Signal *sig);
void remove();
private slots:
void msgRemoved(uint32_t address);
void msgUpdated(uint32_t address);
void signalUpdated(const Signal *sig);
void signalRemoved(const Signal *sig);
private:
QList<ChartView::SigItem>::iterator removeSeries(const QList<ChartView::SigItem>::iterator &it);
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *ev) override;
void leaveEvent(QEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void adjustChartMargins();
void updateAxisY();
void updateTitle();
void updateFromSettings();
QValueAxis *axis_x;
QValueAxis *axis_y;
QGraphicsItemGroup *item_group;
QGraphicsLineItem *line_marker, *track_line;
QGraphicsEllipseItem *track_ellipse;
QGraphicsTextItem *value_text;
QGraphicsProxyWidget *close_btn_proxy;
QVector<QPointF> vals;
std::pair<double, double> events_range = {0, 0};
QList<SigItem> sigs;
};
class ChartsWidget : public QWidget {
@ -54,9 +80,9 @@ class ChartsWidget : public QWidget {
public:
ChartsWidget(QWidget *parent = nullptr);
void showChart(const QString &id, const Signal *sig, bool show);
void showChart(const QString &id, const Signal *sig, bool show, bool merge);
void removeChart(ChartView *chart);
bool isChartOpened(const QString &id, const Signal *sig);
inline bool isChartOpened(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; }
signals:
void dock(bool floating);
@ -69,10 +95,10 @@ private:
void updateState();
void zoomIn(double min, double max);
void zoomReset();
void signalUpdated(const Signal *sig);
void updateToolBar();
void removeAll(const Signal *sig = nullptr);
void removeAll();
bool eventFilter(QObject *obj, QEvent *event) override;
ChartView *findChart(const QString &id, const Signal *sig);
QLabel *title_label;
QLabel *range_label;

@ -2,6 +2,7 @@
#include <QDoubleValidator>
#include <QFormLayout>
#include <QGuiApplication>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QToolBar>
@ -122,7 +123,9 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa
save_timer->callOnTimeout(this, &SignalEdit::saveSignal);
QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked);
QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); });
QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) {
emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier);
});
QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); });
QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); });
QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); });
@ -172,7 +175,7 @@ void SignalEdit::saveSignal() {
}
void SignalEdit::setChartOpened(bool opened) {
plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot"));
plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened chart"));
plot_btn->setChecked(opened);
}

@ -42,7 +42,7 @@ public:
signals:
void highlight(const Signal *sig);
void showChart(const QString &name, const Signal *sig, bool show);
void showChart(const QString &name, const Signal *sig, bool show, bool merge);
void remove(const Signal *sig);
void save(const Signal *sig, const Signal &new_sig);
void showFormClicked();

Loading…
Cancel
Save