| 736 | } |
| 737 | |
| 738 | void FilterEditorPanel::applyToSelected() { |
| 739 | const QList<QListWidgetItem*> selected = ui_->series_list->selectedItems(); |
| 740 | if (selected.isEmpty()) { |
| 741 | ui_->status_label->setText(tr("Select at least one source curve")); |
| 742 | return; |
| 743 | } |
| 744 | |
| 745 | auto& service = session_->dataProcessorService(); |
| 746 | |
| 747 | // Record the visible source's config into per-source memory FIRST, so a pasted / |
| 748 | // edited filter on the source you are looking at (even an existing output) is |
| 749 | // included — this is the single authority Apply reads, same as the preview. |
| 750 | saveActiveSourceFilter(); |
| 751 | |
| 752 | const std::string visible_id = currentFilterId(); |
| 753 | QList<QPair<QString, QString>> replacements; |
| 754 | QStringList failures; |
| 755 | |
| 756 | // Removal: the active source is an existing filter OUTPUT set to "No Transform" -> |
| 757 | // remove its filter and revert the plotted curve to its original input. (Collected, |
| 758 | // not returned, so OTHER configured sources in the same Apply still run.) |
| 759 | if (!active_source_key_.isEmpty() && visible_id == "none") { |
| 760 | for (const CurveDescriptor& descriptor : sources_) { |
| 761 | if (descriptor.name != active_source_key_) { |
| 762 | continue; |
| 763 | } |
| 764 | if (const auto* recipe = service.filterConfig(descriptor.topic_id)) { |
| 765 | const QString output_key = curveKey(recipe->dataset_id, recipe->output_topic_id, 0); |
| 766 | const QString input_key = curveKey(recipe->dataset_id, recipe->input_topic_id, recipe->input_column_index); |
| 767 | if (const auto removed = service.removeFilter(recipe->node_id); removed.has_value()) { |
| 768 | // Drop the now-orphaned output topic (removeNode leaves the series in the |
| 769 | // store); the trash blacklist keeps it off the next rebuild. |
| 770 | catalog_->removeCurves({output_key}); |
| 771 | replacements.push_back({output_key, input_key}); // host swaps output -> input |
| 772 | } else { |
| 773 | failures << tr("remove %1: %2").arg(readableName(descriptor), QString::fromStdString(removed.error())); |
| 774 | } |
| 775 | } |
| 776 | break; |
| 777 | } |
| 778 | } |
| 779 | |
| 780 | // Plain multi-selection convenience: a selected source with no remembered |
| 781 | // transform of its own inherits the currently-visible one, so ctrl-selecting N |
| 782 | // curves and picking a single filter applies it to all N. |
| 783 | if (visible_id != "none") { |
| 784 | const QString visible_params = QString::fromStdString(param_form_->values().dump()); |
| 785 | for (const QListWidgetItem* item : selected) { |
| 786 | const QString key = sources_[static_cast<std::size_t>(item->data(Qt::UserRole).toInt())].name; |
| 787 | if (!source_filters_.contains(key)) { |
| 788 | source_filters_[key] = {QString::fromStdString(visible_id), visible_params}; |
| 789 | } |
| 790 | } |
| 791 | } |
| 792 | |
| 793 | if (source_filters_.isEmpty() && replacements.isEmpty() && failures.isEmpty()) { |
| 794 | ui_->status_label->setText(tr("Pick a filter to apply")); |
| 795 | return; |
nothing calls this directly
no test coverage detected