File indexing completed on 2024-04-21 14:46:08

0001 /*
0002     SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006     2004-01-15  INDI element is the most basic unit of the INDI KStars client.
0007 */
0008 
0009 #include "indielement.h"
0010 
0011 #include "indiproperty.h"
0012 #include "indigroup.h"
0013 #include "indidevice.h"
0014 
0015 #include "kstars.h"
0016 #include "ksnotification.h"
0017 
0018 #include <indicom.h>
0019 
0020 #include <KSqueezedTextLabel>
0021 #include <KLocalizedString>
0022 #include <KLed>
0023 #include <KMessageBox>
0024 
0025 #include <QButtonGroup>
0026 #include <QCheckBox>
0027 #include <QDir>
0028 #include <QDoubleSpinBox>
0029 #include <QFileDialog>
0030 #include <QLineEdit>
0031 #include <QPushButton>
0032 #include <QSlider>
0033 
0034 extern const char *libindi_strings_context;
0035 
0036 /*******************************************************************
0037 ** INDI Element
0038 *******************************************************************/
0039 INDI_E::INDI_E(INDI_P *gProp, INDI::Property dProp) : QWidget(gProp), guiProp(gProp), dataProp(dProp)
0040 {
0041     EHBox = new QHBoxLayout;
0042     EHBox->setObjectName("Element Horizontal Layout");
0043     EHBox->setContentsMargins(0, 0, 0, 0);
0044 }
0045 
0046 void INDI_E::buildSwitch(QButtonGroup *groupB, ISwitch *sw)
0047 {
0048     name  = sw->name;
0049     label = i18nc(libindi_strings_context, sw->label);
0050 
0051     if (label == "(I18N_EMPTY_MESSAGE)")
0052         label = sw->label;
0053 
0054     sp = sw;
0055 
0056     if (label.isEmpty())
0057         label = i18nc(libindi_strings_context, sw->name);
0058 
0059     if (label == "(I18N_EMPTY_MESSAGE)")
0060         label = sw->name;
0061 
0062     if (groupB == nullptr)
0063         return;
0064 
0065     switch (guiProp->getGUIType())
0066     {
0067         case PG_BUTTONS:
0068             push_w = new QPushButton(label, this);
0069             push_w->setStyleSheet(":checked {background-color: darkGreen}");
0070             push_w->setCheckable(true);
0071             groupB->addButton(push_w);
0072 
0073             syncSwitch();
0074 
0075             guiProp->addWidget(push_w);
0076 
0077             push_w->show();
0078 
0079             if (dataProp.getPermission() == IP_RO)
0080                 push_w->setEnabled(sw->s == ISS_ON);
0081 
0082             break;
0083 
0084         case PG_RADIO:
0085             check_w = new QCheckBox(label, this);
0086             groupB->addButton(check_w);
0087 
0088             syncSwitch();
0089 
0090             guiProp->addWidget(check_w);
0091 
0092             check_w->show();
0093 
0094             if (dataProp.getPermission() == IP_RO)
0095                 check_w->setEnabled(sw->s == ISS_ON);
0096 
0097             break;
0098 
0099         default:
0100             break;
0101     }
0102 }
0103 
0104 void INDI_E::buildMenuItem(ISwitch *sw)
0105 {
0106     buildSwitch(nullptr, sw);
0107 }
0108 
0109 void INDI_E::buildText(IText *itp)
0110 {
0111     name = itp->name;
0112     if (itp->label[0])
0113         label = i18nc(libindi_strings_context, itp->label);
0114 
0115     if (label == "(I18N_EMPTY_MESSAGE)")
0116         label = itp->label;
0117 
0118     tp = itp;
0119 
0120     if (label.isEmpty())
0121         label = i18nc(libindi_strings_context, itp->name);
0122 
0123     if (label == "(I18N_EMPTY_MESSAGE)")
0124         label = itp->name;
0125 
0126     setupElementLabel();
0127 
0128     if (tp->text[0])
0129         text = i18nc(libindi_strings_context, tp->text);
0130 
0131     switch (dataProp.getPermission())
0132     {
0133         case IP_RW:
0134             setupElementRead(ELEMENT_READ_WIDTH  * KStars::Instance()->devicePixelRatio());
0135             setupElementWrite(ELEMENT_WRITE_WIDTH  * KStars::Instance()->devicePixelRatio());
0136 
0137             break;
0138 
0139         case IP_RO:
0140             setupElementRead(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0141             break;
0142 
0143         case IP_WO:
0144             setupElementWrite(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0145             break;
0146     }
0147 
0148     guiProp->addLayout(EHBox);
0149 }
0150 
0151 void INDI_E::setupElementLabel()
0152 {
0153     QPalette palette;
0154 
0155     label_w = new KSqueezedTextLabel(this);
0156     label_w->setMinimumWidth(ELEMENT_LABEL_WIDTH * KStars::Instance()->devicePixelRatio());
0157     label_w->setMaximumWidth(ELEMENT_LABEL_WIDTH * KStars::Instance()->devicePixelRatio());
0158     label_w->setMargin(2);
0159 
0160     palette.setColor(label_w->backgroundRole(), QColor(224, 232, 238));
0161     label_w->setPalette(palette);
0162     label_w->setTextFormat(Qt::RichText);
0163     label_w->setAlignment(Qt::AlignCenter);
0164     label_w->setWordWrap(true);
0165 
0166     label_w->setStyleSheet("border: 1px solid grey; border-radius: 2px");
0167 
0168     if (label.length() > MAX_LABEL_LENGTH)
0169     {
0170         QFont tempFont(label_w->font());
0171         tempFont.setPointSize(tempFont.pointSize() - MED_INDI_FONT);
0172         label_w->setFont(tempFont);
0173     }
0174 
0175     label_w->setText(label);
0176 
0177     EHBox->addWidget(label_w);
0178 }
0179 
0180 void INDI_E::syncSwitch()
0181 {
0182     if (sp == nullptr)
0183         return;
0184 
0185     QFont buttonFont;
0186 
0187     switch (guiProp->getGUIType())
0188     {
0189         case PG_BUTTONS:
0190             if (sp->s == ISS_ON)
0191             {
0192                 push_w->setChecked(true);
0193                 //push_w->setDown(true);
0194                 buttonFont = push_w->font();
0195                 buttonFont.setBold(true);
0196                 push_w->setFont(buttonFont);
0197 
0198                 if (dataProp.getPermission() == IP_RO)
0199                     push_w->setEnabled(true);
0200             }
0201             else
0202             {
0203                 push_w->setChecked(false);
0204                 //push_w->setDown(false);
0205                 buttonFont = push_w->font();
0206                 buttonFont.setBold(false);
0207                 push_w->setFont(buttonFont);
0208 
0209                 if (dataProp.getPermission() == IP_RO)
0210                     push_w->setEnabled(false);
0211             }
0212             break;
0213 
0214         case PG_RADIO:
0215             if (sp->s == ISS_ON)
0216             {
0217                 check_w->setChecked(true);
0218                 if (dataProp.getPermission() == IP_RO)
0219                     check_w->setEnabled(true);
0220             }
0221             else
0222             {
0223                 check_w->setChecked(false);
0224                 if (dataProp.getPermission() == IP_RO)
0225                     check_w->setEnabled(false);
0226             }
0227             break;
0228 
0229         default:
0230             break;
0231     }
0232 }
0233 
0234 void INDI_E::syncText()
0235 {
0236     if (tp == nullptr)
0237         return;
0238 
0239     if (dataProp.getPermission() != IP_WO)
0240     {
0241         if (tp->text[0])
0242             read_w->setText(i18nc(libindi_strings_context, tp->text));
0243         else
0244             read_w->setText(tp->text);
0245     }
0246     // If write-only
0247     else
0248     {
0249         write_w->setText(tp->text);
0250     }
0251 }
0252 
0253 void INDI_E::syncNumber()
0254 {
0255     char iNumber[MAXINDIFORMAT];
0256     if (np == nullptr || read_w == nullptr)
0257         return;
0258 
0259     numberFormat(iNumber, np->format, np->value);
0260 
0261     text = iNumber;
0262 
0263     read_w->setText(text);
0264 
0265     if (spin_w)
0266     {
0267         if (np->min != spin_w->minimum())
0268             setMin();
0269         if (np->max != spin_w->maximum())
0270             setMax();
0271     }
0272 }
0273 
0274 void INDI_E::updateTP()
0275 {
0276     if (tp == nullptr)
0277         return;
0278 
0279     IUSaveText(tp, write_w->text().toHtmlEscaped().toLatin1().constData());
0280 }
0281 
0282 void INDI_E::updateNP()
0283 {
0284     if (np == nullptr)
0285         return;
0286 
0287     if (write_w != nullptr)
0288     {
0289         if (write_w->text().isEmpty())
0290             return;
0291 
0292         f_scansexa(write_w->text().replace(',', '.').toLatin1().constData(), &(np->value));
0293         return;
0294     }
0295 
0296     if (spin_w != nullptr)
0297         np->value = spin_w->value();
0298 }
0299 
0300 void INDI_E::setText(const QString &newText)
0301 {
0302     if (tp == nullptr)
0303         return;
0304 
0305     switch (dataProp.getPermission())
0306     {
0307         case IP_RO:
0308             read_w->setText(newText);
0309             break;
0310 
0311         case IP_WO:
0312         case IP_RW:
0313             text = newText;
0314             IUSaveText(tp, newText.toLatin1().constData());
0315             read_w->setText(newText);
0316             write_w->setText(newText);
0317             break;
0318     }
0319 }
0320 
0321 void INDI_E::setValue(double value)
0322 {
0323     if (spin_w == nullptr || np == nullptr)
0324         return;
0325     // ensure that min <= value <= max
0326     if (value < np->min || value > np->max)
0327         return;
0328 
0329     spin_w->setValue(value);
0330     spinChanged(value);
0331 }
0332 
0333 void INDI_E::buildBLOB(IBLOB *ibp)
0334 {
0335     name  = ibp->name;
0336     label = i18nc(libindi_strings_context, ibp->label);
0337 
0338     if (label == "(I18N_EMPTY_MESSAGE)")
0339         label = ibp->label;
0340 
0341     bp = ibp;
0342 
0343     if (label.isEmpty())
0344         label = i18nc(libindi_strings_context, ibp->name);
0345 
0346     if (label == "(I18N_EMPTY_MESSAGE)")
0347         label = ibp->name;
0348 
0349     setupElementLabel();
0350 
0351     text = i18n("INDI DATA STREAM");
0352 
0353     switch (dataProp.getPermission())
0354     {
0355         case IP_RW:
0356             setupElementRead(ELEMENT_READ_WIDTH * KStars::Instance()->devicePixelRatio());
0357             setupElementWrite(ELEMENT_WRITE_WIDTH * KStars::Instance()->devicePixelRatio());
0358             setupBrowseButton();
0359             break;
0360 
0361         case IP_RO:
0362             setupElementRead(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0363             break;
0364 
0365         case IP_WO:
0366             setupElementWrite(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0367             setupBrowseButton();
0368             break;
0369     }
0370 
0371     guiProp->addLayout(EHBox);
0372 }
0373 
0374 void INDI_E::buildNumber(INumber *inp)
0375 {
0376     bool scale = false;
0377     char iNumber[MAXINDIFORMAT];
0378 
0379     name  = inp->name;
0380     label = i18nc(libindi_strings_context, inp->label);
0381 
0382     if (label == "(I18N_EMPTY_MESSAGE)")
0383         label = inp->label;
0384 
0385     np = inp;
0386 
0387     if (label.isEmpty())
0388         label = i18nc(libindi_strings_context, inp->name);
0389 
0390     if (label == "(I18N_EMPTY_MESSAGE)")
0391         label = inp->name;
0392 
0393     numberFormat(iNumber, np->format, np->value);
0394     text = iNumber;
0395 
0396     setupElementLabel();
0397 
0398     if (np->step != 0 && (np->max - np->min) / np->step <= 100)
0399         scale = true;
0400 
0401     switch (dataProp.getPermission())
0402     {
0403         case IP_RW:
0404             setupElementRead(ELEMENT_READ_WIDTH * KStars::Instance()->devicePixelRatio());
0405             if (scale)
0406                 setupElementScale(ELEMENT_WRITE_WIDTH * KStars::Instance()->devicePixelRatio());
0407             else
0408                 setupElementWrite(ELEMENT_WRITE_WIDTH * KStars::Instance()->devicePixelRatio());
0409 
0410             guiProp->addLayout(EHBox);
0411             break;
0412 
0413         case IP_RO:
0414             setupElementRead(ELEMENT_READ_WIDTH * KStars::Instance()->devicePixelRatio());
0415             guiProp->addLayout(EHBox);
0416             break;
0417 
0418         case IP_WO:
0419             if (scale)
0420                 setupElementScale(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0421             else
0422                 setupElementWrite(ELEMENT_FULL_WIDTH * KStars::Instance()->devicePixelRatio());
0423 
0424             guiProp->addLayout(EHBox);
0425 
0426             break;
0427     }
0428 }
0429 
0430 void INDI_E::buildLight(ILight *ilp)
0431 {
0432     name  = ilp->name;
0433     label = i18nc(libindi_strings_context, ilp->label);
0434 
0435     if (label == "(I18N_EMPTY_MESSAGE)")
0436         label = ilp->label;
0437 
0438     lp = ilp;
0439 
0440     if (label.isEmpty())
0441         label = i18nc(libindi_strings_context, ilp->name);
0442 
0443     if (label == "(I18N_EMPTY_MESSAGE)")
0444         label = ilp->name;
0445 
0446     led_w = new KLed(this);
0447     led_w->setMaximumSize(16, 16);
0448     led_w->setLook(KLed::Sunken);
0449 
0450     syncLight();
0451 
0452     EHBox->addWidget(led_w);
0453 
0454     setupElementLabel();
0455 
0456     guiProp->addLayout(EHBox);
0457 }
0458 
0459 void INDI_E::syncLight()
0460 {
0461     if (lp == nullptr)
0462         return;
0463 
0464     switch (lp->s)
0465     {
0466         case IPS_IDLE:
0467             led_w->setColor(Qt::gray);
0468             break;
0469 
0470         case IPS_OK:
0471             led_w->setColor(Qt::green);
0472             break;
0473 
0474         case IPS_BUSY:
0475             led_w->setColor(Qt::yellow);
0476             break;
0477 
0478         case IPS_ALERT:
0479             led_w->setColor(Qt::red);
0480             break;
0481     }
0482 }
0483 
0484 void INDI_E::setupElementScale(int length)
0485 {
0486     if (np == nullptr)
0487         return;
0488 
0489     int steps = static_cast<int>((np->max - np->min) / np->step);
0490     spin_w    = new QDoubleSpinBox(this);
0491     spin_w->setRange(np->min, np->max);
0492     spin_w->setSingleStep(np->step);
0493     spin_w->setValue(np->value);
0494     spin_w->setDecimals(3);
0495 
0496     slider_w = new QSlider(Qt::Horizontal, this);
0497     slider_w->setRange(0, steps);
0498     slider_w->setPageStep(1);
0499     slider_w->setValue(static_cast<int>((np->value - np->min) / np->step));
0500 
0501     connect(spin_w, SIGNAL(valueChanged(double)), this, SLOT(spinChanged(double)));
0502     connect(slider_w, SIGNAL(sliderMoved(int)), this, SLOT(sliderChanged(int)));
0503 
0504     if (length == ELEMENT_FULL_WIDTH  * KStars::Instance()->devicePixelRatio())
0505         spin_w->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
0506     else
0507         spin_w->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0508 
0509     spin_w->setMinimumWidth(static_cast<int>(length * 0.45));
0510     slider_w->setMinimumWidth(static_cast<int>(length * 0.55));
0511 
0512     EHBox->addWidget(slider_w);
0513     EHBox->addWidget(spin_w);
0514 }
0515 
0516 void INDI_E::spinChanged(double value)
0517 {
0518     int slider_value = static_cast<int>((value - np->min) / np->step);
0519     slider_w->setValue(slider_value);
0520 }
0521 
0522 void INDI_E::sliderChanged(int value)
0523 {
0524     double spin_value = (value * np->step) + np->min;
0525     spin_w->setValue(spin_value);
0526 }
0527 
0528 void INDI_E::setMin()
0529 {
0530     if (spin_w)
0531     {
0532         spin_w->setMinimum(np->min);
0533         spin_w->setValue(np->value);
0534     }
0535     if (slider_w)
0536     {
0537         slider_w->setMaximum(static_cast<int>((np->max - np->min) / np->step));
0538         slider_w->setMinimum(0);
0539         slider_w->setPageStep(1);
0540         slider_w->setValue(static_cast<int>((np->value - np->min) / np->step));
0541     }
0542 }
0543 
0544 void INDI_E::setMax()
0545 {
0546     if (spin_w)
0547     {
0548         spin_w->setMaximum(np->max);
0549         spin_w->setValue(np->value);
0550     }
0551     if (slider_w)
0552     {
0553         slider_w->setMaximum(static_cast<int>((np->max - np->min) / np->step));
0554         slider_w->setMinimum(0);
0555         slider_w->setPageStep(1);
0556         slider_w->setValue(static_cast<int>((np->value - np->min) / np->step));
0557     }
0558 }
0559 
0560 void INDI_E::setupElementWrite(int length)
0561 {
0562     write_w = new QLineEdit(this);
0563     write_w->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0564     write_w->setMinimumWidth(length);
0565     write_w->setMaximumWidth(length);
0566 
0567     write_w->setText(text);
0568 
0569     QObject::connect(write_w, SIGNAL(returnPressed()), guiProp, SLOT(sendText()));
0570     EHBox->addWidget(write_w);
0571 }
0572 
0573 void INDI_E::setupElementRead(int length)
0574 {
0575     read_w = new QLineEdit(this);
0576     read_w->setMinimumWidth(length);
0577     read_w->setFocusPolicy(Qt::NoFocus);
0578     read_w->setCursorPosition(0);
0579     read_w->setAlignment(Qt::AlignCenter);
0580     read_w->setReadOnly(true);
0581     read_w->setText(text);
0582 
0583     EHBox->addWidget(read_w);
0584 }
0585 
0586 void INDI_E::setupBrowseButton()
0587 {
0588     browse_w = new QPushButton(this);
0589     browse_w->setIcon(QIcon::fromTheme("document-open"));
0590     browse_w->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0591     browse_w->setMinimumWidth(MIN_SET_WIDTH  * KStars::Instance()->devicePixelRatio());
0592     browse_w->setMaximumWidth(MAX_SET_WIDTH  * KStars::Instance()->devicePixelRatio());
0593 
0594     EHBox->addWidget(browse_w);
0595     QObject::connect(browse_w, SIGNAL(clicked()), this, SLOT(browseBlob()));
0596 }
0597 
0598 void INDI_E::browseBlob()
0599 {
0600     QFile fp;
0601     QString filename;
0602     QString format;
0603     int pos = 0;
0604     QUrl currentURL;
0605 
0606     currentURL = QFileDialog::getOpenFileUrl();
0607 
0608     // if user presses cancel
0609     if (currentURL.isEmpty())
0610         return;
0611 
0612     if (currentURL.isValid())
0613         write_w->setText(currentURL.toLocalFile());
0614 
0615     fp.setFileName(currentURL.toLocalFile());
0616 
0617     if ((pos = filename.lastIndexOf(".")) != -1)
0618         format = filename.mid(pos, filename.length());
0619 
0620     //qDebug() << Q_FUNC_INFO << "Filename is " << fp.fileName() << Qt::endl;
0621 
0622     if (!fp.open(QIODevice::ReadOnly))
0623     {
0624         KSNotification::error( i18n("Cannot open file %1 for reading", filename));
0625         return;
0626     }
0627 
0628     bp->bloblen = bp->size = fp.size();
0629 
0630     bp->blob = static_cast<uint8_t *>(realloc(bp->blob, bp->size));
0631     if (bp->blob == nullptr)
0632     {
0633         KSNotification::error( i18n("Not enough memory for file %1", filename));
0634         fp.close();
0635         return;
0636     }
0637 
0638     memcpy(bp->blob, fp.readAll().constData(), bp->size);
0639 
0640     blobDirty = true;
0641 }
0642 
0643 QString INDI_E::getWriteField()
0644 {
0645     if (write_w)
0646         return write_w->text();
0647     else
0648         return nullptr;
0649 }
0650 
0651 QString INDI_E::getReadField()
0652 {
0653     if (read_w)
0654         return read_w->text();
0655     else
0656         return nullptr;
0657 }