save/load markers in setup

This commit is contained in:
Jan Käberich 2020-12-05 12:59:23 +01:00
parent 9ad8def2ea
commit 8d382e8b9c
9 changed files with 229 additions and 28 deletions

View File

@ -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;

View File

@ -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()

View File

@ -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<double> newdata;
if(!parentTrace || parentTrace->numSamples() == 0) {
// no data, invalidate
newdata = numeric_limits<complex<double>>::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<complex<double>>::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<std::string>{}(json_string);
}
const std::vector<TraceMarker *> &TraceMarker::getHelperMarkers() const
{
return helperMarkers;

View File

@ -6,10 +6,11 @@
#include "trace.h"
#include <QComboBox>
#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<TraceMarker *>& 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<Type> 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();

View File

@ -5,6 +5,8 @@
#include "CustomWidgets/siunitedit.h"
#include <QDebug>
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;i<markers.size();i++) {
if(markers[i]->getType() == 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);

View File

@ -6,6 +6,7 @@
#include <vector>
#include "tracemodel.h"
#include <QStyledItemDelegate>
#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);

View File

@ -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();

View File

@ -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();

View File

@ -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;