File indexing completed on 2025-02-16 06:40:22
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 }