PC Application: partial firmware update dialog

This commit is contained in:
Jan Käberich 2020-08-30 22:03:41 +02:00
parent 8c8749accd
commit 07ba714f1f
134 changed files with 13954 additions and 7 deletions

View file

@ -0,0 +1,118 @@
#include "bodeplotaxisdialog.h"
#include "ui_bodeplotaxisdialog.h"
BodeplotAxisDialog::BodeplotAxisDialog(TraceBodePlot *plot) :
QDialog(nullptr),
ui(new Ui::BodeplotAxisDialog),
plot(plot)
{
ui->setupUi(this);
// Setup GUI connections
connect(ui->Y1type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
ui->Y1log->setEnabled(index != 0);
ui->Y1linear->setEnabled(index != 0);
ui->Y1auto->setEnabled(index != 0);
bool autoRange = ui->Y1auto->isChecked();
ui->Y1min->setEnabled(index != 0 && !autoRange);
ui->Y1max->setEnabled(index != 0 && !autoRange);
ui->Y1divs->setEnabled(index != 0 && !autoRange);
auto type = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y1min->setUnit(unit);
ui->Y1max->setUnit(unit);
ui->Y1divs->setUnit(unit);
});
connect(ui->Y1auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y1min->setEnabled(!checked);
ui->Y1max->setEnabled(!checked);
ui->Y1divs->setEnabled(!checked);
});
connect(ui->Y2type, qOverload<int>(&QComboBox::currentIndexChanged), [this](int index) {
ui->Y2log->setEnabled(index != 0);
ui->Y2linear->setEnabled(index != 0);
ui->Y2auto->setEnabled(index != 0);
bool autoRange = ui->Y2auto->isChecked();
ui->Y2min->setEnabled(index != 0 && !autoRange);
ui->Y2max->setEnabled(index != 0 && !autoRange);
ui->Y2divs->setEnabled(index != 0 && !autoRange);
auto type = (TraceBodePlot::YAxisType) index;
QString unit;
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: unit = "db"; break;
case TraceBodePlot::YAxisType::Phase: unit = "°"; break;
case TraceBodePlot::YAxisType::VSWR: unit = ""; break;
default: unit = ""; break;
}
ui->Y2min->setUnit(unit);
ui->Y2max->setUnit(unit);
ui->Y2divs->setUnit(unit);
});
connect(ui->Y2auto, &QCheckBox::toggled, [this](bool checked) {
ui->Y2min->setEnabled(!checked);
ui->Y2max->setEnabled(!checked);
ui->Y2divs->setEnabled(!checked);
});
connect(ui->Xauto, &QCheckBox::toggled, [this](bool checked) {
ui->Xmin->setEnabled(!checked);
ui->Xmax->setEnabled(!checked);
ui->Xdivs->setEnabled(!checked);
});
ui->Xmin->setUnit("Hz");
ui->Xmax->setUnit("Hz");
ui->Xdivs->setUnit("Hz");
ui->Xmin->setPrefixes(" kMG");
ui->Xmax->setPrefixes(" kMG");
ui->Xdivs->setPrefixes(" kMG");
// Fill initial values
// assume same order in YAxisType enum as in ComboBox items
ui->Y1type->setCurrentIndex((int) plot->YAxis[0].type);
if(plot->YAxis[0].log) {
ui->Y1log->setChecked(true);
} else {
ui->Y1linear->setChecked(true);
}
ui->Y1auto->setChecked(plot->YAxis[0].autorange);
ui->Y1min->setValueQuiet(plot->YAxis[0].rangeMin);
ui->Y1max->setValueQuiet(plot->YAxis[0].rangeMax);
ui->Y1divs->setValueQuiet(plot->YAxis[0].rangeDiv);
ui->Y2type->setCurrentIndex((int) plot->YAxis[1].type);
if(plot->YAxis[1].log) {
ui->Y2log->setChecked(true);
} else {
ui->Y2linear->setChecked(true);
}
ui->Y2auto->setChecked(plot->YAxis[1].autorange);
ui->Y2min->setValueQuiet(plot->YAxis[1].rangeMin);
ui->Y2max->setValueQuiet(plot->YAxis[1].rangeMax);
ui->Y2divs->setValueQuiet(plot->YAxis[1].rangeDiv);
ui->Xauto->setChecked(plot->XAxis.autorange);
ui->Xmin->setValueQuiet(plot->XAxis.rangeMin);
ui->Xmax->setValueQuiet(plot->XAxis.rangeMax);
ui->Xdivs->setValueQuiet(plot->XAxis.rangeDiv);
}
BodeplotAxisDialog::~BodeplotAxisDialog()
{
delete ui;
}
void BodeplotAxisDialog::on_buttonBox_accepted()
{
// set plot values to the ones selected in the dialog
plot->setYAxis(0, (TraceBodePlot::YAxisType) ui->Y1type->currentIndex(), ui->Y1log->isChecked(), ui->Y1auto->isChecked(), ui->Y1min->value(), ui->Y1max->value(), ui->Y1divs->value());
plot->setYAxis(1, (TraceBodePlot::YAxisType) ui->Y2type->currentIndex(), ui->Y2log->isChecked(), ui->Y2auto->isChecked(), ui->Y2min->value(), ui->Y2max->value(), ui->Y2divs->value());
plot->setXAxis(ui->Xauto->isChecked(), ui->Xmin->value(), ui->Xmax->value(), ui->Xdivs->value());
}

View file

@ -0,0 +1,27 @@
#ifndef BODEPLOTAXISDIALOG_H
#define BODEPLOTAXISDIALOG_H
#include <QDialog>
#include "tracebodeplot.h"
namespace Ui {
class BodeplotAxisDialog;
}
class BodeplotAxisDialog : public QDialog
{
Q_OBJECT
public:
explicit BodeplotAxisDialog(TraceBodePlot *plot);
~BodeplotAxisDialog();
private slots:
void on_buttonBox_accepted();
private:
Ui::BodeplotAxisDialog *ui;
TraceBodePlot *plot;
};
#endif // BODEPLOTAXISDIALOG_H

View file

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BodeplotAxisDialog</class>
<widget class="QDialog" name="BodeplotAxisDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>715</width>
<height>282</height>
</rect>
</property>
<property name="windowTitle">
<string>Axis Setup</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Primary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y1type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="Y1linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y1log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y1group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y1auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y1max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y1min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y1divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_7">
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Secondary Y axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Y2type">
<item>
<property name="text">
<string>Disabled</string>
</property>
</item>
<item>
<property name="text">
<string>Magnitude</string>
</property>
</item>
<item>
<property name="text">
<string>Phase</string>
</property>
</item>
<item>
<property name="text">
<string>VSWR</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QRadioButton" name="Y2linear">
<property name="text">
<string>Linear</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="Y2log">
<property name="text">
<string>Log</string>
</property>
<attribute name="buttonGroup">
<string notr="true">Y2group</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QFormLayout" name="formLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Y2auto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Y2max"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Y2min"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Y2divs"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_13">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>X axis</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</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>
<item>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Range:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="Xauto">
<property name="text">
<string>Auto</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Maximum:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="SIUnitEdit" name="Xmax"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Minimum:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="SIUnitEdit" name="Xmin"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Divisions:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="SIUnitEdit" name="Xdivs"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SIUnitEdit</class>
<extends>QLineEdit</extends>
<header>CustomWidgets/siunitedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>BodeplotAxisDialog</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>BodeplotAxisDialog</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>
<buttongroups>
<buttongroup name="Y1group"/>
<buttongroup name="Y2group"/>
</buttongroups>
</ui>

View file

@ -0,0 +1,41 @@
#include "markerwidget.h"
#include "ui_markerwidget.h"
MarkerWidget::MarkerWidget(TraceMarkerModel &model, QWidget *parent) :
QWidget(parent),
ui(new Ui::MarkerWidget),
model(model)
{
ui->setupUi(this);
ui->tableView->setModel(&model);
ui->tableView->setItemDelegateForColumn(1, new TraceChooserDelegate);
connect(&model.getModel(), &TraceModel::traceAdded, this, &MarkerWidget::updatePersistentEditors);
connect(&model.getModel(), &TraceModel::traceRemoved, this, &MarkerWidget::updatePersistentEditors);
}
MarkerWidget::~MarkerWidget()
{
delete ui;
}
void MarkerWidget::on_bDelete_clicked()
{
model.removeMarker(ui->tableView->currentIndex().row());
}
void MarkerWidget::on_bAdd_clicked()
{
auto marker = model.createDefaultMarker();
model.addMarker(marker);
updatePersistentEditors();
}
void MarkerWidget::updatePersistentEditors(Trace *)
{
for(int i=0;i<model.rowCount();i++) {
auto index = model.index(i, TraceMarkerModel::ColIndexTrace);
ui->tableView->closePersistentEditor(index);
ui->tableView->openPersistentEditor(index);
}
}

View file

@ -0,0 +1,29 @@
#ifndef MARKERWIDGET_H
#define MARKERWIDGET_H
#include <QWidget>
#include "tracemarkermodel.h"
namespace Ui {
class MarkerWidget;
}
class MarkerWidget : public QWidget
{
Q_OBJECT
public:
explicit MarkerWidget(TraceMarkerModel &model, QWidget *parent = nullptr);
~MarkerWidget();
private slots:
void on_bDelete_clicked();
void on_bAdd_clicked();
void updatePersistentEditors(Trace *dummy = nullptr);
private:
Ui::MarkerWidget *ui;
TraceMarkerModel &model;
};
#endif // MARKERWIDGET_H

