1 /*
2 SPDX-FileCopyrightText: 2003 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "indiproperty.h"
8
9 #include "clientmanager.h"
10 #include "indidevice.h"
11 #include "indielement.h"
12 #include "indigroup.h"
13 #include "kstars.h"
14 #include "Options.h"
15 #include "skymap.h"
16 #include "dialogs/timedialog.h"
17
18 #include <indicom.h>
19 #include <indiproperty.h>
20
21 #include <KLed>
22 #include <KSqueezedTextLabel>
23
24 #include <QAbstractButton>
25 #include <QButtonGroup>
26 #include <QCheckBox>
27 #include <QComboBox>
28 #include <QHBoxLayout>
29 #include <QPushButton>
30 #include <QVBoxLayout>
31
32 extern const char *libindi_strings_context;
33
34 /*******************************************************************
35 ** INDI Property: contains widgets, labels, and their status
36 *******************************************************************/
INDI_P(INDI_G * ipg,INDI::Property prop)37 INDI_P::INDI_P(INDI_G *ipg, INDI::Property prop) : QWidget(ipg), pg(ipg), dataProp(prop)
38 {
39 name = QString(prop.getName());
40
41 PHBox = new QHBoxLayout(this);
42 PHBox->setObjectName("Property Horizontal Layout");
43 PHBox->setContentsMargins(0, 0, 0, 0);
44 PVBox = new QVBoxLayout;
45 PVBox->setContentsMargins(0, 0, 0, 0);
46 PVBox->setObjectName("Property Vertical Layout");
47
48 initGUI();
49 }
50
updateStateLED()51 void INDI_P::updateStateLED()
52 {
53 /* set state light */
54 switch (dataProp.getState())
55 {
56 case IPS_IDLE:
57 ledStatus->setColor(Qt::gray);
58 break;
59
60 case IPS_OK:
61 ledStatus->setColor(Qt::green);
62 break;
63
64 case IPS_BUSY:
65 ledStatus->setColor(Qt::yellow);
66 break;
67
68 case IPS_ALERT:
69 ledStatus->setColor(Qt::red);
70 break;
71 }
72 }
73
74 /* build widgets for property pp using info in root.
75 */
initGUI()76 void INDI_P::initGUI()
77 {
78 QString label = i18nc(libindi_strings_context, dataProp.getLabel());
79
80 if (label == "(I18N_EMPTY_MESSAGE)")
81 label = dataProp.getLabel();
82
83 /* add to GUI group */
84 ledStatus = new KLed(this);
85 ledStatus->setMaximumSize(16, 16);
86 ledStatus->setLook(KLed::Sunken);
87
88 updateStateLED();
89
90 /* Create a horizontally layout widget around light and label */
91 QWidget *labelWidget = new QWidget(this);
92 QHBoxLayout *labelLayout = new QHBoxLayout(labelWidget);
93 labelLayout->setContentsMargins(0, 0, 0, 0);
94
95 /* #1 First widget is the LED status indicator */
96 labelLayout->addWidget(ledStatus);
97
98 if (label.isEmpty())
99 {
100 label = i18nc(libindi_strings_context, name.toUtf8());
101 if (label == "(I18N_EMPTY_MESSAGE)")
102 label = name.toUtf8();
103
104 labelW = new KSqueezedTextLabel(label, this);
105 }
106 else
107 labelW = new KSqueezedTextLabel(label, this);
108
109 //labelW->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
110 labelW->setFrameShape(QFrame::Box);
111 labelW->setFrameShadow(QFrame::Sunken);
112 labelW->setMargin(2);
113 labelW->setFixedWidth(PROPERTY_LABEL_WIDTH * KStars::Instance()->devicePixelRatio());
114 labelW->setTextFormat(Qt::RichText);
115 labelW->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
116 labelW->setWordWrap(true);
117
118 labelLayout->addWidget(labelW);
119 PHBox->addWidget(labelWidget, 0, Qt::AlignTop | Qt::AlignLeft);
120
121 ledStatus->show();
122 labelW->show();
123
124 // #3 Add the Vertical layout which may contain several elements
125 PHBox->addLayout(PVBox);
126
127 switch (dataProp.getType())
128 {
129 case INDI_SWITCH:
130 if (dataProp.getSwitch()->getRule() == ISR_NOFMANY)
131 guiType = PG_RADIO;
132 else if (dataProp.getSwitch()->count() > 4)
133 guiType = PG_MENU;
134 else
135 guiType = PG_BUTTONS;
136
137 if (guiType == PG_MENU)
138 buildMenuGUI();
139 else
140 buildSwitchGUI();
141 break;
142
143 case INDI_TEXT:
144 buildTextGUI();
145 break;
146
147 case INDI_NUMBER:
148 buildNumberGUI();
149 break;
150
151 case INDI_LIGHT:
152 buildLightGUI();
153 break;
154
155 case INDI_BLOB:
156 buildBLOBGUI();
157 break;
158
159 default:
160 break;
161 }
162 }
163
buildSwitchGUI()164 void INDI_P::buildSwitchGUI()
165 {
166 auto svp = dataProp.getSwitch();
167
168 if (!svp)
169 return;
170
171 groupB = new QButtonGroup(this);
172
173 if (guiType == PG_BUTTONS)
174 {
175 if (svp->getRule() == ISR_1OFMANY)
176 groupB->setExclusive(true);
177 else
178 groupB->setExclusive(false);
179 }
180 else if (guiType == PG_RADIO)
181 groupB->setExclusive(false);
182
183 if (svp->p != IP_RO)
184 QObject::connect(groupB, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(newSwitch(QAbstractButton*)));
185
186 for (auto &it : *svp)
187 {
188 auto lp = new INDI_E(this, dataProp);
189 lp->buildSwitch(groupB, &it);
190 elementList.append(lp);
191 }
192
193 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
194
195 PHBox->addItem(horSpacer);
196 }
197
buildTextGUI()198 void INDI_P::buildTextGUI()
199 {
200 auto tvp = dataProp.getText();
201
202 if (!tvp)
203 return;
204
205 for (auto &it : *tvp)
206 {
207 auto lp = new INDI_E(this, dataProp);
208 lp->buildText(&it);
209 elementList.append(lp);
210 }
211
212 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
213
214 PHBox->addItem(horSpacer);
215
216 if (tvp->getPermission() == IP_RO)
217 return;
218
219 // INDI STD, but we use our own controls
220 if (name == "TIME_UTC")
221 setupSetButton(i18n("Time"));
222 else
223 setupSetButton(i18n("Set"));
224 }
225
buildNumberGUI()226 void INDI_P::buildNumberGUI()
227 {
228 auto nvp = dataProp.getNumber();
229
230 if (!nvp)
231 return;
232
233 for (auto &it : *nvp)
234 {
235 auto lp = new INDI_E(this, dataProp);
236 lp->buildNumber(&it);
237 elementList.append(lp);
238 }
239
240 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
241
242 PHBox->addItem(horSpacer);
243
244 if (nvp->getPermission() == IP_RO)
245 return;
246
247 setupSetButton(i18n("Set"));
248 }
249
buildLightGUI()250 void INDI_P::buildLightGUI()
251 {
252 auto lvp = dataProp.getLight();
253
254 if (!lvp)
255 return;
256
257 for (auto &it : *lvp)
258 {
259 auto ep = new INDI_E(this, dataProp);
260 ep->buildLight(&it);
261 elementList.append(ep);
262 }
263
264 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
265
266 PHBox->addItem(horSpacer);
267 }
268
buildBLOBGUI()269 void INDI_P::buildBLOBGUI()
270 {
271 auto bvp = dataProp.getBLOB();
272
273 if (!bvp)
274 return;
275
276 for (auto &it : *bvp)
277 {
278 auto lp = new INDI_E(this, dataProp);
279 lp->buildBLOB(&it);
280 elementList.append(lp);
281 }
282
283 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
284
285 PHBox->addItem(horSpacer);
286
287 enableBLOBC = new QCheckBox();
288 enableBLOBC->setIcon(QIcon::fromTheme("network-modem"));
289 enableBLOBC->setChecked(true);
290 enableBLOBC->setToolTip(i18n("Enable binary data transfer from this property to KStars and vice-versa."));
291
292 PHBox->addWidget(enableBLOBC);
293
294 connect(enableBLOBC, SIGNAL(stateChanged(int)), this, SLOT(setBLOBOption(int)));
295
296 if (dataProp.getPermission() != IP_RO)
297 setupSetButton(i18n("Upload"));
298 }
299
setBLOBOption(int state)300 void INDI_P::setBLOBOption(int state)
301 {
302 pg->getDevice()->getClientManager()->setBLOBEnabled(state == Qt::Checked, dataProp.getDeviceName(), dataProp.getName());
303 }
304
newSwitch(QAbstractButton * button)305 void INDI_P::newSwitch(QAbstractButton *button)
306 {
307 auto svp = dataProp.getSwitch();
308 QString buttonText = button->text();
309
310 if (!svp)
311 return;
312
313 buttonText.remove('&');
314
315 for (auto &el : elementList)
316 {
317 if (el->getLabel() == buttonText)
318 {
319 newSwitch(el->getName());
320 return;
321 }
322 }
323 }
324
resetSwitch()325 void INDI_P::resetSwitch()
326 {
327 auto svp = dataProp.getSwitch();
328
329 if (!svp)
330 return;
331
332 if (menuC != nullptr)
333 {
334 menuC->setCurrentIndex(svp->findOnSwitchIndex());
335 }
336 }
337
newSwitch(int index)338 void INDI_P::newSwitch(int index)
339 {
340 auto svp = dataProp.getSwitch();
341
342 if (!svp)
343 return;
344
345 if (index >= svp->count() || index < 0)
346 return;
347
348 auto sp = svp->at(index);
349
350 svp->reset();
351 sp->setState(ISS_ON);
352
353 sendSwitch();
354 }
355
newSwitch(const QString & name)356 void INDI_P::newSwitch(const QString &name)
357 {
358 auto svp = dataProp.getSwitch();
359
360 if (!svp)
361 return;
362
363 auto sp = svp->findWidgetByName(name.toLatin1().constData());
364
365 if (!sp)
366 return;
367
368 if (svp->getRule() == ISR_1OFMANY)
369 {
370 svp->reset();
371 sp->setState(ISS_ON);
372 }
373 else
374 {
375 if (svp->getRule() == ISR_ATMOST1)
376 {
377 ISState prev_state = sp->getState();
378 svp->reset();
379 sp->setState(prev_state);
380 }
381
382 sp->setState(sp->getState() == ISS_ON ? ISS_OFF : ISS_ON);
383 }
384
385 sendSwitch();
386 }
387
sendSwitch()388 void INDI_P::sendSwitch()
389 {
390 auto svp = dataProp.getSwitch();
391
392 if (!svp)
393 return;
394
395 svp->setState(IPS_BUSY);
396
397 for (auto &el : elementList)
398 el->syncSwitch();
399
400 updateStateLED();
401
402 // Send it to server
403 pg->getDevice()->getClientManager()->sendNewSwitch(svp);
404 }
405
sendText()406 void INDI_P::sendText()
407 {
408 switch (dataProp.getType())
409 {
410 case INDI_TEXT:
411 {
412 auto tvp = dataProp.getText();
413 if (!tvp)
414 return;
415
416 tvp->setState(IPS_BUSY);
417
418 for (auto &el : elementList)
419 el->updateTP();
420
421 pg->getDevice()->getClientManager()->sendNewText(tvp);
422
423 break;
424 }
425
426 case INDI_NUMBER:
427 {
428 auto nvp = dataProp.getNumber();
429 if (!nvp)
430 return;
431
432 nvp->setState(IPS_BUSY);
433
434 for (auto &el : elementList)
435 el->updateNP();
436
437 pg->getDevice()->getClientManager()->sendNewNumber(nvp);
438 break;
439 }
440 default:
441 break;
442 }
443
444 updateStateLED();
445 }
446
buildMenuGUI()447 void INDI_P::buildMenuGUI()
448 {
449 QStringList menuOptions;
450 QString oneOption;
451 int onItem = -1;
452 auto svp = dataProp.getSwitch();
453
454 if (!svp)
455 return;
456
457 menuC = new QComboBox(this);
458
459 if (svp->getPermission() == IP_RO)
460 connect(menuC, SIGNAL(activated(int)), this, SLOT(resetSwitch()));
461 else
462 connect(menuC, SIGNAL(activated(int)), this, SLOT(newSwitch(int)));
463
464 for (int i = 0; i < svp->nsp; i++)
465 {
466 auto tp = svp->at(i);
467
468 if (tp->getState() == ISS_ON)
469 onItem = i;
470
471 auto lp = new INDI_E(this, dataProp);
472
473 lp->buildMenuItem(tp);
474
475 oneOption = i18nc(libindi_strings_context, lp->getLabel().toUtf8());
476
477 if (oneOption == "(I18N_EMPTY_MESSAGE)")
478 oneOption = lp->getLabel().toUtf8();
479
480 menuOptions.append(oneOption);
481
482 elementList.append(lp);
483 }
484
485 menuC->addItems(menuOptions);
486 menuC->setCurrentIndex(onItem);
487
488 horSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
489
490 PHBox->addWidget(menuC);
491 PHBox->addItem(horSpacer);
492 }
493
setupSetButton(const QString & caption)494 void INDI_P::setupSetButton(const QString &caption)
495 {
496 setB = new QPushButton(caption, this);
497 setB->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
498 setB->setMinimumWidth(MIN_SET_WIDTH * KStars::Instance()->devicePixelRatio());
499 setB->setMaximumWidth(MAX_SET_WIDTH * KStars::Instance()->devicePixelRatio());
500
501 connect(setB, SIGNAL(clicked()), this, SLOT(processSetButton()));
502
503 PHBox->addWidget(setB);
504 }
505
addWidget(QWidget * w)506 void INDI_P::addWidget(QWidget *w)
507 {
508 PHBox->addWidget(w);
509 }
510
addLayout(QHBoxLayout * layout)511 void INDI_P::addLayout(QHBoxLayout *layout)
512 {
513 PVBox->addLayout(layout);
514 }
515
updateMenuGUI()516 void INDI_P::updateMenuGUI()
517 {
518 auto svp = dataProp.getSwitch();
519
520 if (!svp)
521 return;
522
523 int currentIndex = svp->findOnSwitchIndex();
524 menuC->setCurrentIndex(currentIndex);
525 }
526
processSetButton()527 void INDI_P::processSetButton()
528 {
529 switch (dataProp.getType())
530 {
531 case INDI_TEXT:
532 //if (!strcmp(dataProp.getName(), "TIME_UTC"))
533 if (dataProp.isNameMatch("TIME_UTC"))
534 newTime();
535 else
536 sendText();
537
538 break;
539
540 case INDI_NUMBER:
541 sendText();
542 break;
543
544 case INDI_BLOB:
545 sendBlob();
546 break;
547
548 default:
549 break;
550 }
551 }
552
sendBlob()553 void INDI_P::sendBlob()
554 {
555 //int index=0;
556 //bool openingTag=false;
557 auto bvp = dataProp.getBLOB();
558
559 if (!bvp)
560 return;
561
562 bvp->setState(IPS_BUSY);
563
564 pg->getDevice()->getClientManager()->startBlob(bvp->getDeviceName(), bvp->getName(), timestamp());
565
566 for (int i = 0; i < elementList.count(); i++)
567 {
568 INDI::WidgetView<IBLOB> *bp = bvp->at(i);
569 #if (INDI_VERSION_MINOR >= 4 && INDI_VERSION_RELEASE >= 2)
570 pg->getDevice()->getClientManager()->sendOneBlob(bp);
571 #else
572 pg->getDevice()->getClientManager()->sendOneBlob(bp->getName(), bp->getSize(), bp->getFormat(), const_cast<void *>(bp->getBlob()));
573 #endif
574 }
575
576 // JM: Why we need dirty here? We should be able to upload multiple time
577 /*foreach(INDI_E *ep, elementList)
578 {
579 if (ep->getBLOBDirty() == true)
580 {
581
582 if (openingTag == false)
583 {
584 pg->getDevice()->getClientManager()->startBlob(bvp->device, bvp->name, timestamp());
585 openingTag = true;
586 }
587
588 IBLOB *bp = &(bvp->bp[index]);
589 ep->setBLOBDirty(false);
590
591 //qDebug() << "SENDING BLOB " << bp->name << " has size of " << bp->size << " and bloblen of " << bp->bloblen << endl;
592 pg->getDevice()->getClientManager()->sendOneBlob(bp->name, bp->size, bp->format, bp->blob);
593
594 }
595
596 index++;
597
598 }*/
599
600 //if (openingTag)
601 pg->getDevice()->getClientManager()->finishBlob();
602
603 updateStateLED();
604 }
605
newTime()606 void INDI_P::newTime()
607 {
608 INDI_E *timeEle = getElement("UTC");
609 INDI_E *offsetEle = getElement("OFFSET");
610 if (!timeEle || !offsetEle)
611 return;
612
613 TimeDialog timedialog(KStars::Instance()->data()->ut(), KStars::Instance()->data()->geo(), KStars::Instance(),
614 true);
615
616 if (timedialog.exec() == QDialog::Accepted)
617 {
618 QTime newTime(timedialog.selectedTime());
619 QDate newDate(timedialog.selectedDate());
620
621 timeEle->setText(QString("%1-%2-%3T%4:%5:%6")
622 .arg(newDate.year())
623 .arg(newDate.month())
624 .arg(newDate.day())
625 .arg(newTime.hour())
626 .arg(newTime.minute())
627 .arg(newTime.second()));
628
629 offsetEle->setText(QString().setNum(KStars::Instance()->data()->geo()->TZ(), 'g', 2));
630
631 sendText();
632 }
633 else
634 return;
635 }
636
getElement(const QString & elementName) const637 INDI_E *INDI_P::getElement(const QString &elementName) const
638 {
639 for (auto *ep : elementList)
640 {
641 if (ep->getName() == elementName)
642 return ep;
643 }
644
645 return nullptr;
646 }
647
isRegistered() const648 bool INDI_P::isRegistered() const
649 {
650 return (dataProp && dataProp.getRegistered());
651 }
652