diff --git a/Software/PC_Application/Calibration/calkitdialog.ui b/Software/PC_Application/Calibration/calkitdialog.ui index 4048d94..2f95442 100644 --- a/Software/PC_Application/Calibration/calkitdialog.ui +++ b/Software/PC_Application/Calibration/calkitdialog.ui @@ -933,10 +933,10 @@ - - + + diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index de8fd0c..f872b0c 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -79,6 +79,7 @@ HEADERS += \ Traces/Math/parser/suStringTokens.h \ Traces/Math/parser/utGeneric.h \ Traces/Math/tdr.h \ + Traces/Math/timegate.h \ Traces/Math/tracemath.h \ Traces/Math/windowfunction.h \ Traces/fftcomplex.h \ @@ -190,6 +191,7 @@ SOURCES += \ Traces/Math/parser/mpValueCache.cpp \ Traces/Math/parser/mpVariable.cpp \ Traces/Math/tdr.cpp \ + Traces/Math/timegate.cpp \ Traces/Math/tracemath.cpp \ Traces/Math/windowfunction.cpp \ Traces/fftcomplex.cpp \ @@ -260,6 +262,9 @@ FORMS += \ Traces/Math/newtracemathdialog.ui \ Traces/Math/tdrdialog.ui \ Traces/Math/tdrexplanationwidget.ui \ + Traces/Math/timedomaingatingexplanationwidget.ui \ + Traces/Math/timegatedialog.ui \ + Traces/Math/timegateexplanationwidget.ui \ Traces/markerwidget.ui \ Traces/smithchartdialog.ui \ Traces/tracecsvexport.ui \ diff --git a/Software/PC_Application/Traces/Math/timedomaingatingexplanationwidget.ui b/Software/PC_Application/Traces/Math/timedomaingatingexplanationwidget.ui new file mode 100644 index 0000000..bf6e88c --- /dev/null +++ b/Software/PC_Application/Traces/Math/timedomaingatingexplanationwidget.ui @@ -0,0 +1,44 @@ + + + TimeDomainGatingExplanationWidget + + + + 0 + 0 + 364 + 412 + + + + Form + + + + + + <html><head/><body><p><span style=" font-weight:600;">Time Domain Gating</span></p><p>This is not a dedicated math operation but rather a convenience option to automatically setup a TDR with a time gate and followed by a DFT. The combination of these three operations performs time domain gating<br/></p><p><br/></p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Software/PC_Application/Traces/Math/timegate.cpp b/Software/PC_Application/Traces/Math/timegate.cpp new file mode 100644 index 0000000..8fd4974 --- /dev/null +++ b/Software/PC_Application/Traces/Math/timegate.cpp @@ -0,0 +1,404 @@ +#include "timegate.h" +#include +#include +#include "ui_timegatedialog.h" +#include "ui_timegateexplanationwidget.h" +#include "preferences.h" +#include +#include "Util/util.h" +#include "Traces/fftcomplex.h" +#include "unit.h" +#include + +Math::TimeGate::TimeGate() +{ + bandpass = true; +// if(input->rData().size()) { +// center = input->rData().back().x/2; +// span = center / 2; +// } else { + center = 10e-9; + span = 2e-9; +// } + connect(&window, &WindowFunction::changed, this, &TimeGate::updateFilter); +} + +TraceMath::DataType Math::TimeGate::outputType(TraceMath::DataType inputType) +{ + if(inputType == DataType::Time) { + return DataType::Time; + } else { + return DataType::Invalid; + } +} + +QString Math::TimeGate::description() +{ + QString d = "Time gate ("; + if(bandpass) { + d.append("bandpass"); + } else { + d.append("notch"); + } + d.append(") center: "+Unit::ToString(center, "s", "pnum ", 3)+", span: "+Unit::ToString(span, "s", "pnum ", 3)); + d += ", window: " + window.getDescription(); + return d; +} + +void Math::TimeGate::edit() +{ + if(dataType == DataType::Invalid) { + return; + } + + auto d = new QDialog(); + auto ui = new Ui::TimeGateDialog(); + ui->setupUi(d); + ui->graph->setGate(this); + ui->windowBox->setLayout(new QVBoxLayout); + ui->windowBox->layout()->addWidget(window.createEditor()); + + ui->start->setUnit("s"); + ui->start->setPrefixes("pnum "); + ui->start->setValue(center - span / 2); + + ui->stop->setUnit("s"); + ui->stop->setPrefixes("pnum "); + ui->stop->setValue(center + span / 2); + + ui->center->setUnit("s"); + ui->center->setPrefixes("pnum "); + ui->center->setValue(center); + + ui->span->setUnit("s"); + ui->span->setPrefixes("pnum "); + ui->span->setValue(center); + + if(bandpass) { + ui->type->setCurrentIndex(0); + } else { + ui->type->setCurrentIndex(1); + } + + ui->graph->setFocus(); + + connect(ui->span, &SIUnitEdit::valueChanged, this, &TimeGate::setSpan); + connect(this, &TimeGate::spanChanged, ui->span, &SIUnitEdit::setValueQuiet); + connect(ui->center, &SIUnitEdit::valueChanged, this, &TimeGate::setCenter); + connect(this, &TimeGate::centerChanged, ui->center, &SIUnitEdit::setValueQuiet); + connect(ui->start, &SIUnitEdit::valueChanged, this, &TimeGate::setStart); + connect(this, &TimeGate::startChanged, ui->start, &SIUnitEdit::setValueQuiet); + connect(ui->stop, &SIUnitEdit::valueChanged, this, &TimeGate::setStop); + connect(this, &TimeGate::stopChanged, ui->stop, &SIUnitEdit::setValueQuiet); + + connect(ui->type, qOverload(&QComboBox::currentIndexChanged), [=](int index) { + bandpass = index == 0; + updateFilter(); + }); + + connect(this, &TimeGate::outputSamplesChanged, ui->graph, qOverload<>(&QWidget::update)); + connect(this, &TimeGate::filterUpdated, ui->graph, qOverload<>(&QWidget::update)); + + updateFilter(); + + d->show(); +} + +QWidget *Math::TimeGate::createExplanationWidget() +{ + auto w = new QWidget(); + auto ui = new Ui::TimeGateExplanationWidget; + ui->setupUi(w); + return w; +} + +nlohmann::json Math::TimeGate::toJSON() +{ + nlohmann::json j; + j["center"] = center; + j["span"] = span; + j["bandpass"] = bandpass; + j["window"] = window.toJSON(); + return j; +} + +void Math::TimeGate::fromJSON(nlohmann::json j) +{ + if(j.contains("window")) { + window.fromJSON(j["window"]); + } + bandpass = j.value("bandpass", true); + center = j.value("center", center); + span = j.value("span", span); + updateFilter(); +} + +void Math::TimeGate::setStart(double start) +{ + double stop = center + span / 2; + if(start < stop) { + span = stop - start; + center = (stop + start) / 2; + updateFilter(); + } + emit centerChanged(center); + emit startChanged(center - span / 2); + emit spanChanged(span); +} + +void Math::TimeGate::setStop(double stop) +{ + double start = center - span / 2; + if(stop > start) { + span = stop - start; + center = (start + stop) / 2; + updateFilter(); + } + emit centerChanged(center); + emit stopChanged(center + span / 2); + emit spanChanged(span); +} + +void Math::TimeGate::setCenter(double center) +{ + this->center = center; + updateFilter(); + emit centerChanged(center); + emit startChanged(center - span / 2); + emit stopChanged(center + span / 2); +} + +void Math::TimeGate::setSpan(double span) +{ + this->span = span; + updateFilter(); + emit spanChanged(span); + emit startChanged(center - span / 2); + emit stopChanged(center + span / 2); +} + +double Math::TimeGate::getStart() +{ + return center - span / 2; +} + +double Math::TimeGate::getStop() +{ + return center + span / 2; +} + +void Math::TimeGate::inputSamplesChanged(unsigned int begin, unsigned int end) +{ + if(data.size() != input->rData().size()) { + data.resize(input->rData().size()); + updateFilter(); + } + for(auto i = begin;irData()[i]; + data[i].y *= filter[i]; + } + emit outputSamplesChanged(begin, end); + success(); +} + +void Math::TimeGate::updateFilter() +{ + if(!input) { + return; + } + std::vector> buf; + filter.clear(); + buf.resize(input->rData().size() * 2); + if(!buf.size()) { + return; + } + auto maxX = input->rData().back().x; + auto minX = input->rData().front().x; + if(center - span / 2 < minX) { + span = (center - minX) * 2; + } + if(center + span / 2 > maxX) { + span = (maxX - center) * 2; + } + auto wc1 = Util::Scale(center - span / 2, minX, maxX, 0, 1); + auto wc2 = Util::Scale(center + span / 2, minX, maxX, 0, 1); + + // create ideal filter coefficients + for(unsigned int i=0;irData().size()); +} + +Math::TimeGateGraph::TimeGateGraph(QWidget *parent) + : QWidget(parent) +{ + gate = nullptr; + grabbedStop = false; + grabbedStart = false; + setMouseTracking(true); +} + +QPoint Math::TimeGateGraph::plotValueToPixel(double x, double y) +{ + auto input = gate->getInput()->rData(); + auto minX = input.front().x; + auto maxX = input.back().x; + + int plotLeft = 0; + int plotRight = size().width(); + int plotTop = 0; + int plotBottom = size().height(); + + QPoint p; + p.setX(Util::Scale(x, minX, maxX, plotLeft, plotRight)); + p.setY(Util::Scale(y, -120, 20, plotBottom, plotTop)); + return p; +} + +QPointF Math::TimeGateGraph::pixelToPlotValue(QPoint p) +{ + auto input = gate->getInput()->rData(); + auto minX = input.front().x; + auto maxX = input.back().x; + + int plotLeft = 0; + int plotRight = size().width(); + int plotTop = 0; + int plotBottom = size().height(); + + QPointF ret; + ret.setX(Util::Scale(p.x(), plotLeft, plotRight, minX, maxX)); + ret.setY(Util::Scale(p.y(), plotBottom, plotTop, -120, 20)); + return ret; +} + +void Math::TimeGateGraph::paintEvent(QPaintEvent *event) +{ + if(!gate) { + return; + } + // grab input data + auto input = gate->getInput()->rData(); + + Q_UNUSED(event) + auto pref = Preferences::getInstance(); + QPainter p(this); + // fill background + p.setBackground(QBrush(pref.General.graphColors.background)); + p.fillRect(0, 0, width(), height(), QBrush(pref.General.graphColors.background)); + + // plot trace + auto pen = QPen(Qt::green, 1); + pen.setCosmetic(true); + pen.setStyle(Qt::SolidLine); + p.setPen(pen); + for(unsigned int i=1;irFilter(); + pen = QPen(Qt::red, 1); + p.setPen(pen); + for(unsigned int i=1;igetStart(), 0).x(); + auto stopX = plotValueToPixel(gate->getStop(), 0).x(); + if(abs(event->pos().x() - startX) < catchDistance) { + grabbedStart = true; + } else if(abs(event->pos().x() - stopX) < catchDistance) { + grabbedStop = true; + } +} + +void Math::TimeGateGraph::mouseReleaseEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + grabbedStop = false; + grabbedStart = false; +} + +void Math::TimeGateGraph::mouseMoveEvent(QMouseEvent *event) +{ + if(!gate) { + return; + } + auto value = pixelToPlotValue(event->pos()); + if(grabbedStop) { + gate->setStop(value.x()); + } else if(grabbedStart) { + gate->setStart(value.x()); + } else { + // nothing grabbed but might be above start/stop -> check and change cursor + auto startX = plotValueToPixel(gate->getStart(), 0).x(); + auto stopX = plotValueToPixel(gate->getStop(), 0).x(); + if(abs(event->pos().x() - startX) < catchDistance || abs(event->pos().x() - stopX) < catchDistance) { + setCursor(Qt::SizeHorCursor); + } else { + setCursor(Qt::ArrowCursor); + } + } +} diff --git a/Software/PC_Application/Traces/Math/timegate.h b/Software/PC_Application/Traces/Math/timegate.h new file mode 100644 index 0000000..41c2fa4 --- /dev/null +++ b/Software/PC_Application/Traces/Math/timegate.h @@ -0,0 +1,86 @@ +#ifndef TIMEGATE_H +#define TIMEGATE_H + +#include "tracemath.h" +#include "windowfunction.h" + +namespace Math { + +class TimeGate; + +class TimeGateGraph : public QWidget { +public: + TimeGateGraph(QWidget *parent); + + void setGate(TimeGate *gate) { + this->gate = gate; + } +private: + static constexpr int catchDistance = 5; + + QPoint plotValueToPixel(double x, double y); + QPointF pixelToPlotValue(QPoint p); + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + TimeGate *gate; + bool grabbedStart; + bool grabbedStop; +}; + +class TimeGate : public TraceMath +{ + Q_OBJECT +public: + TimeGate(); + + virtual DataType outputType(DataType inputType) override; + virtual QString description() override; + + virtual void edit() override; + + static QWidget *createExplanationWidget(); + + virtual nlohmann::json toJSON() override; + virtual void fromJSON(nlohmann::json j) override; + Type getType() override {return Type::TimeGate;}; + + const std::vector &rFilter() { return filter;}; + + double getStart(); + double getStop(); + +public slots: + // a single value of the input data has changed, index determines which sample has changed + virtual void inputSamplesChanged(unsigned int begin, unsigned int end) override; + + void setStart(double start); + void setStop(double stop); + void setCenter(double center); + void setSpan(double span); + +private slots: + void updateFilter(); +signals: + void filterUpdated(); + void startChanged(double newval); + void stopChanged(double newval); + void centerChanged(double newval); + void spanChanged(double newval); +private: + enum class Filter { + None, + Hamming, + Hann + }; + + bool bandpass; + double center, span; + WindowFunction window; + std::vector filter; +}; + +} + +#endif // TIMEGATE_H diff --git a/Software/PC_Application/Traces/Math/timegatedialog.ui b/Software/PC_Application/Traces/Math/timegatedialog.ui new file mode 100644 index 0000000..796ca17 --- /dev/null +++ b/Software/PC_Application/Traces/Math/timegatedialog.ui @@ -0,0 +1,163 @@ + + + TimeGateDialog + + + + 0 + 0 + 973 + 317 + + + + Time Gate + + + true + + + + + + + + + + + + Start: + + + + + + + + + + Stop: + + + + + + + + + + Center: + + + + + + + + + + Span: + + + + + + + + + + Type: + + + + + + + + Bandpass + + + + + Notch + + + + + + + + + + Shapewindow: + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + SIUnitEdit + QLineEdit +
CustomWidgets/siunitedit.h
+
+ + Math::TimeGateGraph + QWidget +
Traces/Math/timegate.h
+ 1 +
+
+ + + + buttonBox + accepted() + TimeGateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TimeGateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/Software/PC_Application/Traces/Math/timegateexplanationwidget.ui b/Software/PC_Application/Traces/Math/timegateexplanationwidget.ui new file mode 100644 index 0000000..323e013 --- /dev/null +++ b/Software/PC_Application/Traces/Math/timegateexplanationwidget.ui @@ -0,0 +1,44 @@ + + + TimeGateExplanationWidget + + + + 0 + 0 + 364 + 412 + + + + Form + + + + + + <html><head/><body><p><span style=" font-weight:600;">Time Gate</span></p><p>A bandpass or notch filter in the time domain. Depending on the setup, this operation can isolate or remove a response at a certain distance in time.<br/></p><p><br/></p></body></html> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/Software/PC_Application/Traces/Math/tracemath.cpp b/Software/PC_Application/Traces/Math/tracemath.cpp index fce714a..44a6192 100644 --- a/Software/PC_Application/Traces/Math/tracemath.cpp +++ b/Software/PC_Application/Traces/Math/tracemath.cpp @@ -4,7 +4,9 @@ #include "tdr.h" #include "dft.h" #include "expression.h" +#include "timegate.h" #include "Traces/trace.h" +#include "ui_timedomaingatingexplanationwidget.h" TraceMath::TraceMath() { @@ -13,20 +15,34 @@ TraceMath::TraceMath() error("Invalid input"); } -TraceMath *TraceMath::createMath(TraceMath::Type type) +std::vector TraceMath::createMath(TraceMath::Type type) { + std::vector ret; switch(type) { case Type::MedianFilter: - return new Math::MedianFilter(); + ret.push_back(new Math::MedianFilter()); + break; case Type::TDR: - return new Math::TDR(); + ret.push_back(new Math::TDR()); + break; case Type::DFT: - return new Math::DFT(); + ret.push_back(new Math::DFT()); + break; case Type::Expression: - return new Math::Expression(); + ret.push_back(new Math::Expression()); + break; + case Type::TimeGate: + ret.push_back(new Math::TimeGate()); + break; + case Type::TimeDomainGating: + ret.push_back(new Math::TDR()); + ret.push_back(new Math::TimeGate()); + ret.push_back(new Math::DFT()); + break; default: - return nullptr; + break; } + return ret; } TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type) @@ -49,6 +65,17 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type) ret.name = "Custom Expression"; ret.explanationWidget = Math::Expression::createExplanationWidget(); break; + case Type::TimeGate: + ret.name = "Time Gate"; + ret.explanationWidget = Math::TimeGate::createExplanationWidget(); + break; + case Type::TimeDomainGating: { + ret.name = "Time Domain Gating"; + ret.explanationWidget = new QWidget(); + auto ui = new Ui::TimeDomainGatingExplanationWidget; + ui->setupUi(ret.explanationWidget); + } + break; default: break; } diff --git a/Software/PC_Application/Traces/Math/tracemath.h b/Software/PC_Application/Traces/Math/tracemath.h index ff29cbb..ecb6015 100644 --- a/Software/PC_Application/Traces/Math/tracemath.h +++ b/Software/PC_Application/Traces/Math/tracemath.h @@ -74,11 +74,13 @@ public: TDR, DFT, Expression, + TimeGate, + TimeDomainGating, // Add new math operations here, do not explicitly assign values and keep the Last entry at the last position Last, }; - static TraceMath *createMath(Type type); + static std::vector createMath(Type type); class TypeInfo { public: QString name; diff --git a/Software/PC_Application/Traces/trace.cpp b/Software/PC_Application/Traces/trace.cpp index 0f55b9d..751bba0 100644 --- a/Software/PC_Application/Traces/trace.cpp +++ b/Software/PC_Application/Traces/trace.cpp @@ -396,7 +396,7 @@ void Trace::fromJSON(nlohmann::json j) continue; } qDebug() << "Creating math operation of type:" << operation; - auto op = TraceMath::createMath(type); + auto op = TraceMath::createMath(type)[0]; if(jm.contains("settings")) { op->fromJSON(jm["settings"]); } @@ -731,6 +731,18 @@ void Trace::addMathOperation(TraceMath *math) updateLastMath(mathOps.rbegin()); } +void Trace::addMathOperations(std::vector maths) +{ + TraceMath *input = lastMath; + for(auto m : maths) { + MathInfo info = {.math = m, .enabled = true}; + m->assignInput(input); + input = m; + mathOps.push_back(info); + } + updateLastMath(mathOps.rbegin()); +} + void Trace::removeMathOperation(unsigned int index) { if(index < 1 || index >= mathOps.size()) { diff --git a/Software/PC_Application/Traces/trace.h b/Software/PC_Application/Traces/trace.h index 8f51b31..b2d197a 100644 --- a/Software/PC_Application/Traces/trace.h +++ b/Software/PC_Application/Traces/trace.h @@ -99,7 +99,8 @@ public: bool hasMathOperations(); // check if math operations are set up (not necessarily enabled) void enableMath(bool enable); // Adds a new math operation at the end of the list and enables it - void addMathOperation(TraceMath *mathOps); + void addMathOperation(TraceMath *math); + void addMathOperations(std::vector maths); // removes the math operation at the given index. Index 0 is invalid as this would be the trace itself void removeMathOperation(unsigned int index); // swaps the order of math operations at index and index+1. Does nothing if either index is invalid diff --git a/Software/PC_Application/Traces/traceeditdialog.cpp b/Software/PC_Application/Traces/traceeditdialog.cpp index a535794..8010880 100644 --- a/Software/PC_Application/Traces/traceeditdialog.cpp +++ b/Software/PC_Application/Traces/traceeditdialog.cpp @@ -141,10 +141,23 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) : connect(ui->list, &QListWidget::doubleClicked, ui->buttonBox, &QDialogButtonBox::accepted); connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ - auto newMath = TraceMath::createMath(static_cast(ui->list->currentRow())); - if(newMath) { - model->addOperation(newMath); - } + auto type = static_cast(ui->list->currentRow()); + auto newMath = TraceMath::createMath(type); + model->addOperations(newMath); + if(newMath.size() == 1) { + // any normal math operation added, edit now + newMath[0]->edit(); + } else { + // composite operation added, check which one and edit the correct suboperation + switch(type) { + case TraceMath::Type::TimeDomainGating: + // TDR/DFT can be left at default, edit the actual gate + newMath[1]->edit(); + break; + default: + break; + } + } }); ui->list->setCurrentRow(0); ui->stack->setCurrentIndex(0); @@ -305,8 +318,13 @@ void MathModel::addOperation(TraceMath *math) beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size()); t.addMathOperation(math); endInsertRows(); - // open the editor for the newly added operation - math->edit(); +} + +void MathModel::addOperations(std::vector maths) +{ + beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size() + maths.size() - 1); + t.addMathOperations(maths); + endInsertRows(); } void MathModel::deleteRow(unsigned int row) diff --git a/Software/PC_Application/Traces/traceeditdialog.h b/Software/PC_Application/Traces/traceeditdialog.h index a4a794b..a590548 100644 --- a/Software/PC_Application/Traces/traceeditdialog.h +++ b/Software/PC_Application/Traces/traceeditdialog.h @@ -29,6 +29,7 @@ public: Qt::ItemFlags flags(const QModelIndex &index) const override; void addOperation(TraceMath *math); + void addOperations(std::vector maths); void deleteRow(unsigned int row); private: diff --git a/Software/PC_Application/Traces/tracemarker.cpp b/Software/PC_Application/Traces/tracemarker.cpp index 12e807b..39fbbac 100644 --- a/Software/PC_Application/Traces/tracemarker.cpp +++ b/Software/PC_Application/Traces/tracemarker.cpp @@ -7,6 +7,8 @@ #include #include "tracemarkermodel.h" #include "unit.h" +#include +#include using namespace std; @@ -19,13 +21,16 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *paren data(0), type(Type::Manual), description(descr), + contextmenu(nullptr), delta(nullptr), parent(parent), cutoffAmplitude(-3.0), peakThreshold(-40.0), offset(10000) { - + connect(this, &TraceMarker::traceChanged, this, &TraceMarker::updateContextmenu); + connect(this, &TraceMarker::typeChanged, this, &TraceMarker::updateContextmenu); + updateContextmenu(); } TraceMarker::~TraceMarker() @@ -363,6 +368,29 @@ void TraceMarker::deltaDeleted() update(); } +void TraceMarker::updateContextmenu() +{ + if(contextmenu) { + delete contextmenu; + } + contextmenu = new QMenu(); + auto typemenu = new QMenu("Type"); + auto typegroup = new QActionGroup(contextmenu); + for(auto t : getSupportedTypes()) { + auto setTypeAction = new QAction(typeToString(t)); + setTypeAction->setCheckable(true); + if(t == type) { + setTypeAction->setChecked(true); + } + connect(setTypeAction, &QAction::triggered, [=](){ + setType(t); + }); + typegroup->addAction(setTypeAction); + typemenu->addAction(setTypeAction); + } + contextmenu->addMenu(typemenu); +} + std::set TraceMarker::getSupportedTypes() { set supported; diff --git a/Software/PC_Application/Traces/tracemarker.h b/Software/PC_Application/Traces/tracemarker.h index 5d9700d..8f2fbe0 100644 --- a/Software/PC_Application/Traces/tracemarker.h +++ b/Software/PC_Application/Traces/tracemarker.h @@ -57,6 +57,8 @@ public: SIUnitEdit* getSettingsEditor(); void adjustSettings(double value); + QMenu *getContextMenu() { return contextmenu;} + // Updates marker position and data on automatic markers. Should be called whenever the tracedata is complete void update(); TraceMarker *getParent() const; @@ -93,6 +95,7 @@ private slots: void updateSymbol(); void checkDeltaMarker(); void deltaDeleted(); + void updateContextmenu(); signals: void rawDataChanged(); void domainChanged(); @@ -133,6 +136,8 @@ private: QString suffix; QString description; + QMenu *contextmenu; + TraceMarker *delta; std::vector helperMarkers; TraceMarker *parent; diff --git a/Software/PC_Application/Traces/traceplot.cpp b/Software/PC_Application/Traces/traceplot.cpp index 08dd6b8..34aca49 100644 --- a/Software/PC_Application/Traces/traceplot.cpp +++ b/Software/PC_Application/Traces/traceplot.cpp @@ -92,7 +92,16 @@ void TracePlot::initializeTraceInfo() void TracePlot::contextMenuEvent(QContextMenuEvent *event) { - contextmenu->exec(event->globalPos()); + auto m = markerAtPosition(event->pos()); + QMenu *menu; + if(m) { + // right click on marker, execute its contextmenu + menu = m->getContextMenu(); + } else { + // no marker, contextmenu of graph + menu = contextmenu; + } + menu->exec(event->globalPos()); if(markedForDeletion) { emit deleted(this); delete this; @@ -138,38 +147,7 @@ void TracePlot::paintEvent(QPaintEvent *event) void TracePlot::mousePressEvent(QMouseEvent *event) { - auto clickPoint = event->pos() - QPoint(marginLeft, marginTop); - // check if click was near a marker - unsigned int closestDistance = numeric_limits::max(); - TraceMarker *closestMarker = nullptr; - for(auto t : traces) { - if(!t.second) { - // this trace is disabled, skip - continue; - } - auto markers = t.first->getMarkers(); - for(auto m : markers) { - if(!m->isMovable()) { - continue; - } - auto markerPoint = markerToPixel(m); - if(markerPoint.isNull()) { - // invalid, skip - continue; - } - auto diff = markerPoint - clickPoint; - unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); - if(distance < closestDistance) { - closestDistance = distance; - closestMarker = m; - } - } - } - if(closestDistance <= 400) { - selectedMarker = closestMarker; - } else { - selectedMarker = nullptr; - } + selectedMarker = markerAtPosition(event->pos(), true); } void TracePlot::mouseReleaseEvent(QMouseEvent *event) @@ -205,6 +183,46 @@ void TracePlot::leaveEvent(QEvent *event) selectedMarker = nullptr; } +TraceMarker *TracePlot::markerAtPosition(QPoint p, bool onlyMovable) +{ + auto clickPoint = p - QPoint(marginLeft, marginTop); + // check if click was near a marker + unsigned int closestDistance = numeric_limits::max(); + TraceMarker *closestMarker = nullptr; + for(auto t : traces) { + if(!t.second) { + // this trace is disabled, skip + continue; + } + auto markers = t.first->getMarkers(); + for(auto m : markers) { + if(!m->isMovable() && onlyMovable) { + continue; + } + auto markerPoint = markerToPixel(m); + if(markerPoint.isNull()) { + // invalid, skip + continue; + } + auto diff = markerPoint - clickPoint; + unsigned int distance = diff.x() * diff.x() + diff.y() * diff.y(); + if(distance < closestDistance) { + closestDistance = distance; + if(m->getParent()) { + closestMarker = m->getParent(); + } else { + closestMarker = m; + } + } + } + } + if(closestDistance <= 400) { + return closestMarker; + } else { + return nullptr; + } +} + void TracePlot::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("trace/pointer")) { diff --git a/Software/PC_Application/Traces/traceplot.h b/Software/PC_Application/Traces/traceplot.h index 28790e5..afdb63a 100644 --- a/Software/PC_Application/Traces/traceplot.h +++ b/Software/PC_Application/Traces/traceplot.h @@ -55,6 +55,8 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; void leaveEvent(QEvent *event) override; + TraceMarker *markerAtPosition(QPoint p, bool onlyMovable = false); + // handle trace drops virtual bool dropSupported(Trace *t) = 0; void dragEnterEvent(QDragEnterEvent *event) override; diff --git a/Software/PC_Application/Traces/tracexyplot.cpp b/Software/PC_Application/Traces/tracexyplot.cpp index 9c8dd71..b23f0d9 100644 --- a/Software/PC_Application/Traces/tracexyplot.cpp +++ b/Software/PC_Application/Traces/tracexyplot.cpp @@ -859,7 +859,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo ret.setY(real(data.y)); break; case YAxisType::ImpulseMag: - ret.setY(abs(data.y)); + ret.setY(20*log10(abs(data.y))); break; case YAxisType::Step: ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real()); @@ -945,7 +945,7 @@ void TraceXYPlot::traceDropped(Trace *t, QPoint position) InformationBox::ShowMessage("X Axis Domain Change", "You dropped a time domain trace but the graph is still set up for the frequency domain." " All current traces will be removed and the graph changed to time domain."); setXAxis(XAxisType::Time, XAxisMode::FitTraces, 0, 1, 0.1); - setYAxis(0, YAxisType::ImpulseReal, false, true, 0, 1, 1.0); + setYAxis(0, YAxisType::ImpulseMag, false, true, 0, 1, 1.0); setYAxis(1, YAxisType::Disabled, false, true, 0, 1, 1.0); } @@ -1002,7 +1002,7 @@ QString TraceXYPlot::AxisUnit(TraceXYPlot::YAxisType type) case TraceXYPlot::YAxisType::Phase: return "°"; break; case TraceXYPlot::YAxisType::VSWR: return ""; break; case TraceXYPlot::YAxisType::ImpulseReal: return ""; break; - case TraceXYPlot::YAxisType::ImpulseMag: return ""; break; + case TraceXYPlot::YAxisType::ImpulseMag: return "db"; break; case TraceXYPlot::YAxisType::Step: return ""; break; case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break; default: return ""; break;