TraceWidget: support dropping files for importing measurements

This commit is contained in:
Jan Käberich 2024-12-02 18:11:20 +01:00
parent 0b8e1a7b50
commit 6ea8869f7f
9 changed files with 174 additions and 131 deletions

View file

@ -8,7 +8,7 @@
#include <QFileDialog>
TraceWidgetSA::TraceWidgetSA(TraceModel &model, QWidget *parent)
: TraceWidget(model, parent)
: TraceWidget(model, nullptr, nullptr, parent)
{
}
@ -20,32 +20,3 @@ void TraceWidgetSA::exportDialog()
csv->show();
}
}
void TraceWidgetSA::importDialog()
{
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
if (!filename.isEmpty()) {
try {
std::vector<Trace*> traces;
QString prefix = QString();
auto csv = CSV::fromFile(filename);
traces = Trace::createFromCSV(csv);
// contruct prefix from filename
prefix = filename;
// remove any directory names (keep only the filename itself)
int lastSlash = qMax(prefix.lastIndexOf('/'), prefix.lastIndexOf('\\'));
if(lastSlash != -1) {
prefix.remove(0, lastSlash + 1);
}
// remove file type
prefix.truncate(prefix.indexOf('.'));
prefix.append("_");
auto i = new TraceImportDialog(model, traces, prefix);
if(AppWindow::showGUI()) {
i->show();
}
} catch(const std::exception &e) {
InformationBox::ShowError("Failed to import file", QString("Attempt to import file ended with error: \"") + e.what()+"\"");
}
}
}

View file

@ -9,7 +9,7 @@ public:
TraceWidgetSA(TraceModel &model, QWidget *parent = nullptr);
public slots:
virtual void exportDialog() override;
virtual void importDialog() override;
virtual QStringList supportsImportFileFormats() override {return {"csv"};}
protected:
virtual QString defaultParameter() override {return "PORT1";}

View file

