From 74e6a439af916d529efeaac1accdcd1f3879a601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 5 Aug 2022 18:29:31 +0200 Subject: [PATCH] Compound device edit dialog --- .../PC_Application/Device/compounddevice.cpp | 77 ++ .../PC_Application/Device/compounddevice.h | 43 ++ .../Device/compounddeviceeditdialog.cpp | 548 +++++++++++++++ .../Device/compounddeviceeditdialog.h | 74 ++ .../Device/compounddeviceeditdialog.ui | 183 +++++ .../PC_Application/Device/virtualdevice.cpp | 6 + .../PC_Application/Device/virtualdevice.h | 4 + Software/PC_Application/LibreVNA-GUI.pro | 6 + .../PC_Application/Util/qpointervariant.h | 2 +- Software/PC_Application/icons.qrc | 9 + Software/PC_Application/icons/DUT2.png | Bin 0 -> 275 bytes Software/PC_Application/icons/LibreVNAV1.svg | 640 +++++++++++++++++ .../icons/compound_V1_Ref_Left.png | Bin 0 -> 6185 bytes .../icons/compound_V1_Ref_Left.svg | 663 +++++++++++++++++ .../icons/compound_V1_Ref_Middle.png | Bin 0 -> 6266 bytes .../icons/compound_V1_Ref_Middle.svg | 664 ++++++++++++++++++ .../icons/compound_V1_Ref_Right.png | Bin 0 -> 6284 bytes .../icons/compound_V1_Ref_Right.svg | 653 +++++++++++++++++ .../PC_Application/icons/compound_V1_USB.png | Bin 0 -> 6101 bytes .../PC_Application/icons/compound_V1_USB.svg | 651 +++++++++++++++++ Software/PC_Application/main.cpp | 1 + Software/PC_Application/preferences.cpp | 83 +++ Software/PC_Application/preferences.h | 17 +- Software/PC_Application/preferencesdialog.ui | 65 +- Software/PC_Application/savable.cpp | 54 ++ Software/PC_Application/savable.h | 3 + .../Application/Communication/Protocol.cpp | 1 + 27 files changed, 4439 insertions(+), 8 deletions(-) create mode 100644 Software/PC_Application/Device/compounddevice.cpp create mode 100644 Software/PC_Application/Device/compounddevice.h create mode 100644 Software/PC_Application/Device/compounddeviceeditdialog.cpp create mode 100644 Software/PC_Application/Device/compounddeviceeditdialog.h create mode 100644 Software/PC_Application/Device/compounddeviceeditdialog.ui create mode 100644 Software/PC_Application/icons/DUT2.png create mode 100644 Software/PC_Application/icons/LibreVNAV1.svg create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Left.png create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Left.svg create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Middle.png create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Middle.svg create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Right.png create mode 100644 Software/PC_Application/icons/compound_V1_Ref_Right.svg create mode 100644 Software/PC_Application/icons/compound_V1_USB.png create mode 100644 Software/PC_Application/icons/compound_V1_USB.svg create mode 100644 Software/PC_Application/savable.cpp 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 0000000000000000000000000000000000000000..2050daa9143e26b85b7effb40799c5461c6af659 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^g&@obBp6Py7S;kOmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VW5yxS$b1ju7A z@$_|Nf5yQoD5spTG!JM1gS4lMV~EA+UO_QmvAUQh^kMk%5tsu7QQFfoX`Lsg<#Tm9eR|fsvJgft|;0FBA>A b`6-!cmAEzdi!y%$YGCkm^>bP0l+XkK!lOnl literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9ea9ae982d272751a990de7fb47ebc6a632dcdff GIT binary patch literal 6185 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 Drg=^c literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6245937e9a039f3a07454353a70796103a17dc16 GIT binary patch literal 6266 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..da126fe3945d6a179deb3ed3e2fb758153d6b666 GIT binary patch literal 6284 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..3c477a066de78b833bc2d3a8fd4e23981f4ca142 GIT binary patch literal 6101 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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: