File indexing completed on 2024-07-21 06:28:23

0001 /*
0002     SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "indiproperty.h"
0008 
0009 #include "clientmanager.h"
0010 #include "indidevice.h"
0011 #include "indielement.h"
0012 #include "indigroup.h"
0013 #include "kstars.h"
0014 #include "Options.h"
0015 #include "skymap.h"
0016 #include "dialogs/timedialog.h"
0017 
0018 #include <indicom.h>
0019 #include <indiproperty.h>
0020 
0021 #include <KLed>
0022 #include <KSqueezedTextLabel>
0023 
0024 #include <QAbstractButton>
0025 #include <QButtonGroup>
0026 #include <QCheckBox>
0027 #include <QComboBox>
0028 #include <QHBoxLayout>
0029 #include <QPushButton>
0030 #include <QVBoxLayout>
0031 
0032 extern const char *libindi_strings_context;
0033 
0034 /*******************************************************************
0035 ** INDI Property: contains widgets, labels, and their status
0036 *******************************************************************/
0037 INDI_P::INDI_P(INDI_G *ipg, INDI::Property prop) : QWidget(ipg), pg(ipg), dataProp(prop)
0038 {
0039     name = QString(prop.getName());
0040 
0041     PHBox = new QHBoxLayout(this);
0042     PHBox->setObjectName("Property Horizontal Layout");
0043     PHBox->setContentsMargins(0, 0, 0, 0);
0044     PVBox = new QVBoxLayout;
0045     PVBox->setContentsMargins(0, 0, 0, 0);
0046     PVBox->setObjectName("Property Vertical Layout");
0047 
0048     initGUI();
0049 }
0050 
0051 void INDI_P::updateStateLED()
0052 {
0053     /* set state light */
0054     switch (dataProp.getState())
0055     {
0056         case IPS_IDLE:
0057             ledStatus->setColor(Qt::gray);
0058             break;
0059 
0060         case IPS_OK:
0061             ledStatus->setColor(Qt::green);
0062             break;
0063 
0064         case IPS_BUSY:
0065             ledStatus->setColor(Qt::yellow);
0066             break;
0067 
0068         case IPS_ALERT:
0069             ledStatus->setColor(Qt::red);
0070             break;
0071     }
0072 }
0073 
0074 /* build widgets for property pp using info in root.
0075  */
0076 void INDI_P::initGUI()
0077 {
0078     QString label = i18nc(libindi_strings_context, dataProp.getLabel());
0079 
0080     if (label == "(I18N_EMPTY_MESSAGE)")
0081         label = dataProp.getLabel();
0082 
0083     /* add to GUI group */
0084     ledStatus = new KLed(this);
0085     ledStatus->setMaximumSize(16, 16);
0086     ledStatus->setLook(KLed::Sunken);
0087 
0088     updateStateLED();
0089 
0090     /* Create a horizontally layout widget around light and label */
0091     QWidget *labelWidget = new QWidget(this);
0092     QHBoxLayout *labelLayout =  new QHBoxLayout(labelWidget);
0093     labelLayout->setContentsMargins(0, 0, 0, 0);
0094 
0095     /* #1 First widget is the LED status indicator */
0096     labelLayout->addWidget(ledStatus);
0097 
0098     if (label.isEmpty())
0099     {
0100         label = i18nc(libindi_strings_context, name.toUtf8());
0101         if (label == "(I18N_EMPTY_MESSAGE)")
0102             label = name.toUtf8();
0103 
0104         labelW = new KSqueezedTextLabel(label, this);
0105     }
0106     else
0107         labelW = new KSqueezedTextLabel(label, this);
0108 
0109     //labelW->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0110     labelW->setFrameShape(QFrame::Box);
0111     labelW->setFrameShadow(QFrame::Sunken);
0112     labelW->setMargin(2);
0113     labelW->setFixedWidth(PROPERTY_LABEL_WIDTH * KStars::Instance()->devicePixelRatio());
0114     labelW->setTextFormat(Qt::RichText);
0115     labelW->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
0116     labelW->setWordWrap(true);
0117 
0118     labelLayout->addWidget(labelW);
0119     PHBox->addWidget(labelWidget, 0, Qt::AlignTop | Qt::AlignLeft);
0120 
0121     labelWidget->setToolTip(label);
0122     ledStatus->show();
0123     labelW->show();
0124 
0125     // #3 Add the Vertical layout which may contain several elements
0126     PHBox->addLayout(PVBox);
0127 
0128     switch (dataProp.getType())
0129     {
0130         case INDI_SWITCH:
0131             if (dataProp.getSwitch()->getRule() == ISR_NOFMANY)
0132                 guiType = PG_RADIO;
0133             else if (dataProp.getSwitch()->count() > 4)
0134                 guiType = PG_MENU;
0135             else
0136                 guiType = PG_BUTTONS;
0137 
0138             if (guiType == PG_MENU)
0139                 buildMenuGUI();
0140             else
0141                 buildSwitchGUI();
0142             break;
0143 
0144         case INDI_TEXT:
0145             buildTextGUI();
0146             break;
0147 
0148         case INDI_NUMBER:
0149             buildNumberGUI();
0150             break;
0151 
0152         case INDI_LIGHT:
0153             buildLightGUI();
0154             break;
0155 
0156         case INDI_BLOB:
0157             buildBLOBGUI();
0158             break;
0159 
0160         default:
0161             break;
0162     }
0163     labelWidget->raise();
0164 }
0165 
0166 void INDI_P::buildSwitchGUI()
0167 {
0168     auto svp = static_cast<ISwitchVectorProperty*>(dataProp.getSwitch());
0169 
0170     if (!svp)
0171         return;
0172 
0173     groupB = new QButtonGroup(this);
0174 
0175     if (guiType == PG_BUTTONS)
0176     {
0177         if (svp->r == ISR_1OFMANY)
0178             groupB->setExclusive(true);
0179         else
0180             groupB->setExclusive(false);
0181     }
0182     else if (guiType == PG_RADIO)
0183         groupB->setExclusive(false);
0184 
0185     if (svp->p != IP_RO)
0186         QObject::connect(groupB, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(newSwitch(QAbstractButton*)));
0187 
0188     for (int i = 0; i < svp->nsp; i++)
0189     {
0190         auto lp = new INDI_E(this, dataProp);
0191         lp->buildSwitch(groupB, svp->sp + i);
0192         elementList.append(lp);
0193     }
0194 
0195     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0196 
0197     PHBox->addItem(horSpacer);
0198 }
0199 
0200 void INDI_P::buildTextGUI()
0201 {
0202     auto tvp = static_cast<ITextVectorProperty*>(dataProp.getText());
0203 
0204     if (!tvp)
0205         return;
0206 
0207     for (int i = 0; i < tvp->ntp; i++)
0208     {
0209         auto lp = new INDI_E(this, dataProp);
0210         lp->buildText(tvp->tp + i);
0211         elementList.append(lp);
0212     }
0213 
0214     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0215 
0216     PHBox->addItem(horSpacer);
0217 
0218     if (tvp->p == IP_RO)
0219         return;
0220 
0221     // INDI STD, but we use our own controls
0222     if (name == "TIME_UTC")
0223         setupSetButton(i18n("Time"));
0224     else
0225         setupSetButton(i18n("Set"));
0226 }
0227 
0228 void INDI_P::buildNumberGUI()
0229 {
0230     auto nvp = static_cast<INumberVectorProperty*>(dataProp.getNumber());
0231 
0232     if (!nvp)
0233         return;
0234 
0235     for (int i = 0; i < nvp->nnp; i++)
0236     {
0237         auto lp = new INDI_E(this, dataProp);
0238         lp->buildNumber(nvp->np + i);
0239         elementList.append(lp);
0240     }
0241 
0242     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0243 
0244     PHBox->addItem(horSpacer);
0245 
0246     if (nvp->p == IP_RO)
0247         return;
0248 
0249     setupSetButton(i18n("Set"));
0250 }
0251 
0252 void INDI_P::buildLightGUI()
0253 {
0254     auto lvp = static_cast<ILightVectorProperty*>(dataProp.getLight());
0255 
0256     if (!lvp)
0257         return;
0258 
0259     for (int i = 0; i < lvp->nlp; i++)
0260     {
0261         auto ep = new INDI_E(this, dataProp);
0262         ep->buildLight(lvp->lp + i);
0263         elementList.append(ep);
0264     }
0265 
0266     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0267 
0268     PHBox->addItem(horSpacer);
0269 }
0270 
0271 void INDI_P::buildBLOBGUI()
0272 {
0273     auto bvp = static_cast<IBLOBVectorProperty*>(dataProp.getBLOB());
0274 
0275     if (!bvp)
0276         return;
0277 
0278     for (int i = 0; i < bvp->nbp; i++)
0279     {
0280         auto lp = new INDI_E(this, dataProp);
0281         lp->buildBLOB(bvp->bp + i);
0282         elementList.append(lp);
0283     }
0284 
0285     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0286 
0287     PHBox->addItem(horSpacer);
0288 
0289     enableBLOBC = new QCheckBox();
0290     enableBLOBC->setIcon(QIcon::fromTheme("network-modem"));
0291     enableBLOBC->setChecked(true);
0292     enableBLOBC->setToolTip(i18n("Enable binary data transfer from this property to KStars and vice-versa."));
0293 
0294     PHBox->addWidget(enableBLOBC);
0295 
0296     connect(enableBLOBC, SIGNAL(stateChanged(int)), this, SLOT(setBLOBOption(int)));
0297 
0298     if (dataProp.getPermission() != IP_RO)
0299         setupSetButton(i18n("Upload"));
0300 }
0301 
0302 void INDI_P::setBLOBOption(int state)
0303 {
0304     pg->getDevice()->getClientManager()->setBLOBEnabled(state == Qt::Checked, dataProp.getDeviceName(), dataProp.getName());
0305 }
0306 
0307 void INDI_P::newSwitch(QAbstractButton *button)
0308 {
0309     auto svp = dataProp.getSwitch();
0310     QString buttonText = button->text();
0311 
0312     if (!svp)
0313         return;
0314 
0315     buttonText.remove('&');
0316 
0317     for (auto &el : elementList)
0318     {
0319         if (el->getLabel() == buttonText)
0320         {
0321             newSwitch(el->getName());
0322             return;
0323         }
0324     }
0325 }
0326 
0327 void INDI_P::resetSwitch()
0328 {
0329     auto svp = dataProp.getSwitch();
0330 
0331     if (!svp)
0332         return;
0333 
0334     if (menuC != nullptr)
0335     {
0336         menuC->setCurrentIndex(svp->findOnSwitchIndex());
0337     }
0338 }
0339 
0340 void INDI_P::newSwitch(int index)
0341 {
0342     auto svp = dataProp.getSwitch();
0343 
0344     if (!svp)
0345         return;
0346 
0347     if (index >= svp->count() || index < 0)
0348         return;
0349 
0350     auto sp = svp->at(index);
0351 
0352     svp->reset();
0353     sp->setState(ISS_ON);
0354 
0355     sendSwitch();
0356 }
0357 
0358 void INDI_P::newSwitch(const QString &name)
0359 {
0360     auto svp = dataProp.getSwitch();
0361 
0362     if (!svp)
0363         return;
0364 
0365     auto sp = svp->findWidgetByName(name.toLatin1().constData());
0366 
0367     if (!sp)
0368         return;
0369 
0370     if (svp->getRule() == ISR_1OFMANY)
0371     {
0372         svp->reset();
0373         sp->setState(ISS_ON);
0374     }
0375     else
0376     {
0377         if (svp->getRule() == ISR_ATMOST1)
0378         {
0379             ISState prev_state = sp->getState();
0380             svp->reset();
0381             sp->setState(prev_state);
0382         }
0383 
0384         sp->setState(sp->getState() == ISS_ON ? ISS_OFF : ISS_ON);
0385     }
0386 
0387     sendSwitch();
0388 }
0389 
0390 void INDI_P::sendSwitch()
0391 {
0392     auto svp = dataProp.getSwitch();
0393 
0394     if (!svp)
0395         return;
0396 
0397     svp->setState(IPS_BUSY);
0398 
0399     for (auto &el : elementList)
0400         el->syncSwitch();
0401 
0402     updateStateLED();
0403 
0404     // Send it to server
0405     pg->getDevice()->getClientManager()->sendNewProperty(svp);
0406 }
0407 
0408 void INDI_P::sendText()
0409 {
0410     switch (dataProp.getType())
0411     {
0412         case INDI_TEXT:
0413         {
0414             auto tvp = dataProp.getText();
0415             if (!tvp)
0416                 return;
0417 
0418             tvp->setState(IPS_BUSY);
0419 
0420             for (auto &el : elementList)
0421                 el->updateTP();
0422 
0423             pg->getDevice()->getClientManager()->sendNewProperty(tvp);
0424 
0425             break;
0426         }
0427 
0428         case INDI_NUMBER:
0429         {
0430             auto nvp = dataProp.getNumber();
0431             if (!nvp)
0432                 return;
0433 
0434             nvp->setState(IPS_BUSY);
0435 
0436             for (auto &el : elementList)
0437                 el->updateNP();
0438 
0439             pg->getDevice()->getClientManager()->sendNewProperty(nvp);
0440             break;
0441         }
0442         default:
0443             break;
0444     }
0445 
0446     updateStateLED();
0447 }
0448 
0449 void INDI_P::buildMenuGUI()
0450 {
0451     QStringList menuOptions;
0452     QString oneOption;
0453     int onItem = -1;
0454     auto svp = dataProp.getSwitch();
0455 
0456     if (!svp)
0457         return;
0458 
0459     menuC = new QComboBox(this);
0460 
0461     if (svp->getPermission() == IP_RO)
0462         connect(menuC, SIGNAL(activated(int)), this, SLOT(resetSwitch()));
0463     else
0464         connect(menuC, SIGNAL(activated(int)), this, SLOT(newSwitch(int)));
0465 
0466     for (int i = 0; i < svp->nsp; i++)
0467     {
0468         auto tp = svp->at(i);
0469 
0470         if (tp->getState() == ISS_ON)
0471             onItem = i;
0472 
0473         auto lp = new INDI_E(this, dataProp);
0474 
0475         lp->buildMenuItem(tp);
0476 
0477         oneOption = i18nc(libindi_strings_context, lp->getLabel().toUtf8());
0478 
0479         if (oneOption == "(I18N_EMPTY_MESSAGE)")
0480             oneOption = lp->getLabel().toUtf8();
0481 
0482         menuOptions.append(oneOption);
0483 
0484         elementList.append(lp);
0485     }
0486 
0487     menuC->addItems(menuOptions);
0488     menuC->setCurrentIndex(onItem);
0489 
0490     horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
0491 
0492     PHBox->addWidget(menuC);
0493     PHBox->addItem(horSpacer);
0494 }
0495 
0496 void INDI_P::setupSetButton(const QString &caption)
0497 {
0498     setB = new QPushButton(caption, this);
0499     setB->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0500     setB->setMinimumWidth(MIN_SET_WIDTH * KStars::Instance()->devicePixelRatio());
0501     setB->setMaximumWidth(MAX_SET_WIDTH * KStars::Instance()->devicePixelRatio());
0502 
0503     connect(setB, SIGNAL(clicked()), this, SLOT(processSetButton()));
0504 
0505     PHBox->addWidget(setB);
0506 }
0507 
0508 void INDI_P::addWidget(QWidget *w)
0509 {
0510     PHBox->addWidget(w);
0511 }
0512 
0513 void INDI_P::addLayout(QHBoxLayout *layout)
0514 {
0515     PVBox->addLayout(layout);
0516 }
0517 
0518 void INDI_P::updateMenuGUI()
0519 {
0520     auto svp = dataProp.getSwitch();
0521 
0522     if (!svp)
0523         return;
0524 
0525     int currentIndex = svp->findOnSwitchIndex();
0526     menuC->setCurrentIndex(currentIndex);
0527 }
0528 
0529 void INDI_P::processSetButton()
0530 {
0531     switch (dataProp.getType())
0532     {
0533         case INDI_TEXT:
0534             //if (!strcmp(dataProp.getName(), "TIME_UTC"))
0535             if (dataProp.isNameMatch("TIME_UTC"))
0536                 newTime();
0537             else
0538                 sendText();
0539 
0540             break;
0541 
0542         case INDI_NUMBER:
0543             sendText();
0544             break;
0545 
0546         case INDI_BLOB:
0547             sendBlob();
0548             break;
0549 
0550         default:
0551             break;
0552     }
0553 }
0554 
0555 void INDI_P::sendBlob()
0556 {
0557     auto bvp = dataProp.getBLOB();
0558 
0559     if (!bvp)
0560         return;
0561 
0562     bvp->setState(IPS_BUSY);
0563 
0564     pg->getDevice()->getClientManager()->startBlob(bvp->getDeviceName(), bvp->getName(),
0565             QDateTime::currentDateTimeUtc().toString(Qt::ISODate).remove("Z").toLatin1().constData());
0566 
0567     for (int i = 0; i < elementList.count(); i++)
0568     {
0569         auto bp = bvp->at(i);
0570         pg->getDevice()->getClientManager()->sendOneBlob(bp);
0571     }
0572 
0573     pg->getDevice()->getClientManager()->finishBlob();
0574 
0575     updateStateLED();
0576 }
0577 
0578 void INDI_P::newTime()
0579 {
0580     INDI_E *timeEle   = getElement("UTC");
0581     INDI_E *offsetEle = getElement("OFFSET");
0582     if (!timeEle || !offsetEle)
0583         return;
0584 
0585     TimeDialog timedialog(KStars::Instance()->data()->ut(), KStars::Instance()->data()->geo(), KStars::Instance(),
0586                           true);
0587 
0588     if (timedialog.exec() == QDialog::Accepted)
0589     {
0590         QTime newTime(timedialog.selectedTime());
0591         QDate newDate(timedialog.selectedDate());
0592 
0593         timeEle->setText(QString("%1-%2-%3T%4:%5:%6")
0594                          .arg(newDate.year())
0595                          .arg(newDate.month())
0596                          .arg(newDate.day())
0597                          .arg(newTime.hour())
0598                          .arg(newTime.minute())
0599                          .arg(newTime.second()));
0600 
0601         offsetEle->setText(QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2));
0602 
0603         sendText();
0604     }
0605     else
0606         return;
0607 }
0608 
0609 INDI_E *INDI_P::getElement(const QString &elementName) const
0610 {
0611     for (auto *ep : elementList)
0612     {
0613         if (ep->getName() == elementName)
0614             return ep;
0615     }
0616 
0617     return nullptr;
0618 }
0619 
0620 bool INDI_P::isRegistered() const
0621 {
0622     return (dataProp && dataProp.getRegistered());
0623 }