#include "tracewidget.h" #include "ui_tracewidget.h" #include "traceeditdialog.h" #include "traceimportdialog.h" #include "tracetouchstoneexport.h" #include "trace.h" #include "unit.h" #include "Util/util.h" #include "appwindow.h" #include #include #include #include #include #include TraceWidget::TraceWidget(TraceModel &model, QWidget *parent) : QWidget(parent), SCPINode("TRACe"), dragTrace(nullptr), ui(new Ui::TraceWidget), model(model) { 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); installEventFilter(this); 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(); 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::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::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;igetDataType() != 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("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); }); ctxmenu->addAction(action_duplicate); ctxmenu->exec(event->globalPos()); }