View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MarkerWidget</class>
<widget class="QWidget" name="MarkerWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>729</width>
<height>195</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableView" name="tableView">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="bAdd">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Add</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-add"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bDelete">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-remove"/>
</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>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,238 @@
#include "trace.h"
using namespace std;
Trace::Trace(QString name, QColor color)
: _name(name),
_color(color),
_liveType(LivedataType::Overwrite),
reflection(true),
visible(true),
paused(false),
touchstone(false),
calibration(false)
{
}
Trace::~Trace()
{
emit deleted(this);
}
void Trace::clear() {
if(paused) {
return;
}
_data.clear();
emit cleared(this);
emit dataChanged();
}
void Trace::addData(Trace::Data d) {
// add or replace data in vector while keeping it sorted with increasing frequency
auto lower = lower_bound(_data.begin(), _data.end(), d, [](const Data &lhs, const Data &rhs) -> bool {
return lhs.frequency < rhs.frequency;
});
if(lower == _data.end()) {
// highest frequency yet, add to vector
_data.push_back(d);
} else if(lower->frequency == d.frequency) {
switch(_liveType) {
case LivedataType::Overwrite:
// replace this data element
*lower = d;
break;
case LivedataType::MaxHold:
// replace this data element
if(abs(d.S) > abs(lower->S)) {
*lower = d;
}
break;
case LivedataType::MinHold:
// replace this data element
if(abs(d.S) < abs(lower->S)) {
*lower = d;
}
break;
}
} else {
// insert at this position
_data.insert(lower, d);
}
emit dataAdded(this, d);
emit dataChanged();
}
void Trace::setName(QString name) {
_name = name;
emit nameChanged();
}
void Trace::fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename)
{
if(parameter >= t.ports()*t.ports()) {
throw runtime_error("Parameter for touchstone out of range");
}
clear();
setTouchstoneParameter(parameter);
setTouchstoneFilename(filename);
for(unsigned int i=0;i<t.points();i++) {
auto tData = t.point(i);
Data d;
d.frequency = tData.frequency;
d.S = t.point(i).S[parameter];
addData(d);
}
// check if parameter is square (e.i. S11/S22/S33/...)
parameter++;
bool isSquare = false;
for (unsigned int i = 1; i * i <= parameter; i++) {
// If (i * i = n)
if ((parameter % i == 0) && (parameter / i == i)) {
isSquare = true;
break;
}
}
if(isSquare == 1) {
reflection = true;
} else {
reflection = false;
}
touchstone = true;
emit typeChanged(this);
}
void Trace::fromLivedata(Trace::LivedataType type, LiveParameter param)
{
touchstone = false;
_liveType = type;
_liveParam = param;
if(param == LiveParameter::S11 || param == LiveParameter::S22) {
reflection = true;
} else {
reflection = false;
}
emit typeChanged(this);
}
void Trace::setColor(QColor color) {
if(_color != color) {
_color = color;
emit colorChanged(this);
}
}
void Trace::addMarker(TraceMarker *m)
{
markers.insert(m);
emit markerAdded(m);
}
void Trace::removeMarker(TraceMarker *m)
{
markers.erase(m);
emit markerRemoved(m);
}
void Trace::setReflection(bool value)
{
reflection = value;
}
void Trace::setCalibration(bool value)
{
calibration = value;
}
std::set<TraceMarker *> Trace::getMarkers() const
{
return markers;
}
void Trace::setVisible(bool visible)
{
if(visible != this->visible) {
this->visible = visible;
emit visibilityChanged(this);
}
}
bool Trace::isVisible()
{
return visible;
}
void Trace::pause()
{
paused = true;
}
void Trace::resume()
{
paused = false;
}
bool Trace::isPaused()
{
return paused;
}
bool Trace::isTouchstone()
{
return touchstone;
}
bool Trace::isCalibration()
{
return calibration;
}
bool Trace::isLive()
{
return !isCalibration() && !isTouchstone() && !isPaused();
}
bool Trace::isReflection()
{
return reflection;
}
QString Trace::getTouchstoneFilename() const
{
return touchstoneFilename;
}
void Trace::setTouchstoneFilename(const QString &value)
{
touchstoneFilename = value;
}
unsigned int Trace::getTouchstoneParameter() const
{
return touchstoneParameter;
}
std::complex<double> Trace::getData(double frequency)
{
if(_data.size() == 0 || frequency < minFreq() || frequency > maxFreq()) {
return std::numeric_limits<std::complex<double>>::quiet_NaN();
}
return sample(index(frequency)).S;
}
int Trace::index(double frequency)
{
auto lower = lower_bound(_data.begin(), _data.end(), frequency, [](const Data &lhs, const double freq) -> bool {
return lhs.frequency < freq;
});
return lower - _data.begin();
}
void Trace::setTouchstoneParameter(int value)
{
touchstoneParameter = value;
}

View file

@ -0,0 +1,105 @@
#ifndef TRACE_H
#define TRACE_H
#include <QObject>
#include <complex>
#include <map>
#include <QColor>
#include <set>
#include "touchstone.h"
class TraceMarker;
class Trace : public QObject
{
Q_OBJECT
public:
class Data {
public:
double frequency;
std::complex<double> S;
};
Trace(QString name = QString(), QColor color = Qt::darkYellow);
~Trace();
enum class LivedataType {
Overwrite,
MaxHold,
MinHold,
};
enum class LiveParameter {
S11,
S12,
S21,
S22,
};
void clear();
void addData(Data d);
void setName(QString name);
void fillFromTouchstone(Touchstone &t, unsigned int parameter, QString filename = QString());
void fromLivedata(LivedataType type, LiveParameter param);
QString name() { return _name; };
QColor color() { return _color; };
bool isVisible();
void pause();
void resume();
bool isPaused();
bool isTouchstone();
bool isCalibration();
bool isLive();
bool isReflection();
LiveParameter liveParameter() { return _liveParam; }
LivedataType liveType() { return _liveType; }
unsigned int size() { return _data.size(); }
double minFreq() { return _data.front().frequency; };
double maxFreq() { return _data.back().frequency; };
Data sample(unsigned int index) { return _data.at(index); }
QString getTouchstoneFilename() const;
unsigned int getTouchstoneParameter() const;
std::complex<double> getData(double frequency);
int index(double frequency);
std::set<TraceMarker *> getMarkers() const;
void setCalibration(bool value);
void setReflection(bool value);
public slots:
void setTouchstoneParameter(int value);
void setTouchstoneFilename(const QString &value);
void setVisible(bool visible);
void setColor(QColor color);
void addMarker(TraceMarker *m);
void removeMarker(TraceMarker *m);
private:
signals:
void cleared(Trace *t);
void typeChanged(Trace *t);
void dataAdded(Trace *t, Data d);
void deleted(Trace *t);
void visibilityChanged(Trace *t);
void dataChanged();
void nameChanged();
void colorChanged(Trace *t);
void markerAdded(TraceMarker *m);
void markerRemoved(TraceMarker *m);
private:
std::vector<Data> _data;
QString _name;
QColor _color;
LivedataType _liveType;
LiveParameter _liveParam;
bool reflection;
bool visible;
bool paused;
bool touchstone;
bool calibration;
QString touchstoneFilename;
unsigned int touchstoneParameter;
std::set<TraceMarker*> markers;
};
#endif // TRACE_H

View file

