diff --git a/Software/PC_Application/Device/compounddevice.cpp b/Software/PC_Application/Device/compounddevice.cpp new file mode 100644 index 0000000..260400d --- /dev/null +++ b/Software/PC_Application/Device/compounddevice.cpp @@ -0,0 +1,77 @@ +#include "compounddevice.h" + +CompoundDevice::CompoundDevice() +{ + name = ""; + sync = Synchronization::USB; +} + +nlohmann::json CompoundDevice::toJSON() +{ + nlohmann::json j; + j["name"] = name.toStdString(); + j["synchronization"] = SyncToString(sync).toStdString(); + nlohmann::json jserials; + for(auto d : deviceSerials) { + jserials.push_back(d.toStdString()); + } + j["devices"] = jserials; + nlohmann::json jmappings; + for(auto m : portMapping) { + nlohmann::json jmapping; + jmapping["device"] = m.device; + jmapping["port"] = m.port; + jmappings.push_back(jmapping); + } + j["mapping"] = jmappings; + + return j; +} + +void CompoundDevice::fromJSON(nlohmann::json j) +{ + name = QString::fromStdString(j.value("name", "CompoundDevice")); + sync = SyncFromString(QString::fromStdString(j.value("synchronization", "USB"))); + deviceSerials.clear(); + if(j.contains("devices")) { + for(auto js : j["devices"]) { + deviceSerials.push_back(QString::fromStdString(js)); + } + } + portMapping.clear(); + if(j.contains("mapping")) { + for(auto jm : j["mapping"]) { + PortMapping mapping; + mapping.device = jm.value("device", 0); + mapping.port = jm.value("port", 0); + portMapping.push_back(mapping); + } + } +} + +QString CompoundDevice::SyncToString(CompoundDevice::Synchronization sync) +{ + switch(sync) { + case Synchronization::USB: return "USB"; + case Synchronization::ExtRef: return "Ext. Ref."; + case Synchronization::Trigger: return "Trigger"; + default: + case Synchronization::Last: return "Invalid"; + } +} + +CompoundDevice::Synchronization CompoundDevice::SyncFromString(QString s) +{ + for(int i=0;i<(int) Synchronization::Last;i++) { + if(SyncToString((Synchronization)i) == s) { + return (Synchronization) i; + } + } + // default to USB + return Synchronization::USB; +} + +QString CompoundDevice::getDesription() +{ + return name + ", "+QString::number(deviceSerials.size())+" devices, "+QString::number(portMapping.size())+" ports in total"; +} diff --git a/Software/PC_Application/Device/compounddevice.h b/Software/PC_Application/Device/compounddevice.h new file mode 100644 index 0000000..3f86ed7 --- /dev/null +++ b/Software/PC_Application/Device/compounddevice.h @@ -0,0 +1,43 @@ +#ifndef COMPOUNDDEVICE_H +#define COMPOUNDDEVICE_H + +#include "savable.h" + +#include + +#include + +class CompoundDevice : public Savable +{ + friend class CompoundDeviceEditDialog; +public: + CompoundDevice(); + + virtual nlohmann::json toJSON(); + virtual void fromJSON(nlohmann::json j); + + class PortMapping { + public: + unsigned int device; + unsigned int port; + }; + + enum class Synchronization { + USB, + ExtRef, + Trigger, + Last + }; + + static QString SyncToString(Synchronization sync); + static Synchronization SyncFromString(QString s); + + QString getDesription(); + + QString name; + Synchronization sync; + std::vector deviceSerials; + std::vector portMapping; +}; + +#endif // COMPOUNDDEVICE_H diff --git a/Software/PC_Application/Device/compounddeviceeditdialog.cpp b/Software/PC_Application/Device/compounddeviceeditdialog.cpp new file mode 100644 index 0000000..e927734 --- /dev/null +++ b/Software/PC_Application/Device/compounddeviceeditdialog.cpp @@ -0,0 +1,548 @@ +#include "compounddeviceeditdialog.h" +#include "ui_compounddeviceeditdialog.h" + +#include "device.h" + +#include +#include +#include +#include +#include + +using namespace std; + +CompoundDeviceEditDialog::CompoundDeviceEditDialog(CompoundDevice *cdev, QWidget *parent) : + QDialog(parent), + ui(new Ui::CompoundDeviceEditDialog) +{ + ldev = *cdev; + ui->setupUi(this); + + connect(ui->name, &QLineEdit::editingFinished, [=](){ + ldev.name = ui->name->text(); + checkIfOkay(); + }); + for(int i=0;i<(int)CompoundDevice::Synchronization::Last;i++) { + ui->sync->addItem(CompoundDevice::SyncToString((CompoundDevice::Synchronization) i)); + if((CompoundDevice::Synchronization) i == CompoundDevice::Synchronization::Trigger) { + // Disable for now + auto *model = qobject_cast(ui->sync->model()); + Q_ASSERT(model != nullptr); + bool disabled = true; + auto *item = model->item(i); + item->setFlags(disabled ? item->flags() & ~Qt::ItemIsEnabled + : item->flags() | Qt::ItemIsEnabled); + } + } + connect(ui->sync, &QComboBox::currentTextChanged, [=](){ + ldev.sync = CompoundDevice::SyncFromString(ui->sync->currentText()); + updateDeviceFrames(); + }); + + connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, [=](){ + *cdev = ldev; + accept(); + }); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &QDialog::reject); + + connect(ui->buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=](){ + ldev.saveToFileDialog("Save compound device", "Compound device file (*.cdev)", ".cdev"); + }); + connect(ui->buttonBox->button(QDialogButtonBox::Open), &QPushButton::clicked, [=](){ + if(ldev.openFromFileDialog("Load compound device", "Compound device file (*.cdev)")) { + setInitialGUI(); + } + }); + + ui->status->setStyleSheet("QLabel { color : red; }"); + + graph = new QWidget(); + ui->scrollArea->setWidget(graph); + auto layout = new QHBoxLayout(); + graph->setLayout(layout); + graph->setAcceptDrops(true); + graph->setObjectName("Graph"); + graph->installEventFilter(this); + ui->lLibreVNA1->installEventFilter(this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(0); + + setInitialGUI(); +} + +CompoundDeviceEditDialog::~CompoundDeviceEditDialog() +{ + delete ui; +} + +void CompoundDeviceEditDialog::checkIfOkay() +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); + + if(deviceFrames.size() != ldev.deviceSerials.size()) { + // probably still populating the GUI, skip check + return; + } + + if(ldev.name.isEmpty()) { + ui->status->setText("Name must not be empty"); + return; + } + if(ldev.deviceSerials.size() < 2) { + ui->status->setText("At least two devices are required"); + return; + } + if(ldev.deviceSerials.size() > 4) { + ui->status->setText("Only up to four devices supported"); + return; + } + // Check serials + for(int i=0;igetSerial(); + if(ldev.deviceSerials[i].isEmpty()) { + ui->status->setText("Device "+QString::number(i+1)+" has no serial number"); + return; + } + } + // Check serials for duplicates + for(int i=0;istatus->setText("Duplicate serial number ("+ldev.deviceSerials[i]+") in devices "+QString::number(i+1)+" and "+QString::number(j+1)); + return; + } + } + } + // Check port mapping + // Looking for duplicate and missing ports + bool highestPortFound = false; + int highestPort; + for(int port=0;port<2*ldev.deviceSerials.size();port++) { + int num = 0; + for(int i=0;igetPort1() == port) { + num++; + } + if(deviceFrames[i]->getPort2() == port) { + num++; + } + } + if(num > 1) { + ui->status->setText("Duplicate port "+QString::number(port+1)); + return; + } else if(num == 0) { + if(port == 0) { + ui->status->setText("Port 1 must be present"); + return; + } + if(!highestPortFound) { + highestPort = port; + } + highestPortFound = true; + } else if(highestPortFound) { + // port is present, but earlier port was missing + ui->status->setText("Missing port: "+QString::number(port+1)+" is selected, but port "+QString::number(port)+" is not present."); + return; + } + } + if(!highestPortFound) { + highestPort = 2*ldev.deviceSerials.size(); + } + + // All good, actually create the port mapping + ldev.portMapping.clear(); + for(int port=0;portgetPort1() == port) { + found = true; + map.device = i; + map.port = 0; + break; + } + if(deviceFrames[i]->getPort2() == port) { + found = true; + map.device = i; + map.port = 1; + break; + } + } + if(!found) { + ui->status->setText("Failed to find port "+QString::number(port+1)+" (likely a bug)"); + return; + } + ldev.portMapping.push_back(map); + } + + ui->status->clear(); + + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); +} + +void CompoundDeviceEditDialog::setInitialGUI() +{ + ui->name->setText(ldev.name); + ui->sync->setCurrentText(CompoundDevice::SyncToString(ldev.sync)); + + // Removing the old frames actually modifies the state of ldev as well. Block signals to prevent this from happening + for(auto f : deviceFrames) { + f->blockSignals(true); + } + + QHBoxLayout* layout = (QHBoxLayout*) graph->layout(); + // remove all widgets from the layout + if (layout != NULL ) { + QLayoutItem* item; + while ((item = layout->takeAt(0)) != NULL ) { + delete item->widget(); + delete item; + } + } + deviceFrames.clear(); + layout->addStretch(1); + for(int i=0;iaddStretch(1); + + checkIfOkay(); +} + +DeviceFrame *CompoundDeviceEditDialog::frameAtPosition(int pos) +{ + pos -= graph->layout()->itemAt(0)->geometry().width(); + if(pos > 0 && pos <= (int) deviceFrames.size() * frameSize) { + return deviceFrames[pos / frameSize]; + } + return nullptr; +} + +unsigned int CompoundDeviceEditDialog::findInsertPosition(int xcoord) +{ + xcoord -= graph->layout()->itemAt(0)->geometry().width(); + // added in port 1 network + int index = (xcoord + frameSize / 2) / frameSize; + if(index < 0) { + index = 0; + } else if(index > (int) deviceFrames.size()) { + index = deviceFrames.size(); + } + // add 1 (first widget is always the stretch) + return index + 1; +} + +void CompoundDeviceEditDialog::addFrameAtPosition(int pos, DeviceFrame *c) +{ + auto index = findInsertPosition(pos); + + // add component to the deviceFrame vector + index -= 1; // first widget is fixed + if(index <= deviceFrames.size()) { + addFrame(index, c); + } +} + +void CompoundDeviceEditDialog::addFrame(int index, DeviceFrame *c) +{ + if(deviceFrames.size() == ldev.deviceSerials.size()) { + ldev.deviceSerials.insert(ldev.deviceSerials.begin() + index, ""); + } + deviceFrames.insert(deviceFrames.begin() + index, c); + deviceFrames[index]->setPosition(index); + // remove from list when the component deletes itself + connect(c, &DeviceFrame::deleted, [=](){ + removeDeviceFrame(c); + }); + QHBoxLayout *l = static_cast(graph->layout()); + l->insertWidget(index + 1, c); + c->show(); + updateDeviceFrames(); +} + +void CompoundDeviceEditDialog::createDragFrame(DeviceFrame *c) +{ + QDrag *drag = new QDrag(this); + QMimeData *mimeData = new QMimeData; + + QByteArray encodedPointer; + QDataStream stream(&encodedPointer, QIODevice::WriteOnly); + stream << quintptr(c); + + mimeData->setData("compoundDeviceFrame/pointer", encodedPointer); + drag->setMimeData(mimeData); + + drag->exec(Qt::MoveAction); +} + +void CompoundDeviceEditDialog::updateInsertIndicator(int xcoord) +{ + auto index = findInsertPosition(xcoord); + QHBoxLayout *l = static_cast(graph->layout()); + l->removeWidget(insertIndicator); + l->insertWidget(index, insertIndicator); +} + +bool CompoundDeviceEditDialog::eventFilter(QObject *object, QEvent *event) +{ + if(object->objectName() == "Graph") { + if(event->type() == QEvent::MouseButtonPress) { + auto mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + dragFrame = frameAtPosition(mouseEvent->pos().x()); + if(dragFrame) { + dragStartPosition = mouseEvent->pos(); + return true; + } + } + return false; + } else if(event->type() == QEvent::MouseMove) { + auto mouseEvent = static_cast(event); + if (!(mouseEvent->buttons() & Qt::LeftButton)) { + return false; + } + if (!dragFrame) { + return false; + } + if ((mouseEvent->pos() - dragStartPosition).manhattanLength() + < QApplication::startDragDistance()) { + return false; + } + + // remove and hide component while it is being dragged + graph->layout()->removeWidget(dragFrame); + dragFrame->hide(); + removeDeviceFrame(dragFrame); + graph->update(); + + createDragFrame(dragFrame); + return true; + } else if(event->type() == QEvent::DragEnter) { + auto dragEvent = static_cast(event); + if(dragEvent->mimeData()->hasFormat("compoundDeviceFrame/pointer")) { + dropPending = true; + auto data = dragEvent->mimeData()->data("compoundDeviceFrame/pointer"); + QDataStream stream(&data, QIODevice::ReadOnly); + quintptr dropPtr; + stream >> dropPtr; + dropFrame = (DeviceFrame*) dropPtr; + dragEvent->acceptProposedAction(); + insertIndicator = new QWidget(); + insertIndicator->setMinimumSize(2, frameSize); + insertIndicator->setMaximumSize(2, frameSize); + insertIndicator->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + insertIndicator->setStyleSheet("background-color:red;"); + updateInsertIndicator(dragEvent->pos().x()); + return true; + } + } else if(event->type() == QEvent::DragMove) { + auto dragEvent = static_cast(event); + updateInsertIndicator(dragEvent->pos().x()); + return true; + } else if(event->type() == QEvent::Drop) { + auto dragEvent = static_cast(event); + delete insertIndicator; + addFrameAtPosition(dragEvent->pos().x(), dropFrame); + return true; + } else if(event->type() == QEvent::DragLeave) { + dropPending = false; + dropFrame = nullptr; + delete insertIndicator; + } + } else { + // clicked/dragged one of the components outside of the graph + if(event->type() == QEvent::MouseButtonPress) { + auto mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::LeftButton) { + dragStartPosition = mouseEvent->pos(); + if(object->objectName() == "lLibreVNA1") { + dragFrame = new DeviceFrame(&ldev, 0); + connect(dragFrame, &DeviceFrame::settingChanged, this, &CompoundDeviceEditDialog::checkIfOkay); + } else { + dragFrame = nullptr; + } + return true; + } + return false; + } else if(event->type() == QEvent::MouseMove) { + auto mouseEvent = static_cast(event); + if (!(mouseEvent->buttons() & Qt::LeftButton)) { + return false; + } + if (!dragFrame) { + return false; + } + if ((mouseEvent->pos() - dragStartPosition).manhattanLength() + < QApplication::startDragDistance()) { + return false; + } + + createDragFrame(dragFrame); + return true; + } + } + return false; +} + +void CompoundDeviceEditDialog::removeDeviceFrame(DeviceFrame *dev) +{ + auto it = std::find(deviceFrames.begin(), deviceFrames.end(), dev); + if(it == deviceFrames.end()) { + // not found, shouldn't happen + return; + } + auto pos = it - deviceFrames.begin(); + deviceFrames.erase(it); + ldev.deviceSerials.erase(ldev.deviceSerials.begin() + pos); + // remove all port mappings from the removed device + bool mappingFound; + do { + mappingFound = false; + for(int i=0;isetPosition(i++); + } +} + +DeviceFrame::DeviceFrame(CompoundDevice *dev, int position) : + dev(dev), + position(position) +{ + setMinimumSize(frameSize, frameSize); + setMaximumSize(frameSize, frameSize); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFocusPolicy(Qt::FocusPolicy::ClickFocus); + + serial = new QComboBox(); + serial->setEditable(true); + port1 = new QComboBox(); + port2 = new QComboBox(); + + connect(serial, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged); + connect(port1, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged); + connect(port2, &QComboBox::currentTextChanged, this, &DeviceFrame::settingChanged); + + auto layout = new QVBoxLayout; + layout->addStretch(1); + auto l1 = new QHBoxLayout; + l1->addStretch(1); + l1->addWidget(serial); + l1->addStretch(1); + layout->addLayout(l1); + layout->addStretch(1); + auto l2 = new QHBoxLayout; + l2->addWidget(port1); + l2->addStretch(1); + l2->addWidget(port2); + layout->addLayout(l2); + setLayout(layout); + + update(); + + // Set initial state + if(position < dev->deviceSerials.size()) { + serial->setCurrentText(dev->deviceSerials[position]); + for(int i=0;iportMapping.size();i++) { + if(dev->portMapping[i].device == position) { + if(dev->portMapping[i].port == 0) { + port1->setCurrentIndex(i + 1); + } else if(dev->portMapping[i].port == 1) { + port2->setCurrentIndex(i + 1); + } + } + } + } +} + +DeviceFrame::~DeviceFrame() +{ + emit deleted(); +} + +void DeviceFrame::setPosition(int pos) +{ + position = pos; + update(); +} + +void DeviceFrame::update() +{ + auto p1 = port1->currentText(); + auto p2 = port2->currentText(); + auto s = serial->currentText(); + port1->clear(); + port2->clear(); + serial->clear(); + + port1->addItem("Unused"); + port2->addItem("Unused"); + for(int i=0;ideviceSerials.size();i++) { + port1->addItem("Port "+QString::number(i*2+1)); + port2->addItem("Port "+QString::number(i*2+1)); + port1->addItem("Port "+QString::number(i*2+2)); + port2->addItem("Port "+QString::number(i*2+2)); + } + if(port1->findText(p1) >= 0) { + port1->setCurrentText(p1); + } else { + port1->setCurrentIndex(0); + } + if(port2->findText(p2) >= 0) { + port2->setCurrentText(p2); + } else { + port2->setCurrentIndex(0); + } + + auto devices = Device::GetDevices(); + for(auto d : devices) { + serial->addItem(d); + } +// if(!serial->findText(s) >= 0) { +// serial->addItem(s); +// } + serial->setCurrentText(s); + + if(dev->sync == CompoundDevice::Synchronization::USB) { + setStyleSheet("image: url(:/icons/compound_V1_USB.png);"); + } else if(dev->sync == CompoundDevice::Synchronization::ExtRef) { + if(position == 0) { + setStyleSheet("image: url(:/icons/compound_V1_Ref_Left.png);"); + } else if(position == dev->deviceSerials.size() - 1) { + setStyleSheet("image: url(:/icons/compound_V1_Ref_Right.png);"); + } else { + setStyleSheet("image: url(:/icons/compound_V1_Ref_Middle.png);"); + } + } +} + +QString DeviceFrame::getSerial() +{ + return serial->currentText(); +} + +int DeviceFrame::getPort1() +{ + return port1->currentIndex() - 1; +} + +int DeviceFrame::getPort2() +{ + return port2->currentIndex() - 1; +} diff --git a/Software/PC_Application/Device/compounddeviceeditdialog.h b/Software/PC_Application/Device/compounddeviceeditdialog.h new file mode 100644 index 0000000..f9d492b --- /dev/null +++ b/Software/PC_Application/Device/compounddeviceeditdialog.h @@ -0,0 +1,74 @@ +#ifndef COMPOUNDDEVICEEDITDIALOG_H +#define COMPOUNDDEVICEEDITDIALOG_H + +#include "compounddevice.h" + +#include +#include +#include + +namespace Ui { +class CompoundDeviceEditDialog; +} + +class DeviceFrame : public QFrame { + Q_OBJECT +public: + DeviceFrame(CompoundDevice *dev, int position); + ~DeviceFrame(); + void setPosition(int pos); + void update(); + QString getSerial(); + int getPort1(); + int getPort2(); +signals: + void deleted(); + void settingChanged(); +private: + static constexpr int frameSize = 350; + QComboBox *serial; + QComboBox *port1, *port2; + QLabel *image; + + int position; + CompoundDevice *dev; +}; + +class CompoundDeviceEditDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CompoundDeviceEditDialog(CompoundDevice *cdev, QWidget *parent = nullptr); + ~CompoundDeviceEditDialog(); + +private slots: + void checkIfOkay(); +private: + static constexpr int frameSize = 350; + void setInitialGUI(); + DeviceFrame *frameAtPosition(int pos); + unsigned int findInsertPosition(int xcoord); + void addFrameAtPosition(int pos, DeviceFrame *c); + void addFrame(int index, DeviceFrame *c); + void createDragFrame(DeviceFrame *c); + void updateInsertIndicator(int xcoord); + bool eventFilter(QObject *object, QEvent *event) override; + + void removeDeviceFrame(DeviceFrame *dev); + + void updateDeviceFrames(); + + Ui::CompoundDeviceEditDialog *ui; + CompoundDevice ldev; + + QWidget *graph, *insertIndicator; + QPoint dragStartPosition; + bool dropPending; + DeviceFrame *dragFrame; + DeviceFrame *dropFrame; + + std::vector deviceFrames; +}; + +#endif // COMPOUNDDEVICEEDITDIALOG_H diff --git a/Software/PC_Application/Device/compounddeviceeditdialog.ui b/Software/PC_Application/Device/compounddeviceeditdialog.ui new file mode 100644 index 0000000..99bd756 --- /dev/null +++ b/Software/PC_Application/Device/compounddeviceeditdialog.ui @@ -0,0 +1,183 @@ + + + CompoundDeviceEditDialog + + + + 0 + 0 + 1097 + 598 + + + + Compound Device Edit + + + true + + + + + + + + + + Name: + + + + + + + + + + Synchronization: + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Standalone Devices + + + + + + + 8 + + + + Drag/drop into compound device + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + + 80 + 80 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 9 + + + + border-image: url(:/icons/LibreVNAV1.svg); + + + QFrame::NoFrame + + + QFrame::Plain + + + LibreVNA V1 + + + Qt::AlignCenter + + + + + + + + + + + + + + + true + + + + + 0 + 0 + 1077 + 372 + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save + + + + + + + + diff --git a/Software/PC_Application/Device/virtualdevice.cpp b/Software/PC_Application/Device/virtualdevice.cpp index 1f439c1..acbae2b 100644 --- a/Software/PC_Application/Device/virtualdevice.cpp +++ b/Software/PC_Application/Device/virtualdevice.cpp @@ -136,6 +136,7 @@ VirtualDevice::VirtualDevice(QString serial) info{}, status{} { + cdev = nullptr; isCompound = false; zerospan = false; auto dev = new Device(serial); @@ -243,6 +244,11 @@ Device *VirtualDevice::getDevice() } } +CompoundDevice *VirtualDevice::getCompoundDevice() +{ + return cdev; +} + std::vector VirtualDevice::getDevices() { return devices; diff --git a/Software/PC_Application/Device/virtualdevice.h b/Software/PC_Application/Device/virtualdevice.h index 3b06a62..c8c3513 100644 --- a/Software/PC_Application/Device/virtualdevice.h +++ b/Software/PC_Application/Device/virtualdevice.h @@ -3,6 +3,7 @@ #include "device.h" #include "Tools/parameters.h" +#include "compounddevice.h" #include #include @@ -52,6 +53,7 @@ public: bool isCompoundDevice() const; Device *getDevice(); + CompoundDevice *getCompoundDevice(); std::vector getDevices(); const Info& getInfo() const; static const VirtualDevice::Info &getInfo(VirtualDevice *vdev); @@ -179,6 +181,8 @@ private: bool zerospan; std::map results; + + CompoundDevice *cdev; }; Q_DECLARE_METATYPE(VirtualDevice::Status) diff --git a/Software/PC_Application/LibreVNA-GUI.pro b/Software/PC_Application/LibreVNA-GUI.pro index 5113fc1..b155d39 100644 --- a/Software/PC_Application/LibreVNA-GUI.pro +++ b/Software/PC_Application/LibreVNA-GUI.pro @@ -18,6 +18,8 @@ HEADERS += \ CustomWidgets/tilewidget.h \ CustomWidgets/toggleswitch.h \ CustomWidgets/touchstoneimport.h \ + Device/compounddevice.h \ + Device/compounddeviceeditdialog.h \ Device/device.h \ Device/devicelog.h \ Device/firmwareupdatedialog.h \ @@ -153,6 +155,8 @@ SOURCES += \ CustomWidgets/tilewidget.cpp \ CustomWidgets/toggleswitch.cpp \ CustomWidgets/touchstoneimport.cpp \ + Device/compounddevice.cpp \ + Device/compounddeviceeditdialog.cpp \ Device/device.cpp \ Device/devicelog.cpp \ Device/firmwareupdatedialog.cpp \ @@ -249,6 +253,7 @@ SOURCES += \ mode.cpp \ modewindow.cpp \ preferences.cpp \ + savable.cpp \ scpi.cpp \ tcpserver.cpp \ touchstone.cpp \ @@ -274,6 +279,7 @@ FORMS += \ CustomWidgets/jsonpickerdialog.ui \ CustomWidgets/tilewidget.ui \ CustomWidgets/touchstoneimport.ui \ + Device/compounddeviceeditdialog.ui \ Device/devicelog.ui \ Device/firmwareupdatedialog.ui \ Device/manualcontroldialog.ui \ diff --git a/Software/PC_Application/Util/qpointervariant.h b/Software/PC_Application/Util/qpointervariant.h index 224f7b4..7f77e2f 100644 --- a/Software/PC_Application/Util/qpointervariant.h +++ b/Software/PC_Application/Util/qpointervariant.h @@ -7,7 +7,7 @@ class QPointerVariant { public: template QPointerVariant(T *ptr) : ptr(static_cast(ptr)), - variant(QVariant(*ptr)){}; + variant(QVariant(*ptr)){} void setValue(const QVariant &value) { auto destType = variant.type(); if(!value.canConvert(destType)) { diff --git a/Software/PC_Application/icons.qrc b/Software/PC_Application/icons.qrc index 148fb55..eb88c3a 100644 --- a/Software/PC_Application/icons.qrc +++ b/Software/PC_Application/icons.qrc @@ -58,5 +58,14 @@ icons/parallelL.png icons/parallelC.png icons/definedThrough.png + icons/LibreVNAV1.svg + icons/compound_V1_Ref_Left.svg + icons/compound_V1_Ref_Middle.svg + icons/compound_V1_Ref_Right.svg + icons/compound_V1_USB.svg + icons/compound_V1_Ref_Left.png + icons/compound_V1_Ref_Middle.png + icons/compound_V1_Ref_Right.png + icons/compound_V1_USB.png diff --git a/Software/PC_Application/icons/DUT2.png b/Software/PC_Application/icons/DUT2.png new file mode 100644 index 0000000..2050daa Binary files /dev/null and b/Software/PC_Application/icons/DUT2.png differ diff --git a/Software/PC_Application/icons/LibreVNAV1.svg b/Software/PC_Application/icons/LibreVNAV1.svg new file mode 100644 index 0000000..f493be6 --- /dev/null +++ b/Software/PC_Application/icons/LibreVNAV1.svg @@ -0,0 +1,640 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/compound_V1_Ref_Left.png b/Software/PC_Application/icons/compound_V1_Ref_Left.png new file mode 100644 index 0000000..9ea9ae9 Binary files /dev/null and b/Software/PC_Application/icons/compound_V1_Ref_Left.png differ diff --git a/Software/PC_Application/icons/compound_V1_Ref_Left.svg b/Software/PC_Application/icons/compound_V1_Ref_Left.svg new file mode 100644 index 0000000..088e1e8 --- /dev/null +++ b/Software/PC_Application/icons/compound_V1_Ref_Left.svg @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/compound_V1_Ref_Middle.png b/Software/PC_Application/icons/compound_V1_Ref_Middle.png new file mode 100644 index 0000000..6245937 Binary files /dev/null and b/Software/PC_Application/icons/compound_V1_Ref_Middle.png differ diff --git a/Software/PC_Application/icons/compound_V1_Ref_Middle.svg b/Software/PC_Application/icons/compound_V1_Ref_Middle.svg new file mode 100644 index 0000000..6fa4f3a --- /dev/null +++ b/Software/PC_Application/icons/compound_V1_Ref_Middle.svg @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/compound_V1_Ref_Right.png b/Software/PC_Application/icons/compound_V1_Ref_Right.png new file mode 100644 index 0000000..da126fe Binary files /dev/null and b/Software/PC_Application/icons/compound_V1_Ref_Right.png differ diff --git a/Software/PC_Application/icons/compound_V1_Ref_Right.svg b/Software/PC_Application/icons/compound_V1_Ref_Right.svg new file mode 100644 index 0000000..db6b96f --- /dev/null +++ b/Software/PC_Application/icons/compound_V1_Ref_Right.svg @@ -0,0 +1,653 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/icons/compound_V1_USB.png b/Software/PC_Application/icons/compound_V1_USB.png new file mode 100644 index 0000000..3c477a0 Binary files /dev/null and b/Software/PC_Application/icons/compound_V1_USB.png differ diff --git a/Software/PC_Application/icons/compound_V1_USB.svg b/Software/PC_Application/icons/compound_V1_USB.svg new file mode 100644 index 0000000..edc1248 --- /dev/null +++ b/Software/PC_Application/icons/compound_V1_USB.svg @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Software/PC_Application/main.cpp b/Software/PC_Application/main.cpp index 5789468..ced0fea 100644 --- a/Software/PC_Application/main.cpp +++ b/Software/PC_Application/main.cpp @@ -31,6 +31,7 @@ int main(int argc, char *argv[]) { QCoreApplication::setApplicationVersion(window->getAppVersion() + "-" + window->getAppGitHash().left(9)); + #ifdef Q_OS_UNIX signal(SIGINT, tryExitGracefully); #endif diff --git a/Software/PC_Application/preferences.cpp b/Software/PC_Application/preferences.cpp index 54e89c7..b33ea87 100644 --- a/Software/PC_Application/preferences.cpp +++ b/Software/PC_Application/preferences.cpp @@ -3,6 +3,7 @@ #include "ui_preferencesdialog.h" #include "CustomWidgets/informationbox.h" #include "appwindow.h" +#include "Device/compounddeviceeditdialog.h" #include #include @@ -124,6 +125,52 @@ PreferencesDialog::PreferencesDialog(Preferences *pref, QWidget *parent) : ui->MarkerShowAllMarkerData->setEnabled(enabled); }); + // Compound device page + connect(ui->compoundList, &QListWidget::currentRowChanged, [=](){ + if(VirtualDevice::getConnected() && VirtualDevice::getConnected()->getCompoundDevice() == p->compoundDevices[ui->compoundList->currentRow()]) { + // can't remove the device we are connected to + ui->compoundDelete->setEnabled(false); + } else { + ui->compoundDelete->setEnabled(true); + } + }); + connect(ui->compoundList, &QListWidget::doubleClicked, [=](){ + auto index = ui->compoundList->currentRow(); + if(index >= 0 && index < p->compoundDevices.size()) { + auto d = new CompoundDeviceEditDialog(p->compoundDevices[index]); + d->show(); + } + }); + connect(ui->compoundAdd, &QPushButton::clicked, [=](){ + auto cd = new CompoundDevice; + auto d = new CompoundDeviceEditDialog(cd); + connect(d, &QDialog::accepted, [=](){ + p->compoundDevices.push_back(cd); + ui->compoundList->addItem(cd->getDesription()); + p->nonTrivialWriting(); + }); + connect(d, &QDialog::rejected, [=](){ + delete cd; + }); + d->show(); + }); + connect(ui->compoundDelete, &QPushButton::clicked, [=](){ + auto index = ui->compoundList->currentRow(); + if(index >= 0 && index < p->compoundDevices.size()) { + // delete the actual compound device + if(VirtualDevice::getConnected() && VirtualDevice::getConnected()->getCompoundDevice() == p->compoundDevices[index]) { + // can't remove the device we are currently connected to + return; + } + delete p->compoundDevices[index]; + // delete the line in the GUI list + delete ui->compoundList->takeItem(index); + // remove compound device from list + p->compoundDevices.erase(p->compoundDevices.begin() + index); + p->nonTrivialWriting(); + } + }); + // Page selection connect(ui->treeWidget, &QTreeWidget::currentItemChanged, [=](QTreeWidgetItem *current, QTreeWidgetItem *) { auto name = current->text(0); @@ -263,6 +310,10 @@ void PreferencesDialog::setInitialGUIState() ui->SCPIServerEnabled->setChecked(p->SCPIServer.enabled); ui->SCPIServerPort->setValue(p->SCPIServer.port); + for(auto cd : p->compoundDevices) { + ui->compoundList->addItem(cd->getDesription()); + } + QTreeWidgetItem *item = ui->treeWidget->topLevelItem(0); if (item != nullptr) { ui->treeWidget->setCurrentItem(item); // visually select first item @@ -344,10 +395,12 @@ void Preferences::load() qDebug() << "Setting" << d.name << "reset to default:" << d.def; } } + nonTrivialParsing(); } void Preferences::store() { + nonTrivialWriting(); QSettings settings; // store settings for(auto d : descr) { @@ -373,9 +426,39 @@ void Preferences::setDefault() void Preferences::fromJSON(nlohmann::json j) { parseJSON(j, descr); + nonTrivialParsing(); } nlohmann::json Preferences::toJSON() { + nonTrivialWriting(); return createJSON(descr); } + +void Preferences::nonTrivialParsing() +{ + try { + compoundDevices.clear(); + nlohmann::json jc = nlohmann::json::parse(compoundDeviceJSON.toStdString()); + for(auto j : jc) { + auto cd = new CompoundDevice(); + cd->fromJSON(j); + compoundDevices.push_back(cd); + } + } catch(const exception& e){ + qDebug() << "Failed to parse compound device string: " << e.what(); + } +} + +void Preferences::nonTrivialWriting() +{ + if(compoundDevices.size() > 0) { + nlohmann::json j; + for(auto cd : compoundDevices) { + j.push_back(cd->toJSON()); + } + compoundDeviceJSON = QString::fromStdString(j.dump()); + } else { + compoundDeviceJSON = "[]"; + } +} diff --git a/Software/PC_Application/preferences.h b/Software/PC_Application/preferences.h index 440c182..b859bd2 100644 --- a/Software/PC_Application/preferences.h +++ b/Software/PC_Application/preferences.h @@ -4,6 +4,8 @@ #include "Util/qpointervariant.h" #include "savable.h" +#include "Device/compounddevice.h" + #include #include #include @@ -14,7 +16,7 @@ enum GraphDomainChangeBehavior { AdjustGrahpsIfOnlyTrace = 2, }; -Q_DECLARE_METATYPE(GraphDomainChangeBehavior); +Q_DECLARE_METATYPE(GraphDomainChangeBehavior) enum GraphLimitIndication { DontShowAnything = 0, @@ -22,7 +24,7 @@ enum GraphLimitIndication { Overlay = 2, }; -Q_DECLARE_METATYPE(GraphLimitIndication); +Q_DECLARE_METATYPE(GraphLimitIndication) enum MarkerSortOrder { PrefMarkerSortXCoord = 0, @@ -30,7 +32,7 @@ enum MarkerSortOrder { PrefMarkerSortTimestamp = 2, }; -Q_DECLARE_METATYPE(MarkerSortOrder); +Q_DECLARE_METATYPE(MarkerSortOrder) class Preferences : public Savable { @@ -132,12 +134,18 @@ public: bool TCPoverride; // in case of manual port specification via command line + QString compoundDeviceJSON; + std::vector compoundDevices; + void fromJSON(nlohmann::json j) override; nlohmann::json toJSON() override; + void nonTrivialParsing(); + void nonTrivialWriting(); + private: Preferences() : - TCPoverride(false) {}; + TCPoverride(false) {} static Preferences instance; const std::vector descr = {{ @@ -196,6 +204,7 @@ private: {&Marker.sortOrder, "Marker.sortOrder", MarkerSortOrder::PrefMarkerSortXCoord}, {&SCPIServer.enabled, "SCPIServer.enabled", true}, {&SCPIServer.port, "SCPIServer.port", 19542}, + {&compoundDeviceJSON, "compoundDeviceJSON", "[]"}, }}; }; diff --git a/Software/PC_Application/preferencesdialog.ui b/Software/PC_Application/preferencesdialog.ui index eab56a9..f8bf760 100644 --- a/Software/PC_Application/preferencesdialog.ui +++ b/Software/PC_Application/preferencesdialog.ui @@ -66,6 +66,11 @@ SCPI Server + + + Compound Devices + + @@ -79,7 +84,7 @@ 0 0 687 - 938 + 884 @@ -98,7 +103,7 @@ - 0 + 5 @@ -1371,6 +1376,58 @@ + + + + + + + + + + + + + + + + + :/icons/add.png + :/icons/add.png:/icons/add.png + + + + + + + + + + + :/icons/remove.png + :/icons/remove.png:/icons/remove.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + @@ -1406,7 +1463,9 @@
CustomWidgets/siunitedit.h
- + + + diff --git a/Software/PC_Application/savable.cpp b/Software/PC_Application/savable.cpp new file mode 100644 index 0000000..6d06a55 --- /dev/null +++ b/Software/PC_Application/savable.cpp @@ -0,0 +1,54 @@ +#include "savable.h" +#include "CustomWidgets/informationbox.h" + +#include +#include +#include + +using namespace std; + +bool Savable::openFromFileDialog(QString title, QString filetype) +{ + auto filename = QFileDialog::getOpenFileName(nullptr, title, "", filetype, nullptr, QFileDialog::DontUseNativeDialog); + if(filename.isEmpty()) { + // aborted selection + return false; + } + ifstream file; + file.open(filename.toStdString()); + if(!file.is_open()) { + qWarning() << "Unable to open file:" << filename; + return false; + } + nlohmann::json j; + try { + file >> j; + } catch (exception &e) { + InformationBox::ShowError("Error", "Failed to parse the setup file (" + QString(e.what()) + ")"); + qWarning() << "Parsing of setup file failed: " << e.what(); + file.close(); + return false; + } + file.close(); + fromJSON(j); + return true; +} + +bool Savable::saveToFileDialog(QString title, QString filetype, QString ending) +{ + auto filename = QFileDialog::getSaveFileName(nullptr, title, "", filetype, nullptr, QFileDialog::DontUseNativeDialog); + if(filename.isEmpty()) { + // aborted selection + return false; + } + if(!ending.isEmpty()) { + if(!filename.endsWith(ending)) { + filename.append(ending); + } + } + ofstream file; + file.open(filename.toStdString()); + file << setw(4) << toJSON() << endl; + file.close(); + return true; +} diff --git a/Software/PC_Application/savable.h b/Software/PC_Application/savable.h index ebb146e..e359761 100644 --- a/Software/PC_Application/savable.h +++ b/Software/PC_Application/savable.h @@ -16,6 +16,9 @@ public: virtual nlohmann::json toJSON() = 0; virtual void fromJSON(nlohmann::json j) = 0; + bool openFromFileDialog(QString title, QString filetype); + bool saveToFileDialog(QString title, QString filetype, QString ending = ""); + using SettingDescription = struct { QPointerVariant var; QString name; diff --git a/Software/VNA_embedded/Application/Communication/Protocol.cpp b/Software/VNA_embedded/Application/Communication/Protocol.cpp index 99b0654..b79c5be 100644 --- a/Software/VNA_embedded/Application/Communication/Protocol.cpp +++ b/Software/VNA_embedded/Application/Communication/Protocol.cpp @@ -112,6 +112,7 @@ uint16_t Protocol::EncodePacket(const PacketInfo &packet, uint8_t *dest, uint16_ case PacketType::SetIdle: case PacketType::RequestFrequencyCorrection: case PacketType::RequestAcquisitionFrequencySettings: + case PacketType::RequestDeviceStatus: // no payload break; case PacketType::None: