#include "tracewidget.h" #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" #include "Util/util.h" #include "appwindow.h" #include #include #include #include #include #include #include #include TraceWidget::TraceWidget(TraceModel &model, Calibration *cal, Deembedding *deembed, QWidget *parent) : QWidget(parent), SCPINode("TRACe"), dragTrace(nullptr), ui(new Ui::TraceWidget), model(model), dropFilename(""), cal(cal), deembed(deembed) { ui->setupUi(this); ui->view->setModel(&model); ui->view->setAutoScroll(false); ui->view->viewport()->installEventFilter(this); connect(ui->bImport, &QPushButton::clicked, this, &TraceWidget::importDialog); connect(ui->bExport, &QPushButton::clicked, this, &TraceWidget::exportDialog); connect(ui->view->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [=](const QModelIndex ¤t, const QModelIndex &previous){ Q_UNUSED(previous) ui->edit->setEnabled(current.isValid()); ui->remove->setEnabled(current.isValid()); }); installEventFilter(this); setAcceptDrops(true); createCount = 0; SetupSCPI(); } TraceWidget::~TraceWidget() { delete ui; } void TraceWidget::on_add_clicked() { createCount++; auto t = new Trace("Trace #"+QString::number(createCount), Qt::darkYellow, defaultParameter()); t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128)); model.addTrace(t); ui->view->selectRow(model.getTraces().size() - 1); on_edit_clicked(); } void TraceWidget::on_remove_clicked() { QModelIndex index = ui->view->currentIndex(); if (index.isValid()) { // if nothing clicked, index.row() = -1 model.removeTrace(index.row()); // otherwise, TraceModel casts index to unsigned int and compares with traces.size() which is int }; } bool TraceWidget::eventFilter(QObject *, QEvent *event) { if (event->type() == QEvent::KeyPress) { int key = static_cast(event)->key(); if(key == Qt::Key_Escape) { ui->view->clearSelection(); ui->edit->setEnabled(false); ui->remove->setEnabled(false); return true; } else if(key == Qt::Key_Delete) { model.removeTrace(ui->view->currentIndex().row()); return true; } } else if(event->type() == QEvent::MouseButtonPress) { auto mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { auto index = ui->view->indexAt(mouseEvent->pos()); if(index.isValid()) { dragStartPosition = mouseEvent->pos(); dragTrace = model.trace(index.row()); } else { dragTrace = nullptr; } } return false; } else if(event->type() == QEvent::MouseMove) { auto mouseEvent = static_cast(event); if (!(mouseEvent->buttons() & Qt::LeftButton)) { return false; } if (!dragTrace) { return false; } if ((mouseEvent->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { return false; } QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QByteArray encodedPointer; QDataStream stream(&encodedPointer, QIODevice::WriteOnly); stream << quintptr(dragTrace); mimeData->setData("trace/pointer", encodedPointer); drag->setMimeData(mimeData); drag->exec(Qt::CopyAction); return true; } return false; } void TraceWidget::dragEnterEvent(QDragEnterEvent *event) { QString data = ""; if(event->mimeData()->hasFormat("text/plain")) { data = QString(event->mimeData()->data("text/plain")); if (data.startsWith("file://")) { data = data.trimmed(); data.remove(0, 7); } else { // something else data = ""; } } else if(event->mimeData()->hasFormat("text/uri-list")) { data = QString(event->mimeData()->data("text/uri-list")); if (data.startsWith("file:///")) { data = data.trimmed(); data.remove(0, 8); } else { // something else data = ""; } } if(!data.isEmpty()) { // extract file path/name and type 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()) { auto edit = new TraceEditDialog(*model.trace(ui->view->currentIndex().row())); if(AppWindow::showGUI()) { edit->show(); } } } void TraceWidget::on_view_doubleClicked(const QModelIndex &index) { if(index.column() == TraceModel::ColIndexName) { auto edit = new TraceEditDialog(*model.trace(index.row())); if(AppWindow::showGUI()) { edit->show(); } } } void TraceWidget::on_view_clicked(const QModelIndex &index) { switch(index.column()) { case TraceModel::ColIndexVisible: model.toggleVisibility(index.row()); break; case TraceModel::ColIndexPlayPause: model.togglePause(index.row()); break; case TraceModel::ColIndexDeembedding: model.toggleDeembedding(index.row()); break; case TraceModel::ColIndexMath: model.toggleMath(index.row()); break; default: break; } } 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", Preferences::getInstance().UISettings.Paths.data, supported, nullptr, Preferences::QFileDialogOptions()); if (!filename.isEmpty()) { Preferences::getInstance().UISettings.Paths.data = QFileInfo(filename).path(); 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 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 &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 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* { // check if trace is specified by number bool ok; auto n = name.toUInt(&ok); if(ok) { // check if enough traces exist if(n < model.getTraces().size()) { return model.getTraces()[n]; } else { // invalid number return nullptr; } } else { // trace specified by name for(auto t : model.getTraces()) { if(t->name().compare(name, Qt::CaseInsensitive) == 0) { return t; } } // not found return nullptr; } }; auto findTrace = [=](QStringList params) -> Trace* { if(params.size() < 1) { return nullptr; } return findTraceFromName(params[0]); }; auto createStringFromData = [](Trace *t, const Trace::Data &d) -> QString { if(Trace::isSAParameter(t->liveParameter())) { if(std::isnan(d.x)) { return "NaN"; } return QString::number(Util::SparamTodB(d.y.real())); } else { if(std::isnan(d.x)) { return "NaN,NaN"; } return QString::number(d.y.real())+","+QString::number(d.y.imag()); } }; add(new SCPICommand("LIST", nullptr, [=](QStringList){ QString ret; for(auto t : model.getTraces()) { ret += t->name() + ","; } ret.chop(1); return ret; })); add(new SCPICommand("DATA", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } QString ret; if(t->size() > 0) { for(unsigned int i=0;isize();i++) { auto d = t->sample(i); int precision = 0; switch(t->outputType()) { case Trace::DataType::Invalid: case Trace::DataType::Frequency: precision = 0; break; case Trace::DataType::Time: precision = 12; break; case Trace::DataType::Power: precision = 3; break; case Trace::DataType::TimeZeroSpan: precision = 4; break; } ret += "[" + QString::number(d.x, 'f', precision) + ","+createStringFromData(t, d)+"],"; } ret.chop(1); } else { ret = "EMPTY"; } return ret; })); add(new SCPICommand("AT", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } double x; if(!SCPI::paramToDouble(params, 1, x)) { return SCPI::getResultName(SCPI::Result::Error); } else { auto d = t->interpolatedSample(x); if(std::isnan(d.x)) { return "NaN,NaN"; } else { return createStringFromData(t, d); } } })); add(new SCPICommand("TOUCHSTONE", nullptr, [=](QStringList params) -> QString { if(params.size() < 1) { // no traces given return SCPI::getResultName(SCPI::Result::Error); } // check number of paramaters, must be a square number int numTraces = params.size(); int ports = round(sqrt(numTraces)); if(ports * ports != numTraces) { // invalid number of traces return SCPI::getResultName(SCPI::Result::Error); } Trace* traces[numTraces]; for(int i=0;isize(); auto f_start = traces[0]->minX(); auto f_stop = traces[0]->maxX(); for(int i=0;ioutputType() != Trace::DataType::Frequency) { // invalid domain return SCPI::getResultName(SCPI::Result::Error); } if(t->isReflection() != need_reflection) { // invalid measurement at this position return SCPI::getResultName(SCPI::Result::Error); } if((t->size() != npoints) || (t->minX() != f_start) || (t->maxX() != f_stop)) { // frequency points are not identical return SCPI::getResultName(SCPI::Result::Error); } } } // all traces checked, they are valid. // Constructing touchstone Touchstone t = Touchstone(ports); for(unsigned int i=0;igetSample(i).x; for(auto trace : traces) { d.S.push_back(trace->getSample(i).y); } t.AddDatapoint(d); } // touchstone assembled, save to dummyfile auto s = t.toString(Touchstone::Scale::GHz, Touchstone::Format::RealImaginary); return QString::fromStdString(s.str()); })); add(new SCPICommand("MAXFrequency", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return QString::number(t->maxX(), 'f', 0); })); add(new SCPICommand("MINFrequency", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return QString::number(t->minX(), 'f', 0); })); add(new SCPICommand("MAXAmplitude", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } auto d = t->interpolatedSample(t->findExtremum(true)); return QString::number(d.x, 'f', 0)+","+createStringFromData(t, d); })); add(new SCPICommand("MINAmplitude", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } auto d = t->interpolatedSample(t->findExtremum(false)); return QString::number(d.x, 'f', 0)+","+createStringFromData(t, d); })); add(new SCPICommand("NEW", [=](QStringList params) -> QString { if(params.size() != 1) { return SCPI::getResultName(SCPI::Result::Error); } createCount++; auto t = new Trace(params[0], Qt::darkYellow, defaultParameter()); t->setColor(QColor::fromHsl((createCount * 50) % 360, 250, 128)); model.addTrace(t); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("DELete", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } model.removeTrace(t); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("RENAME", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } if(params.size() != 2) { return SCPI::getResultName(SCPI::Result::Error); } t->setName(params[1]); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("PAUSE", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } t->pause(); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("RESUME", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } t->resume(); return SCPI::getResultName(SCPI::Result::Empty); }, nullptr)); add(new SCPICommand("PAUSED", nullptr, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return t->isPaused() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); auto deembed = new SCPINode("DEEMBedding"); deembed->add(new SCPICommand("ACTive", [=](QStringList params) -> QString { Trace* t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } bool activate = false; if(!SCPI::paramToBool(params, 1, activate)) { return SCPI::getResultName(SCPI::Result::Error); } if(activate) { if(!t->deembeddingAvailable()) { return SCPI::getResultName(SCPI::Result::Error); } t->setDeembeddingActive(true); } else { t->setDeembeddingActive(false); } return SCPI::getResultName(SCPI::Result::Empty); }, [=](QStringList params) -> QString { Trace* t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return t->isDeembeddingActive() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); deembed->add(new SCPICommand("AVAILable", nullptr, [=](QStringList params) -> QString { Trace* t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return t->deembeddingAvailable() ? SCPI::getResultName(SCPI::Result::True) : SCPI::getResultName(SCPI::Result::False); })); add(deembed); add(new SCPICommand("PARAMeter", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t || params.size() < 2) { return SCPI::getResultName(SCPI::Result::Error); } auto newparam = params[1]; if((Trace::isVNAParameter(t->liveParameter()) && Trace::isVNAParameter(newparam)) || (Trace::isSAParameter(t->liveParameter()) && Trace::isSAParameter(newparam))) { t->fromLivedata(t->liveType(), newparam); return SCPI::getResultName(SCPI::Result::Empty); } else { return SCPI::getResultName(SCPI::Result::Error); } }, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return t->liveParameter(); })); add(new SCPICommand("TYPE", [=](QStringList params) -> QString { auto t = findTrace(params); if(!t || params.size() < 2) { return SCPI::getResultName(SCPI::Result::Error); } auto newtype = Trace::TypeFromString(params[1]); if(newtype != Trace::LivedataType::Invalid) { t->fromLivedata(newtype, t->liveParameter()); return SCPI::getResultName(SCPI::Result::Empty); } else { return SCPI::getResultName(SCPI::Result::Error); } }, [=](QStringList params) -> QString { auto t = findTrace(params); if(!t) { return SCPI::getResultName(SCPI::Result::Error); } return Trace::TypeToString(t->liveType()); })); } void TraceWidget::contextMenuEvent(QContextMenuEvent *event) { auto index = ui->view->indexAt(event->pos()); if(!index.isValid()) { return; } auto trace = model.trace(index.row()); auto ctxmenu = new QMenu(); auto action_delete = new QAction("Delete"); connect(action_delete, &QAction::triggered, this, &TraceWidget::on_remove_clicked); ctxmenu->addAction(action_delete); auto action_duplicate = new QAction("Duplicate"); connect(action_duplicate, &QAction::triggered, this, [=](){ auto json = trace->toJSON(); auto duplicate = new Trace(); duplicate->fromJSON(json); duplicate->setName(duplicate->name() + " - Duplicate"); // force update of hash duplicate->toHash(true); model.addTrace(duplicate); // resolve math sources if(!duplicate->resolveMathSourceHashes()) { qWarning() << "Failed to resolve all math source hashes for"<addAction(action_duplicate); ctxmenu->exec(event->globalPos()); }