@ -0,0 +1,489 @@
#include "tracebodeplot.h"
#include <QGridLayout>
#include <qwt_plot_grid.h>
#include "qwtplotpiecewisecurve.h"
#include "qwt_series_data.h"
#include "trace.h"
#include <cmath>
#include <QFrame>
#include <qwt_plot_canvas.h>
#include <qwt_scale_div.h>
#include <qwt_plot_layout.h>
#include "tracemarker.h"
#include <qwt_symbol.h>
#include <qwt_plot_picker.h>
#include <qwt_picker_machine.h>
#include "bodeplotaxisdialog.h"
using namespace std;
static double AxisTransformation(TraceBodePlot::YAxisType type, complex<double> data) {
switch(type) {
case TraceBodePlot::YAxisType::Magnitude: return 20*log10(abs(data)); break;
case TraceBodePlot::YAxisType::Phase: return arg(data) * 180.0 / M_PI; break;
case TraceBodePlot::YAxisType::VSWR:
if(abs(data) < 1.0) {
return (1+abs(data)) / (1-abs(data));
}
break;
default: break;
}
return numeric_limits<double>::quiet_NaN();
}
template<TraceBodePlot::YAxisType E> class QwtTraceSeries : public QwtSeriesData<QPointF> {
public:
QwtTraceSeries(Trace &t)
: QwtSeriesData<QPointF>(),
t(t){};
size_t size() const override {
return t.size();
}
QPointF sample(size_t i) const override {
Trace::Data d = t.sample(i);
QPointF p;
p.setX(d.frequency);
p.setY(AxisTransformation(E, d.S));
return p;
}
QRectF boundingRect() const override {
return qwtBoundingRect(*this);
}
private:
Trace &t;
};
// Derived plotpicker, exposing transformation functions
class BodeplotPicker : public QwtPlotPicker {
public:
BodeplotPicker(int xAxis, int yAxis, RubberBand rubberBand, DisplayMode trackerMode, QWidget *w)
: QwtPlotPicker(xAxis, yAxis, rubberBand, trackerMode, w) {};
QPoint plotToPixel(const QPointF &pos) {
return transform(pos);
}
QPointF pixelToPlot(const QPoint &pos) {
return invTransform(pos);
}
};
TraceBodePlot::TraceBodePlot(TraceModel &model, QWidget *parent)
: TracePlot(parent),
selectedMarker(nullptr)
{
plot = new QwtPlot(this);
plot->setCanvasBackground(Background);
auto pal = plot->palette();
pal.setColor(QPalette::Window, Background);
pal.setColor(QPalette::WindowText, Border);
pal.setColor(QPalette::Text, Border);
auto canvas = new QwtPlotCanvas(plot);
canvas->setFrameStyle(QFrame::Plain);
plot->setCanvas(canvas);
plot->setPalette(pal);
plot->setAutoFillBackground(true);
auto selectPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
selectPicker->setStateMachine(new QwtPickerClickPointMachine);
auto drawPicker = new BodeplotPicker(plot->xBottom, plot->yLeft, QwtPicker::NoRubberBand, QwtPicker::ActiveOnly, plot->canvas());
drawPicker->setStateMachine(new QwtPickerDragPointMachine);
drawPicker->setTrackerPen(QPen(Qt::white));
// Marker selection
connect(selectPicker, qOverload<const QPointF&>(&QwtPlotPicker::selected), [=](const QPointF pos) {
auto clickPoint = drawPicker->plotToPixel(pos);
unsigned int closestDistance = numeric_limits<unsigned int>::max();
TraceMarker *closestMarker = nullptr;
for(auto m : markers) {
auto markerPoint = drawPicker->plotToPixel(m.second->value());
auto yDiff = abs(markerPoint.y() - clickPoint.y());
auto xDiff = abs(markerPoint.x() - clickPoint.x());
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
if(distance < closestDistance) {
closestDistance = distance;
closestMarker = m.first;
}
}
if(closestDistance <= 400) {
selectedMarker = closestMarker;
selectedCurve = curves[0][selectedMarker->trace()].curve;
} else {
selectedMarker = nullptr;
selectedCurve = nullptr;
}
});
// Marker movement
connect(drawPicker, qOverload<const QPointF&>(&QwtPlotPicker::moved), [=](const QPointF pos) {
if(!selectedMarker || !selectedCurve) {
return;
}
// int index = selectedCurve->closestPoint(pos.toPoint());
// qDebug() << index;
// if(index < 0) {
// // unable to find closest point
// return;
// }
// selectedMarker->setFrequency(selectedCurve->sample(index).x());
selectedMarker->setFrequency(pos.x());
});
QwtPlotGrid *grid = new QwtPlotGrid();
grid->setMajorPen(QPen(Divisions, 1.0, Qt::DotLine));
grid->attach(plot);
auto layout = new QGridLayout;
layout->addWidget(plot);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
plot->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
// plot->plotLayout()->setAlignCanvasToScales(true);
initializeTraceInfo(model);
setAutoFillBackground(true);
// Setup default axis
setYAxis(0, YAxisType::Magnitude, false, false, -120, 20, 10);
setYAxis(1, YAxisType::Phase, false, false, -180, 180, 30);
// enable autoscaling and set for full span (no information about actual span available yet)
setXAxis(0, 6000000000);
setXAxis(true, 0, 6000000000, 600000000);
}
TraceBodePlot::~TraceBodePlot()
{
for(int axis = 0;axis < 2;axis++) {
for(auto pd : curves[axis]) {
delete pd.second.curve;
}
}
}
void TraceBodePlot::setXAxis(double min, double max)
{
sweep_fmin = min;
sweep_fmax = max;
updateXAxis();
}
void TraceBodePlot::setYAxis(int axis, TraceBodePlot::YAxisType type, bool log, bool autorange, double min, double max, double div)
{
if(YAxis[axis].type != type) {
YAxis[axis].type = type;
// remove traces that are active but not supported with the new axis type
bool erased = false;
do {
erased = false;
for(auto t : tracesAxis[axis]) {
if(!supported(t, type)) {
enableTraceAxis(t, axis, false);
erased = true;
break;
}
}
} while(erased);
for(auto t : tracesAxis[axis]) {
// supported but needs an adjusted QwtSeriesData
auto td = curves[axis][t];
td.data = createQwtSeriesData(*t, axis);
// call to setSamples deletes old QwtSeriesData
td.curve->setSamples(td.data);
if(axis == 0) {
// update marker data
auto marker = t->getMarkers();
for(auto m : marker) {
markerDataChanged(m);
}
}
}
}
YAxis[axis].log = log;
YAxis[axis].autorange = autorange;
YAxis[axis].rangeMin = min;
YAxis[axis].rangeMax = max;
YAxis[axis].rangeDiv = div;
// enable/disable y axis
auto qwtaxis = axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight;
plot->enableAxis(qwtaxis, type != YAxisType::Disabled);
if(autorange) {
plot->setAxisAutoScale(qwtaxis, true);
} else {
plot->setAxisScale(qwtaxis, min, max, div);
}
updateContextMenu();
replot();
}
void TraceBodePlot::setXAxis(bool autorange, double min, double max, double div)
{
XAxis.autorange = autorange;
XAxis.rangeMin = min;
XAxis.rangeMax = max;
XAxis.rangeDiv = div;
updateXAxis();
}
void TraceBodePlot::enableTrace(Trace *t, bool enabled)
{
for(int axis = 0;axis < 2;axis++) {
if(supported(t, YAxis[axis].type)) {
enableTraceAxis(t, axis, enabled);
}
}
}
void TraceBodePlot::updateContextMenu()
{
contextmenu->clear();
// for(int axis = 0;axis < 2;axis++) {
// QMenu *axisMenu;
// if(axis == 0) {
// axisMenu = contextmenu->addMenu("Primary Axis");
// } else {
// axisMenu = contextmenu->addMenu("Secondary Axis");
// }
// auto group = new QActionGroup(this);
// for(int i=0;i<(int) YAxisType::Last;i++) {
// auto action = new QAction(AxisTypeToName((YAxisType) i));
// action->setCheckable(true);
// group->addAction(action);
// if(YAxis[axis].type == (YAxisType) i) {
// action->setChecked(true);
// }
// connect(action, &QAction::triggered, [=](bool active) {
// if(active) {
// setYAxisType(axis, (YAxisType) i);
// }
// });
// }
// axisMenu->addActions(group->actions());
// }
auto setup = new QAction("Axis setup...");
connect(setup, &QAction::triggered, [this]() {
auto setup = new BodeplotAxisDialog(this);
setup->show();
});
contextmenu->addAction(setup);
for(int axis = 0;axis < 2;axis++) {
if(YAxis[axis].type == YAxisType::Disabled) {
continue;
}
if(axis == 0) {
contextmenu->addSection("Primary Traces");
} else {
contextmenu->addSection("Secondary Traces");
}
for(auto t : traces) {
// Skip traces that are not applicable for the selected axis type
if(!supported(t.first, YAxis[axis].type)) {
continue;
}
auto action = new QAction(t.first->name());
action->setCheckable(true);
if(tracesAxis[axis].find(t.first) != tracesAxis[axis].end()) {
action->setChecked(true);
}
connect(action, &QAction::toggled, [=](bool active) {
enableTraceAxis(t.first, axis, active);
});
contextmenu->addAction(action);
}
}
contextmenu->addSeparator();
auto close = new QAction("Close");
contextmenu->addAction(close);
connect(close, &QAction::triggered, [=]() {
markedForDeletion = true;
});
}
bool TraceBodePlot::supported(Trace *)
{
// potentially possible to add every kind of trace (depends on axis)
return true;
}
void TraceBodePlot::replot()
{
plot->replot();
}
QString TraceBodePlot::AxisTypeToName(TraceBodePlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled: return "Disabled"; break;
case YAxisType::Magnitude: return "Magnitude"; break;
case YAxisType::Phase: return "Phase"; break;
case YAxisType::VSWR: return "VSWR"; break;
default: return "Unknown"; break;
}
}
void TraceBodePlot::enableTraceAxis(Trace *t, int axis, bool enabled)
{
bool alreadyEnabled = tracesAxis[axis].find(t) != tracesAxis[axis].end();
if(alreadyEnabled != enabled) {
if(enabled) {
tracesAxis[axis].insert(t);
CurveData cd;
cd.data = createQwtSeriesData(*t, axis);
cd.curve = new QwtPlotPiecewiseCurve();
cd.curve->attach(plot);
cd.curve->setYAxis(axis == 0 ? QwtPlot::yLeft : QwtPlot::yRight);
cd.curve->setSamples(cd.data);
curves[axis][t] = cd;
// connect signals
connect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
connect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
connect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
if(axis == 0) {
connect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerAdded(m);
}
}
traceColorChanged(t);
} else {
tracesAxis[axis].erase(t);
// clean up and delete
if(curves[axis].find(t) != curves[axis].end()) {
if(curves[axis][t].curve) {
delete curves[axis][t].curve;
}
curves[axis].erase(t);
}
int otherAxis = axis == 0 ? 1 : 0;
if(curves[otherAxis].find(t) == curves[otherAxis].end()) {
// this trace is not used anymore, disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TraceBodePlot::triggerReplot);
disconnect(t, &Trace::colorChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::traceColorChanged);
disconnect(t, &Trace::visibilityChanged, this, &TraceBodePlot::triggerReplot);
}
if(axis == 0) {
disconnect(t, &Trace::markerAdded, this, &TraceBodePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TraceBodePlot::markerRemoved);
auto tracemarkers = t->getMarkers();
for(auto m : tracemarkers) {
markerRemoved(m);
}
}
}
updateContextMenu();
replot();
}
}
bool TraceBodePlot::supported(Trace *t, TraceBodePlot::YAxisType type)
{
switch(type) {
case YAxisType::Disabled:
return false;
case YAxisType::VSWR:
if(!t->isReflection()) {
return false;
}
break;
default:
break;
}
return true;
}
void TraceBodePlot::updateXAxis()
{
if(XAxis.autorange) {
QList<double> tickList;
for(double tick = sweep_fmin;tick <= sweep_fmax;tick+= (sweep_fmax-sweep_fmin)/10) {
tickList.append(tick);
}
QwtScaleDiv scalediv(sweep_fmin, sweep_fmax, QList<double>(), QList<double>(), tickList);
plot->setAxisScaleDiv(QwtPlot::xBottom, scalediv);
} else {
plot->setAxisScale(QwtPlot::xBottom, XAxis.rangeMin, XAxis.rangeMax, XAxis.rangeDiv);
}
triggerReplot();
}
QwtSeriesData<QPointF> *TraceBodePlot::createQwtSeriesData(Trace &t, int axis)
{
switch(YAxis[axis].type) {
case YAxisType::Magnitude:
return new QwtTraceSeries<YAxisType::Magnitude>(t);
case YAxisType::Phase:
return new QwtTraceSeries<YAxisType::Phase>(t);
case YAxisType::VSWR:
return new QwtTraceSeries<YAxisType::VSWR>(t);
default:
return nullptr;
}
}
void TraceBodePlot::traceColorChanged(Trace *t)
{
for(int axis = 0;axis < 2;axis++) {
if(curves[axis].find(t) != curves[axis].end()) {
// trace active, change the pen color
if(t->isVisible()) {
if(axis == 0) {
curves[axis][t].curve->setPen(t->color());
} else {
curves[axis][t].curve->setPen(t->color(), 1.0, Qt::DashLine);
}
for(auto m : t->getMarkers()) {
if(markers.count(m)) {
markers[m]->attach(plot);
}
}
} else {
curves[axis][t].curve->setPen(t->color(), 0.0, Qt::NoPen);
for(auto m : t->getMarkers()) {
if(markers.count(m)) {
markers[m]->detach();
}
}
}
}
}
}
void TraceBodePlot::markerAdded(TraceMarker *m)
{
if(markers.count(m)) {
return;
}
QwtSymbol *sym=new QwtSymbol;
sym->setPixmap(m->getSymbol());
sym->setPinPoint(QPointF(m->getSymbol().width()/2, m->getSymbol().height()));
auto qwtMarker = new QwtPlotMarker;
qwtMarker->setSymbol(sym);
connect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
markers[m] = qwtMarker;
markerDataChanged(m);
qwtMarker->attach(plot);
triggerReplot();
}
void TraceBodePlot::markerRemoved(TraceMarker *m)
{
disconnect(m, &TraceMarker::dataChanged, this, &TraceBodePlot::markerDataChanged);
if(markers.count(m)) {
markers[m]->detach();
delete markers[m];
markers.erase(m);
}
triggerReplot();
}
void TraceBodePlot::markerDataChanged(TraceMarker *m)
{
auto qwtMarker = markers[m];
qwtMarker->setXValue(m->getFrequency());
qwtMarker->setYValue(AxisTransformation(YAxis[0].type, m->getData()));
triggerReplot();
}

View file

