      QStyledItemDelegate::paint(painter, option, index);
    }
  }
  
  void SignalItemDelegate::drawSparkline(QPainter *painter, const QRect &rect, const QStyleOptionViewItem &option, const QModelIndex &index) const {
    static std::vector<QPointF> 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<int64_t>(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<double>::max();
      double max = std::numeric_limits<double>::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 ||
        item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) {
      QLineEdit *e = new QLineEdit(parent);
 
</s> remove   model->updateState(nullptr);
 </s> add   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<MessageId, CanData> *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<int>(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<void> 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); </s> remove     bool chart_opened = charts->hasSignal(msg_id, item->sig);
 </s> add     bool chart_opened = charts->hasSignal(model->msg_id, item->sig); </s> remove       current_charts[i]->setVisible(true);
 </s> add       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);
      } </s> remove   QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalModel::updateState);
 </s> add 
      QStyledItemDelegate::paint(painter, option, index);
    }
  }
  

























































  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 ||
        item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) {
      QLineEdit *e = new QLineEdit(parent);
keep keep keep keep replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace replace keep keep keep keep keep
 cabana: paint sparkline in threads (#27925)

* multiple threading realtime sparkline

* delay visible

* remove msg_id

* cleanup 
tools/cabana/signalview.cc