LibreVNA/Software/PC_Application/Device/compounddeviceeditdialog.cpp
2022-08-05 18:29:31 +02:00

549 lines
18 KiB
C++

#include "compounddeviceeditdialog.h"
#include "ui_compounddeviceeditdialog.h"
#include "device.h"
#include <QPushButton>
#include <QDrag>
#include <QMimeData>
#include <QMouseEvent>
#include <QStandardItemModel>
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<QStandardItemModel *>(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;i<ldev.deviceSerials.size();i++) {
ldev.deviceSerials[i] = deviceFrames[i]->getSerial();
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;i<ldev.deviceSerials.size();i++) {
for(int j=i+1;j<ldev.deviceSerials.size();j++) {
if(ldev.deviceSerials[i] == ldev.deviceSerials[j]) {
ui->status->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;i<deviceFrames.size();i++) {
if(deviceFrames[i]->getPort1() == 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;port<highestPort;port++) {
CompoundDevice::PortMapping map;
bool found = false;
for(int i=0;i<deviceFrames.size();i++) {
if(deviceFrames[i]->getPort1() == 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;i<ldev.deviceSerials.size();i++) {
auto frame = new DeviceFrame(&ldev, i);
connect(frame, &DeviceFrame::settingChanged, this, &CompoundDeviceEditDialog::checkIfOkay);
addFrame(i, frame);
}
layout->addStretch(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<QHBoxLayout*>(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<QHBoxLayout*>(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<QMouseEvent*>(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<QMouseEvent*>(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<QDragEnterEvent*>(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<QDragMoveEvent*>(event);
updateInsertIndicator(dragEvent->pos().x());
return true;
} else if(event->type() == QEvent::Drop) {
auto dragEvent = static_cast<QDropEvent*>(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<QMouseEvent*>(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<QMouseEvent*>(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;i<ldev.portMapping.size();i++) {
if(ldev.portMapping[i].device == pos) {
mappingFound = true;
ldev.portMapping.erase(ldev.portMapping.begin() + i);
break;
}
}
} while(mappingFound);
updateDeviceFrames();
}
void CompoundDeviceEditDialog::updateDeviceFrames()
{
int i=0;
for(auto df : deviceFrames) {
df->setPosition(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;i<dev->portMapping.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;i<dev->deviceSerials.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;
}