@ -0,0 +1,76 @@
#ifndef TRACEBODEPLOT_H
#define TRACEBODEPLOT_H
#include "traceplot.h"
#include <set>
#include <qwt_plot.h>
#include <qwt_plot_curve.h>
#include <qwt_series_data.h>
#include <qwt_plot_marker.h>
class TraceBodePlot : public TracePlot
{
friend class BodeplotAxisDialog;
Q_OBJECT
public:
TraceBodePlot(TraceModel &model, QWidget *parent = nullptr);
~TraceBodePlot();
enum class YAxisType {
Disabled = 0,
Magnitude = 1,
Phase = 2,
VSWR = 3,
Last,
};
virtual void setXAxis(double min, double max) override;
void setYAxis(int axis, YAxisType type, bool log, bool autorange, double min, double max, double div);
void setXAxis(bool autorange, double min, double max, double div);
void enableTrace(Trace *t, bool enabled) override;
protected:
virtual void updateContextMenu();
virtual bool supported(Trace *t);
void replot() override;
private slots:
void traceColorChanged(Trace *t);
void markerAdded(TraceMarker *m) override;
void markerRemoved(TraceMarker *m) override;
void markerDataChanged(TraceMarker *m);
private:
QString AxisTypeToName(YAxisType type);
void enableTraceAxis(Trace *t, int axis, bool enabled);
bool supported(Trace *t, YAxisType type);
void updateXAxis();
QwtSeriesData<QPointF> *createQwtSeriesData(Trace &t, int axis);
std::set<Trace*> tracesAxis[2];
class Axis {
public:
YAxisType type;
bool log;
bool autorange;
double rangeMin;
double rangeMax;
double rangeDiv;
};
Axis YAxis[2];
Axis XAxis;
double sweep_fmin, sweep_fmax;
using CurveData = struct {
QwtPlotCurve *curve;
QwtSeriesData<QPointF> *data;
};
std::map<Trace*, CurveData> curves[2];
std::map<TraceMarker*, QwtPlotMarker*> markers;
QwtPlot *plot;
TraceMarker *selectedMarker;
QwtPlotCurve *selectedCurve;
};
#endif // TRACEBODEPLOT_H

View file

@ -0,0 +1,123 @@
#include "traceeditdialog.h"
#include "ui_traceeditdialog.h"
#include <QColorDialog>
#include <QFileDialog>
TraceEditDialog::TraceEditDialog(Trace &t, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceEditDialog),
trace(t)
{
ui->setupUi(this);
ui->name->setText(t.name());
setColor(trace.color());
ui->GSource->setId(ui->bLive, 0);
ui->GSource->setId(ui->bFile, 1);
if(t.isCalibration()) {
// prevent editing imported calibration traces
ui->bLive->setEnabled(false);
ui->bFile->setEnabled(false);
ui->CLiveType->setEnabled(false);
ui->CLiveParam->setEnabled(false);
}
if(t.isTouchstone()) {
ui->bFile->click();
ui->touchstoneImport->setFile(t.getTouchstoneFilename());
}
auto updateFileStatus = [this]() {
// remove all options from paramater combo box
while(ui->CParameter->count() > 0) {
ui->CParameter->removeItem(0);
}
if (ui->bFile->isChecked() && !ui->touchstoneImport->getStatus()) {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
} else {
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
auto touchstone = ui->touchstoneImport->getTouchstone();
for(unsigned int i=0;i<touchstone.ports();i++) {
for(unsigned int j=0;j<touchstone.ports();j++) {
QString name = "S"+QString::number(i+1)+QString::number(j+1);
ui->CParameter->addItem(name);
}
}
if(trace.getTouchstoneParameter() < touchstone.ports()*touchstone.ports()) {
ui->CParameter->setCurrentIndex(trace.getTouchstoneParameter());
} else {
ui->CParameter->setCurrentIndex(0);
}
}
};
switch(t.liveType()) {
case Trace::LivedataType::Overwrite: ui->CLiveType->setCurrentIndex(0); break;
case Trace::LivedataType::MaxHold: ui->CLiveType->setCurrentIndex(1); break;
case Trace::LivedataType::MinHold: ui->CLiveType->setCurrentIndex(2); break;
}
switch(t.liveParameter()) {
case Trace::LiveParameter::S11: ui->CLiveParam->setCurrentIndex(0); break;
case Trace::LiveParameter::S12: ui->CLiveParam->setCurrentIndex(1); break;
case Trace::LiveParameter::S21: ui->CLiveParam->setCurrentIndex(2); break;
case Trace::LiveParameter::S22: ui->CLiveParam->setCurrentIndex(3); break;
}
connect(ui->GSource, qOverload<int>(&QButtonGroup::buttonClicked), updateFileStatus);
connect(ui->touchstoneImport, &TouchstoneImport::statusChanged, updateFileStatus);
connect(ui->touchstoneImport, &TouchstoneImport::filenameChanged, updateFileStatus);
updateFileStatus();
}
TraceEditDialog::~TraceEditDialog()
{
delete ui;
}
void TraceEditDialog::on_color_clicked()
{
auto color = QColorDialog::getColor(trace.color(), this, "Select color", QColorDialog::DontUseNativeDialog);
setColor(color);
}
void TraceEditDialog::on_buttonBox_accepted()
{
trace.setName(ui->name->text());
if(!trace.isCalibration()) {
// only apply changes if it is not a calibration trace
if (ui->bFile->isChecked()) {
auto t = ui->touchstoneImport->getTouchstone();
trace.fillFromTouchstone(t, ui->CParameter->currentIndex(), ui->touchstoneImport->getFilename());
} else {
Trace::LivedataType type;
Trace::LiveParameter param;
switch(ui->CLiveType->currentIndex()) {
case 0: type = Trace::LivedataType::Overwrite; break;
case 1: type = Trace::LivedataType::MaxHold; break;
case 2: type = Trace::LivedataType::MinHold; break;
}
switch(ui->CLiveParam->currentIndex()) {
case 0: param = Trace::LiveParameter::S11; break;
case 1: param = Trace::LiveParameter::S12; break;
case 2: param = Trace::LiveParameter::S21; break;
case 3: param = Trace::LiveParameter::S22; break;
}
trace.fromLivedata(type, param);
}
}
delete this;
}
void TraceEditDialog::setColor(QColor c)
{
QPalette pal = ui->color->palette();
pal.setColor(QPalette::Button, c);
ui->color->setAutoFillBackground(true);
ui->color->setPalette(pal);
ui->color->update();
trace.setColor(c);
}

View file

@ -0,0 +1,29 @@
#ifndef TRACEEDITDIALOG_H
#define TRACEEDITDIALOG_H
#include <QDialog>
#include "trace.h"
namespace Ui {
class TraceEditDialog;
}
class TraceEditDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceEditDialog(Trace &t, QWidget *parent = nullptr);
~TraceEditDialog();
private slots:
void on_color_clicked();
void on_buttonBox_accepted();
private:
void setColor(QColor c);
Ui::TraceEditDialog *ui;
Trace &trace;
};
#endif // TRACEEDITDIALOG_H

View file

@ -0,0 +1,209 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceEditDialog</class>
<widget class="QDialog" name="TraceEditDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>282</width>
<height>355</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Trace</string>
</property>
<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>Name:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Color:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="color">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="bLive">
<property name="text">
<string>Live Capture</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">GSource</string>
</attribute>
</widget>
</item>
<item>
<widget class="QRadioButton" name="bFile">
<property name="text">
<string>From File</string>
</property>
<attribute name="buttonGroup">
<string notr="true">GSource</string>
</attribute>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QStackedWidget" name="stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="CLiveType">
<item>
<property name="text">
<string>Overwrite</string>
</property>
</item>
<item>
<property name="text">
<string>Max hold</string>
</property>
</item>
<item>
<property name="text">
<string>Min hold</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Parameter:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="CLiveParam">
<item>
<property name="text">
<string>S11</string>
</property>
</item>
<item>
<property name="text">
<string>S12</string>
</property>
</item>
<item>
<property name="text">
<string>S21</string>
</property>
</item>
<item>
<property name="text">
<string>S22</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="TouchstoneImport" name="touchstoneImport" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Parameter:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="CParameter"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TouchstoneImport</class>
<extends>QWidget</extends>
<header>CustomWidgets/touchstoneimport.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>GSource</sender>
<signal>buttonClicked(int)</signal>
<receiver>stack</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>146</x>
<y>216</y>
</hint>
</hints>
</connection>
</connections>
<buttongroups>
<buttongroup name="GSource"/>
</buttongroups>
</ui>

View file