@ -352,6 +352,7 @@ std::set<YAxis::Type> YAxis::getSupported(XAxis::Type type, TraceModel::DataSour
} else if(source == TraceModel::DataSource::SA) {
switch(type) {
case XAxis::Type::Frequency:
case XAxis::Type::TimeZeroSpan:
ret.insert(YAxis::Type::Magnitude);
ret.insert(YAxis::Type::MagnitudedBuV);
break;

View file

@ -626,7 +626,6 @@ void TracePlot::dragMoveEvent(QDragMoveEvent *event)
// transform to relative coordinates from 0 to 1
auto x = (double) pos.x() / (width() - marginLeft - marginRight);
auto y = (double) pos.y() / (height() - marginTop - marginBottom);
qDebug() << "x:" << x << "y:" << y;
if(y < 0.5) {
if(x < y) {
dropSection = DropSection::ToTheLeft;

View file

@ -3,6 +3,8 @@
#include "ui_tracewidget.h"
#include "traceeditdialog.h"
#include "traceimportdialog.h"
#include "ui_s2pImportOptions.h"
#include "CustomWidgets/informationbox.h"
#include "tracetouchstoneexport.h"
#include "trace.h"
#include "unit.h"
@ -16,14 +18,17 @@
#include <QDebug>
#include <QMenu>
#include <QTableView>
#include <QDebug>
TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) :
TraceWidget::TraceWidget(TraceModel &model, Calibration *cal, Deembedding *deembed, QWidget *parent) :
QWidget(parent),
SCPINode("TRACe"),
dragTrace(nullptr),
ui(new Ui::TraceWidget),
model(model)
model(model),
dropFilename(""),
cal(cal),
deembed(deembed)
{
ui->setupUi(this);
ui->view->setModel(&model);
@ -37,6 +42,7 @@ TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) :
ui->remove->setEnabled(current.isValid());
});
installEventFilter(this);
setAcceptDrops(true);
createCount = 0;
SetupSCPI();
}
@ -119,6 +125,41 @@ bool TraceWidget::eventFilter(QObject *, QEvent *event)
return false;
}
void TraceWidget::dragEnterEvent(QDragEnterEvent *event)
{
if(event->mimeData()->hasFormat("text/plain")) {
// might be a file drop
auto data = QString(event->mimeData()->data("text/plain"));
if (data.startsWith("file://")) {
// extract file path/name and type
data = data.trimmed();
data.remove(0, 7);
if(data.contains(".")) {
auto type = data.split(".").last();
if (supportsImportFileFormats().contains(type)) {
dropFilename = data;
qDebug() << "prepared to drop file " << dropFilename;
event->accept();
return;
}
}
}
}
event->ignore();
dropFilename = "";
}
void TraceWidget::dropEvent(QDropEvent *event)
{
Q_UNUSED(event);
if(dropFilename.size() > 0) {
if(importFile(dropFilename)) {
event->accept();
}
}
dropFilename = "";
}
void TraceWidget::on_edit_clicked()
{
if(ui->view->currentIndex().isValid()) {
@ -159,6 +200,111 @@ void TraceWidget::on_view_clicked(const QModelIndex &index)
}
}
void TraceWidget::importDialog()
{
QString supported = "Supported files (";
for(auto f : supportsImportFileFormats()) {
supported += "*."+f+" ";
}
supported.chop(1);
supported += ")";
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", supported, nullptr, Preferences::QFileDialogOptions());
if (!filename.isEmpty()) {
importFile(filename);
}
}
bool TraceWidget::importFile(QString filename)
{
if(!filename.contains(".")) {
// no ending, not supported
return false;
}
auto format = filename.split(".").last();
if(!supportsImportFileFormats().contains(format)) {
// unsupported format
return false;
}
// try to import the file
try {
std::vector<Trace*> traces;
int touchstonePorts = 0;
QString prefix = QString();
if(filename.endsWith(".csv")) {
auto csv = CSV::fromFile(filename);
traces = Trace::createFromCSV(csv);
} else {
// must be a touchstone file
auto t = Touchstone::fromFile(filename.toStdString());
traces = Trace::createFromTouchstone(t);
touchstonePorts = t.ports();
}
// contruct prefix from filename
prefix = filename;
// remove any directory names (keep only the filename itself)
int lastSlash = qMax(prefix.lastIndexOf('/'), prefix.lastIndexOf('\\'));
if(lastSlash != -1) {
prefix.remove(0, lastSlash + 1);
}
// remove file type
prefix.truncate(prefix.indexOf('.'));
prefix.append("_");
auto i = new TraceImportDialog(model, traces, prefix);
if(AppWindow::showGUI()) {
i->show();
}
// potential candidate to process via calibration/de-embedding
connect(i, &TraceImportDialog::importFinsished, [=](const std::vector<Trace*> &traces) {
if(traces.size() == touchstonePorts*touchstonePorts) {
// all traces imported, can calculate calibration/de-embedding
bool calAvailable = cal && cal->getNumPoints() > 0;
bool deembedAvailable = deembed && deembed->getOptions().size() > 0;
if(calAvailable || deembedAvailable) {
// check if user wants to apply either one to the imported traces
auto dialog = new QDialog();
auto ui = new Ui::s2pImportOptions;
ui->setupUi(dialog);
connect(dialog, &QDialog::finished, [=](){
delete ui;
});
ui->applyCal->setEnabled(calAvailable);
ui->deembed->setEnabled(deembedAvailable);
bool applyCal = false;
bool applyDeembed = false;
connect(ui->applyCal, &QCheckBox::toggled, [&](bool checked) {
applyCal = checked;
});
connect(ui->deembed, &QCheckBox::toggled, [&](bool checked) {
applyDeembed = checked;
});
if(AppWindow::showGUI()) {
dialog->exec();
}
// assemble trace set
std::map<QString, Trace*> set;
for(int i=1;i<=touchstonePorts;i++) {
for(int j=1;j<=touchstonePorts;j++) {
QString name = "S"+QString::number(i)+QString::number(j);
int index = (i-1)*touchstonePorts+(j-1);
set[name] = traces[index];
}
}
if(applyCal) {
cal->correctTraces(set);
}
if(applyDeembed) {
deembed->Deembed(set);
}
}
}
});
return true;
} catch(const std::exception& e) {
InformationBox::ShowError("Failed to import file", QString("Attempt to import file ended with error: \"") + e.what()+"\"");
return false;
}
}
void TraceWidget::SetupSCPI()
{
auto findTraceFromName = [=](QString name) -> Trace* {

View file

@ -3,6 +3,8 @@
#include "tracemodel.h"
#include "scpi.h"
#include "Calibration/calibration.h"
#include "VNA/Deembedding/deembedding.h"
#include <QWidget>
@ -15,28 +17,40 @@ class TraceWidget : public QWidget, public SCPINode
Q_OBJECT
public:
explicit TraceWidget(TraceModel &model, QWidget *parent = nullptr);
explicit TraceWidget(TraceModel &model, Calibration *cal, Deembedding *deembed, QWidget *parent = nullptr);
virtual ~TraceWidget();
public:
virtual void exportDialog() = 0;
virtual void importDialog();
protected slots:
void on_add_clicked();
void on_remove_clicked();
void on_edit_clicked();
void on_view_doubleClicked(const QModelIndex &index);
void on_view_clicked(const QModelIndex &index);
virtual void exportDialog() = 0;
virtual void importDialog() = 0;
virtual QStringList supportsImportFileFormats() = 0;
bool importFile(QString filename);
protected:
void SetupSCPI();
void contextMenuEvent(QContextMenuEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
virtual QString defaultParameter() = 0;
QPoint dragStartPosition;
Trace *dragTrace;
Ui::TraceWidget *ui;
TraceModel &model;
int createCount;
private:
QString dropFilename;
// These can optionally be applied when importing an s2p file
Calibration *cal;
Deembedding *deembed;
};
#endif // TRACEWIDGET_H

View file

@ -4,17 +4,13 @@
#include "Traces/tracetouchstoneexport.h"
#include "Traces/tracecsvexport.h"
#include "ui_tracewidget.h"
#include "ui_s2pImportOptions.h"
#include "CustomWidgets/informationbox.h"
#include "appwindow.h"
#include <QFileDialog>
#include <QMenu>
TraceWidgetVNA::TraceWidgetVNA(TraceModel &model, Calibration &cal, Deembedding &deembed, QWidget *parent)
: TraceWidget(model, parent),
cal(cal),
deembed(deembed)
TraceWidgetVNA::TraceWidgetVNA(TraceModel &model, Calibration *cal, Deembedding *deembed, QWidget *parent)
: TraceWidget(model, cal, deembed, parent)
{
auto exportMenu = new QMenu();
auto exportTouchstoneAction = new QAction("Touchstone");
@ -54,84 +50,3 @@ void TraceWidgetVNA::exportTouchstone()
}
}
void TraceWidgetVNA::importDialog()
{
auto filename = QFileDialog::getOpenFileName(nullptr, "Open measurement file", "", "Touchstone files (*.s1p *.s2p *.s3p *.s4p);;CSV files (*.csv)", nullptr, Preferences::QFileDialogOptions());
if (!filename.isEmpty()) {
try {
std::vector<Trace*> traces;
int touchstonePorts = 0;
QString prefix = QString();
if(filename.endsWith(".csv")) {
auto csv = CSV::fromFile(filename);
traces = Trace::createFromCSV(csv);
} else {
// must be a touchstone file
auto t = Touchstone::fromFile(filename.toStdString());
traces = Trace::createFromTouchstone(t);
touchstonePorts = t.ports();
}
// contruct prefix from filename
prefix = filename;
// remove any directory names (keep only the filename itself)
int lastSlash = qMax(prefix.lastIndexOf('/'), prefix.lastIndexOf('\\'));
if(lastSlash != -1) {
prefix.remove(0, lastSlash + 1);
}
// remove file type
prefix.truncate(prefix.indexOf('.'));
prefix.append("_");
auto i = new TraceImportDialog(model, traces, prefix);
if(AppWindow::showGUI()) {
i->show();
}
// potential candidate to process via calibration/de-embedding
connect(i, &TraceImportDialog::importFinsished, [=](const std::vector<Trace*> &traces) {
if(traces.size() == touchstonePorts*touchstonePorts) {
// all traces imported, can calculate calibration/de-embedding
bool calAvailable = cal.getNumPoints() > 0;
bool deembedAvailable = deembed.getOptions().size() > 0;
if(calAvailable || deembedAvailable) {
// check if user wants to apply either one to the imported traces
auto dialog = new QDialog();
auto ui = new Ui::s2pImportOptions;
ui->setupUi(dialog);
connect(dialog, &QDialog::finished, [=](){
delete ui;
});
ui->applyCal->setEnabled(calAvailable);
ui->deembed->setEnabled(deembedAvailable);
bool applyCal = false;
bool applyDeembed = false;
connect(ui->applyCal, &QCheckBox::toggled, [&](bool checked) {
applyCal = checked;
});
connect(ui->deembed, &QCheckBox::toggled, [&](bool checked) {
applyDeembed = checked;
});
if(AppWindow::showGUI()) {
dialog->exec();
}
// assemble trace set
std::map<QString, Trace*> set;
for(int i=1;i<=touchstonePorts;i++) {
for(int j=1;j<=touchstonePorts;j++) {
QString name = "S"+QString::number(i)+QString::number(j);
int index = (i-1)*touchstonePorts+(j-1);
set[name] = traces[index];
}
}
if(applyCal) {
cal.correctTraces(set);
}
if(applyDeembed) {
deembed.Deembed(set);
}
}
}
});
} catch(const std::exception& e) {
InformationBox::ShowError("Failed to import file", QString("Attempt to import file ended with error: \"") + e.what()+"\"");
}
}
}

View file

@ -8,18 +8,15 @@
class TraceWidgetVNA : public TraceWidget
{
public:
TraceWidgetVNA(TraceModel &model, Calibration &cal, Deembedding &deembed, QWidget *parent = nullptr);
TraceWidgetVNA(TraceModel &model, Calibration *cal, Deembedding *deembed, QWidget *parent = nullptr);
public slots:
void exportCSV();
void exportTouchstone();
virtual void exportDialog() override {}
virtual void importDialog() override;
virtual QStringList supportsImportFileFormats() override {return {"csv", "s1p", "s2p", "s3p", "s4p"};}
protected:
virtual QString defaultParameter() override {return "S11";}
// These can optionally be applied when importing an s2p file
Calibration &cal;
Deembedding &deembed;
};
#endif // TRACEWIDGETVNA_H

View file

@ -596,7 +596,7 @@ VNA::VNA(AppWindow *window, QString name)
markerModel = new MarkerModel(traceModel, this);
auto tracesDock = new QDockWidget("Traces");
traceWidget = new TraceWidgetVNA(traceModel, cal, deembedding);
traceWidget = new TraceWidgetVNA(traceModel, &cal, &deembedding);
tracesDock->setWidget(traceWidget);
window->addDockWidget(Qt::LeftDockWidgetArea, tracesDock);
docks.insert(tracesDock);