From 40caca7e24c81ce30ab42e068486b4c20f94a98b Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Mon, 17 Apr 2023 01:59:24 +0800 Subject: [PATCH] cabana: paint sparkline in threads (#27925) * multiple threading realtime sparkline * delay visible * remove msg_id * cleanup old-commit-hash: ed1f03dc41bd508543265e2b4417d7ad2a08d928 --- tools/cabana/SConscript | 2 +- tools/cabana/chart/chartswidget.cc | 7 +- tools/cabana/chart/sparkline.cc | 68 ++++++++ tools/cabana/chart/sparkline.h | 24 +++ tools/cabana/signalview.cc | 251 ++++++++++++++--------------- tools/cabana/signalview.h | 22 ++- 6 files changed, 233 insertions(+), 141 deletions(-) create mode 100644 tools/cabana/chart/sparkline.cc create mode 100644 tools/cabana/chart/sparkline.h diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 77738afae3..975ff64057 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -31,7 +31,7 @@ prev_moc_path = cabana_env['QT_MOCHPREFIX'] cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', + 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 84bd3885cf..d015e97c9f 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -314,7 +314,12 @@ void ChartsWidget::updateLayout(bool force) { } for (int i = 0; i < current_charts.size(); ++i) { charts_layout->addWidget(current_charts[i], i / n, i % n); - current_charts[i]->setVisible(true); + if (current_charts[i]->sigs.isEmpty()) { + // the chart will be resized after add signal. delay setVisible to reduce flicker. + QTimer::singleShot(0, [c = current_charts[i]]() { c->setVisible(true); }); + } else { + current_charts[i]->setVisible(true); + } } charts_container->setUpdatesEnabled(true); } diff --git a/tools/cabana/chart/sparkline.cc b/tools/cabana/chart/sparkline.cc new file mode 100644 index 0000000000..dd2720aa54 --- /dev/null +++ b/tools/cabana/chart/sparkline.cc @@ -0,0 +1,68 @@ +#include "tools/cabana/chart/sparkline.h" + +#include + +#include "tools/cabana/streams/abstractstream.h" + +void Sparkline::update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size) { + const auto &msgs = can->events().at(msg_id); + uint64_t ts = (last_msg_ts + can->routeStartTime()) * 1e9; + auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max(ts - range * 1e9, 0)}); + auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts}); + + bool update_values = last_ts != last_msg_ts || time_range != range; + last_ts = last_msg_ts; + time_range = range; + + if (first != last) { + if (update_values) { + values.clear(); + values.reserve(std::distance(first, last)); + min_val = std::numeric_limits::max(); + max_val = std::numeric_limits::lowest(); + for (auto it = first; it != last; ++it) { + double value = get_raw_value(it->dat, it->size, *sig); + values.emplace_back((it->mono_time - first->mono_time) / 1e9, value); + if (min_val > value) min_val = value; + if (max_val < value) max_val = value; + } + if (min_val == max_val) { + min_val -= 1; + max_val += 1; + } + } + render(getColor(sig), size); + } else { + pixmap = QPixmap(); + min_val = -1; + max_val = 1; + } +} + +void Sparkline::render(const QColor &color, QSize size) { + const double xscale = (size.width() - 1) / (double)time_range; + const double yscale = (size.height() - 3) / (max_val - min_val); + points.clear(); + points.reserve(values.size()); + for (auto &v : values) { + points.emplace_back(v.x() * xscale, 1 + std::abs(v.y() - max_val) * yscale); + } + + qreal dpr = qApp->devicePixelRatio(); + size *= dpr; + if (size != pixmap.size()) { + pixmap = QPixmap(size); + } + pixmap.setDevicePixelRatio(dpr); + pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); + painter.setRenderHint(QPainter::Antialiasing, points.size() < 500); + painter.setPen(color); + painter.drawPolyline(points.data(), points.size()); + painter.setPen(QPen(color, 3)); + if ((points.back().x() - points.front().x()) / points.size() > 8) { + painter.drawPoints(points.data(), points.size()); + } else { + painter.drawPoint(points.back()); + } +} diff --git a/tools/cabana/chart/sparkline.h b/tools/cabana/chart/sparkline.h new file mode 100644 index 0000000000..69d4f4bc55 --- /dev/null +++ b/tools/cabana/chart/sparkline.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +#include "tools/cabana/dbc/dbcmanager.h" + +class Sparkline { +public: + void update(const MessageId &msg_id, const cabana::Signal *sig, double last_msg_ts, int range, QSize size); + const QSize size() const { return pixmap.size() / pixmap.devicePixelRatio(); } + + QPixmap pixmap; + double min_val = 0; + double max_val = 0; + double last_ts = 0; + int time_range = 0; + +private: + void render(const QColor &color, QSize size); + std::vector values; + std::vector points; +}; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index ef05decda6..4fb52839d5 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include "tools/cabana/commands.h" @@ -22,7 +24,6 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p QObject::connect(dbc(), &DBCManager::signalAdded, this, &SignalModel::handleSignalAdded); QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalModel::handleSignalUpdated); QObject::connect(dbc(), &DBCManager::signalRemoved, this, &SignalModel::handleSignalRemoved); - QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalModel::updateState); } void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { @@ -37,9 +38,7 @@ void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const caba void SignalModel::setMessage(const MessageId &id) { msg_id = id; filter_str = ""; - value_width = 0; refresh(); - updateState(nullptr); } void SignalModel::setFilter(const QString &txt) { @@ -60,33 +59,6 @@ void SignalModel::refresh() { endResetModel(); } -void SignalModel::updateState(const QHash *msgs) { - if (!msgs || msgs->contains(msg_id)) { - auto &dat = can->lastMessage(msg_id).dat; - for (auto item : root->children) { - double value = get_raw_value((uint8_t *)dat.constData(), dat.size(), *item->sig); - item->sig_val = QString::number(value, 'f', item->sig->precision); - - // Show unit - if (!item->sig->unit.isEmpty()) { - item->sig_val += " " + item->sig->unit; - } - - // Show enum string - for (auto &[val, desc] : item->sig->val_desc) { - if (std::abs(value - val.toInt()) < 1e-6) { - item->sig_val = desc; - } - } - value_width = std::max(value_width, QFontMetrics(QFont()).width(item->sig_val)); - } - - for (int i = 0; i < root->children.size(); ++i) { - emit dataChanged(index(i, 1), index(i, 1), {Qt::DisplayRole}); - } - } -} - SignalModel::Item *SignalModel::getItem(const QModelIndex &index) const { SignalModel::Item *item = nullptr; if (index.isValid()) { @@ -293,7 +265,6 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); - updateState(nullptr); } } @@ -355,120 +326,68 @@ void SignalItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptio QRect geom = option.rect; geom.setLeft(geom.right() - editor->sizeHint().width()); editor->setGeometry(geom); + button_size = geom.size(); return; } QStyledItemDelegate::updateEditorGeometry(editor, option, index); } void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; - int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); auto item = (SignalModel::Item *)index.internalPointer(); - if (index.column() == 0 && item && item->type == SignalModel::Item::Sig) { - painter->save(); + if (item && item->type == SignalModel::Item::Sig) { painter->setRenderHint(QPainter::Antialiasing); if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, option.palette.highlight()); } - // color label - auto bg_color = getColor(item->sig); - QRect rc{option.rect.left() + h_margin, option.rect.top(), color_label_width, option.rect.height()}; - painter->setPen(Qt::NoPen); - painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color); - painter->drawRoundedRect(rc.adjusted(0, v_margin, 0, -v_margin), 3, 3); - painter->setPen(item->highlight ? Qt::white : Qt::black); - painter->setFont(label_font); - painter->drawText(rc, Qt::AlignCenter, QString::number(item->row() + 1)); - - // signal name - painter->setFont(option.font); - painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); - QString text = index.data(Qt::DisplayRole).toString(); - QRect text_rect = option.rect; - text_rect.setLeft(rc.right() + h_margin * 2); - text = painter->fontMetrics().elidedText(text, Qt::ElideRight, text_rect.width()); - painter->drawText(text_rect, option.displayAlignment, text); - painter->restore(); - } else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) { - painter->save(); - if (option.state & QStyle::State_Selected) { - painter->fillRect(option.rect, option.palette.highlight()); + int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); + QRect r = option.rect.adjusted(h_margin, v_margin, -h_margin, -v_margin); + if (index.column() == 0) { + // color label + QPainterPath path; + QRect icon_rect{r.x(), r.y(), color_label_width, r.height()}; + path.addRoundedRect(icon_rect, 3, 3); + painter->setPen(item->highlight ? Qt::white : Qt::black); + painter->setFont(label_font); + painter->fillPath(path, getColor(item->sig).darker(item->highlight ? 125 : 0)); + painter->drawText(icon_rect, Qt::AlignCenter, QString::number(item->row() + 1)); + + r.setLeft(icon_rect.right() + h_margin * 2); + auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, r.width()); + painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); + painter->setFont(option.font); + painter->drawText(r, option.displayAlignment, text); + } else if (index.column() == 1) { + // sparkline + QSize sparkline_size = item->sparkline.pixmap.size() / item->sparkline.pixmap.devicePixelRatio(); + painter->drawPixmap(QRect(r.topLeft(), sparkline_size), item->sparkline.pixmap); + // min-max value + painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); + QRect rect = r.adjusted(sparkline_size.width() + 1, 0, 0, 0); + int value_adjust = 10; + if (item->highlight || option.state & QStyle::State_Selected) { + painter->drawLine(rect.topLeft(), rect.bottomLeft()); + rect.adjust(5, -v_margin, 0, v_margin); + painter->setFont(minmax_font); + QString min = QString::number(item->sparkline.min_val); + QString max = QString::number(item->sparkline.max_val); + painter->drawText(rect, Qt::AlignLeft | Qt::AlignTop, max); + painter->drawText(rect, Qt::AlignLeft | Qt::AlignBottom, min); + QFontMetrics fm(minmax_font); + value_adjust = std::max(fm.width(min), fm.width(max)) + 5; + } + // value + painter->setFont(option.font); + rect.adjust(value_adjust, 0, -button_size.width(), 0); + auto text = option.fontMetrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rect.width()); + painter->drawText(rect, Qt::AlignRight | Qt::AlignVCenter, text); } - - SignalModel *model = (SignalModel*)index.model(); - int adjust_right = ((SignalView *)parent())->tree->indexWidget(index)->sizeHint().width() + 2 * h_margin; - QRect r = option.rect.adjusted(h_margin, v_margin, -adjust_right, -v_margin); - - int value_width = std::min(model->value_width, r.width() * 0.4); - QRect value_rect = r.adjusted(r.width() - value_width - h_margin, 0, 0, 0); - auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, value_rect.width()); - painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); - painter->drawText(value_rect, Qt::AlignRight | Qt::AlignVCenter, text); - drawSparkline(painter, r.adjusted(0, 0, -value_width, 0), option, index); - painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } -void SignalItemDelegate::drawSparkline(QPainter *painter, const QRect &rect, const QStyleOptionViewItem &option, const QModelIndex &index) const { - static std::vector points; - const auto &msg_id = ((SignalView *)parent())->msg_id; - const auto &msgs = can->events().at(msg_id); - - uint64_t ts = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9; - auto first = std::lower_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = (uint64_t)std::max(ts - settings.sparkline_range * 1e9, 0)}); - auto last = std::upper_bound(first, msgs.cend(), CanEvent{.mono_time = ts}); - - if (first != last) { - double min = std::numeric_limits::max(); - double max = std::numeric_limits::lowest(); - const auto item = (const SignalModel::Item *)index.internalPointer(); - const auto sig = item->sig; - points.clear(); - for (auto it = first; it != last; ++it) { - double value = get_raw_value(it->dat, it->size, *sig); - points.emplace_back((it->mono_time - first->mono_time) / 1e9, value); - min = std::min(min, value); - max = std::max(max, value); - } - if (min == max) { - min -= 1; - max += 1; - } - - const double min_max_width = std::min(rect.width() - 10, QFontMetrics(minmax_font).width("000.00") + 5); - const QRect r = rect.adjusted(0, 0, -min_max_width, 0); - const double xscale = r.width() / (double)settings.sparkline_range; - const double yscale = r.height() / (max - min); - for (auto &pt : points) { - pt.rx() = r.left() + pt.x() * xscale; - pt.ry() = r.top() + std::abs(pt.y() - max) * yscale; - } - - auto color = item->highlight ? getColor(sig).darker(125) : getColor(sig); - painter->setPen(color); - painter->drawPolyline(points.data(), points.size()); - if ((points.back().x() - points.front().x()) / points.size() > 10) { - painter->setPen(Qt::NoPen); - painter->setBrush(color); - for (const auto &pt : points) { - painter->drawEllipse(pt, 2, 2); - } - } - - if (item->highlight || option.state & QStyle::State_Selected) { - painter->setFont(minmax_font); - painter->setPen(option.state & QStyle::State_Selected ? option.palette.color(QPalette::HighlightedText) : Qt::darkGray); - painter->drawLine(r.topRight(), r.bottomRight()); - QRect minmax_rect{r.right() + 5, r.top(), 1000, r.height()}; - painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignTop, QString::number(max)); - painter->drawText(minmax_rect, Qt::AlignLeft | Qt::AlignBottom, QString::number(min)); - } - } -} - QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (SignalModel::Item *)index.internalPointer(); if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || @@ -534,7 +453,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), // tree view tree = new TreeView(this); tree->setModel(model = new SignalModel(this)); - tree->setItemDelegate(new SignalItemDelegate(this)); + tree->setItemDelegate(delegate = new SignalItemDelegate(this)); tree->setFrameShape(QFrame::NoFrame); tree->setHeaderHidden(true); tree->setMouseTracking(true); @@ -560,6 +479,10 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); }); + QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalView::updateState); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalView::handleSignalUpdated); + QObject::connect(tree->verticalScrollBar(), &QScrollBar::valueChanged, [this]() { updateState(); }); + QObject::connect(tree->verticalScrollBar(), &QScrollBar::rangeChanged, [this]() { updateState(); }); setWhatsThis(tr(R"( Signal view
@@ -568,7 +491,7 @@ SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), } void SignalView::setMessage(const MessageId &id) { - msg_id = id; + max_value_width = 0; filter_edit->clear(); model->setMessage(id); } @@ -594,12 +517,13 @@ void SignalView::rowsChanged() { auto sig = model->getItem(index)->sig; QObject::connect(remove_btn, &QToolButton::clicked, [=]() { model->removeSignal(sig); }); QObject::connect(plot_btn, &QToolButton::clicked, [=](bool checked) { - emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); + emit showChart(model->msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); }); } } updateToolBar(); updateChartState(); + updateState(); } void SignalView::rowClicked(const QModelIndex &index) { @@ -626,7 +550,7 @@ void SignalView::selectSignal(const cabana::Signal *sig, bool expand) { void SignalView::updateChartState() { int i = 0; for (auto item : model->root->children) { - bool chart_opened = charts->hasSignal(msg_id, item->sig); + bool chart_opened = charts->hasSignal(model->msg_id, item->sig); auto buttons = tree->indexWidget(model->index(i, 1))->findChildren(); if (buttons.size() > 0) { buttons[0]->setChecked(chart_opened); @@ -655,7 +579,70 @@ void SignalView::updateToolBar() { void SignalView::setSparklineRange(int value) { settings.sparkline_range = value; updateToolBar(); - model->updateState(nullptr); + updateState(); +} + +void SignalView::handleSignalUpdated(const cabana::Signal *sig) { + if (int row = model->signalRow(sig); row != -1) { + auto item = model->getItem(model->index(row, 1)); + // invalidate the sparkline + item->sparkline.last_ts = 0; + updateState(); + } +} + +void SignalView::updateState(const QHash *msgs) { + if (model->rowCount() == 0 || (msgs && !msgs->contains(model->msg_id))) return; + + const auto &last_msg = can->lastMessage(model->msg_id); + for (auto item : model->root->children) { + double value = get_raw_value((uint8_t *)last_msg.dat.constData(), last_msg.dat.size(), *item->sig); + item->sig_val = QString::number(value, 'f', item->sig->precision); + // Show unit + if (!item->sig->unit.isEmpty()) { + item->sig_val += " " + item->sig->unit; + } + // Show enum string + for (auto &[val, desc] : item->sig->val_desc) { + if (std::abs(value - val.toInt()) < 1e-6) { + item->sig_val = desc; + } + } + max_value_width = std::max(max_value_width, fontMetrics().width(item->sig_val)); + } + + // update visible sparkline + QSize size(tree->columnWidth(1) - delegate->button_size.width(), delegate->button_size.height()); + int min_max_width = std::min(size.width() - 10, QFontMetrics(delegate->minmax_font).width("-000.00") + 5); + int value_width = std::min(max_value_width, size.width() * 0.35); + size -= {value_width + min_max_width, style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2}; + + QModelIndex top = tree->indexAt(QPoint(0, 0)); + QModelIndex bottom = tree->indexAt(tree->viewport()->rect().bottomLeft()); + int start_row = top.parent().isValid() ? top.parent().row() + 1 : top.row(); + int end_row = model->rowCount() - 1; + if (bottom.isValid()) { + end_row = bottom.parent().isValid() ? bottom.parent().row() : bottom.row(); + } + QFutureSynchronizer synchronizer; + for (int i = start_row; i <= end_row; ++i) { + auto item = model->getItem(model->index(i, 1)); + auto &s = item->sparkline; + if (s.last_ts != last_msg.ts || s.size() != size || s.time_range != settings.sparkline_range) { + synchronizer.addFuture(QtConcurrent::run( + &s, &Sparkline::update, model->msg_id, item->sig, last_msg.ts, settings.sparkline_range, size)); + } + } + synchronizer.waitForFinished(); + + for (int i = 0; i < model->rowCount(); ++i) { + emit model->dataChanged(model->index(i, 1), model->index(i, 1), {Qt::DisplayRole}); + } +} + +void SignalView::resizeEvent(QResizeEvent* event) { + updateState(); + QFrame::resizeEvent(event); } void SignalView::leaveEvent(QEvent *event) { diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index a2102ff9a7..02741234a6 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -9,6 +9,7 @@ #include #include "tools/cabana/chart/chartswidget.h" +#include "tools/cabana/chart/sparkline.h" class SignalModel : public QAbstractItemModel { Q_OBJECT @@ -27,6 +28,7 @@ public: bool highlight = false; bool extra_expanded = false; QString sig_val = "-"; + Sparkline sparkline; }; SignalModel(QObject *parent); @@ -54,12 +56,10 @@ private: void handleSignalRemoved(const cabana::Signal *sig); void handleMsgChanged(MessageId id); void refresh(); - void updateState(const QHash *msgs); MessageId msg_id; QString filter_str; std::unique_ptr root; - int value_width = 0; friend class SignalView; friend class SignalItemDelegate; }; @@ -86,11 +86,12 @@ public: QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override; - void drawSparkline(QPainter *painter, const QRect &rect, const QStyleOptionViewItem &option, const QModelIndex &index) const; - void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QValidator *name_validator, *double_validator; QFont label_font, minmax_font; const int color_label_width = 18; + mutable QSize button_size; mutable QHash width_cache; }; @@ -105,7 +106,6 @@ public: void selectSignal(const cabana::Signal *sig, bool expand = false); void rowClicked(const QModelIndex &index); SignalModel *model = nullptr; - MessageId msg_id; signals: void highlight(const cabana::Signal *sig); @@ -113,9 +113,12 @@ signals: private: void rowsChanged(); - void leaveEvent(QEvent *event); + void leaveEvent(QEvent *event) override; + void resizeEvent(QResizeEvent* event) override; void updateToolBar(); void setSparklineRange(int value); + void handleSignalUpdated(const cabana::Signal *sig); + void updateState(const QHash *msgs = nullptr); struct TreeView : public QTreeView { TreeView(QWidget *parent) : QTreeView(parent) {} @@ -124,13 +127,18 @@ private: // update widget geometries in QTreeView::rowsInserted QTreeView::rowsInserted(parent, start, end); } + void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()) override { + // Bypass the slow call to QTreeView::dataChanged. + QAbstractItemView::dataChanged(topLeft, bottomRight, roles); + } }; - + int max_value_width = 0; TreeView *tree; QLabel *sparkline_label; QSlider *sparkline_range_slider; QLineEdit *filter_edit; ChartsWidget *charts; QLabel *signal_count_lb; + SignalItemDelegate *delegate; friend SignalItemDelegate; };