@ -0,0 +1,186 @@
#include "traceexportdialog.h"
#include "ui_traceexportdialog.h"
#include <QDebug>
#include <QFileDialog>
#include "touchstone.h"
#include <QPushButton>
TraceExportDialog::TraceExportDialog(TraceModel &model, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceExportDialog),
model(model),
freqsSet(false)
{
ui->setupUi(this);
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
ui->gbTraces->setLayout(new QGridLayout);
on_sbPorts_valueChanged(ui->sbPorts->value());
}
TraceExportDialog::~TraceExportDialog()
{
delete ui;
}
bool TraceExportDialog::setTrace(int portFrom, int portTo, Trace *t)
{
if(portFrom < 0 || portFrom >= ui->sbPorts->value() || portTo < 0 || portTo >= ui->sbPorts->value()) {
// invalid port selection
return false;
}
auto c = cTraces[portTo][portFrom];
if(t) {
for(int i=1;i<c->count();i++) {
if(t == qvariant_cast<Trace*>(c->itemData(i))) {
// select this trace
c->setCurrentIndex(i);
return true;
}
}
// requested trace is not an option
return false;
} else {
// select 'none' option
c->setCurrentIndex(0);
return true;
}
}
bool TraceExportDialog::setPortNum(int ports)
{
if(ports < 1 || ports > 4) {
return false;
}
ui->sbPorts->setValue(ports);
return true;
}
void TraceExportDialog::on_buttonBox_accepted()
{
auto filename = QFileDialog::getSaveFileName(this, "Select file for exporting traces", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
if(filename.length() > 0) {
auto ports = ui->sbPorts->value();
auto t = Touchstone(ports);
// add trace points to touchstone
for(unsigned int s=0;s<points;s++) {
Touchstone::Datapoint tData;
for(int i=0;i<ports;i++) {
for(int j=0;j<ports;j++) {
if(cTraces[i][j]->currentIndex() == 0) {
// missing trace, set to 0
tData.S.push_back(0.0);
} else {
Trace *t = qvariant_cast<Trace*>(cTraces[i][j]->itemData(cTraces[i][j]->currentIndex()));
// extract frequency (will overwrite for each trace but all traces have the same frequency points anyway)
tData.frequency = t->sample(s).frequency;
// add S parameter from trace to touchstone
tData.S.push_back(t->sample(s).S);
}
}
}
t.AddDatapoint(tData);
}
Touchstone::Unit unit;
switch(ui->cUnit->currentIndex()) {
case 0: unit = Touchstone::Unit::Hz; break;
case 1: unit = Touchstone::Unit::kHz; break;
case 2: unit = Touchstone::Unit::MHz; break;
case 3: unit = Touchstone::Unit::GHz; break;
}
Touchstone::Format format;
switch(ui->cFormat->currentIndex()) {
case 0: format = Touchstone::Format::DBAngle; break;
case 1: format = Touchstone::Format::MagnitudeAngle; break;
case 2: format = Touchstone::Format::RealImaginary; break;
}
t.toFile(filename.toStdString(), unit, format);
delete this;
}
}
void TraceExportDialog::on_sbPorts_valueChanged(int ports)
{
// remove the previous widgets
QGridLayout *layout = static_cast<QGridLayout*>(ui->gbTraces->layout());
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0) {
delete child->widget();
delete child;
}
cTraces.clear();
auto availableTraces = model.getTraces();
for(int i=0;i<ports;i++) {
cTraces.push_back(std::vector<QComboBox*>());
for(int j=0;j<ports;j++) {
auto l = new QLabel("S"+QString::number(i+1)+QString::number(j+1)+":");
auto c = new QComboBox();
// create possible trace selections
c->addItem("None");
for(auto t : availableTraces) {
if(i == j && !t->isReflection()) {
// can not add through measurement at reflection port
continue;
} else if(i != j && t->isReflection()) {
// can not add reflection measurement at through port
continue;
}
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
}
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [=](int) {
selectionChanged(c);
});
cTraces[i].push_back(c);
layout->addWidget(l, i, j*2);
layout->addWidget(c, i, j*2 + 1);
}
}
}
void TraceExportDialog::selectionChanged(QComboBox *w)
{
if(w->currentIndex() != 0 && !freqsSet) {
// the first trace has been selected, extract frequency info
Trace *t = qvariant_cast<Trace*>(w->itemData(w->currentIndex()));
points = t->size();
ui->points->setText(QString::number(points));
if(points > 0) {
lowerFreq = t->minFreq();
upperFreq = t->maxFreq();
ui->lowerFreq->setText(QString::number(lowerFreq));
ui->upperFreq->setText(QString::number(upperFreq));
}
freqsSet = true;
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
// remove all trace options with incompatible frequencies
for(auto v1 : cTraces) {
for(auto c : v1) {
for(int i=1;i<c->count();i++) {
Trace *t = qvariant_cast<Trace*>(c->itemData(i));
if(t->size() != points || (points > 0 && (t->minFreq() != lowerFreq || t->maxFreq() != upperFreq))) {
// this trace is not available anymore
c->removeItem(i);
// decrement to check the next index in the next loop iteration
i--;
}
}
}
}
} else if(w->currentIndex() == 0 && freqsSet) {
// Check if all trace selections are set for none
for(auto v1 : cTraces) {
for(auto c : v1) {
if(c->currentIndex() != 0) {
// some trace is still selected, abort
return;
}
}
}
// all traces set for none
freqsSet = false;
ui->points->clear();
ui->lowerFreq->clear();
ui->upperFreq->clear();
ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
}
}

View file

@ -0,0 +1,38 @@
#ifndef TRACEEXPORTDIALOG_H
#define TRACEEXPORTDIALOG_H
#include <QDialog>
#include <QComboBox>
#include "tracemodel.h"
#include <QSignalMapper>
namespace Ui {
class TraceExportDialog;
}
class TraceExportDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceExportDialog(TraceModel &model, QWidget *parent = nullptr);
~TraceExportDialog();
bool setTrace(int portFrom, int portTo, Trace *t);
bool setPortNum(int ports);
private slots:
void on_buttonBox_accepted();
void on_sbPorts_valueChanged(int ports);
void selectionChanged(QComboBox *w);
private:
Ui::TraceExportDialog *ui;
TraceModel &model;
std::vector<std::vector<QComboBox*>> cTraces;
unsigned int points;
double lowerFreq, upperFreq;
bool freqsSet;
};
#endif // TRACEEXPORTDIALOG_H

View file

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceExportDialog</class>
<widget class="QDialog" name="TraceExportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>516</width>
<height>486</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Ports:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="sbPorts">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
</property>
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="gbTraces">
<property name="title">
<string>Traces</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Touchstone Settings</string>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Unit:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="cUnit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>3</number>
</property>
<item>
<property name="text">
<string>Hz</string>
</property>
</item>
<item>
<property name="text">
<string>kHz</string>
</property>
</item>
<item>
<property name="text">
<string>MHz</string>
</property>
</item>
<item>
<property name="text">
<string>GHz</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Format:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="cFormat">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>db/Angle</string>
</property>
</item>
<item>
<property name="text">
<string>Mag/Angle</string>
</property>
</item>
<item>
<property name="text">
<string>Real/Imag</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Trace Info</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>Points:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="points">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_24">
<property name="text">
<string>Lower Frequency:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lowerFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Upper Frequency:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="upperFreq">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>TraceExportDialog</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,178 @@
#include "traceimportdialog.h"
#include "ui_traceimportdialog.h"
#include <QAbstractTableModel>
#include <QObject>
#include <QModelIndex>
#include <QColorDialog>
TraceImportDialog::TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix, QWidget *parent) :
QDialog(parent),
ui(new Ui::TraceImportDialog),
model(model)
{
ui->setupUi(this);
tableModel = new TraceParameterModel(traces, prefix);
ui->tableView->setModel(tableModel);
}
TraceImportDialog::~TraceImportDialog()
{
delete ui;
delete tableModel;
}
void TraceImportDialog::on_buttonBox_accepted()
{
tableModel->import(model);
}
TraceParameterModel::TraceParameterModel(std::vector<Trace *> traces, QString prefix, QObject *parent)
: QAbstractTableModel(parent),
traces(traces)
{
int hue = 0;
for(auto t : traces) {
Parameter p;
p.name = prefix + t->name();
p.color = QColor::fromHsl((hue++ * 30) % 360, 250, 128);
p.enabled = true;
p.trace = t->name();
params.push_back(p);
}
}
int TraceParameterModel::rowCount(const QModelIndex &) const {
return params.size();
}
int TraceParameterModel::columnCount(const QModelIndex &) const {
return 4;
}
QVariant TraceParameterModel::data(const QModelIndex &index, int role) const {
if (!index.isValid())
return QVariant();
if ((unsigned int) index.row() >= params.size())
return QVariant();
auto p = params[index.row()];
if (index.column() == 0) {
if (role == Qt::DisplayRole) {
return p.trace;
} else {
return QVariant();
}
} else if (index.column() == 1) {
if (role == Qt::CheckStateRole) {
if(p.enabled) {
return Qt::Checked;
} else {
return Qt::Unchecked;
}
} else {
return QVariant();
}
} else if (index.column() == 2) {
if (role == Qt::BackgroundRole || role == Qt::EditRole) {
if(p.enabled) {
return p.color;
} else {
return (QColor) Qt::gray;
}
} else {
return QVariant();
}
} else if (index.column() == 3) {
if (role == Qt::DisplayRole || role == Qt::EditRole) {
return p.name;
} else {
return QVariant();
}
} else {
return QVariant();
}
}
QVariant TraceParameterModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case 0: return "Parameter"; break;
case 1: return "Import"; break;
case 2: return "Color"; break;
case 3: return "Tracename"; break;
default: return QVariant(); break;
}
} else {
return QVariant();
}
}
bool TraceParameterModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if((unsigned int) index.row() >= params.size()) {
return false;
}
auto &p = params[index.row()];
if(role == Qt::CheckStateRole && index.column()==1) {
if((Qt::CheckState)value.toInt() == Qt::Checked) {
p.enabled = true;
} else {
p.enabled = false;
}
dataChanged(this->index(index.row(),2), this->index(index.row(),3));
return true;
} else if(role == Qt::EditRole && index.column() == 2) {
p.color = value.value<QColor>();
return true;
} else if(role == Qt::EditRole && index.column() == 3) {
p.name = value.toString();
return true;
}
return false;
}
Qt::ItemFlags TraceParameterModel::flags(const QModelIndex &index) const
{
int flags = Qt::NoItemFlags;
if(index.column() == 0) {
flags |= Qt::ItemIsEnabled;
} else if(index.column() == 1) {
flags |= Qt::ItemIsUserCheckable;
flags |= Qt::ItemIsEnabled;
} else if(index.column() == 2) {
if(params[index.row()].enabled) {
flags |= Qt::ItemIsEnabled;
}
} else if(index.column() == 3) {
flags |= Qt::ItemIsEditable;
if(params[index.row()].enabled) {
flags |= Qt::ItemIsEnabled;
}
}
return (Qt::ItemFlags) flags;
}
void TraceParameterModel::import(TraceModel &model)
{
for(unsigned int i=0;i<params.size();i++) {
if(params[i].enabled) {
traces[i]->setColor(params[i].color);
traces[i]->setName(params[i].name);
model.addTrace(traces[i]);
} else {
delete traces[i];
}
}
}
void TraceImportDialog::on_tableView_doubleClicked(const QModelIndex &index)
{
if(index.column() == 2 && tableModel->params[index.row()].enabled) {
auto initialColor = tableModel->params[index.row()].color;
auto newColor = QColorDialog::getColor(initialColor, this, "Select color", QColorDialog::DontUseNativeDialog);
if(newColor.isValid()) {
tableModel->setData(index, newColor);
}
}
}

View file

