diff --git a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp index 775d312..a341fc6 100644 --- a/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp +++ b/Software/PC_Application/SpectrumAnalyzer/spectrumanalyzer.cpp @@ -298,6 +298,7 @@ nlohmann::json SpectrumAnalyzer::toJSON() nlohmann::json j; j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); + j["markers"] = markerModel->toJSON(); return j; } @@ -309,6 +310,9 @@ void SpectrumAnalyzer::fromJSON(nlohmann::json j) if(j.contains("tiles")) { central->fromJSON(j["tiles"]); } + if(j.contains("markers")) { + markerModel->fromJSON(j["markers"]); + } } using namespace std; diff --git a/Software/PC_Application/Traces/markerwidget.cpp b/Software/PC_Application/Traces/markerwidget.cpp index a9957d9..8bb2d11 100644 --- a/Software/PC_Application/Traces/markerwidget.cpp +++ b/Software/PC_Application/Traces/markerwidget.cpp @@ -19,6 +19,13 @@ MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) : connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors); connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors); connect(&model.getModel(), &TraceModel::traceNameChanged, this, &MarkerWidget::updatePersistentEditors); + connect(&model, &TraceMarkerModel::markerAdded, [=](TraceMarker *m) { + connect(m, &TraceMarker::typeChanged, this, &MarkerWidget::updatePersistentEditors); + connect(m, &TraceMarker::traceChanged, this, &MarkerWidget::updatePersistentEditors); + connect(m, &TraceMarker::assignedDeltaChanged, this, &MarkerWidget::updatePersistentEditors); + updatePersistentEditors(); + }); + connect(&model, &TraceMarkerModel::setupLoadComplete, this, &MarkerWidget::updatePersistentEditors); } MarkerWidget::~MarkerWidget() @@ -46,18 +53,13 @@ void MarkerWidget::on_bDelete_clicked() // can't delete child markers directly return; } - model.removeMarker(marker); - marker->blockSignals(true); delete marker; } void MarkerWidget::on_bAdd_clicked() { auto marker = model.createDefaultMarker(); - connect(marker, &TraceMarker::typeChanged, this, &MarkerWidget::updatePersistentEditors); - connect(marker, &TraceMarker::traceChanged, this, &MarkerWidget::updatePersistentEditors); model.addMarker(marker); - updatePersistentEditors(); } void MarkerWidget::updatePersistentEditors() diff --git a/Software/PC_Application/Traces/tracemarker.cpp b/Software/PC_Application/Traces/tracemarker.cpp index 30f46ac..dc2eed6 100644 --- a/Software/PC_Application/Traces/tracemarker.cpp +++ b/Software/PC_Application/Traces/tracemarker.cpp @@ -127,7 +127,7 @@ QString TraceMarker::readableData() return QString::number(toDecibel(), 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4); } case Type::Delta: - if(!delta && delta->isTimeDomain()) { + if(!delta || delta->isTimeDomain()) { return "Invalid delta marker"; } else { // calculate difference between markers @@ -288,10 +288,21 @@ void TraceMarker::parentTraceDeleted(Trace *t) void TraceMarker::traceDataChanged() { - // some data of the parent trace changed, check if marker data also changed complex newdata; - auto sampleType = isTimeDomain() ? Trace::SampleType::TimeImpulse : Trace::SampleType::Frequency; - newdata = parentTrace->sample(parentTrace->index(position), sampleType).y; + if(!parentTrace || parentTrace->numSamples() == 0) { + // no data, invalidate + newdata = numeric_limits>::quiet_NaN(); + } else { + if(position < parentTrace->minX() || position > parentTrace->maxX()) { + // this normally should not happen because the position is constrained to the trace X range. + // However, when loading a setup, the trace might have been just created and essentially empty + newdata = numeric_limits>::quiet_NaN(); + } else { + // some data of the parent trace changed, check if marker data also changed + auto sampleType = isTimeDomain() ? Trace::SampleType::TimeImpulse : Trace::SampleType::Frequency; + newdata = parentTrace->sample(parentTrace->index(position), sampleType).y; + } + } if (newdata != data) { data = newdata; update(); @@ -328,7 +339,7 @@ void TraceMarker::checkDeltaMarker() return; } // Check if type of delta marker is still okay - if(delta->isTimeDomain() != isTimeDomain()) { + if(!delta || delta->isTimeDomain() != isTimeDomain()) { // not the same domain anymore, adjust delta assignDeltaMarker(bestDeltaCandidate()); } @@ -418,6 +429,10 @@ TraceMarker *TraceMarker::bestDeltaCandidate() void TraceMarker::assignDeltaMarker(TraceMarker *m) { + if(type != Type::Delta) { + // ignore + return; + } if(delta) { disconnect(delta, &TraceMarker::dataChanged, this, &TraceMarker::update); } @@ -427,10 +442,13 @@ void TraceMarker::assignDeltaMarker(TraceMarker *m) connect(delta, &TraceMarker::rawDataChanged, this, &TraceMarker::update); connect(delta, &TraceMarker::domainChanged, this, &TraceMarker::checkDeltaMarker); connect(delta, &TraceMarker::deleted, [=](){ + delta = nullptr; + qDebug() << "assigned delta deleted"; assignDeltaMarker(bestDeltaCandidate()); update(); }); } + emit assignedDeltaChanged(this); } void TraceMarker::deleteHelperMarkers() @@ -507,11 +525,104 @@ bool TraceMarker::isVisible() } } +TraceMarker::Type TraceMarker::getType() const +{ + return type; +} + QString TraceMarker::getSuffix() const { return suffix; } +nlohmann::json TraceMarker::toJSON() +{ + nlohmann::json j; + j["trace"] = parentTrace->toHash(); + j["type"] = typeToString(type).toStdString(); + j["number"] = number; + j["position"] = position; + switch(type) { + case Type::Delta: + j["delta_marker"] = delta->toHash(); + break; + case Type::PeakTable: + j["peak_threshold"] = peakThreshold; + break; + case Type::Lowpass: + case Type::Highpass: + case Type::Bandpass: + j["cutoff"] = cutoffAmplitude; + break; + case Type::PhaseNoise: + j["offset"] = offset; + break; + default: + // other types have no settings + break; + } + return j; +} + +void TraceMarker::fromJSON(nlohmann::json j) +{ + if(!j.contains("trace")) { + throw runtime_error("Marker has no trace assigned"); + } + number = j.value("number", 1); + position = j.value("position", 0.0); + + unsigned int hash = j["trace"]; + // find correct trace + bool found = false; + for(auto t : model->getModel().getTraces()) { + if(t->toHash() == hash) { + found = true; + assignTrace(t); + break; + } + } + if(!found) { + throw runtime_error("Unable to find trace with hash " + to_string(hash)); + } + auto typeString = QString::fromStdString(j.value("type", "Manual")); + for(unsigned int i=0;i<(int) Type::Last;i++) { + if(typeToString((Type) i) == typeString) { + setType((Type) i); + break; + } + } + switch(type) { + case Type::Delta: + // can't assign delta marker here, because it might not have been created (if it was below this marker in the table). + // Instead it will be correctly assigned in TraceMarkerModel::fromJSON() + break; + case Type::PeakTable: + peakThreshold = j.value("peak_threshold", -40); + break; + case Type::Lowpass: + case Type::Highpass: + case Type::Bandpass: + cutoffAmplitude = j.value("cutoff", -3.0); + break; + case Type::PhaseNoise: + j.value("offset", 10000); + break; + default: + // other types have no settings + break; + } + update(); +} + +unsigned int TraceMarker::toHash() +{ + // taking the easy way: create the json string and hash it (already contains all necessary information) + // This is slower than it could be, but this function is only used when loading setups, so this isn't a big problem + std::string json_string = toJSON().dump(); + return hash{}(json_string); +} + const std::vector &TraceMarker::getHelperMarkers() const { return helperMarkers; diff --git a/Software/PC_Application/Traces/tracemarker.h b/Software/PC_Application/Traces/tracemarker.h index a212339..3ec7f23 100644 --- a/Software/PC_Application/Traces/tracemarker.h +++ b/Software/PC_Application/Traces/tracemarker.h @@ -6,10 +6,11 @@ #include "trace.h" #include #include "CustomWidgets/siunitedit.h" +#include "savable.h" class TraceMarkerModel; -class TraceMarker : public QObject +class TraceMarker : public QObject, public Savable { Q_OBJECT; public: @@ -35,7 +36,22 @@ public: bool editingFrequeny; Trace *getTrace() const; - + enum class Type { + Manual, + Maximum, + Minimum, + Delta, + Noise, + PeakTable, + Lowpass, + Highpass, + Bandpass, + TOI, + PhaseNoise, + // keep last at end + Last, + }; + Type getType() const; QWidget *getTypeEditor(QAbstractItemDelegate *delegate = nullptr); void updateTypeFromEditor(QWidget *c); SIUnitEdit* getSettingsEditor(); @@ -46,8 +62,19 @@ public: TraceMarker *getParent() const; const std::vector& getHelperMarkers() const; TraceMarker *helperMarker(unsigned int i); + void assignDeltaMarker(TraceMarker *m); QString getSuffix() const; + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; + // Markers are referenced by pointers throughout this project (e.g. when added to a trace) + // When saving the current marker configuration, the pointer is not useful (e.g. for determining + // the associated delta marker. Instead a marker hash is saved to identify the correct marker. + // The hash should be influenced by every setting the marker can have. It should not depend on + // the marker data. + unsigned int toHash(); + + public slots: void setPosition(double freq); signals: @@ -55,6 +82,7 @@ signals: void dataChanged(TraceMarker *m); void symbolChanged(TraceMarker *m); void typeChanged(TraceMarker *m); + void assignedDeltaChanged(TraceMarker *m); void traceChanged(TraceMarker *m); void beginRemoveHelperMarkers(TraceMarker *m); void endRemoveHelperMarkers(TraceMarker *m); @@ -68,20 +96,6 @@ signals: void rawDataChanged(); void domainChanged(); private: - - enum class Type { - Manual, - Maximum, - Minimum, - Delta, - Noise, - PeakTable, - Lowpass, - Highpass, - Bandpass, - TOI, - PhaseNoise, - }; std::set getSupportedTypes(); static QString typeToString(Type t) { switch(t) { @@ -101,7 +115,6 @@ private: } void constrainPosition(); TraceMarker *bestDeltaCandidate(); - void assignDeltaMarker(TraceMarker *m); void deleteHelperMarkers(); void setType(Type t); double toDecibel(); diff --git a/Software/PC_Application/Traces/tracemarkermodel.cpp b/Software/PC_Application/Traces/tracemarkermodel.cpp index f4d274c..45a7779 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.cpp +++ b/Software/PC_Application/Traces/tracemarkermodel.cpp @@ -5,6 +5,8 @@ #include "CustomWidgets/siunitedit.h" #include +using namespace std; + static constexpr int rowHeight = 21; TraceMarkerModel::TraceMarkerModel(TraceModel &model, QObject *parent) @@ -268,6 +270,57 @@ TraceMarker *TraceMarkerModel::markerFromIndex(const QModelIndex &index) const } } +nlohmann::json TraceMarkerModel::toJSON() +{ + nlohmann::json j; + for(auto m : markers) { + j.push_back(m->toJSON()); + } + return j; +} + +void TraceMarkerModel::fromJSON(nlohmann::json j) +{ + // remove old markers + while(markers.size() > 0) { + removeMarker((unsigned int) 0); + } + for(auto jm : j) { + auto m = new TraceMarker(this); + try { + m->fromJSON(jm); + addMarker(m); + } catch (const exception &e) { + qWarning() << "Failed to creat marker from JSON:" << e.what(); + delete m; + } + } + // second pass to assign delta markers + for(unsigned int i=0;igetType() == TraceMarker::Type::Delta) { + if(!j[i].contains("delta_marker")) { + qWarning() << "JSON data does not contain assigned delta marker"; + continue; + } + unsigned int hash = j[i]["delta_marker"]; + // attempt to find correct marker + unsigned int m_delta = 0; + for(;m_delta < markers.size();m_delta++) { + auto m = markers[m_delta]; + if(m->toHash() == hash) { + markers[i]->assignDeltaMarker(m); + break; + } + } + if(m_delta >= markers.size()) { + qWarning() << "Unable to find assigned delta marker:" << hash; + } + } + } + // All done loading the markers, trigger update of persistent editors + emit setupLoadComplete(); +} + QSize MarkerTraceDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const { return QSize(0, rowHeight); diff --git a/Software/PC_Application/Traces/tracemarkermodel.h b/Software/PC_Application/Traces/tracemarkermodel.h index 43582dc..13937d2 100644 --- a/Software/PC_Application/Traces/tracemarkermodel.h +++ b/Software/PC_Application/Traces/tracemarkermodel.h @@ -6,6 +6,7 @@ #include #include "tracemodel.h" #include +#include "savable.h" class MarkerTraceDelegate : public QStyledItemDelegate { @@ -43,7 +44,7 @@ class MarkerSettingsDelegate : public QStyledItemDelegate void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; }; -class TraceMarkerModel : public QAbstractItemModel +class TraceMarkerModel : public QAbstractItemModel, public Savable { Q_OBJECT public: @@ -76,6 +77,9 @@ public: void updateMarkers(); TraceMarker *markerFromIndex(const QModelIndex &index) const; + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; + public slots: void addMarker(TraceMarker *t); void removeMarker(unsigned int index); @@ -84,6 +88,7 @@ public slots: signals: void markerAdded(TraceMarker *t); + void setupLoadComplete(); private slots: void markerDataChanged(TraceMarker *m); diff --git a/Software/PC_Application/Traces/tracesmithchart.cpp b/Software/PC_Application/Traces/tracesmithchart.cpp index 7e04091..53e9fce 100644 --- a/Software/PC_Application/Traces/tracesmithchart.cpp +++ b/Software/PC_Application/Traces/tracesmithchart.cpp @@ -194,6 +194,10 @@ void TraceSmithChart::draw(QPainter &p) { if (limitToSpan && (m->getPosition() < sweep_fmin || m->getPosition() > sweep_fmax)) { continue; } + if(m->getPosition() < trace->minX() || m->getPosition() > trace->maxX()) { + // marker not in trace range + continue; + } auto coords = m->getData(); coords *= smithCoordMax; auto symbol = m->getSymbol(); diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index a3d92d3..279d61f 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -465,6 +465,11 @@ void TraceXYPlot::draw(QPainter &p) xPosition = m->getPosition(); // } if (xPosition < XAxis.rangeMin || xPosition > XAxis.rangeMax) { + // marker not in graph range + continue; + } + if(xPosition < t->minX() || xPosition > t->maxX()) { + // marker not in trace range continue; } auto t = m->getTrace(); diff --git a/Software/PC_Application/VNA/vna.cpp b/Software/PC_Application/VNA/vna.cpp index efeceb7..1ecd786 100644 --- a/Software/PC_Application/VNA/vna.cpp +++ b/Software/PC_Application/VNA/vna.cpp @@ -448,6 +448,7 @@ nlohmann::json VNA::toJSON() nlohmann::json j; j["traces"] = traceModel.toJSON(); j["tiles"] = central->toJSON(); + j["markers"] = markerModel->toJSON(); return j; } @@ -459,6 +460,9 @@ void VNA::fromJSON(nlohmann::json j) if(j.contains("tiles")) { central->fromJSON(j["tiles"]); } + if(j.contains("markers")) { + markerModel->fromJSON(j["markers"]); + } } using namespace std;