This commit is contained in:
Jan Käberich 2021-04-27 20:32:10 +02:00
commit 36398f626e
18 changed files with 914 additions and 54 deletions

View File

@ -933,10 +933,10 @@
</connection> </connection>
</connections> </connections>
<buttongroups> <buttongroups>
<buttongroup name="TRL_Rtype"/>
<buttongroup name="LoadType"/> <buttongroup name="LoadType"/>
<buttongroup name="ThroughType"/> <buttongroup name="TRL_Rtype"/>
<buttongroup name="ShortType"/> <buttongroup name="ShortType"/>
<buttongroup name="OpenType"/> <buttongroup name="OpenType"/>
<buttongroup name="ThroughType"/>
</buttongroups> </buttongroups>
</ui> </ui>

View File

@ -79,6 +79,7 @@ HEADERS += \
Traces/Math/parser/suStringTokens.h \ Traces/Math/parser/suStringTokens.h \
Traces/Math/parser/utGeneric.h \ Traces/Math/parser/utGeneric.h \
Traces/Math/tdr.h \ Traces/Math/tdr.h \
Traces/Math/timegate.h \
Traces/Math/tracemath.h \ Traces/Math/tracemath.h \
Traces/Math/windowfunction.h \ Traces/Math/windowfunction.h \
Traces/fftcomplex.h \ Traces/fftcomplex.h \
@ -190,6 +191,7 @@ SOURCES += \
Traces/Math/parser/mpValueCache.cpp \ Traces/Math/parser/mpValueCache.cpp \
Traces/Math/parser/mpVariable.cpp \ Traces/Math/parser/mpVariable.cpp \
Traces/Math/tdr.cpp \ Traces/Math/tdr.cpp \
Traces/Math/timegate.cpp \
Traces/Math/tracemath.cpp \ Traces/Math/tracemath.cpp \
Traces/Math/windowfunction.cpp \ Traces/Math/windowfunction.cpp \
Traces/fftcomplex.cpp \ Traces/fftcomplex.cpp \
@ -260,6 +262,9 @@ FORMS += \
Traces/Math/newtracemathdialog.ui \ Traces/Math/newtracemathdialog.ui \
Traces/Math/tdrdialog.ui \ Traces/Math/tdrdialog.ui \
Traces/Math/tdrexplanationwidget.ui \ Traces/Math/tdrexplanationwidget.ui \
Traces/Math/timedomaingatingexplanationwidget.ui \
Traces/Math/timegatedialog.ui \
Traces/Math/timegateexplanationwidget.ui \
Traces/markerwidget.ui \ Traces/markerwidget.ui \
Traces/smithchartdialog.ui \ Traces/smithchartdialog.ui \
Traces/tracecsvexport.ui \ Traces/tracecsvexport.ui \

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TimeDomainGatingExplanationWidget</class>
<widget class="QWidget" name="TimeDomainGatingExplanationWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>364</width>
<height>412</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="widget">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Time Domain Gating&lt;/span&gt;&lt;/p&gt;&lt;p&gt;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&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,404 @@
#include "timegate.h"
#include <QWidget>
#include <QDialog>
#include "ui_timegatedialog.h"
#include "ui_timegateexplanationwidget.h"
#include "preferences.h"
#include <QPainter>
#include "Util/util.h"
#include "Traces/fftcomplex.h"
#include "unit.h"
#include <QMouseEvent>
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<int>(&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;i<end;i++) {
data[i] = input->rData()[i];
data[i].y *= filter[i];
}
emit outputSamplesChanged(begin, end);
success();
}
void Math::TimeGate::updateFilter()
{
if(!input) {
return;
}
std::vector<std::complex<double>> 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<double>(center - span / 2, minX, maxX, 0, 1);
auto wc2 = Util::Scale<double>(center + span / 2, minX, maxX, 0, 1);
// create ideal filter coefficients
for(unsigned int i=0;i<buf.size();i++) {
int n = i - buf.size() / 2;
if(n == 0) {
buf[i] = wc2 - wc1;
} else {
buf[i] = (sin(M_PI * wc2 * n) - sin(M_PI * wc1 * n)) / (n * M_PI);
}
if(!bandpass) {
if(n == 0) {
buf[i] = 1.0 - buf[i];
} else {
buf[i] = sin(M_PI * n) / M_PI - buf[i];
}
}
}
window.apply(buf);
Fft::shift(buf, true);
Fft::transform(buf, false);
filter.resize(buf.size());
for(unsigned int i=0;i<buf.size() / 2;i++) {
filter[i] = abs(buf[i]);
}
emit filterUpdated();
// needs to update output samples, pretend that input samples have changed
inputSamplesChanged(0, input->rData().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<double>(x, minX, maxX, plotLeft, plotRight));
p.setY(Util::Scale<double>(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<double>(p.x(), plotLeft, plotRight, minX, maxX));
ret.setY(Util::Scale<double>(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;i<input.size();i++) {
auto last = input[i-1];
auto now = input[i];
auto y_last = 20*log10(abs(last.y));
auto y_now = 20*log10(abs(now.y));
if(std::isnan(y_last) || std::isnan(y_now) || std::isinf(y_last) || std::isinf(y_now)) {
continue;
}
// scale to plot coordinates
auto p1 = plotValueToPixel(last.x, y_last);
auto p2 = plotValueToPixel(now.x, y_now);
// draw line
p.drawLine(p1, p2);
}
// plot filter shape
auto filter = gate->rFilter();
pen = QPen(Qt::red, 1);
p.setPen(pen);
for(unsigned int i=1;i<filter.size();i++) {
auto x_last = input[i-1].x;
auto x_now = input[i].x;
auto f_last = 20*log10(filter[i-1]);
auto f_now = 20*log10(filter[i]);
if(std::isnan(f_last) || std::isnan(f_now) || std::isinf(f_last) || std::isinf(f_now)) {
continue;
}
// scale to plot coordinates
auto p1 = plotValueToPixel(x_last, f_last);
auto p2 = plotValueToPixel(x_now, f_now);
// draw line
p.drawLine(p1, p2);
}
}
void Math::TimeGateGraph::mousePressEvent(QMouseEvent *event)
{
if(!gate) {
return;
}
grabbedStop = false;
grabbedStart = false;
auto startX = plotValueToPixel(gate->getStart(), 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);
}
}
}

