diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc index 1b46bd9da7..3ffc29916f 100644 --- a/tools/cabana/canmessages.cc +++ b/tools/cabana/canmessages.cc @@ -28,7 +28,7 @@ bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool replay = new Replay(route, {"can", "roadEncodeIdx", "carParams"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); replay->setSegmentCacheLimit(settings.cached_segment_limit); replay->installEventFilter(event_filter, this); - QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::segmentsMerged); + QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::eventsMerged); if (replay->load()) { replay->start(); return true; @@ -63,41 +63,26 @@ QList CANMessages::findSignalValues(const QString &id, const Signal *si return ret; } -void CANMessages::process(QHash> *messages) { +void CANMessages::process(QHash *messages) { for (auto it = messages->begin(); it != messages->end(); ++it) { - auto &msgs = can_msgs[it.key()]; - const auto &new_msgs = it.value(); - if (new_msgs.size() == settings.can_msg_log_size || msgs.empty()) { - msgs = std::move(new_msgs); - } else { - msgs.insert(msgs.begin(), std::make_move_iterator(new_msgs.begin()), std::make_move_iterator(new_msgs.end())); - if (msgs.size() > settings.can_msg_log_size) { - msgs.resize(settings.can_msg_log_size); - } - } + can_msgs[it.key()] = it.value(); } delete messages; - - if (current_sec < begin_sec || current_sec > end_sec) { - // loop replay in selected range. - seekTo(begin_sec); - } else { - emit updated(); - } + emit updated(); } bool CANMessages::eventFilter(const Event *event) { + static std::unique_ptr> new_msgs; static double prev_update_ts = 0; - // drop packets when the GUI thread is calling seekTo. to make sure the current_sec is accurate. - if (!seeking && event->which == cereal::Event::Which::CAN) { - if (!received_msgs) { - received_msgs.reset(new QHash>); - received_msgs->reserve(1000); + + if (event->which == cereal::Event::Which::CAN) { + if (!new_msgs) { + new_msgs.reset(new QHash); + new_msgs->reserve(1000); } - current_sec = (event->mono_time - replay->routeStartTime()) / (double)1e9; - if (counters_begin_sec > current_sec) { - // clear counters + double current_sec = replay->currentSeconds(); + if (counters_begin_sec == 0) { counters.clear(); counters_begin_sec = current_sec; } @@ -105,8 +90,10 @@ bool CANMessages::eventFilter(const Event *event) { auto can_events = event->event.getCan(); for (const auto &c : can_events) { QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); - auto &list = (*received_msgs)[id]; - while (list.size() >= settings.can_msg_log_size) { + + std::lock_guard lk(lock); + auto &list = received_msgs[id]; + while (list.size() > settings.can_msg_log_size) { list.pop_back(); } CanData &data = list.emplace_front(); @@ -119,49 +106,27 @@ bool CANMessages::eventFilter(const Event *event) { if (double delta = (current_sec - counters_begin_sec); delta > 0) { data.freq = count / delta; } + (*new_msgs)[id] = data; } double ts = millis_since_boot(); if ((ts - prev_update_ts) > (1000.0 / settings.fps)) { prev_update_ts = ts; // use pointer to avoid data copy in queued connection. - emit received(received_msgs.release()); + emit received(new_msgs.release()); } } return true; } -void CANMessages::seekTo(double ts) { - seeking = true; - replay->seekTo(ts, false); - seeking = false; +const std::deque CANMessages::messages(const QString &id) { + std::lock_guard lk(lock); + return received_msgs[id]; } -void CANMessages::setRange(double min, double max) { - if (begin_sec != min || end_sec != max) { - begin_sec = min; - end_sec = max; - is_zoomed = begin_sec != event_begin_sec || end_sec != event_end_sec; - emit rangeChanged(min, max); - } -} - -void CANMessages::segmentsMerged() { - auto events = replay->events(); - if (!events || events->empty()) return; - - auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); - event_begin_sec = it == events->end() ? 0 : ((*it)->mono_time - replay->routeStartTime()) / (double)1e9; - event_end_sec = double(events->back()->mono_time - replay->routeStartTime()) / 1e9; - if (!is_zoomed) { - begin_sec = event_begin_sec; - end_sec = event_end_sec; - } - emit eventsMerged(); -} - -void CANMessages::resetRange() { - setRange(event_begin_sec, event_end_sec); +void CANMessages::seekTo(double ts) { + replay->seekTo(ts, false); + counters_begin_sec = 0; } void CANMessages::settingChanged() { diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index d1bb1b73b6..5fbccdbe12 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -29,20 +29,16 @@ public: ~CANMessages(); bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); void seekTo(double ts); - void resetRange(); - void setRange(double min, double max); QList findSignalValues(const QString&id, const Signal* signal, double value, FindFlags flag, int max_count); bool eventFilter(const Event *event); - inline std::pair range() const { return {begin_sec, end_sec}; } inline QString route() const { return routeName; } inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } inline double totalSeconds() const { return replay->totalSeconds(); } inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } - inline double currentSec() const { return current_sec; } - inline bool isZoomed() const { return is_zoomed; } - inline const std::deque &messages(const QString &id) { return can_msgs[id]; } - inline const CanData &lastMessage(const QString &id) { return can_msgs[id].front(); } + inline double currentSec() const { return replay->currentSeconds(); } + const std::deque messages(const QString &id); + inline const CanData &lastMessage(const QString &id) { return can_msgs[id]; } inline const std::vector *events() const { return replay->events(); } inline void setSpeed(float speed) { replay->setSpeed(speed); } @@ -52,32 +48,23 @@ public: signals: void eventsMerged(); - void rangeChanged(double min, double max); void updated(); - void received(QHash> *); + void received(QHash *); public: - QMap> can_msgs; - std::unique_ptr>> received_msgs = nullptr; + QMap can_msgs; protected: - void process(QHash> *); - void segmentsMerged(); + void process(QHash *); void settingChanged(); - std::atomic current_sec = 0.; - std::atomic seeking = false; - - double begin_sec = 0; - double end_sec = 0; - double event_begin_sec = 0; - double event_end_sec = 0; - bool is_zoomed = false; QString routeName; Replay *replay = nullptr; - double counters_begin_sec = std::numeric_limits::max(); + std::mutex lock; + std::atomic counters_begin_sec = 0; QHash counters; + QHash> received_msgs; }; inline QString toHex(const QByteArray &dat) { diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index f0bef36f4a..ea44a4033c 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/chartswidget.h" #include +#include #include #include #include @@ -57,47 +58,85 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); }); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll); - QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const Signal *sig) { - for (auto chart : charts) { - if (chart->signal == sig) { - chart->chart_view->updateSeries(); - } - } - }); - QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &id) { - for (auto chart : charts) { - if (chart->id == id) - chart->updateTitle(); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated); + QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &msg_id) { + for (auto c : charts) { + if (c->id == msg_id) c->updateTitle(); } }); - - QObject::connect(can, &CANMessages::rangeChanged, [this]() { updateTitleBar(); }); - QObject::connect(reset_zoom_btn, &QPushButton::clicked, can, &CANMessages::resetRange); + QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged); + QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); QObject::connect(remove_all_btn, &QPushButton::clicked, [this]() { removeAll(); }); + QObject::connect(reset_zoom_btn, &QPushButton::clicked, this, &ChartsWidget::zoomReset); QObject::connect(dock_btn, &QPushButton::clicked, [this]() { emit dock(!docking); docking = !docking; updateTitleBar(); }); - QObject::connect(&settings, &Settings::changed, [this]() { - for (auto chart : charts) { - chart->setHeight(settings.chart_height); +} + +void ChartsWidget::eventsMerged() { + if (auto events = can->events(); events && !events->empty()) { + auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); + event_range.first = it == events->end() ? 0 : (*it)->mono_time / (double)1e9 - can->routeStartTime(); + event_range.second = it == events->end() ? 0 : events->back()->mono_time / (double)1e9 - can->routeStartTime(); + if (display_range.first == 0 && event_range.second == 0) { + display_range.first = event_range.first; + display_range.second = std::min(event_range.first + settings.max_chart_x_range, event_range.second); } - }); + } +} + +void ChartsWidget::zoomIn(double min, double max) { + zoomed_range = {min, max}; + is_zoomed = zoomed_range != display_range; + updateTitleBar(); + emit rangeChanged(min, max, is_zoomed); + updateState(); +} + +void ChartsWidget::zoomReset() { + zoomIn(display_range.first, display_range.second); +} + +void ChartsWidget::updateState() { + if (charts.isEmpty()) return; + + const double current_sec = can->currentSec(); + if (is_zoomed) { + if (current_sec < zoomed_range.first || current_sec >= zoomed_range.second) { + can->seekTo(zoomed_range.first); + } + } else { + auto prev_range = display_range; + if (current_sec < display_range.first || current_sec >= (display_range.second - 5)) { + // line marker reached the end, or seeked to a timestamp out of range. + display_range.first = current_sec - 5; + } + display_range.first = std::max(display_range.first, event_range.first); + display_range.second = std::min(display_range.first + settings.max_chart_x_range, event_range.second); + if (prev_range != display_range) { + for (auto c : charts) + c->chart_view->updateSeries(display_range); + } + } + + const auto &range = is_zoomed ? zoomed_range : display_range; + for (auto c : charts) { + c->chart_view->setRange(range.first, range.second); + c->chart_view->updateLineMarker(current_sec); + } } void ChartsWidget::updateTitleBar() { title_bar->setVisible(!charts.isEmpty()); if (charts.isEmpty()) return; - // show select range - range_label->setVisible(can->isZoomed()); - reset_zoom_btn->setEnabled(can->isZoomed()); - if (can->isZoomed()) { - auto [min, max] = can->range(); - range_label->setText(tr("%1 - %2").arg(min, 0, 'f', 2).arg(max, 0, 'f', 2)); + range_label->setVisible(is_zoomed); + reset_zoom_btn->setEnabled(is_zoomed); + if (is_zoomed) { + range_label->setText(tr("%1 - %2").arg(zoomed_range.first, 0, 'f', 2).arg(zoomed_range.second, 0, 'f', 2)); } - title_label->setText(tr("Charts (%1)").arg(charts.size())); dock_btn->setText(docking ? "⬈" : "⬋"); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); @@ -107,23 +146,19 @@ void ChartsWidget::addChart(const QString &id, const Signal *sig) { auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); if (it == charts.end()) { auto chart = new ChartWidget(id, sig, this); - QObject::connect(chart, &ChartWidget::remove, this, &ChartsWidget::removeChart); + chart->chart_view->updateSeries(display_range); + QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(chart); });; + QObject::connect(chart->chart_view, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); + QObject::connect(chart->chart_view, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); charts_layout->insertWidget(0, chart); charts.push_back(chart); } updateTitleBar(); } -void ChartsWidget::removeChart(const QString &msg_id, const Signal *sig) { - QMutableListIterator it(charts); - while (it.hasNext()) { - auto c = it.next(); - if (c->id == msg_id && c->signal == sig) { - c->deleteLater(); - it.remove(); - break; - } - } +void ChartsWidget::removeChart(ChartWidget *chart) { + charts.removeOne(chart); + chart->deleteLater(); updateTitleBar(); } @@ -139,6 +174,17 @@ void ChartsWidget::removeAll(const Signal *sig) { updateTitleBar(); } +void ChartsWidget::signalUpdated(const Signal *sig) { + for (auto c : charts) { + if (c->signal == sig) { + c->updateTitle(); + c->chart_view->updateSeries(display_range); + c->chart_view->setRange(display_range.first, display_range.second, true); + } + } +} + + bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Close) { emit dock_btn->clicked(); @@ -156,17 +202,19 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) QWidget *header = new QWidget(this); header->setStyleSheet("background-color:white"); - QHBoxLayout *header_layout = new QHBoxLayout(header); + QGridLayout *header_layout = new QGridLayout(header); header_layout->setContentsMargins(11, 11, 11, 0); - title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); - header_layout->addWidget(title); - header_layout->addStretch(); + msg_name_label = new QLabel(this); + msg_name_label->setTextFormat(Qt::RichText); + header_layout->addWidget(msg_name_label, 0, 0, Qt::AlignLeft); + sig_name_label = new QLabel(this); + sig_name_label->setStyleSheet("font-weight:bold"); + header_layout->addWidget(sig_name_label, 0, 1, Qt::AlignCenter); //, 0, Qt::AlignCenter); QPushButton *remove_btn = new QPushButton("✖", this); - remove_btn->setFixedSize(30, 30); + remove_btn->setFixedSize(20, 20); remove_btn->setToolTip(tr("Remove chart")); - QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); }); - header_layout->addWidget(remove_btn); + header_layout->addWidget(remove_btn, 0, 2, Qt::AlignRight); main_layout->addWidget(header); chart_view = new ChartView(id, sig, this); @@ -175,10 +223,15 @@ ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) main_layout->addStretch(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + updateTitle(); + + QObject::connect(remove_btn, &QPushButton::clicked, [=]() { emit remove(id, sig); }); + QObject::connect(&settings, &Settings::changed, [this]() { chart_view->setFixedHeight(settings.chart_height); }); } void ChartWidget::updateTitle() { - title->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); + msg_name_label->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); + sig_name_label->setText(signal->name.c_str()); } // ChartView @@ -186,15 +239,10 @@ void ChartWidget::updateTitle() { ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QChartView(nullptr, parent) { QLineSeries *series = new QLineSeries(); - series->setUseOpenGL(true); QChart *chart = new QChart(); - chart->setTitle(sig->name.c_str()); chart->addSeries(series); chart->createDefaultAxes(); chart->legend()->hide(); - QFont font; - font.setBold(true); - chart->setTitleFont(font); chart->setMargins({0, 0, 0, 0}); chart->layout()->setContentsMargins(0, 0, 0, 0); @@ -220,16 +268,18 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) timer->setSingleShot(true); timer->callOnTimeout(this, &ChartView::adjustChartMargins); - QObject::connect(can, &CANMessages::updated, this, &ChartView::updateState); - QObject::connect(can, &CANMessages::rangeChanged, this, &ChartView::rangeChanged); - QObject::connect(can, &CANMessages::eventsMerged, this, &ChartView::updateSeries); - QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange); QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { // use a singleshot timer to avoid recursion call. timer->start(); }); +} - updateSeries(); +void ChartView::setRange(double min, double max, bool force_update) { + auto axis_x = dynamic_cast(chart()->axisX()); + if (force_update || (min != axis_x->min() || max != axis_x->max())) { + axis_x->setRange(min, max); + updateAxisY(); + } } void ChartView::adjustChartMargins() { @@ -241,18 +291,17 @@ void ChartView::adjustChartMargins() { } } -void ChartWidget::setHeight(int height) { - chart_view->setFixedHeight(height); -} - -void ChartView::updateState() { +void ChartView::updateLineMarker(double current_sec) { auto axis_x = dynamic_cast(chart()->axisX()); - int x = chart()->plotArea().left() + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); - line_marker->setLine(x, 0, x, height()); + 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) { + line_marker->setLine(x, 0, x, height()); + chart()->update(); + } } -void ChartView::updateSeries() { - chart()->setTitle(signal->name.c_str()); +void ChartView::updateSeries(const std::pair &range) { auto events = can->events(); if (!events) return; @@ -261,15 +310,18 @@ void ChartView::updateSeries() { uint32_t address = l[1].toUInt(nullptr, 16); vals.clear(); - vals.reserve(3 * 60 * 100); - uint64_t route_start_time = can->routeStartTime(); - for (auto &evt : *events) { - if (evt->which == cereal::Event::Which::CAN) { - for (auto c : evt->event.getCan()) { + vals.reserve((range.second - range.first) * 100); // [n]minutes * 100hz + 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 (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 = (evt->mono_time / (double)1e9) - route_start_time; // seconds + double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds vals.push_back({ts, value}); } } @@ -277,40 +329,20 @@ void ChartView::updateSeries() { } QLineSeries *series = (QLineSeries *)chart()->series()[0]; series->replace(vals); - series->setPointLabelsColor(Qt::black); - auto [begin, end] = can->range(); - chart()->axisX()->setRange(begin, end); - updateAxisY(); -} - -void ChartView::rangeChanged(qreal min, qreal max) { - auto axis_x = dynamic_cast(chart()->axisX()); - if (axis_x->min() != min || axis_x->max() != max) { - axis_x->setRange(min, max); - } - updateAxisY(); } // auto zoom on yaxis void ChartView::updateAxisY() { const auto axis_x = dynamic_cast(chart()->axisX()); const auto axis_y = dynamic_cast(chart()->axisY()); - // vals is a sorted list 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 (min->y() == max->y()) { - if (max->y() < 0) { - axis_y->setRange(max->y(), 0); - } else { - axis_y->setRange(0, max->y() == 0 ? 1 : max->y()); - } - } else { - axis_y->setRange(min->y(), max->y()); - } + (min->y() == max->y()) ? axis_y->setRange(min->y() - 1, max->y() + 1) + : axis_y->setRange(min->y(), max->y()); } void ChartView::enterEvent(QEvent *event) { @@ -328,39 +360,35 @@ void ChartView::leaveEvent(QEvent *event) { void ChartView::mouseReleaseEvent(QMouseEvent *event) { auto rubber = findChild(); if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { - auto [begin, end] = can->range(); + rubber->hide(); + QRectF rect = rubber->geometry().normalized(); + rect.translate(-chart()->plotArea().topLeft()); + const auto axis_x = dynamic_cast(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) { - double seek_to = begin + ((event->pos().x() - chart()->plotArea().x()) / chart()->plotArea().width()) * (end - begin); - can->seekTo(seek_to); - } else if (((double)rubber->width() / chart()->plotArea().width()) * (end - begin) < 0.5) { - // don't zoom if selected range is less than 0.5s - rubber->hide(); - event->accept(); - return; + // no rubber dragged, seek to mouse position + can->seekTo(min); + } else if ((max - min) >= 0.5) { + // zoom in if selected range is greater than 0.5s + emit zoomIn(min, max); } } else if (event->button() == Qt::RightButton) { - // reset zoom - if (can->isZoomed()) { - can->resetRange(); - event->accept(); - return; - } + emit zoomReset(); } - QChartView::mouseReleaseEvent(event); - line_marker->setVisible(true); + event->accept(); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { auto rubber = findChild(); - bool show = !(rubber && rubber->isVisible()); - - if (show) { + bool dragging = rubber && rubber->isVisible(); + if (!dragging) { const auto plot_area = chart()->plotArea(); float x = std::clamp((float)ev->pos().x(), (float)plot_area.left(), (float)plot_area.right()); track_line->setLine(x, plot_area.top(), x, plot_area.bottom()); - auto [begin, end] = can->range(); - double sec = begin + ((x - plot_area.x()) / plot_area.width()) * (end - begin); + auto axis_x = dynamic_cast(chart()->axisX()); + double sec = axis_x->min() + ((x - plot_area.x()) / plot_area.width()) * (axis_x->max() - axis_x->min()); auto value = std::lower_bound(vals.begin(), vals.end(), sec, [](auto &p, double x) { return p.x() < x; }); value_text->setPos(x + 6, plot_area.bottom() - 25); if (value != vals.end()) { @@ -369,9 +397,5 @@ void ChartView::mouseMoveEvent(QMouseEvent *ev) { value_text->setText("(--, --)"); } } - - value_text->setVisible(show); - track_line->setVisible(show); - line_marker->setVisible(show); QChartView::mouseMoveEvent(ev); } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 5adbdfcd04..a7c6f4bbd3 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -20,7 +20,13 @@ class ChartView : public QChartView { public: ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); - void updateSeries(); + void updateSeries(const std::pair &range); + void setRange(double min, double max, bool force_update = false); + void updateLineMarker(double current_sec); + +signals: + void zoomIn(double min, double max); + void zoomReset(); private: void mouseReleaseEvent(QMouseEvent *event) override; @@ -29,9 +35,7 @@ private: void leaveEvent(QEvent *event) override; void adjustChartMargins(); - void rangeChanged(qreal min, qreal max); void updateAxisY(); - void updateState(); QGraphicsLineItem *track_line; QGraphicsSimpleTextItem *value_text; @@ -47,7 +51,6 @@ Q_OBJECT public: ChartWidget(const QString &id, const Signal *sig, QWidget *parent); void updateTitle(); - void setHeight(int height); signals: void remove(const QString &msg_id, const Signal *sig); @@ -55,7 +58,8 @@ signals: public: QString id; const Signal *signal; - QLabel *title; + QLabel *msg_name_label; + QLabel *sig_name_label; ChartView *chart_view = nullptr; }; @@ -65,16 +69,21 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); void addChart(const QString &id, const Signal *sig); - void removeChart(const QString &id, const Signal *sig); + void removeChart(ChartWidget *chart); signals: void dock(bool floating); + void rangeChanged(double min, double max, bool is_zommed); private: + void eventsMerged(); void updateState(); + void zoomIn(double min, double max); + void zoomReset(); + void signalUpdated(const Signal *sig); void updateTitleBar(); void removeAll(const Signal *sig = nullptr); - bool eventFilter(QObject *obj, QEvent *event); + bool eventFilter(QObject *obj, QEvent *event) override; QWidget *title_bar; QLabel *title_label; @@ -85,4 +94,9 @@ private: QPushButton *remove_all_btn; QVBoxLayout *charts_layout; QList charts; + + bool is_zoomed = false; + std::pair event_range; + std::pair display_range; + std::pair zoomed_range; }; diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index a20845f15f..8136d0577c 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -7,7 +7,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); if (role == Qt::DisplayRole) { - const auto &m = can->messages(msg_id)[index.row()]; + const auto &m = messages[index.row()]; if (index.column() == 0) { return QString::number(m.ts, 'f', 2); } @@ -49,7 +49,8 @@ void HistoryLogModel::updateState() { if (msg_id.isEmpty()) return; int prev_row_count = row_count; - row_count = can->messages(msg_id).size(); + messages = can->messages(msg_id); + row_count = messages.size(); int delta = row_count - prev_row_count; if (delta > 0) { beginInsertRows({}, prev_row_count, row_count - 1); diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index bc6d1f9376..d39bcf9f1d 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -22,6 +22,7 @@ private: int row_count = 0; int column_count = 2; const Msg *dbc_msg = nullptr; + std::deque messages; }; class HistoryLog : public QWidget { diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 65e7bb1af8..30c7e5e0a2 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -83,6 +83,7 @@ MainWindow::MainWindow() : QWidget() { QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); + QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); QObject::connect(can, &CANMessages::eventsMerged, [=]() { fingerprint_label->setText(can->carFingerprint() ); }); diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index 0b36f25fbc..6e9d7f17cc 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -17,6 +17,7 @@ void Settings::save() { s.setValue("log_size", can_msg_log_size); s.setValue("cached_segment", cached_segment_limit); s.setValue("chart_height", chart_height); + s.setValue("max_chart_x_range", max_chart_x_range); emit changed(); } @@ -26,6 +27,7 @@ void Settings::load() { can_msg_log_size = s.value("log_size", 100).toInt(); cached_segment_limit = s.value("cached_segment", 3).toInt(); chart_height = s.value("chart_height", 200).toInt(); + max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); } // SettingsDlg @@ -45,19 +47,25 @@ SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { log_size->setRange(50, 500); log_size->setSingleStep(10); log_size->setValue(settings.can_msg_log_size); - form_layout->addRow(tr("Log size"), log_size); + form_layout->addRow(tr("Signal history log size"), log_size); cached_segment = new QSpinBox(this); cached_segment->setRange(3, 60); cached_segment->setSingleStep(1); cached_segment->setValue(settings.cached_segment_limit); - form_layout->addRow(tr("Cached segments limit"), cached_segment); + form_layout->addRow(tr("Cached segments limit(minute)"), cached_segment); + + max_chart_x_range = new QSpinBox(this); + max_chart_x_range->setRange(1, 60); + max_chart_x_range->setSingleStep(1); + max_chart_x_range->setValue(settings.max_chart_x_range / 60); + form_layout->addRow(tr("Chart's max X-axis range(minute)"), max_chart_x_range); chart_height = new QSpinBox(this); chart_height->setRange(100, 500); chart_height->setSingleStep(10); chart_height->setValue(settings.chart_height); - form_layout->addRow(tr("Chart height"), chart_height); + form_layout->addRow(tr("Chart's height"), chart_height); main_layout->addLayout(form_layout); @@ -74,6 +82,7 @@ void SettingsDlg::save() { settings.can_msg_log_size = log_size->value(); settings.cached_segment_limit = cached_segment->value(); settings.chart_height = chart_height->value(); + settings.max_chart_x_range = max_chart_x_range->value() * 60; settings.save(); accept(); } diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index 22542fc04c..fa97c49140 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -15,6 +15,7 @@ public: int can_msg_log_size = 100; int cached_segment_limit = 3; int chart_height = 200; + int max_chart_x_range = 3 * 60; // 3 minutes signals: void changed(); @@ -30,6 +31,7 @@ public: QSpinBox *log_size ; QSpinBox *cached_segment; QSpinBox *chart_height; + QSpinBox *max_chart_x_range; }; extern Settings settings; diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 78965e3e8c..7180d5ac0f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -57,7 +57,6 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - QObject::connect(can, &CANMessages::rangeChanged, this, &VideoWidget::rangeChanged); QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); @@ -70,8 +69,8 @@ void VideoWidget::pause(bool pause) { can->pause(pause); } -void VideoWidget::rangeChanged(double min, double max) { - if (!can->isZoomed()) { +void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { + if (!is_zoomed) { min = 0; max = can->totalSeconds(); } diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index d6d036c461..51dae4c76f 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -26,9 +26,9 @@ class VideoWidget : public QWidget { public: VideoWidget(QWidget *parnet = nullptr); + void rangeChanged(double min, double max, bool is_zommed); protected: - void rangeChanged(double min, double max); void updateState(); void pause(bool pause);