File indexing completed on 2024-04-28 15:09:12

0001 /*
0002     SPDX-FileCopyrightText: 2021 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "portselector.h"
0008 
0009 #include "indipropertyswitch.h"
0010 #include "ksnotification.h"
0011 #include "ekos_debug.h"
0012 #include "kspaths.h"
0013 #include "indi/driverinfo.h"
0014 #include "indi/clientmanager.h"
0015 #include "Options.h"
0016 
0017 #include <QDialogButtonBox>
0018 #include <QPushButton>
0019 
0020 namespace  Selector
0021 {
0022 
0023 const QStringList Device::BAUD_RATES = { "9600", "19200", "38400", "57600", "115200", "230400" };
0024 const QString Device::ACTIVE_STYLESHEET = "background-color: #004400;";
0025 //const QString Device::ACTIVE_STYLESHEET = "border-color: #007700; font-weight:bold;";
0026 //////////////////////////////////////////////////////////////////////////////////////////
0027 ///
0028 //////////////////////////////////////////////////////////////////////////////////////////
0029 Device::Device(const QSharedPointer<ISD::GenericDevice> &device, QGridLayout *grid,
0030                uint8_t row) :  m_Name(device->getDeviceName()), m_Device(device), m_Grid(grid), m_Row(row)
0031 {
0032     ColorCode[IPS_IDLE] = Qt::gray;
0033     ColorCode[IPS_OK] = Qt::green;
0034     ColorCode[IPS_BUSY] = Qt::yellow;
0035     ColorCode[IPS_ALERT] = Qt::red;
0036 
0037     connect(m_Device.get(), &ISD::GenericDevice::propertyUpdated, this, &Device::updateProperty);
0038 
0039     if (initGUI())
0040         syncGUI();
0041 }
0042 
0043 //////////////////////////////////////////////////////////////////////////////////////////
0044 ///
0045 //////////////////////////////////////////////////////////////////////////////////////////
0046 bool Device::initGUI()
0047 {
0048     INDI::Property modeProperty = m_Device->getBaseDevice().getSwitch("CONNECTION_MODE");
0049     if (!modeProperty.isValid())
0050         return false;
0051 
0052     m_LED = new KLed;
0053     m_LED->setLook(KLed::Sunken);
0054     m_LED->setState(KLed::On);
0055     m_LED->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
0056     m_Grid->addWidget(m_LED, m_Row, 0, Qt::AlignVCenter);
0057 
0058     m_Label = new QLabel;
0059     m_Label->setText(m_Name);
0060     m_Label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
0061     m_Grid->addWidget(m_Label, m_Row, 1, Qt::AlignLeft);
0062 
0063     // Serial Button
0064     m_SerialB = new QPushButton(i18n("Serial"));
0065     m_SerialB->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
0066     m_SerialB->setToolTip(i18n("Select <b>Serial</b> if your device is connected via Serial to USB adapter."));
0067     m_Grid->addWidget(m_SerialB, m_Row, 2);
0068     connect(m_SerialB, &QPushButton::clicked, this, [this]()
0069     {
0070         INDI::PropertyView<ISwitch> connectionMode = *(m_Device->getBaseDevice().getSwitch("CONNECTION_MODE"));
0071         IUResetSwitch(&connectionMode);
0072         connectionMode.at(0)->setState(ISS_ON);
0073         connectionMode.at(1)->setState(ISS_OFF);
0074         m_Device->sendNewProperty(&connectionMode);
0075     });
0076 
0077     // Network Button
0078     m_NetworkB = new QPushButton(i18n("Network"));
0079     m_NetworkB->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Ignored);
0080     m_NetworkB->setToolTip(i18n("Select <b>Network</b> if your device is connected via Ethernet or WiFi."));
0081     m_Grid->addWidget(m_NetworkB, m_Row, 3);
0082     connect(m_NetworkB, &QPushButton::clicked, this, [this]()
0083     {
0084         INDI::PropertyView<ISwitch> connectionMode = *(m_Device->getBaseDevice().getSwitch("CONNECTION_MODE"));
0085         IUResetSwitch(&connectionMode);
0086         connectionMode.at(0)->setState(ISS_OFF);
0087         connectionMode.at(1)->setState(ISS_ON);
0088         m_Device->sendNewProperty(&connectionMode);
0089     });
0090 
0091     INDI::PropertyView<ISwitch> connectionMode = *(modeProperty.getSwitch());
0092     if (connectionMode.findWidgetByName("CONNECTION_SERIAL"))
0093     {
0094         if (m_Device->getBaseDevice().getProperty("SYSTEM_PORTS").isValid())
0095         {
0096             m_Ports = new QComboBox;
0097             m_Ports->setEditable(true);
0098             m_Ports->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
0099             m_Ports->setToolTip(i18n("Select Serial port"));
0100 
0101             INDI::PropertyView<ISwitch> systemPorts = *(m_Device->getBaseDevice().getSwitch("SYSTEM_PORTS"));
0102             for (auto &oneSwitch : systemPorts)
0103                 m_Ports->addItem(oneSwitch.getLabel());
0104             connect(m_Ports, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this,
0105                     [this](int index)
0106             {
0107                 // Double check property still exists.
0108                 INDI::Property systemPorts = m_Device->getBaseDevice().getProperty("SYSTEM_PORTS");
0109                 if (systemPorts.isValid())
0110                 {
0111                     INDI::PropertyView<ISwitch> systemPortsSwitch = *(systemPorts.getSwitch());
0112                     IUResetSwitch(&systemPortsSwitch);
0113                     systemPortsSwitch.at(index)->setState(ISS_ON);
0114                     m_Device->sendNewProperty(&systemPortsSwitch);
0115                 }
0116             });
0117             // When combo box text changes
0118             connect(m_Ports->lineEdit(), &QLineEdit::editingFinished, this, [this]()
0119             {
0120                 INDI::PropertyView<IText> port = *(m_Device->getBaseDevice().getText("DEVICE_PORT"));
0121                 port.at(0)->setText(m_Ports->lineEdit()->text().toLatin1().constData());
0122                 m_Device->sendNewProperty(&port);
0123             });
0124         }
0125 
0126         if (m_Device->getBaseDevice()->getProperty("DEVICE_BAUD_RATE").isValid())
0127         {
0128             m_BaudRates = new QComboBox;
0129             m_BaudRates->addItems(BAUD_RATES);
0130             m_BaudRates->setToolTip(i18n("Select Baud rate"));
0131             connect(m_BaudRates, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), this,
0132                     [this](int index)
0133             {
0134                 INDI::PropertyView<ISwitch> systemBauds = *(m_Device->getBaseDevice().getSwitch("DEVICE_BAUD_RATE"));
0135                 IUResetSwitch(&systemBauds);
0136                 systemBauds.at(index)->setState(ISS_ON);
0137                 m_Device->sendNewProperty(&systemBauds);
0138             });
0139         }
0140     }
0141     else
0142         m_SerialB->setEnabled(false);
0143 
0144     if (connectionMode.findWidgetByName("CONNECTION_TCP"))
0145     {
0146         m_HostName = new QLineEdit;
0147         m_HostName->setPlaceholderText(i18n("Host name or IP address."));
0148         m_HostName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
0149         connect(m_HostName, &QLineEdit::editingFinished, this, [this]()
0150         {
0151             INDI::PropertyView<IText> server = *(m_Device->getBaseDevice().getText("DEVICE_ADDRESS"));
0152             server.at(0)->setText(m_HostName->text().toLatin1().constData());
0153             m_Device->sendNewProperty(&server);
0154         });
0155 
0156         m_HostPort = new QLineEdit;
0157         m_HostPort->setPlaceholderText(i18n("Port"));
0158         connect(m_HostPort, &QLineEdit::editingFinished, this, [this]()
0159         {
0160             auto server = m_Device->getProperty("DEVICE_ADDRESS").getText();
0161             server->at(1)->setText(m_HostPort->text().toLatin1().constData());
0162             m_Device->sendNewProperty(server);
0163         });
0164 
0165         m_HostProtocolTCP = new QPushButton("TCP");
0166         connect(m_HostProtocolTCP, &QPushButton::clicked, this, [this]()
0167         {
0168             auto connectionType = m_Device->getProperty("CONNECTION_TYPE").getSwitch();
0169             connectionType->reset();
0170             connectionType->at(0)->setState(ISS_ON);
0171             m_Device->sendNewProperty(connectionType);
0172         });
0173 
0174         m_HostProtocolUDP = new QPushButton("UDP");
0175         connect(m_HostProtocolUDP, &QPushButton::clicked, this, [this]()
0176         {
0177             auto connectionType = m_Device->getProperty("CONNECTION_TYPE").getSwitch();
0178             connectionType->reset();
0179             connectionType->at(1)->setState(ISS_ON);
0180             m_Device->sendNewProperty(connectionType);
0181         });
0182     }
0183     else
0184         m_NetworkB->setEnabled(false);
0185 
0186     m_ConnectB = new QPushButton;
0187     m_ConnectB->setFixedSize(QSize(28, 28));
0188     m_ConnectB->setToolTip(i18n("Connect"));
0189     m_ConnectB->setIcon(QIcon::fromTheme("network-connect"));
0190     m_Grid->addWidget(m_ConnectB, m_Row, 8);
0191     connect(m_ConnectB, &QPushButton::clicked, m_Device.get(), &ISD::GenericDevice::Connect);
0192 
0193     m_DisconnectB = new QPushButton;
0194     m_DisconnectB->setFixedSize(QSize(28, 28));
0195     m_DisconnectB->setToolTip(i18n("Disconnect"));
0196     m_DisconnectB->setIcon(QIcon::fromTheme("network-disconnect"));
0197     m_Grid->addWidget(m_DisconnectB, m_Row, 9);
0198     connect(m_DisconnectB, &QPushButton::clicked, m_Device.get(), &ISD::GenericDevice::Disconnect);
0199 
0200     setConnectionMode(static_cast<ConnectionMode>(connectionMode.findOnSwitchIndex()));
0201 
0202     return true;
0203 }
0204 
0205 //////////////////////////////////////////////////////////////////////////////////////////
0206 ///
0207 //////////////////////////////////////////////////////////////////////////////////////////
0208 void Device::syncGUI()
0209 {
0210     // Check if initGUI failed before?
0211     if (m_LED == nullptr)
0212     {
0213         // Try to initiGUI again
0214         initGUI();
0215 
0216         // If it failed again, return.
0217         if (m_LED == nullptr)
0218             return;
0219     }
0220 
0221     m_LED->setColor(ColorCode.value(m_Device->getState("CONNECTION")));
0222 
0223     auto connectionMode = m_Device->getProperty("CONNECTION_MODE").getSwitch();
0224 
0225     if (connectionMode->findOnSwitchIndex() == CONNECTION_SERIAL)
0226     {
0227         m_ActiveConnectionMode = CONNECTION_SERIAL;
0228         m_SerialB->setStyleSheet(ACTIVE_STYLESHEET);
0229         m_NetworkB->setStyleSheet(QString());
0230 
0231         if (m_Ports)
0232         {
0233             auto port = m_Device->getProperty("DEVICE_PORT").getText();
0234             m_Ports->setEditText(port->at(0)->getText());
0235         }
0236 
0237         if (m_BaudRates)
0238         {
0239             auto systemBauds = m_Device->getProperty("DEVICE_BAUD_RATE").getSwitch();
0240             m_BaudRates->setCurrentIndex(systemBauds->findOnSwitchIndex());
0241         }
0242     }
0243     else
0244     {
0245         m_ActiveConnectionMode = CONNECTION_NETWORK;
0246         m_SerialB->setStyleSheet(QString());
0247         m_NetworkB->setStyleSheet(ACTIVE_STYLESHEET);
0248 
0249         if (m_Device->getProperty("DEVICE_ADDRESS").isValid())
0250         {
0251             auto server = m_Device->getProperty("DEVICE_ADDRESS").getText();
0252             m_HostName->setText(server->at(0)->getText());
0253             m_HostPort->setText(server->at(1)->getText());
0254         }
0255 
0256         if (m_Device->getProperty("CONNECTION_TYPE").isValid())
0257         {
0258             auto connectionType = m_Device->getBaseDevice().getSwitch("CONNECTION_TYPE").getSwitch();
0259             m_HostProtocolTCP->setStyleSheet(connectionType->findOnSwitchIndex() == 0 ? ACTIVE_STYLESHEET : QString());
0260             m_HostProtocolUDP->setStyleSheet(connectionType->findOnSwitchIndex() == 1 ? ACTIVE_STYLESHEET : QString());
0261         }
0262     }
0263 }
0264 
0265 //////////////////////////////////////////////////////////////////////////////////////////
0266 ///
0267 //////////////////////////////////////////////////////////////////////////////////////////
0268 uint8_t Device::systemPortCount() const
0269 {
0270     if (m_Ports)
0271         return m_Ports->count();
0272     else
0273         return 0;
0274 }
0275 
0276 //////////////////////////////////////////////////////////////////////////////////////////
0277 ///
0278 //////////////////////////////////////////////////////////////////////////////////////////
0279 void Device::setConnectionMode(ConnectionMode mode)
0280 {
0281     m_ActiveConnectionMode = mode;
0282 
0283     if (mode == CONNECTION_SERIAL)
0284     {
0285         if (m_HostName)
0286         {
0287             m_Grid->removeWidget(m_HostName);
0288             m_HostName->setParent(nullptr);
0289             m_Grid->removeWidget(m_HostPort);
0290             m_HostPort->setParent(nullptr);
0291             m_Grid->removeWidget(m_HostProtocolTCP);
0292             m_HostProtocolTCP->setParent(nullptr);
0293             m_Grid->removeWidget(m_HostProtocolUDP);
0294             m_HostProtocolUDP->setParent(nullptr);
0295         }
0296 
0297         m_Grid->addWidget(m_Ports, m_Row, 4, 1, 2);
0298         m_Grid->addWidget(m_BaudRates, m_Row, 6, 1, 2);
0299         m_Grid->setColumnStretch(4, 2);
0300     }
0301     else if (mode == CONNECTION_NETWORK)
0302     {
0303         if (m_Ports)
0304         {
0305             m_Grid->removeWidget(m_Ports);
0306             m_Ports->setParent(nullptr);
0307             m_Grid->removeWidget(m_BaudRates);
0308             m_BaudRates->setParent(nullptr);
0309         }
0310 
0311         m_Grid->addWidget(m_HostName, m_Row, 4);
0312         m_Grid->addWidget(m_HostPort, m_Row, 5);
0313         m_Grid->addWidget(m_HostProtocolTCP, m_Row, 6);
0314         m_Grid->addWidget(m_HostProtocolUDP, m_Row, 7);
0315         m_Grid->setColumnStretch(4, 2);
0316     }
0317 }
0318 
0319 //////////////////////////////////////////////////////////////////////////////////////////
0320 ///
0321 //////////////////////////////////////////////////////////////////////////////////////////
0322 void Device::updateProperty(INDI::Property prop)
0323 {
0324     if (prop.isNameMatch("CONNECTION_MODE"))
0325     {
0326         auto svp = prop.getSwitch();
0327         setConnectionMode(static_cast<ConnectionMode>(svp->findOnSwitchIndex()));
0328         syncGUI();
0329     }
0330     else if (prop.isNameMatch("CONNECTION_TYPE") || prop.isNameMatch("SYSTEM_PORTS") ||
0331              prop.isNameMatch("DEVICE_BAUD_RATE") || prop.isNameMatch("DEVICE_PORT"))
0332         syncGUI();
0333     else if (prop.isNameMatch("CONNECTION"))
0334     {
0335         if (m_Device->isConnected())
0336         {
0337             m_ConnectB->setStyleSheet(ACTIVE_STYLESHEET);
0338             m_DisconnectB->setStyleSheet(QString());
0339         }
0340         else
0341         {
0342             m_ConnectB->setStyleSheet(QString());
0343             m_DisconnectB->setStyleSheet(ACTIVE_STYLESHEET);
0344         }
0345     }
0346 }
0347 
0348 //////////////////////////////////////////////////////////////////////////////////////////
0349 ///
0350 //////////////////////////////////////////////////////////////////////////////////////////
0351 void Device::setConnected()
0352 {
0353     syncGUI();
0354 }
0355 
0356 //////////////////////////////////////////////////////////////////////////////////////////
0357 ///
0358 //////////////////////////////////////////////////////////////////////////////////////////
0359 void Device::setDisconnected()
0360 {
0361     syncGUI();
0362 }
0363 
0364 //////////////////////////////////////////////////////////////////////////////////////////
0365 ///
0366 //////////////////////////////////////////////////////////////////////////////////////////
0367 Dialog::Dialog(QWidget *parent) : QDialog(parent)
0368 {
0369     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0370     QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
0371 
0372     QPushButton *connectAll = new QPushButton(i18n("Connect All"), this);
0373     buttonBox->addButton(connectAll, QDialogButtonBox::AcceptRole);
0374     buttonBox->addButton(QDialogButtonBox::Close);
0375 
0376     connect(buttonBox, &QDialogButtonBox::accepted, this, &Dialog::accept);
0377     connect(buttonBox, &QDialogButtonBox::rejected, this, &Dialog::reject);
0378 
0379     m_Layout = new QGridLayout;
0380 
0381     mainLayout->addLayout(m_Layout);
0382     mainLayout->addStretch();
0383     mainLayout->addWidget(buttonBox);
0384 
0385     setWindowTitle(i18nc("@title:window", "Port Selector"));
0386 #ifdef Q_OS_OSX
0387     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0388 #endif
0389 }
0390 
0391 //////////////////////////////////////////////////////////////////////////////////////////
0392 ///
0393 //////////////////////////////////////////////////////////////////////////////////////////
0394 void Dialog::addDevice(const QSharedPointer<ISD::GenericDevice> &device)
0395 {
0396     auto pos = std::find_if(m_Devices.begin(), m_Devices.end(), [device](std::unique_ptr<Device> &oneDevice)
0397     {
0398         return oneDevice->name() == device->getDeviceName();
0399     });
0400 
0401     // New
0402     if (pos == m_Devices.end())
0403     {
0404         std::unique_ptr<Device> dev(new Device(device, m_Layout, m_Devices.size()));
0405         m_Devices.push_back(std::move(dev));
0406     }
0407     // Existing device
0408     else
0409     {
0410         (*pos)->syncGUI();
0411     }
0412 
0413 }
0414 
0415 //////////////////////////////////////////////////////////////////////////////////////////
0416 ///
0417 //////////////////////////////////////////////////////////////////////////////////////////
0418 void Dialog::removeDevice(const QString &name)
0419 {
0420     m_Devices.erase(std::remove_if(m_Devices.begin(), m_Devices.end(), [name](const auto & oneDevice)
0421     {
0422         return oneDevice->name() == name;
0423     }), m_Devices.end());
0424 }
0425 
0426 //////////////////////////////////////////////////////////////////////////////////////////
0427 /// Should Port Selector be shown automatically?
0428 /// If we only have ONE device with ONE port, then no need to bother the user
0429 /// with useless selection.
0430 //////////////////////////////////////////////////////////////////////////////////////////
0431 bool Dialog::shouldShow() const
0432 {
0433     if (m_Devices.empty())
0434         return false;
0435 
0436     uint32_t systemPortCount = 0;
0437     uint32_t serialDevices = 0;
0438     uint32_t networkDevices = 0;
0439 
0440     for (const auto &oneDevice : m_Devices)
0441     {
0442         if (oneDevice->systemPortCount() > 0)
0443             systemPortCount = oneDevice->systemPortCount();
0444 
0445         if (oneDevice->activeConnectionMode() == CONNECTION_SERIAL)
0446             serialDevices++;
0447         else if (oneDevice->activeConnectionMode() == CONNECTION_NETWORK)
0448             networkDevices++;
0449     }
0450 
0451     // If we just have a single serial device with single port, no need to toggle port selector.
0452     // If have have one or more network devices, we must show the dialog for the first time.
0453     if (networkDevices == 0 && serialDevices <= 1 && systemPortCount <= 1)
0454         return false;
0455 
0456     // Otherwise, show dialog
0457     return true;
0458 }
0459 }