View File

@ -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<double> &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<double> filter;
};
}
#endif // TIMEGATE_H

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TimeGateDialog</class>
<widget class="QDialog" name="TimeGateDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>973</width>
<height>317</height>
</rect>
</property>
<property name="windowTitle">
<string>Time Gate</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Start:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SIUnitEdit" name="start"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Stop:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="stop"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Center:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="center"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Span:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="span"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="type">
<item>
<property name="text">
<string>Bandpass</string>
</property>
</item>
<item>
<property name="text">
<string>Notch</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="windowBox">
<property name="title">
<string>Shapewindow:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Math::TimeGateGraph" name="graph" native="true"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
<customwidget>
<class>Math::TimeGateGraph</class>
<extends>QWidget</extends>
<header>Traces/Math/timegate.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TimeGateDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TimeGateDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TimeGateExplanationWidget</class>
<widget class="QWidget" name="TimeGateExplanationWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>364</width>
<height>412</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="widget">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Time Gate&lt;/span&gt;&lt;/p&gt;&lt;p&gt;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.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -4,7 +4,9 @@
#include "tdr.h" #include "tdr.h"
#include "dft.h" #include "dft.h"
#include "expression.h" #include "expression.h"
#include "timegate.h"
#include "Traces/trace.h" #include "Traces/trace.h"
#include "ui_timedomaingatingexplanationwidget.h"
TraceMath::TraceMath() TraceMath::TraceMath()
{ {
@ -13,20 +15,34 @@ TraceMath::TraceMath()
error("Invalid input"); error("Invalid input");
} }
TraceMath *TraceMath::createMath(TraceMath::Type type) std::vector<TraceMath *> TraceMath::createMath(TraceMath::Type type)
{ {
std::vector<TraceMath*> ret;
switch(type) { switch(type) {
case Type::MedianFilter: case Type::MedianFilter:
return new Math::MedianFilter(); ret.push_back(new Math::MedianFilter());
break;
case Type::TDR: case Type::TDR:
return new Math::TDR(); ret.push_back(new Math::TDR());
break;
case Type::DFT: case Type::DFT:
return new Math::DFT(); ret.push_back(new Math::DFT());
break;
case Type::Expression: 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: default:
return nullptr; break;
} }
return ret;
} }
TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type) TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
@ -49,6 +65,17 @@ TraceMath::TypeInfo TraceMath::getInfo(TraceMath::Type type)
ret.name = "Custom Expression"; ret.name = "Custom Expression";
ret.explanationWidget = Math::Expression::createExplanationWidget(); ret.explanationWidget = Math::Expression::createExplanationWidget();
break; 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: default:
break; break;
} }

View File