@ -0,0 +1,59 @@
#ifndef TRACEIMPORTDIALOG_H
#define TRACEIMPORTDIALOG_H
#include <QDialog>
#include "tracemodel.h"
#include <vector>
namespace Ui {
class TraceImportDialog;
}
class TraceParameterModel : public QAbstractTableModel
{
Q_OBJECT
friend class TraceImportDialog;
public:
TraceParameterModel(std::vector<Trace*> traces, QString prefix, QObject *parent = 0);
~TraceParameterModel(){};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
// adds all enabled traces to the model, deletes other traces
void import(TraceModel &model);
private:
class Parameter {
public:
bool enabled;
QString trace;
QString name;
QColor color;
};
std::vector<Parameter> params;
std::vector<Trace*> traces;
};
class TraceImportDialog : public QDialog
{
Q_OBJECT
public:
explicit TraceImportDialog(TraceModel &model, std::vector<Trace*> traces, QString prefix = QString(), QWidget *parent = nullptr);
~TraceImportDialog();
private slots:
void on_buttonBox_accepted();
void on_tableView_doubleClicked(const QModelIndex &index);
private:
Ui::TraceImportDialog *ui;
TraceModel &model;
TraceParameterModel *tableModel;
};
#endif // TRACEIMPORTDIALOG_H

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceImportDialog</class>
<widget class="QDialog" name="TraceImportDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
<string>Import Traces</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTableView" name="tableView">
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>21</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>80</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>TraceImportDialog</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>TraceImportDialog</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,122 @@
#include "tracemarker.h"
#include <QPainter>
TraceMarker::TraceMarker()
: parentTrace(nullptr),
frequency(1000000000),
number(1),
data(0)
{
}
TraceMarker::~TraceMarker()
{
if(parentTrace) {
parentTrace->removeMarker(this);
}
emit deleted(this);
}
void TraceMarker::assignTrace(Trace *t)
{
if(parentTrace) {
// remove connection from previous parent trace
parentTrace->removeMarker(this);
disconnect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
disconnect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
disconnect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
}
parentTrace = t;
connect(parentTrace, &Trace::deleted, this, &TraceMarker::parentTraceDeleted);
connect(parentTrace, &Trace::dataChanged, this, &TraceMarker::traceDataChanged);
connect(parentTrace, &Trace::colorChanged, this, &TraceMarker::updateSymbol);
constrainFrequency();
updateSymbol();
parentTrace->addMarker(this);
}
Trace *TraceMarker::trace()
{
return parentTrace;
}
QString TraceMarker::readableData()
{
auto db = 20*log10(abs(data));
auto phase = arg(data);
return QString::number(db, 'g', 4) + "db@" + QString::number(phase*180/M_PI, 'g', 4);
}
void TraceMarker::setFrequency(double freq)
{
frequency = freq;
constrainFrequency();
}
void TraceMarker::parentTraceDeleted(Trace *t)
{
if(t == parentTrace) {
delete this;
}
}
void TraceMarker::traceDataChanged()
{
// some data of the parent trace changed, check if marker data also changed
auto tracedata = parentTrace->getData(frequency);
if(tracedata != data) {
data = tracedata;
emit dataChanged(this);
}
}
void TraceMarker::updateSymbol()
{
constexpr int width = 15, height = 15;
symbol = QPixmap(width, height);
symbol.fill(Qt::transparent);
QPainter p(&symbol);
p.setRenderHint(QPainter::Antialiasing);
QPointF points[] = {QPointF(0,0),QPointF(width,0),QPointF(width/2,height)};
auto traceColor = parentTrace->color();
p.setPen(traceColor);
p.setBrush(traceColor);
p.drawConvexPolygon(points, 3);
auto brightness = traceColor.redF() * 0.299 + traceColor.greenF() * 0.587 + traceColor.blueF() * 0.114;
p.setPen((brightness > 0.6) ? Qt::black : Qt::white);
p.drawText(QRectF(0,0,width, height*2.0/3.0), Qt::AlignCenter, QString::number(number));
}
void TraceMarker::constrainFrequency()
{
if(parentTrace && parentTrace->size() > 0) {
if(frequency > parentTrace->maxFreq()) {
frequency = parentTrace->maxFreq();
} else if(frequency < parentTrace->minFreq()) {
frequency = parentTrace->minFreq();
}
traceDataChanged();
}
}
int TraceMarker::getNumber() const
{
return number;
}
std::complex<double> TraceMarker::getData() const
{
return data;
}
QPixmap &TraceMarker::getSymbol()
{
return symbol;
}
double TraceMarker::getFrequency() const
{
return frequency;
}

View file

@ -0,0 +1,45 @@
#ifndef TRACEMARKER_H
#define TRACEMARKER_H
#include <QPixmap>
#include <QObject>
#include "trace.h"
class TraceMarker : public QObject
{
friend class TraceMarkerModel;
Q_OBJECT;
public:
TraceMarker();
~TraceMarker();
void assignTrace(Trace *t);
Trace* trace();
QString readableData();
double getFrequency() const;
std::complex<double> getData() const;
QPixmap& getSymbol();
int getNumber() const;
public slots:
void setFrequency(double freq);
signals:
void deleted(TraceMarker *m);
void dataChanged(TraceMarker *m);
private slots:
void parentTraceDeleted(Trace *t);
void traceDataChanged();
void updateSymbol();
private:
void constrainFrequency();
Trace *parentTrace;
double frequency;
int number;
std::complex<double> data;
QPixmap symbol;
};
#endif // TRACEMARKER_H

View file

@ -0,0 +1,228 @@
#include "tracemarkermodel.h"
#include "unit.h"
#include <QComboBox>
#include <QApplication>
TraceMarkerModel::TraceMarkerModel(TraceModel &model, QObject *parent)
: QAbstractTableModel(parent),
model(model)
{
markers.clear();
}
TraceMarker *TraceMarkerModel::createDefaultMarker()
{
// find lowest free number
int number = 0;
bool used;
do {
number++;
used = false;
for(auto m : markers) {
if(m->number == number) {
used = true;
break;
}
}
} while (used);
auto marker = new TraceMarker();
marker->number = number;
marker->frequency = 2150000000;
marker->assignTrace(model.trace(0));
return marker;
}
void TraceMarkerModel::addMarker(TraceMarker *t)
{
beginInsertRows(QModelIndex(), markers.size(), markers.size());
markers.push_back(t);
endInsertRows();
connect(t, &TraceMarker::dataChanged, this, &TraceMarkerModel::markerDataChanged);
connect(t, &TraceMarker::deleted, this, qOverload<TraceMarker*>(&TraceMarkerModel::removeMarker));
emit markerAdded(t);
}
void TraceMarkerModel::removeMarker(unsigned int index, bool delete_marker)
{
if (index < markers.size()) {
beginRemoveRows(QModelIndex(), index, index);
if(delete_marker) {
delete markers[index];
}
markers.erase(markers.begin() + index);
endRemoveRows();
}
}
void TraceMarkerModel::removeMarker(TraceMarker *m)
{
auto it = std::find(markers.begin(), markers.end(), m);
if(it != markers.end()) {
removeMarker(it - markers.begin(), false);
}
}
void TraceMarkerModel::markerDataChanged(TraceMarker *)
{
emit dataChanged(index(0, ColIndexFreq), index(markers.size()-1, ColIndexData));
}
TraceMarker *TraceMarkerModel::marker(int index)
{
return markers.at(index);
}
int TraceMarkerModel::rowCount(const QModelIndex &) const
{
return markers.size();
}
int TraceMarkerModel::columnCount(const QModelIndex &) const
{
return 4;
}
QVariant TraceMarkerModel::data(const QModelIndex &index, int role) const
{
auto marker = markers[index.row()];
switch(index.column()) {
case ColIndexNumber:
switch(role) {
case Qt::DisplayRole: return QVariant((unsigned int)marker->number); break;
}
case ColIndexTrace:
switch(role) {
case Qt::DisplayRole:
if(marker->parentTrace) {
return marker->parentTrace->name();
}
break;
}
case ColIndexFreq:
switch(role) {
case Qt::DisplayRole: return Unit::ToString(marker->frequency, "Hz", " kMG", 6); break;
}
case ColIndexData:
switch(role) {
case Qt::DisplayRole: return marker->readableData(); break;
}
break;
}
return QVariant();
}
QVariant TraceMarkerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch(section) {
case ColIndexNumber: return "#"; break;
case ColIndexTrace: return "Trace"; break;
case ColIndexFreq: return "Frequency"; break;
case ColIndexData: return "Data"; break;
default: return QVariant(); break;
}
} else {
return QVariant();
}
}
bool TraceMarkerModel::setData(const QModelIndex &index, const QVariant &value, int)
{
if((unsigned int) index.row() >= markers.size()) {
return false;
}
auto m = markers[index.row()];
switch(index.column()) {
case ColIndexNumber: {
bool convertOk;
unsigned int number;
number = value.toUInt(&convertOk);
if(convertOk) {
m->number = number;
return true;
}
break;
}
case ColIndexTrace: {
auto trace = qvariant_cast<Trace*>(value);
m->assignTrace(trace);
}
break;
case ColIndexFreq: {
auto newval = Unit::FromString(value.toString(), "Hz", " kMG");
if(!qIsNaN(newval)) {
m->setFrequency(newval);
}
}
break;
}
return false;
}
Qt::ItemFlags TraceMarkerModel::flags(const QModelIndex &index) const
{
int flags = Qt::NoItemFlags;
switch(index.column()) {
case ColIndexNumber: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexTrace: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexFreq: flags |= Qt::ItemIsEnabled | Qt::ItemIsEditable; break;
case ColIndexData: flags |= Qt::ItemIsEnabled; break;
}
return (Qt::ItemFlags) flags;
}
std::vector<TraceMarker *> TraceMarkerModel::getMarker()
{
return markers;
}
std::vector<TraceMarker *> TraceMarkerModel::getMarker(Trace *t)
{
std::vector<TraceMarker*> attachedMarkers;
for(auto m : markers) {
if(m->parentTrace == t) {
attachedMarkers.push_back(m);
}
}
return attachedMarkers;
}
TraceModel &TraceMarkerModel::getModel()
{
return model;
}
QWidget *TraceChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{
auto model = (TraceMarkerModel*) index.model();
auto c = new QComboBox(parent);
connect(c, qOverload<int>(&QComboBox::currentIndexChanged), [c](int) {
c->clearFocus();
});
auto traces = model->getModel().getTraces();
for(auto t : traces) {
c->addItem(t->name(), QVariant::fromValue<Trace*>(t));
}
return c;
}
void TraceChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto model = (TraceMarkerModel*) index.model();
auto marker = model->getMarker()[index.row()];
auto c = (QComboBox*) editor;
for(int i=0;i<c->count();i++) {
if(qvariant_cast<Trace*>(c->itemData(i)) == marker->trace()) {
c->setCurrentIndex(i);
return;
}
}
}
void TraceChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
auto markerModel = (TraceMarkerModel*) model;
auto c = (QComboBox*) editor;
markerModel->setData(index, c->itemData(c->currentIndex()));
}

View file

