| 564 | } |
| 565 | |
| 566 | void FilterEditorPanel::refreshPreview() { |
| 567 | if (!preview_plot_ || session_ == nullptr) { |
| 568 | return; |
| 569 | } |
| 570 | |
| 571 | // Sources to preview = every selected curve (in list order), or the first |
| 572 | // available if nothing is selected. Each gets its own ghost + filtered pair, so a |
| 573 | // multi-select edit previews EVERY series Apply will touch — not just the first. |
| 574 | std::vector<int> indices; |
| 575 | const QList<QListWidgetItem*> selected = ui_->series_list->selectedItems(); |
| 576 | if (!selected.isEmpty()) { |
| 577 | indices.reserve(static_cast<std::size_t>(selected.size())); |
| 578 | for (const QListWidgetItem* item : selected) { |
| 579 | indices.push_back(item->data(Qt::UserRole).toInt()); |
| 580 | } |
| 581 | std::sort(indices.begin(), indices.end()); // list order, not selection-click order |
| 582 | } else if (!sources_.empty()) { |
| 583 | indices.push_back(0); |
| 584 | } |
| 585 | |
| 586 | // Resolve each to its EFFECTIVE input (a filter-output source previews its ORIGINAL |
| 587 | // input, so re-editing reads as "input -> re-filtered output") and the SELECTED |
| 588 | // curve's plot colour. De-duplicate by input so two outputs of the same input |
| 589 | // collapse to one ghost. |
| 590 | std::vector<PreviewSeries> wanted; |
| 591 | QString set_key; |
| 592 | for (const int idx : indices) { |
| 593 | if (idx < 0 || static_cast<std::size_t>(idx) >= sources_.size()) { |
| 594 | continue; |
| 595 | } |
| 596 | const CurveDescriptor& source = sources_[static_cast<std::size_t>(idx)]; |
| 597 | const CurveDescriptor input = originalInputOf(source); |
| 598 | if (std::any_of(wanted.begin(), wanted.end(), [&](const PreviewSeries& s) { return s.input.name == input.name; })) { |
| 599 | continue; |
| 600 | } |
| 601 | wanted.push_back(PreviewSeries{.input = input, .source_key = source.name, .color = colorForSource(source)}); |
| 602 | set_key += input.name; |
| 603 | set_key += QChar('\n'); |
| 604 | } |
| 605 | if (wanted.empty()) { |
| 606 | return; |
| 607 | } |
| 608 | |
| 609 | // Rebuild curves only when the SET of ghosts changes, so tuning a parameter keeps |
| 610 | // the user's current zoom on the preview. |
| 611 | const bool rebuilt = (set_key != preview_set_key_); |
| 612 | if (rebuilt) { |
| 613 | preview_plot_->removeAllCurves(); |
| 614 | preview_set_key_ = set_key; |
| 615 | for (PreviewSeries& entry : wanted) { |
| 616 | // Ghost = the datastore-backed source. Its dash/fade styling is set below. |
| 617 | if (auto* ghost = preview_plot_->addCurve(entry.input.name, entry.color); ghost != nullptr) { |
| 618 | entry.ghost = ghost->curve; |
| 619 | } |
| 620 | // Filtered = a lazy datastore-backed curve that runs THIS source's filter |
| 621 | // over the input column on read. It reports the input topic as its source, |
| 622 | // so PlotWidget's samplesIngested handler refreshes it on the SAME signal as |
| 623 | // the ghost — the filtered "after" tracks streaming with no timer/extra |
nothing calls this directly
no test coverage detected