@ -74,11 +74,13 @@ public:
TDR, TDR,
DFT, DFT,
Expression, Expression,
TimeGate,
TimeDomainGating,
// Add new math operations here, do not explicitly assign values and keep the Last entry at the last position // Add new math operations here, do not explicitly assign values and keep the Last entry at the last position
Last, Last,
}; };
static TraceMath *createMath(Type type); static std::vector<TraceMath*> createMath(Type type);
class TypeInfo { class TypeInfo {
public: public:
QString name; QString name;

View File

@ -396,7 +396,7 @@ void Trace::fromJSON(nlohmann::json j)
continue; continue;
} }
qDebug() << "Creating math operation of type:" << operation; qDebug() << "Creating math operation of type:" << operation;
auto op = TraceMath::createMath(type); auto op = TraceMath::createMath(type)[0];
if(jm.contains("settings")) { if(jm.contains("settings")) {
op->fromJSON(jm["settings"]); op->fromJSON(jm["settings"]);
} }
@ -731,6 +731,18 @@ void Trace::addMathOperation(TraceMath *math)
updateLastMath(mathOps.rbegin()); updateLastMath(mathOps.rbegin());
} }
void Trace::addMathOperations(std::vector<TraceMath *> 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) void Trace::removeMathOperation(unsigned int index)
{ {
if(index < 1 || index >= mathOps.size()) { if(index < 1 || index >= mathOps.size()) {

View File

@ -99,7 +99,8 @@ public:
bool hasMathOperations(); // check if math operations are set up (not necessarily enabled) bool hasMathOperations(); // check if math operations are set up (not necessarily enabled)
void enableMath(bool enable); void enableMath(bool enable);
// Adds a new math operation at the end of the list and enables it // 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<TraceMath*> maths);
// removes the math operation at the given index. Index 0 is invalid as this would be the trace itself // removes the math operation at the given index. Index 0 is invalid as this would be the trace itself
void removeMathOperation(unsigned int index); void removeMathOperation(unsigned int index);
// swaps the order of math operations at index and index+1. Does nothing if either index is invalid // swaps the order of math operations at index and index+1. Does nothing if either index is invalid

View File

@ -141,10 +141,23 @@ TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
connect(ui->list, &QListWidget::doubleClicked, ui->buttonBox, &QDialogButtonBox::accepted); connect(ui->list, &QListWidget::doubleClicked, ui->buttonBox, &QDialogButtonBox::accepted);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){ connect(ui->buttonBox, &QDialogButtonBox::accepted, [=](){
auto newMath = TraceMath::createMath(static_cast<TraceMath::Type>(ui->list->currentRow())); auto type = static_cast<TraceMath::Type>(ui->list->currentRow());
if(newMath) { auto newMath = TraceMath::createMath(type);
model->addOperation(newMath); 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->list->setCurrentRow(0);
ui->stack->setCurrentIndex(0); ui->stack->setCurrentIndex(0);
@ -305,8 +318,13 @@ void MathModel::addOperation(TraceMath *math)
beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size()); beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size());
t.addMathOperation(math); t.addMathOperation(math);
endInsertRows(); endInsertRows();
// open the editor for the newly added operation }
math->edit();
void MathModel::addOperations(std::vector<TraceMath *> maths)
{
beginInsertRows(QModelIndex(), t.getMathOperations().size(), t.getMathOperations().size() + maths.size() - 1);
t.addMathOperations(maths);
endInsertRows();
} }
void MathModel::deleteRow(unsigned int row) void MathModel::deleteRow(unsigned int row)

View File

@ -29,6 +29,7 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override; Qt::ItemFlags flags(const QModelIndex &index) const override;
void addOperation(TraceMath *math); void addOperation(TraceMath *math);
void addOperations(std::vector<TraceMath*> maths);
void deleteRow(unsigned int row); void deleteRow(unsigned int row);
private: private:

View File

@ -7,6 +7,8 @@
#include <QDebug> #include <QDebug>
#include "tracemarkermodel.h" #include "tracemarkermodel.h"
#include "unit.h" #include "unit.h"
#include <QMenu>
#include <QActionGroup>
using namespace std; using namespace std;
@ -19,13 +21,16 @@ TraceMarker::TraceMarker(TraceMarkerModel *model, int number, TraceMarker *paren
data(0), data(0),
type(Type::Manual), type(Type::Manual),
description(descr), description(descr),
contextmenu(nullptr),
delta(nullptr), delta(nullptr),
parent(parent), parent(parent),
cutoffAmplitude(-3.0), cutoffAmplitude(-3.0),
peakThreshold(-40.0), peakThreshold(-40.0),
offset(10000) offset(10000)
{ {
connect(this, &TraceMarker::traceChanged, this, &TraceMarker::updateContextmenu);
connect(this, &TraceMarker::typeChanged, this, &TraceMarker::updateContextmenu);
updateContextmenu();
} }
TraceMarker::~TraceMarker() TraceMarker::~TraceMarker()
@ -363,6 +368,29 @@ void TraceMarker::deltaDeleted()
update(); 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::Type> TraceMarker::getSupportedTypes() std::set<TraceMarker::Type> TraceMarker::getSupportedTypes()
{ {
set<TraceMarker::Type> supported; set<TraceMarker::Type> supported;

View File

@ -57,6 +57,8 @@ public:
SIUnitEdit* getSettingsEditor(); SIUnitEdit* getSettingsEditor();
void adjustSettings(double value); void adjustSettings(double value);
QMenu *getContextMenu() { return contextmenu;}
// Updates marker position and data on automatic markers. Should be called whenever the tracedata is complete // Updates marker position and data on automatic markers. Should be called whenever the tracedata is complete
void update(); void update();
TraceMarker *getParent() const; TraceMarker *getParent() const;
@ -93,6 +95,7 @@ private slots:
void updateSymbol(); void updateSymbol();
void checkDeltaMarker(); void checkDeltaMarker();
void deltaDeleted(); void deltaDeleted();
void updateContextmenu();
signals: signals:
void rawDataChanged(); void rawDataChanged();
void domainChanged(); void domainChanged();
@ -133,6 +136,8 @@ private:
QString suffix; QString suffix;
QString description; QString description;
QMenu *contextmenu;
TraceMarker *delta; TraceMarker *delta;
std::vector<TraceMarker*> helperMarkers; std::vector<TraceMarker*> helperMarkers;
TraceMarker *parent; TraceMarker *parent;

View File

@ -92,7 +92,16 @@ void TracePlot::initializeTraceInfo()
void TracePlot::contextMenuEvent(QContextMenuEvent *event) 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) { if(markedForDeletion) {
emit deleted(this); emit deleted(this);
delete this; delete this;
@ -138,38 +147,7 @@ void TracePlot::paintEvent(QPaintEvent *event)
void TracePlot::mousePressEvent(QMouseEvent *event) void TracePlot::mousePressEvent(QMouseEvent *event)
{ {
auto clickPoint = event->pos() - QPoint(marginLeft, marginTop); selectedMarker = markerAtPosition(event->pos(), true);
// check if click was near a marker
unsigned int closestDistance = numeric_limits<unsigned int>::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;
}
} }
void TracePlot::mouseReleaseEvent(QMouseEvent *event) void TracePlot::mouseReleaseEvent(QMouseEvent *event)
@ -205,6 +183,46 @@ void TracePlot::leaveEvent(QEvent *event)
selectedMarker = nullptr; 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<unsigned int>::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) void TracePlot::dragEnterEvent(QDragEnterEvent *event)
{ {
if (event->mimeData()->hasFormat("trace/pointer")) { if (event->mimeData()->hasFormat("trace/pointer")) {

View File

@ -55,6 +55,8 @@ protected:
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override; void leaveEvent(QEvent *event) override;
TraceMarker *markerAtPosition(QPoint p, bool onlyMovable = false);
// handle trace drops // handle trace drops
virtual bool dropSupported(Trace *t) = 0; virtual bool dropSupported(Trace *t) = 0;
void dragEnterEvent(QDragEnterEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override;

View File

@ -859,7 +859,7 @@ QPointF TraceXYPlot::traceToCoordinate(Trace *t, unsigned int sample, TraceXYPlo
ret.setY(real(data.y)); ret.setY(real(data.y));
break; break;
case YAxisType::ImpulseMag: case YAxisType::ImpulseMag:
ret.setY(abs(data.y)); ret.setY(20*log10(abs(data.y)));
break; break;
case YAxisType::Step: case YAxisType::Step:
ret.setY(t->sample(sample, Trace::SampleType::TimeStep).y.real()); 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." 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."); " All current traces will be removed and the graph changed to time domain.");
setXAxis(XAxisType::Time, XAxisMode::FitTraces, 0, 1, 0.1); 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); 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::Phase: return "°"; break;
case TraceXYPlot::YAxisType::VSWR: return ""; break; case TraceXYPlot::YAxisType::VSWR: return ""; break;
case TraceXYPlot::YAxisType::ImpulseReal: 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::Step: return ""; break;
case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break; case TraceXYPlot::YAxisType::Impedance: return "Ohm"; break;
default: return ""; break; default: return ""; break;