@ -0,0 +1,61 @@
#ifndef TRACEMARKERMODEL_H
#define TRACEMARKERMODEL_H
#include <QAbstractTableModel>
#include "tracemarker.h"
#include <vector>
#include "tracemodel.h"
#include <QItemDelegate>
class TraceChooserDelegate : public QItemDelegate
{
Q_OBJECT;
QWidget *createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
void setEditorData(QWidget * editor, const QModelIndex & index) const override;
void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
};
class TraceMarkerModel : public QAbstractTableModel
{
Q_OBJECT
public:
TraceMarkerModel(TraceModel &model, QObject *parent = 0);
enum {
ColIndexNumber = 0,
ColIndexTrace = 1,
ColIndexFreq = 2,
ColIndexData = 3,
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
TraceMarker* createDefaultMarker();
TraceMarker *marker(int index);
std::vector<TraceMarker*> getMarker();
std::vector<TraceMarker*> getMarker(Trace *t);
TraceModel& getModel();
public slots:
void addMarker(TraceMarker *t);
void removeMarker(unsigned int index, bool delete_marker = true);
void removeMarker(TraceMarker *m);
signals:
void markerAdded(TraceMarker *t);
private slots:
void markerDataChanged(TraceMarker *m);
private:
std::vector<TraceMarker*> markers;
TraceModel &model;
};
#endif // TRACEMARKERMODEL_H

View file

@ -0,0 +1,137 @@
#include "tracemodel.h"
#include <QIcon>
using namespace std;
TraceModel::TraceModel(QObject *parent)
: QAbstractTableModel(parent)
{
traces.clear();
}
void TraceModel::addTrace(Trace *t)
{
beginInsertRows(QModelIndex(), traces.size(), traces.size());
traces.push_back(t);
endInsertRows();
emit traceAdded(t);
}
void TraceModel::removeTrace(unsigned int index)
{
if (index < traces.size()) {
beginRemoveRows(QModelIndex(), index, index);
auto trace = traces[index];
delete trace;
traces.erase(traces.begin() + index);
endRemoveRows();
emit traceRemoved(trace);
}
}
Trace *TraceModel::trace(unsigned int index)
{
return traces.at(index);
}
void TraceModel::toggleVisibility(unsigned int index)
{
if (index < traces.size()) {
traces[index]->setVisible(!traces[index]->isVisible());
emit dataChanged(createIndex(index, 0), createIndex(index, 0));
}
}
void TraceModel::togglePause(unsigned int index)
{
if (index < traces.size()) {
if(traces[index]->isPaused()) {
traces[index]->resume();
} else {
traces[index]->pause();
}
emit dataChanged(createIndex(index, 1), createIndex(index, 1));
}
}
int TraceModel::rowCount(const QModelIndex &) const
{
return traces.size();
}
int TraceModel::columnCount(const QModelIndex &) const
{
return 3;
}
QVariant TraceModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if ((unsigned int) index.row() >= traces.size())
return QVariant();
if (index.column() == 0) {
if (role == Qt::DecorationRole) {
if (traces[index.row()]->isVisible()) {
return QIcon(":/icons/visible.svg");
} else {
return QIcon(":/icons/invisible.svg");
}
} else {
return QVariant();
}
} else if (index.column() == 1) {
if (role == Qt::DecorationRole && !traces[index.row()]->isTouchstone()) {
if (traces[index.row()]->isPaused()) {
return QIcon(":/icons/pause.svg");
} else {
return QIcon(":/icons/play.svg");
}
} else {
return QVariant();
}
} else if (index.column() == 2) {
if (role == Qt::DisplayRole) {
return traces[index.row()]->name();
} else if (role == Qt::ForegroundRole) {
return traces[index.row()]->color();
} else {
return QVariant();
}
} else {
return QVariant();
}
}
std::vector<Trace *> TraceModel::getTraces()
{
return traces;
}
void TraceModel::clearVNAData()
{
for(auto t : traces) {
if (!t->isTouchstone()) {
// this trace is fed from live data
t->clear();
}
}
}
void TraceModel::addVNAData(Protocol::Datapoint d)
{
for(auto t : traces) {
if (t->isLive()) {
Trace::Data td;
td.frequency = d.frequency;
switch(t->liveParameter()) {
case Trace::LiveParameter::S11: td.S = complex<double>(d.real_S11, d.imag_S11); break;
case Trace::LiveParameter::S12: td.S = complex<double>(d.real_S12, d.imag_S12); break;
case Trace::LiveParameter::S21: td.S = complex<double>(d.real_S21, d.imag_S21); break;
case Trace::LiveParameter::S22: td.S = complex<double>(d.real_S22, d.imag_S22); break;
}
t->addData(td);
}
}
}

View file

@ -0,0 +1,38 @@
#ifndef TRACEMODEL_H
#define TRACEMODEL_H
#include <QAbstractTableModel>
#include "trace.h"
#include <vector>
#include "Device/device.h"
class TraceModel : public QAbstractTableModel
{
Q_OBJECT
public:
TraceModel(QObject *parent = 0);
void addTrace(Trace *t);
void removeTrace(unsigned int index);
Trace *trace(unsigned int index);
void toggleVisibility(unsigned int index);
void togglePause(unsigned int index);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
std::vector<Trace*> getTraces();
signals:
void traceAdded(Trace *t);
void traceRemoved(Trace *t);
public slots:
void clearVNAData();
void addVNAData(Protocol::Datapoint d);
private:
std::vector<Trace*> traces;
};
#endif // TRACEMODEL_H

View file

@ -0,0 +1,145 @@
#include "traceplot.h"
const QColor TracePlot::Background = QColor(0,0,0);
const QColor TracePlot::Border = QColor(255,255,255);
const QColor TracePlot::Divisions = QColor(255,255,255);
#include "tracemarker.h"
std::set<TracePlot*> TracePlot::plots;
TracePlot::TracePlot(QWidget *parent) : QWidget(parent)
{
contextmenu = new QMenu();
markedForDeletion = false;
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
lastUpdate = QTime::currentTime();
plots.insert(this);
}
TracePlot::~TracePlot()
{
plots.erase(this);
delete contextmenu;
}
void TracePlot::enableTrace(Trace *t, bool enabled)
{
if(traces[t] != enabled) {
traces[t] = enabled;
if(enabled) {
// connect signals
connect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
connect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
connect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
connect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
} else {
// disconnect from notifications
disconnect(t, &Trace::dataChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::visibilityChanged, this, &TracePlot::triggerReplot);
disconnect(t, &Trace::markerAdded, this, &TracePlot::markerAdded);
disconnect(t, &Trace::markerRemoved, this, &TracePlot::markerRemoved);
}
updateContextMenu();
triggerReplot();
}
}
void TracePlot::mouseDoubleClickEvent(QMouseEvent *) {
emit doubleClicked(this);
}
void TracePlot::UpdateSpan(double fmin, double fmax)
{
for(auto p : plots) {
p->setXAxis(fmin, fmax);
}
}
void TracePlot::initializeTraceInfo(TraceModel &model)
{
// Populate already present traces
auto tvect = model.getTraces();
for(auto t : tvect) {
newTraceAvailable(t);
}
// connect notification of traces added at later point
connect(&model, &TraceModel::traceAdded, this, &TracePlot::newTraceAvailable);
}
void TracePlot::contextMenuEvent(QContextMenuEvent *event)
{
contextmenu->exec(event->globalPos());
if(markedForDeletion) {
emit deleted(this);
delete this;
}
}
void TracePlot::updateContextMenu()
{
contextmenu->clear();
contextmenu->addSection("Traces");
// Populate context menu
for(auto t : traces) {
auto action = new QAction(t.first->name());
action->setCheckable(true);
if(t.second) {
action->setChecked(true);
}
connect(action, &QAction::toggled, [=](bool active) {
enableTrace(t.first, active);
});
contextmenu->addAction(action);
}
contextmenu->addSeparator();
auto close = new QAction("Close");
contextmenu->addAction(close);
connect(close, &QAction::triggered, [=]() {
markedForDeletion = true;
});
}
std::set<TracePlot *> TracePlot::getPlots()
{
return plots;
}
void TracePlot::newTraceAvailable(Trace *t)
{
if(supported(t)) {
traces[t] = false;
connect(t, &Trace::deleted, this, &TracePlot::traceDeleted);
connect(t, &Trace::nameChanged, this, &TracePlot::updateContextMenu);
connect(t, &Trace::typeChanged, this, &TracePlot::updateContextMenu);
}
updateContextMenu();
}
void TracePlot::traceDeleted(Trace *t)
{
enableTrace(t, false);
traces.erase(t);
updateContextMenu();
triggerReplot();
}
void TracePlot::triggerReplot()
{
auto now = QTime::currentTime();
if (lastUpdate.msecsTo(now) >= MinUpdateInterval) {
replot();
lastUpdate = now;
}
}
void TracePlot::markerAdded(TraceMarker *m)
{
connect(m, &TraceMarker::dataChanged, this, &TracePlot::triggerReplot);
triggerReplot();
}
void TracePlot::markerRemoved(TraceMarker *)
{
triggerReplot();
}

View file

@ -0,0 +1,55 @@
#ifndef TRACEPLOT_H
#define TRACEPLOT_H
#include <QWidget>
#include "tracemodel.h"
#include <QMenu>
#include <QContextMenuEvent>
#include <QTime>
class TracePlot : public QWidget
{
Q_OBJECT
public:
TracePlot( QWidget *parent = nullptr);
~TracePlot();
virtual void enableTrace(Trace *t, bool enabled);
void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void setXAxis(double min, double max){Q_UNUSED(min);Q_UNUSED(max)};
static std::set<TracePlot *> getPlots();
static void UpdateSpan(double fmin, double fmax);
signals:
void doubleClicked(QWidget *w);
void deleted(TracePlot*);
protected:
static const QColor Background;// = QColor(0,0,0);
static const QColor Border;// = QColor(255,255,255);
static const QColor Divisions;// = QColor(255,255,255);
static constexpr int MinUpdateInterval = 100;
// need to be called in derived class constructor
void initializeTraceInfo(TraceModel &model);
void contextMenuEvent(QContextMenuEvent *event) override;
virtual void updateContextMenu();
virtual bool supported(Trace *t) = 0;
virtual void replot(){};
std::map<Trace*, bool> traces;
QMenu *contextmenu;
QTime lastUpdate;
bool markedForDeletion;
static std::set<TracePlot*> plots;
protected slots:
void newTraceAvailable(Trace *t);
void traceDeleted(Trace *t);
void triggerReplot();
virtual void markerAdded(TraceMarker *m);
virtual void markerRemoved(TraceMarker *m);
};
#endif // TRACEPLOT_H

View file

@ -0,0 +1,184 @@
#include "tracesmithchart.h"
#include <QPainter>
#include <array>
#include <math.h>
#include "tracemarker.h"
#include <QDebug>
using namespace std;
TraceSmithChart::TraceSmithChart(TraceModel &model, QWidget *parent)
: TracePlot(parent)
{
chartLinesPen = QPen(palette().windowText(), 0.75);
thinPen = QPen(palette().windowText(), 0.25);
textPen = QPen(palette().windowText(), 0.25);
pointDataPen = QPen(QColor("red"), 4.0, Qt::SolidLine, Qt::RoundCap);
lineDataPen = QPen(QColor("blue"), 1.0);
initializeTraceInfo(model);
}
QPoint TraceSmithChart::plotToPixel(std::complex<double> S)
{
QPoint ret;
ret.setX(S.real() * plotToPixelXScale + plotToPixelXOffset);
ret.setY(S.imag() * plotToPixelYScale + plotToPixelYOffset);
return ret;
}
std::complex<double> TraceSmithChart::pixelToPlot(const QPoint &pos)
{
return complex<double>((pos.x() - plotToPixelXOffset) / plotToPixelXScale, (pos.y() - plotToPixelYOffset) / plotToPixelYScale);
}
void TraceSmithChart::mousePressEvent(QMouseEvent *event)
{
auto clickPoint = event->pos();
unsigned int closestDistance = numeric_limits<unsigned int>::max();
TraceMarker *closestMarker = nullptr;
for(auto t : traces) {
auto markers = t.first->getMarkers();
for(auto m : markers) {
auto S = m->getData();
auto markerPoint = plotToPixel(S);
auto yDiff = abs(markerPoint.y() - clickPoint.y());
auto xDiff = abs(markerPoint.x() - clickPoint.x());
unsigned int distance = xDiff * xDiff + yDiff * yDiff;
if(distance < closestDistance) {
closestDistance = distance;
closestMarker = m;
}
}
}
if(closestDistance <= 400) {
selectedMarker = closestMarker;
} else {
selectedMarker = nullptr;
}
}
void TraceSmithChart::mouseMoveEvent(QMouseEvent *event)
{
if(selectedMarker) {
auto t = selectedMarker->trace();
auto mouseS = pixelToPlot(event->pos());
auto samples = t->size();
double closestDistance = numeric_limits<double>::max();
unsigned int closestIndex = 0;
for(unsigned int i=0;i<samples;i++) {
auto data = t->sample(i);
auto distance = norm(data.S - mouseS);
if(distance < closestDistance) {
closestDistance = distance;
closestIndex = i;
}
}
selectedMarker->setFrequency(t->sample(closestIndex).frequency);
}
}
void TraceSmithChart::draw(QPainter * painter, double width_factor) {
painter->setPen(QPen(1.0 * width_factor));
painter->setBrush(palette().windowText());
painter->setRenderHint(QPainter::Antialiasing);
// // Display parameter name
// QFont font = painter->font();
// font.setPixelSize(48);
// font.setBold(true);
// painter->setFont(font);
// painter->drawText(-512, -512, title);
// Outer circle
painter->setPen(QPen(Border, 1.5 * width_factor));
QRectF rectangle(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
painter->drawArc(rectangle, 0, 5760);
constexpr int Circles = 6;
painter->setPen(QPen(Divisions, 0.5 * width_factor, Qt::DashLine));
for(int i=1;i<Circles;i++) {
rectangle.adjust(2*smithCoordMax/Circles, smithCoordMax/Circles, 0, -smithCoordMax/Circles);
painter->drawArc(rectangle, 0, 5760);
}
painter->drawLine(-smithCoordMax, 0, smithCoordMax, 0);
constexpr std::array<double, 5> impedanceLines = {10, 25, 50, 100, 250};
for(auto z : impedanceLines) {
z /= ReferenceImpedance;
auto radius = smithCoordMax * 1.0/z;
double span = M_PI - 2 * atan(radius/smithCoordMax);
span *= 5760 / (2 * M_PI);
QRectF rectangle(smithCoordMax - radius, -2*radius, 2 * radius, 2 * radius);
painter->drawArc(rectangle, 4320 - span, span);
rectangle = QRectF(smithCoordMax - radius, 0, 2 * radius, 2 * radius);
painter->drawArc(rectangle, 1440, span);
}
for(auto t : traces) {
if(!t.second) {
// trace not enabled in plot
continue;
}
auto trace = t.first;
if(!trace->isVisible()) {
// trace marked invisible
continue;
}
painter->setPen(QPen(trace->color(), 1.5 * width_factor));
int nPoints = trace->size();
for(int i=1;i<nPoints;i++) {
auto last = trace->sample(i-1).S;
auto now = trace->sample(i).S;
if(isnan(now.real())) {
break;
}
// scale to size of smith diagram
last *= smithCoordMax;
now *= smithCoordMax;
// draw line
painter->drawLine(std::real(last), -std::imag(last), std::real(now), -std::imag(now));
}
auto markers = t.first->getMarkers();
for(auto m : markers) {
auto coords = m->getData();
coords *= smithCoordMax;
auto symbol = m->getSymbol();
symbol = symbol.scaled(symbol.width()*width_factor, symbol.height()*width_factor);
painter->drawPixmap(coords.real() - symbol.width()/2, -coords.imag() - symbol.height(), symbol);
}
}
}
void TraceSmithChart::replot()
{
update();
}
void TraceSmithChart::paintEvent(QPaintEvent * /* the event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBackground(QBrush(Background));
painter.fillRect(0, 0, width(), height(), QBrush(Background));
double side = qMin(width(), height()) * screenUsage;
painter.setViewport((width()-side)/2, (height()-side)/2, side, side);
painter.setWindow(-smithCoordMax, -smithCoordMax, 2*smithCoordMax, 2*smithCoordMax);
plotToPixelXOffset = width()/2;
plotToPixelYOffset = height()/2;
plotToPixelXScale = side/2;
plotToPixelYScale = -side/2;
draw(&painter, 2*smithCoordMax/side);
}
bool TraceSmithChart::supported(Trace *t)
{
if(t->isReflection()) {
return true;
} else {
return false;
}
}

View file

@ -0,0 +1,44 @@
#ifndef TRACESMITHCHART_H
#define TRACESMITHCHART_H
#include "traceplot.h"
#include <QPen>
class TraceSmithChart : public TracePlot
{
Q_OBJECT
public:
TraceSmithChart(TraceModel &model, QWidget *parent = 0);
protected:
static constexpr double ReferenceImpedance = 50.0;
static constexpr double screenUsage = 0.9;
static constexpr int smithCoordMax = 4096;
QPoint plotToPixel(std::complex<double> S);
std::complex<double> pixelToPlot(const QPoint &pos);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
bool supported(Trace *t) override;
void draw(QPainter * painter, double width_factor);
void replot() override;
QPen textPen;
QPen chartLinesPen;
QPen thinPen;
QPen pointDataPen;
QPen lineDataPen;
/// Path for the thin arcs
QPainterPath thinArcsPath;
/// Path for the thick arcs
QPainterPath thickArcsPath;
double plotToPixelXOffset, plotToPixelXScale;
double plotToPixelYOffset, plotToPixelYScale;
TraceMarker *selectedMarker;
};
#endif // TRACESMITHCHART_H

View file

@ -0,0 +1,121 @@
#include "tracewidget.h"
#include "ui_tracewidget.h"
#include "trace.h"
#include <QKeyEvent>
#include "traceeditdialog.h"
#include "traceimportdialog.h"
#include "traceexportdialog.h"
#include <QFileDialog>
TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) :
QWidget(parent),
ui(new Ui::TraceWidget),
model(model)
{
ui->setupUi(this);
ui->view->setModel(&model);
ui->view->setAutoScroll(false);
installEventFilter(this);
createCount = 0;
}
TraceWidget::~TraceWidget()
{
delete ui;
}
void TraceWidget::on_add_clicked()
{
createCount++;
auto t = new Trace("Trace #"+QString::number(createCount));
t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128));
model.addTrace(t);
}
void TraceWidget::on_remove_clicked()
{
model.removeTrace(ui->view->currentIndex().row());
}
bool TraceWidget::eventFilter(QObject *, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
int key = static_cast<QKeyEvent *>(event)->key();
if(key == Qt::Key_Escape) {
ui->view->clearSelection();
return true;
} else if(key == Qt::Key_Delete) {
model.removeTrace(ui->view->currentIndex().row());
return true;
}
}
return false;
}
void TraceWidget::on_edit_clicked()
{
if(ui->view->currentIndex().isValid()) {
auto edit = new TraceEditDialog(*model.trace(ui->view->currentIndex().row()));
edit->show();
}
}
void TraceWidget::on_view_doubleClicked(const QModelIndex &index)
{
if(index.column() == 2) {
auto edit = new TraceEditDialog(*model.trace(index.row()));
edit->show();
}
}
void TraceWidget::on_view_clicked(const QModelIndex &index)
{
if(index.column()==0) {
model.toggleVisibility(index.row());
} else if(index.column()==1) {
model.togglePause(index.row());
}
}
void TraceWidget::on_bImport_clicked()
{
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p)", nullptr, QFileDialog::DontUseNativeDialog);
if (filename.length() > 0) {
auto t = Touchstone::fromFile(filename.toStdString());
std::vector<Trace*> traces;
for(unsigned int i=0;i<t.ports()*t.ports();i++) {
auto trace = new Trace();
trace->fillFromTouchstone(t, i, filename);
unsigned int sink = i / t.ports() + 1;
unsigned int source = i % t.ports() + 1;
trace->setName("S"+QString::number(sink)+QString::number(source));
traces.push_back(trace);
}
// contruct prefix from filename
// remove any directory names (keep only the filename itself)
int lastSlash = qMax(filename.lastIndexOf('/'), filename.lastIndexOf('\\'));
if(lastSlash != -1) {
filename.remove(0, lastSlash + 1);
}
// remove file type
filename.truncate(filename.indexOf('.'));
auto i = new TraceImportDialog(model, traces, filename+"_");
i->show();
}
}
void TraceWidget::on_bExport_clicked()
{
auto e = new TraceExportDialog(model);
// Attempt to set default traces (this will result in correctly populated
// 2 port export if the initial 4 traces have not been modified)
e->setPortNum(2);
auto traces = model.getTraces();
for(unsigned int i=0;i<4;i++) {
if(i >= traces.size()) {
break;
}
e->setTrace(i%2, i/2, traces[i]);
}
e->show();
}

View file

@ -0,0 +1,41 @@
#ifndef TRACEWIDGET_H
#define TRACEWIDGET_H
#include <QWidget>
#include "tracemodel.h"
namespace Ui {
class TraceWidget;
}
class TraceWidget : public QWidget
{
Q_OBJECT
public:
explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr);
~TraceWidget();
public slots:
void on_add_clicked();
private slots:
void on_remove_clicked();
void on_edit_clicked();
void on_view_doubleClicked(const QModelIndex &index);
void on_view_clicked(const QModelIndex &index);
void on_bImport_clicked();
void on_bExport_clicked();
private:
bool eventFilter(QObject *obj, QEvent *event) override;
Ui::TraceWidget *ui;
TraceModel &model;
int createCount;
};
#endif // TRACEWIDGET_H

View file

@ -0,0 +1,179 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TraceWidget</class>
<widget class="QWidget" name="TraceWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>206</width>
<height>268</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTableView" name="view">
<property name="sizePolicy">
<sizepolicy hsizetype="Ignored" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerItem</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderMinimumSectionSize">
<number>0</number>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>21</number>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="add">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Add</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-add"/>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Delete</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="list-remove"/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bImport">
<property name="toolTip">
<string>Import</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/import.svg</normaloff>:/icons/import.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bExport">
<property name="toolTip">
<string>Export</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/export.svg</normaloff>:/icons/export.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Edit</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset theme="accessories-text-editor"/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>