Kstars

opticaltrainmanager.cpp
1/*
2 SPDX-FileCopyrightText: 2022 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "opticaltrainmanager.h"
8#include <kstars_debug.h>
9
10#include "ksnotification.h"
11#include "kstarsdata.h"
12#include "Options.h"
13#include "kstars.h"
14#include "indi/indilistener.h"
15#include "ekos/auxiliary/profilesettings.h"
16#include "oal/equipmentwriter.h"
17
18#include <QTimer>
19#include <QSqlTableModel>
20#include <QSqlDatabase>
21#include <QSqlRecord>
22
23#include <basedevice.h>
24
25#include <algorithm>
26
27namespace Ekos
28{
29
30OpticalTrainManager *OpticalTrainManager::m_Instance = nullptr;
31
32////////////////////////////////////////////////////////////////////////////
33///
34////////////////////////////////////////////////////////////////////////////
35OpticalTrainManager *OpticalTrainManager::Instance()
36{
37 if (m_Instance == nullptr)
38 m_Instance = new OpticalTrainManager();
39
40 return m_Instance;
41}
42
43////////////////////////////////////////////////////////////////////////////
44///
45////////////////////////////////////////////////////////////////////////////
46void OpticalTrainManager::release()
47{
48 delete(m_Instance);
49 m_Instance = nullptr;
50}
51
52////////////////////////////////////////////////////////////////////////////
53///
54////////////////////////////////////////////////////////////////////////////
55OpticalTrainManager::OpticalTrainManager() : QDialog(KStars::Instance())
56{
57#ifdef Q_OS_OSX
58 setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
59#endif
60
61 setupUi(this);
62
63 connect(this, &QDialog::finished, this, [this]()
64 {
65 emit configurationRequested(false);
66 });
67
68 // Mount Combo
69 connect(mountComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
70 [this]()
71 {
72 updateOpticalTrainValue(mountComboBox, "mount");
73 });
74
75 // DustCap Combo
76 connect(dustCapComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
77 [this]()
78 {
79 updateOpticalTrainValue(dustCapComboBox, "dustcap");
80 });
81
82 // Light Box
83 connect(lightBoxComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
84 [this]()
85 {
86 updateOpticalTrainValue(lightBoxComboBox, "lightbox");
87 });
88
89 // Scope / Lens
90 connect(scopeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
91 [this]()
92 {
93 updateOpticalTrainValue(scopeComboBox, "scope");
94 });
95
96 // Reducer
97 connect(reducerSpinBox, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
98 [this](double value)
99 {
100 updateOpticalTrainValue(value, "reducer");
101 });
102
103 // Rotator
104 connect(rotatorComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
105 [this]()
106 {
107 updateOpticalTrainValue(rotatorComboBox, "rotator");
108 });
109
110 // Focuser
111 connect(focusComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
112 [this]()
113 {
114 updateOpticalTrainValue(focusComboBox, "focuser");
115 });
116
117 // Filter Wheel
118 connect(filterComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
119 [this]()
120 {
121 updateOpticalTrainValue(filterComboBox, "filterwheel");
122 });
123
124 // Camera
125 connect(cameraComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
126 [this]()
127 {
128 updateOpticalTrainValue(cameraComboBox, "camera");
129 });
130
131 // Guider
132 connect(guiderComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
133 [this]()
134 {
135 updateOpticalTrainValue(guiderComboBox, "guider");
136 });
137
138 connect(addB, &QPushButton::clicked, this, [this]()
139 {
140 addOpticalTrain(m_TrainNames.count(), i18n("New Train"));
141 m_OpticalTrainsModel->select();
142 refreshModel();
143 trainNamesList->setCurrentRow(trainNamesList->count() - 1);
144 selectOpticalTrain(trainNamesList->currentItem());
145 });
146
147 connect(removeB, &QPushButton::clicked, this, [this]()
148 {
149 if (trainNamesList->currentItem() != nullptr)
150 {
151 removeOpticalTrain(trainNamesList->currentItem()->text());
152 removeB->setEnabled(false);
153 }
154 });
155
156 connect(resetB, &QPushButton::clicked, this, &OpticalTrainManager::reset);
157
158 connect(opticalElementsB, &QPushButton::clicked, this, [this]()
159 {
160 QScopedPointer<EquipmentWriter> writer(new EquipmentWriter());
161 writer->loadEquipment();
162 writer->exec();
163 refreshOpticalElements();
164 });
165
166 connect(trainNamesList, &QListWidget::itemClicked, this, [this](QListWidgetItem * item)
167 {
168 selectOpticalTrain(item);
169 });
170 connect(trainNamesList, &QListWidget::itemChanged, this, [this](QListWidgetItem * item)
171 {
172 renameCurrentOpticalTrain(item->text());
173 });
174 connect(trainNamesList, &QListWidget::currentRowChanged, this, [this](int row)
175 {
176 if (row >= 0)
177 selectOpticalTrain(trainNamesList->currentItem());
178 });
179
180 m_CheckMissingDevicesTimer.setInterval(5000);
181 m_CheckMissingDevicesTimer.setSingleShot(true);
182 connect(&m_CheckMissingDevicesTimer, &QTimer::timeout, this, &OpticalTrainManager::checkMissingDevices);
183
184 m_DelegateTimer.setInterval(1000);
185 m_DelegateTimer.setSingleShot(true);
186 connect(&m_DelegateTimer, &QTimer::timeout, this, [this]()
187 {
188 if (m_Profile)
189 setProfile(m_Profile);
190 });
191
192 initModel();
193}
194
195////////////////////////////////////////////////////////////////////////////
196///
197////////////////////////////////////////////////////////////////////////////
198void OpticalTrainManager::initModel()
199{
200 auto userdb = QSqlDatabase::database(KStarsData::Instance()->userdb()->connectionName());
201 m_OpticalTrainsModel = new QSqlTableModel(this, userdb);
202 connect(m_OpticalTrainsModel, &QSqlTableModel::dataChanged, this, [this]()
203 {
204 m_OpticalTrains.clear();
205 for (int i = 0; i < m_OpticalTrainsModel->rowCount(); ++i)
206 {
207 QVariantMap recordMap;
208 QSqlRecord record = m_OpticalTrainsModel->record(i);
209 for (int j = 0; j < record.count(); j++)
210 recordMap[record.fieldName(j)] = record.value(j);
211
212 m_OpticalTrains.append(recordMap);
213 }
214
215 m_TrainNames.clear();
216 for (auto &oneTrain : m_OpticalTrains)
217 m_TrainNames << oneTrain["name"].toString();
218
219 trainNamesList->clear();
220 trainNamesList->addItems(m_TrainNames);
221 trainNamesList->setEditTriggers(QAbstractItemView::AllEditTriggers);
222 emit updated();
223 });
224}
225
226void OpticalTrainManager::syncDevices()
227{
228 syncDelegatesToDevices();
229 if (m_Profile)
230 {
231 refreshModel();
232 emit updated();
233 }
234}
235
236////////////////////////////////////////////////////////////////////////////
237///
238////////////////////////////////////////////////////////////////////////////
239void OpticalTrainManager::refreshModel()
240{
241 KStars::Instance()->data()->userdb()->GetOpticalTrains(m_Profile->id, m_OpticalTrains);
242 m_TrainNames.clear();
243 for (auto &oneTrain : m_OpticalTrains)
244 m_TrainNames << oneTrain["name"].toString();
245
246 trainNamesList->clear();
247 trainNamesList->addItems(m_TrainNames);
248}
249
250////////////////////////////////////////////////////////////////////////////
251///
252////////////////////////////////////////////////////////////////////////////
253void OpticalTrainManager::syncActiveDevices()
254{
255 for (auto &oneTrain : m_OpticalTrains)
256 {
257 auto train = oneTrain["name"].toString();
259
260 if (getGenericDevice(train, Mount, device))
261 syncActiveProperties(oneTrain, device);
262 if (getGenericDevice(train, Camera, device))
263 syncActiveProperties(oneTrain, device);
264 if (getGenericDevice(train, GuideVia, device))
265 syncActiveProperties(oneTrain, device);
266 if (getGenericDevice(train, Focuser, device))
267 syncActiveProperties(oneTrain, device);
268 if (getGenericDevice(train, FilterWheel, device))
269 syncActiveProperties(oneTrain, device);
270 if (getGenericDevice(train, Rotator, device))
271 syncActiveProperties(oneTrain, device);
272 if (getGenericDevice(train, DustCap, device))
273 syncActiveProperties(oneTrain, device);
274 if (getGenericDevice(train, LightBox, device))
275 syncActiveProperties(oneTrain, device);
276 }
277
278 // For non-train specific devices, we only sync them with primary train
280 if (!m_OpticalTrains.isEmpty())
281 {
282 auto name = m_OpticalTrains[0]["name"].toString();
283 if (getGenericDevice(name, Dome, device))
284 syncActiveProperties(m_OpticalTrains[0], device);
285 if (getGenericDevice(name, Weather, device))
286 syncActiveProperties(m_OpticalTrains[0], device);
287 if (getGenericDevice(name, GPS, device))
288 syncActiveProperties(m_OpticalTrains[0], device);
289 }
290}
291
292////////////////////////////////////////////////////////////////////////////
293///
294////////////////////////////////////////////////////////////////////////////
295void OpticalTrainManager::syncActiveProperties(const QVariantMap &train, const QSharedPointer<ISD::GenericDevice> &device)
296{
297 auto tvp = device->getProperty("ACTIVE_DEVICES");
298 if (!tvp)
299 return;
300
301 auto name = train["name"].toString();
302
303 for (auto &it : *tvp.getText())
304 {
306 QString elementText = it.getText();
307 if (it.isNameMatch("ACTIVE_TELESCOPE"))
308 {
309 auto activeDevice = train["mount"].toString();
310 if (activeDevice == "--")
311 elementText.clear();
312 else if (activeDevice != elementText)
313 {
315 if (getGenericDevice(name, Mount, genericDevice))
316 devs.append(genericDevice);
317 }
318 }
319 else if (it.isNameMatch("ACTIVE_DOME"))
320 {
321 devs = INDIListener::devicesByInterface(INDI::BaseDevice::DOME_INTERFACE);
322 }
323 else if (it.isNameMatch("ACTIVE_GPS"))
324 {
325 devs = INDIListener::devicesByInterface(INDI::BaseDevice::GPS_INTERFACE);
326 // If GPS device is neither the location nor time source, then it should be removed
327 // so that INDI devices are NOT synced to it. We only explicitly sync to GPS if it is specified
328 // as either the location or time source
329 devs.erase(std::remove_if(devs.begin(), devs.end(), [](const auto & oneDevice)
330 {
331 return oneDevice->getDeviceName() != Options::locationSource() && oneDevice->getDeviceName() != Options::timeSource();
332 }), devs.end());
333
334 if (devs.isEmpty())
335 elementText.clear();
336 }
337 else if (it.isNameMatch("ACTIVE_ROTATOR"))
338 {
339 auto activeDevice = train["rotator"].toString();
340 if (activeDevice == "--")
341 elementText.clear();
342 else if (activeDevice != elementText)
343 {
345 if (getGenericDevice(name, Rotator, genericDevice))
346 devs.append(genericDevice);
347 }
348 }
349 else if (it.isNameMatch("ACTIVE_FOCUSER"))
350 {
351 auto activeDevice = train["focuser"].toString();
352 if (activeDevice == "--")
353 elementText.clear();
354 else if (activeDevice != elementText)
355 {
357 if (getGenericDevice(name, Focuser, genericDevice))
358 devs.append(genericDevice);
359 }
360 }
361 else if (it.isNameMatch("ACTIVE_FILTER"))
362 {
363 auto activeDevice = train["filterwheel"].toString();
364 if (activeDevice == "--")
365 elementText.clear();
366 else if (activeDevice != elementText)
367 {
369 if (getGenericDevice(name, FilterWheel, genericDevice))
370 devs.append(genericDevice);
371 }
372 }
373
374 if (!devs.empty())
375 {
376 if (it.getText() != devs.first()->getDeviceName())
377 {
378 it.setText(devs.first()->getDeviceName().toLatin1().constData());
379 device->sendNewProperty(tvp.getText());
380 }
381 }
382 // Clear element if required
383 else if (elementText.isEmpty() && !QString(it.getText()).isEmpty())
384 {
385 it.setText("");
386 device->sendNewProperty(tvp.getText());
387 }
388 }
389
390}
391
392////////////////////////////////////////////////////////////////////////////
393///
394////////////////////////////////////////////////////////////////////////////
395void OpticalTrainManager::setProfile(const QSharedPointer<ProfileInfo> &profile)
396{
397 m_DelegateTimer.stop();
398
399 // Load optical train model
400 if (m_Profile != profile)
401 {
402 m_Profile = profile;
403 refreshModel();
404 }
405
406 // Are we still updating delegates? If yes, return.
407 if (syncDelegatesToDevices())
408 {
409 m_CheckMissingDevicesTimer.start();
410
411 // Start delegate timer to ensure no more changes are pending.
412 m_DelegateTimer.start();
413
414 syncActiveDevices();
415 }
416 else
417 {
418 checkOpticalTrains();
419 }
420}
421
422////////////////////////////////////////////////////////////////////////////
423///
424////////////////////////////////////////////////////////////////////////////
425void OpticalTrainManager::checkOpticalTrains()
426{
427 if (m_OpticalTrains.empty())
428 {
429 generateOpticalTrains();
430 refreshModel();
431 if (!m_OpticalTrains.empty())
432 {
433 auto primaryTrainID = m_OpticalTrains[0]["id"].toUInt();
434 ProfileSettings::Instance()->setOneSetting(ProfileSettings::PrimaryOpticalTrain, primaryTrainID);
435 ProfileSettings::Instance()->setOneSetting(ProfileSettings::CaptureOpticalTrain, primaryTrainID);
436 ProfileSettings::Instance()->setOneSetting(ProfileSettings::FocusOpticalTrain, primaryTrainID);
437 ProfileSettings::Instance()->setOneSetting(ProfileSettings::MountOpticalTrain, primaryTrainID);
438 ProfileSettings::Instance()->setOneSetting(ProfileSettings::AlignOpticalTrain, primaryTrainID);
439 ProfileSettings::Instance()->setOneSetting(ProfileSettings::DarkLibraryOpticalTrain, primaryTrainID);
440 if (m_OpticalTrains.count() > 1)
441 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain, m_OpticalTrains[1]["id"].toInt());
442 else
443 ProfileSettings::Instance()->setOneSetting(ProfileSettings::GuideOpticalTrain, primaryTrainID);
444 }
445
446 emit updated();
447 show();
448 raise();
449 emit configurationRequested(true);
450 }
451 else
452 {
453 m_CheckMissingDevicesTimer.start();
454 emit updated();
455 }
456}
457////////////////////////////////////////////////////////////////////////////
458/// This method tries to guess possible optical train configuration
459////////////////////////////////////////////////////////////////////////////
460void OpticalTrainManager::generateOpticalTrains()
461{
462 // We should have primary train
463 addOpticalTrain(0, i18n("Primary"));
464 // Check if need secondary train
465 if (cameraComboBox->count() > 2)
466 addOpticalTrain(1, i18n("Secondary"));
467 // Check if need tertiary train
468 if (cameraComboBox->count() > 3)
469 addOpticalTrain(2, i18n("Tertiary"));
470}
471
472////////////////////////////////////////////////////////////////////////////
473///
474////////////////////////////////////////////////////////////////////////////
475QString OpticalTrainManager::addOpticalTrain(uint8_t index, const QString &name)
476{
477 QVariantMap train;
478 train["profile"] = m_Profile->id;
479 train["name"] = uniqueTrainName(name);
480
481 train["mount"] = mountComboBox->itemText(mountComboBox->count() - 1);
482 train["dustcap"] = dustCapComboBox->itemText(dustCapComboBox->count() - 1);
483 train["lightbox"] = lightBoxComboBox->itemText(lightBoxComboBox->count() - 1);
484 train["reducer"] = 1.0;
485 train["rotator"] = rotatorComboBox->itemText(rotatorComboBox->count() - 1);
486 train["focuser"] = focusComboBox->itemText(focusComboBox->count() - 1);
487 train["filterwheel"] = filterComboBox->itemText(filterComboBox->count() - 1);
488 train["guider"] = guiderComboBox->itemText(guiderComboBox->count() - 1);
489
490 QJsonObject opticalElement;
491 if (KStars::Instance()->data()->userdb()->getLastOpticalElement(opticalElement))
492 train["scope"] = opticalElement["name"].toString();
493
494 train["camera"] = "--";
495 // Primary train
496 if (index == 0 && cameraComboBox->count() > 1)
497 train["camera"] = cameraComboBox->itemText(1);
498 // Any other trains
499 else if (index > 0)
500 {
501 // For 2nd train and beyond, we get the N camera appropiate for this train if one exist.
502 // We add + 1 because first element in combobox is "--"
503 auto cameraIndex = index + 1;
504 if (cameraComboBox->count() >= cameraIndex)
505 train["camera"] = cameraComboBox->itemText(cameraIndex);
506 }
507
508 KStarsData::Instance()->userdb()->AddOpticalTrain(train);
509 return train["name"].toString();
510}
511
512////////////////////////////////////////////////////////////////////////////
513///
514////////////////////////////////////////////////////////////////////////////
515void OpticalTrainManager::addOpticalTrain(const QJsonObject &value)
516{
517 auto newTrain = value.toVariantMap();
518 newTrain["profile"] = m_Profile->id;
519 KStarsData::Instance()->userdb()->AddOpticalTrain(newTrain);
520
521 refreshTrains();
522}
523
524////////////////////////////////////////////////////////////////////////////
525///
526////////////////////////////////////////////////////////////////////////////
527bool OpticalTrainManager::setOpticalTrainValue(const QString &name, const QString &field, const QVariant &value)
528{
529 for (auto &oneTrain : m_OpticalTrains)
530 {
531 if (oneTrain["name"].toString() == name)
532 {
533 // If value did not change, just return true
534 if (oneTrain[field] == value)
535 return true;
536
537 // Update field and database.
538 oneTrain[field] = value;
539 KStarsData::Instance()->userdb()->UpdateOpticalTrain(oneTrain, oneTrain["id"].toInt());
540 syncActiveDevices();
541 emit updated();
542 return true;
543 }
544 }
545 return false;
546}
547
548////////////////////////////////////////////////////////////////////////////
549///
550////////////////////////////////////////////////////////////////////////////
551void OpticalTrainManager::renameCurrentOpticalTrain(const QString &name)
552{
553 if (m_CurrentOpticalTrain != nullptr && (*m_CurrentOpticalTrain)["name"] != name)
554 {
555 auto pos = trainNamesList->currentRow();
556 // ensure train name uniqueness
557 auto unique = uniqueTrainName(name);
558 // update the train database entry
559 setOpticalTrainValue((*m_CurrentOpticalTrain)["name"].toString(), "name", unique);
560 // propagate the unique name to the current selection
561 trainNamesList->currentItem()->setText(unique);
562 // refresh the trains
563 refreshTrains();
564 // refresh selection
565 selectOpticalTrain(unique);
566 trainNamesList->setCurrentRow(pos);
567 }
568}
569
570////////////////////////////////////////////////////////////////////////////
571///
572////////////////////////////////////////////////////////////////////////////
573bool OpticalTrainManager::setOpticalTrain(const QJsonObject &train)
574{
575 auto oneOpticalTrain = getOpticalTrain(train["id"].toInt());
576 if (!oneOpticalTrain.empty())
577 {
578 KStarsData::Instance()->userdb()->UpdateOpticalTrain(train.toVariantMap(), oneOpticalTrain["id"].toInt());
579 refreshTrains();
580 return true;
581 }
582 return false;
583}
584
585////////////////////////////////////////////////////////////////////////////
586///
587////////////////////////////////////////////////////////////////////////////
588bool OpticalTrainManager::removeOpticalTrain(const QString &name)
589{
590 for (auto &oneTrain : m_OpticalTrains)
591 {
592 if (oneTrain["name"].toString() == name)
593 {
594 auto id = oneTrain["id"].toInt();
595 KStarsData::Instance()->userdb()->DeleteOpticalTrain(id);
596 KStarsData::Instance()->userdb()->DeleteOpticalTrainSettings(id);
597 refreshTrains();
598 selectOpticalTrain(nullptr);
599 return true;
600 }
601 }
602
603 return false;
604}
605
606////////////////////////////////////////////////////////////////////////////
607///
608////////////////////////////////////////////////////////////////////////////
609bool OpticalTrainManager::syncDelegatesToDevices()
610{
611 auto changed = false;
612
613 // Mounts
614 auto mounts = INDIListener::devicesByInterface(INDI::BaseDevice::TELESCOPE_INTERFACE);
615 QStringList values;
616 for (auto &oneMount : mounts)
617 values << oneMount->getDeviceName();
618 changed |= !values.empty() && values != m_MountNames;
619 m_MountNames = values;
620 auto currentMount = mountComboBox->currentText();
621 mountComboBox->clear();
622 mountComboBox->addItems(QStringList() << "--" << values);
623 mountComboBox->setCurrentText(currentMount);
624
625 // Dust Caps
626 values.clear();
627 auto dustcaps = INDIListener::devicesByInterface(INDI::BaseDevice::DUSTCAP_INTERFACE);
628 for (auto &oneCap : dustcaps)
629 values << oneCap->getDeviceName();
630 changed |= !values.empty() && values != m_DustCapNames;
631 m_DustCapNames = values;
632 auto currentCap = dustCapComboBox->currentText();
633 dustCapComboBox->clear();
634 dustCapComboBox->addItems(QStringList() << "--" << values);
635 dustCapComboBox->setCurrentText(currentCap);
636
637 // Light Boxes
638 values.clear();
639 auto lightboxes = INDIListener::devicesByInterface(INDI::BaseDevice::LIGHTBOX_INTERFACE);
640 for (auto &oneBox : lightboxes)
641 values << oneBox->getDeviceName();
642 changed |= !values.empty() && values != m_LightBoxNames;
643 auto currentLightBox = lightBoxComboBox->currentText();
644 m_LightBoxNames = values;
645 lightBoxComboBox->clear();
646 lightBoxComboBox->addItems(QStringList() << "--" << values);
647 lightBoxComboBox->setCurrentText(currentLightBox);
648
649 // Scopes
650 values = KStars::Instance()->data()->userdb()->getOpticalElementNames();
651 changed |= !values.empty() && values != m_ScopeNames;
652 m_ScopeNames = values;
653 auto currentScope = scopeComboBox->currentText();
654 scopeComboBox->clear();
655 scopeComboBox->addItems(QStringList() << "--" << values);
656 scopeComboBox->setCurrentText(currentScope);
657
658 // Rotators
659 values.clear();
660 auto rotators = INDIListener::devicesByInterface(INDI::BaseDevice::ROTATOR_INTERFACE);
661 for (auto &oneRotator : rotators)
662 values << oneRotator->getDeviceName();
663 changed |= !values.empty() && values != m_RotatorNames;
664 m_RotatorNames = values;
665 auto currentRotator = rotatorComboBox->currentText();
666 rotatorComboBox->clear();
667 rotatorComboBox->addItems(QStringList() << "--" << values);
668 rotatorComboBox->setCurrentText(currentRotator);
669
670 // Focusers
671 values.clear();
672 auto focusers = INDIListener::devicesByInterface(INDI::BaseDevice::FOCUSER_INTERFACE);
673 for (auto &oneFocuser : focusers)
674 values << oneFocuser->getDeviceName();
675 changed |= !values.empty() && values != m_FocuserNames;
676 m_FocuserNames = values;
677 auto currentFocuser = focusComboBox->currentText();
678 focusComboBox->clear();
679 focusComboBox->addItems(QStringList() << "--" << values);
680 focusComboBox->setCurrentText(currentFocuser);
681
682 // Filter Wheels
683 values.clear();
684 auto filterwheels = INDIListener::devicesByInterface(INDI::BaseDevice::FILTER_INTERFACE);
685 for (auto &oneFilterWheel : filterwheels)
686 values << oneFilterWheel->getDeviceName();
687 changed |= !values.empty() && values != m_FilterWheelNames;
688 m_FilterWheelNames = values;
689 auto currentFilter = filterComboBox->currentText();
690 filterComboBox->clear();
691 filterComboBox->addItems(QStringList() << "--" << values);
692 filterComboBox->setCurrentText(currentFilter);
693
694 // Cameras
695 values.clear();
696 auto cameras = INDIListener::devicesByInterface(INDI::BaseDevice::CCD_INTERFACE);
697 for (auto &oneCamera : cameras)
698 values << oneCamera->getDeviceName();
699 changed |= !values.empty() && values != m_CameraNames;
700 m_CameraNames = values;
701 auto currentCamera = cameraComboBox->currentText();
702 cameraComboBox->clear();
703 cameraComboBox->addItems(QStringList() << "--" << values);
704 cameraComboBox->setCurrentText(currentCamera);
705
706 // Guiders
707 values.clear();
708 auto guiders = INDIListener::devicesByInterface(INDI::BaseDevice::GUIDER_INTERFACE);
709 for (auto &oneGuider : guiders)
710 values << oneGuider->getDeviceName();
711 changed |= !values.empty() && values != m_GuiderNames;
712 m_GuiderNames = values;
713 auto currentGuider = guiderComboBox->currentText();
714 guiderComboBox->clear();
715 guiderComboBox->addItems(QStringList() << "--" << values);
716 guiderComboBox->setCurrentText(currentGuider);
717
718 return changed;
719}
720
721////////////////////////////////////////////////////////////////////////////
722///
723////////////////////////////////////////////////////////////////////////////
724QString OpticalTrainManager::uniqueTrainName(QString name)
725{
726 QString result = name;
727 int nr = 1;
728 while (m_TrainNames.contains(result))
729 result = QString("%1 (%2)").arg(name).arg(nr++);
730
731 return result;
732}
733
734////////////////////////////////////////////////////////////////////////////
735///
736////////////////////////////////////////////////////////////////////////////
737bool OpticalTrainManager::selectOpticalTrain(QListWidgetItem *item)
738{
739 if (item != nullptr && selectOpticalTrain(item->text()))
740 {
741 item->setFlags(item->flags() | Qt::ItemIsEditable);
742 return true;
743 }
744 return false;
745}
746
747////////////////////////////////////////////////////////////////////////////
748///
749////////////////////////////////////////////////////////////////////////////
750QString OpticalTrainManager::findTrainContainingDevice(const QString &name, Role role)
751{
752 for (auto &oneTrain : m_OpticalTrains)
753 {
754 auto train = oneTrain["name"].toString();
755
756 switch (role)
757 {
758 case Mount:
759 if (oneTrain["mount"].toString() == name)
760 return train;
761 break;
762 case Camera:
763 if (oneTrain["camera"].toString() == name)
764 return train;
765 break;
766 case Rotator:
767 if (oneTrain["rotator"].toString() == name)
768 return train;
769 break;
770 case GuideVia:
771 if (oneTrain["guider"].toString() == name)
772 return train;
773 break;
774 case DustCap:
775 if (oneTrain["dustcap"].toString() == name)
776 return train;
777 break;
778 case Scope:
779 if (oneTrain["scope"].toString() == name)
780 return train;
781 break;
782 case FilterWheel:
783 if (oneTrain["filterwheel"].toString() == name)
784 return train;
785 break;
786 case Focuser:
787 if (oneTrain["focuser"].toString() == name)
788 return train;
789 break;
790 case Reducer:
791 if (oneTrain["reducer"].toString() == name)
792 return train;
793 break;
794 case LightBox:
795 if (oneTrain["lightbox"].toString() == name)
796 return train;
797 break;
798 case Dome:
799 case Weather:
800 case GPS:
801 // for those not part of an image train: do nothing
802 break;
803 }
804
805 }
806
807 return QString();
808}
809
810////////////////////////////////////////////////////////////////////////////
811///
812////////////////////////////////////////////////////////////////////////////
813bool OpticalTrainManager::selectOpticalTrain(const QString &name)
814{
815 for (auto &oneTrain : m_OpticalTrains)
816 {
817 if (oneTrain["name"].toString() == name)
818 {
819 m_Persistent = false;
820 m_CurrentOpticalTrain = &oneTrain;
821 mountComboBox->setCurrentText(oneTrain["mount"].toString());
822 dustCapComboBox->setCurrentText(oneTrain["dustcap"].toString());
823 lightBoxComboBox->setCurrentText(oneTrain["lightbox"].toString());
824 scopeComboBox->setCurrentText(oneTrain["scope"].toString());
825 reducerSpinBox->setValue(oneTrain["reducer"].toDouble());
826 rotatorComboBox->setCurrentText(oneTrain["rotator"].toString());
827 focusComboBox->setCurrentText(oneTrain["focuser"].toString());
828 filterComboBox->setCurrentText(oneTrain["filterwheel"].toString());
829 cameraComboBox->setCurrentText(oneTrain["camera"].toString());
830 guiderComboBox->setCurrentText(oneTrain["guider"].toString());
831 removeB->setEnabled(m_OpticalTrains.length() > 1);
832 trainConfigBox->setEnabled(true);
833 m_Persistent = true;
834 return true;
835 }
836 }
837
838 // none found
839 m_Persistent = false;
840 m_CurrentOpticalTrain = nullptr;
841 mountComboBox->setCurrentText("--");
842 dustCapComboBox->setCurrentText("--");
843 lightBoxComboBox->setCurrentText("--");
844 scopeComboBox->setCurrentText("--");
845 reducerSpinBox->setValue(1.0);
846 rotatorComboBox->setCurrentText("--");
847 focusComboBox->setCurrentText("--");
848 filterComboBox->setCurrentText("--");
849 cameraComboBox->setCurrentText("--");
850 guiderComboBox->setCurrentText("--");
851 removeB->setEnabled(false);
852 trainConfigBox->setEnabled(false);
853 m_Persistent = true;
854 return false;
855}
856
857////////////////////////////////////////////////////////////////////////////
858///
859////////////////////////////////////////////////////////////////////////////
860void OpticalTrainManager::openEditor(const QString &name)
861{
862 selectOpticalTrain(name);
863 QList<QListWidgetItem*> matches = trainNamesList->findItems(name, Qt::MatchExactly);
864 if (matches.count() > 0)
865 trainNamesList->setCurrentItem(matches.first());
866 emit configurationRequested(true);
867 show();
868}
869
870////////////////////////////////////////////////////////////////////////////
871///
872////////////////////////////////////////////////////////////////////////////
873bool OpticalTrainManager::getGenericDevice(const QString &train, Role role, QSharedPointer<ISD::GenericDevice> &generic)
874{
875 for (auto &oneTrain : m_OpticalTrains)
876 {
877 if (oneTrain["name"].toString() == train)
878 {
879 switch (role)
880 {
881 case Mount:
882 return INDIListener::findDevice(oneTrain["mount"].toString(), generic);
883 case Camera:
884 return INDIListener::findDevice(oneTrain["camera"].toString(), generic);
885 case Rotator:
886 return INDIListener::findDevice(oneTrain["rotator"].toString(), generic);
887 case GuideVia:
888 return INDIListener::findDevice(oneTrain["guider"].toString(), generic);
889 case DustCap:
890 return INDIListener::findDevice(oneTrain["dustcap"].toString(), generic);
891 case FilterWheel:
892 return INDIListener::findDevice(oneTrain["filterwheel"].toString(), generic);
893 case Focuser:
894 return INDIListener::findDevice(oneTrain["focuser"].toString(), generic);
895 case LightBox:
896 return INDIListener::findDevice(oneTrain["lightbox"].toString(), generic);
897 case Dome:
898 {
899 auto devices = INDIListener::devicesByInterface(INDI::BaseDevice::DOME_INTERFACE);
900 if (!devices.empty())
901 {
902 generic = devices[0];
903 return true;
904 }
905 else
906 return false;
907 }
908 break;
909 case Weather:
910 {
911 auto devices = INDIListener::devicesByInterface(INDI::BaseDevice::WEATHER_INTERFACE);
912 if (!devices.empty())
913 {
914 generic = devices[0];
915 return true;
916 }
917 else
918 return false;
919 }
920 break;
921 case GPS:
922 {
923 auto devices = INDIListener::devicesByInterface(INDI::BaseDevice::GPS_INTERFACE);
924 if (!devices.empty())
925 {
926 generic = devices[0];
927 return true;
928 }
929 else
930 return false;
931 }
932 default:
933 break;
934 }
935 }
936 }
937
938 return false;
939}
940
941////////////////////////////////////////////////////////////////////////////
942///
943////////////////////////////////////////////////////////////////////////////
944ISD::Mount *OpticalTrainManager::getMount(const QString &name)
945{
946 for (auto &oneTrain : m_OpticalTrains)
947 {
948 if (oneTrain["name"].toString() == name)
949 {
951 if (INDIListener::findDevice(oneTrain["mount"].toString(), generic))
952 return generic->getMount();
953 }
954 }
955
956 return nullptr;
957}
958
959////////////////////////////////////////////////////////////////////////////
960///
961////////////////////////////////////////////////////////////////////////////
962ISD::DustCap *OpticalTrainManager::getDustCap(const QString &name)
963{
964 for (auto &oneTrain : m_OpticalTrains)
965 {
966 if (oneTrain["name"].toString() == name)
967 {
969 if (INDIListener::findDevice(oneTrain["dustcap"].toString(), generic))
970 return generic->getDustCap();
971 }
972 }
973
974 return nullptr;
975}
976
977////////////////////////////////////////////////////////////////////////////
978///
979////////////////////////////////////////////////////////////////////////////
980ISD::LightBox *OpticalTrainManager::getLightBox(const QString &name)
981{
982 for (auto &oneTrain : m_OpticalTrains)
983 {
984 if (oneTrain["name"].toString() == name)
985 {
987 if (INDIListener::findDevice(oneTrain["lightbox"].toString(), generic))
988 return generic->getLightBox();
989 }
990 }
991
992 return nullptr;
993}
994
995////////////////////////////////////////////////////////////////////////////
996///
997////////////////////////////////////////////////////////////////////////////
998QJsonObject OpticalTrainManager::getScope(const QString &name)
999{
1000 QJsonObject oneOpticalElement;
1001 for (auto &oneTrain : m_OpticalTrains)
1002 {
1003 if (oneTrain["name"].toString() == name)
1004 {
1005 if (KStars::Instance()->data()->userdb()->getOpticalElementByName(oneTrain["scope"].toString(), oneOpticalElement))
1006 return oneOpticalElement;
1007 }
1008 }
1009
1010 return oneOpticalElement;
1011}
1012
1013////////////////////////////////////////////////////////////////////////////
1014///
1015////////////////////////////////////////////////////////////////////////////
1016double OpticalTrainManager::getReducer(const QString &name)
1017{
1018 for (auto &oneTrain : m_OpticalTrains)
1019 {
1020 if (oneTrain["name"].toString() == name)
1021 return oneTrain["reducer"].toDouble();
1022 }
1023
1024 return 1;
1025}
1026
1027////////////////////////////////////////////////////////////////////////////
1028///
1029////////////////////////////////////////////////////////////////////////////
1030ISD::Rotator *OpticalTrainManager::getRotator(const QString &name)
1031{
1032 for (auto &oneTrain : m_OpticalTrains)
1033 {
1034 if (oneTrain["name"].toString() == name)
1035 {
1037 if (INDIListener::findDevice(oneTrain["rotator"].toString(), generic))
1038 return generic->getRotator();
1039 }
1040 }
1041
1042 return nullptr;
1043}
1044
1045////////////////////////////////////////////////////////////////////////////
1046///
1047////////////////////////////////////////////////////////////////////////////
1048ISD::Focuser *OpticalTrainManager::getFocuser(const QString &name)
1049{
1050 for (auto &oneTrain : m_OpticalTrains)
1051 {
1052 if (oneTrain["name"].toString() == name)
1053 {
1055 if (INDIListener::findDevice(oneTrain["focuser"].toString(), generic))
1056 return generic->getFocuser();
1057 }
1058 }
1059
1060 return nullptr;
1061}
1062
1063////////////////////////////////////////////////////////////////////////////
1064///
1065////////////////////////////////////////////////////////////////////////////
1066ISD::FilterWheel *OpticalTrainManager::getFilterWheel(const QString &name)
1067{
1068 for (auto &oneTrain : m_OpticalTrains)
1069 {
1070 if (oneTrain["name"].toString() == name)
1071 {
1073 if (INDIListener::findDevice(oneTrain["filterwheel"].toString(), generic))
1074 return generic->getFilterWheel();
1075 }
1076 }
1077
1078 return nullptr;
1079}
1080
1081////////////////////////////////////////////////////////////////////////////
1082///
1083////////////////////////////////////////////////////////////////////////////
1084ISD::Camera *OpticalTrainManager::getCamera(const QString &name)
1085{
1086 for (auto &oneTrain : m_OpticalTrains)
1087 {
1088 if (oneTrain["name"].toString() == name)
1089 {
1091 if (INDIListener::findDevice(oneTrain["camera"].toString(), generic))
1092 return generic->getCamera();
1093 }
1094 }
1095
1096 return nullptr;
1097}
1098
1099////////////////////////////////////////////////////////////////////////////
1100///
1101////////////////////////////////////////////////////////////////////////////
1102ISD::Guider *OpticalTrainManager::getGuider(const QString &name)
1103{
1104 for (auto &oneTrain : m_OpticalTrains)
1105 {
1106 if (oneTrain["name"].toString() == name)
1107 {
1109 if (INDIListener::findDevice(oneTrain["guider"].toString(), generic))
1110 return generic->getGuider();
1111 }
1112 }
1113
1114 return nullptr;
1115}
1116
1117////////////////////////////////////////////////////////////////////////////
1118///
1119////////////////////////////////////////////////////////////////////////////
1120ISD::AdaptiveOptics *OpticalTrainManager::getAdaptiveOptics(const QString &name)
1121{
1122 // FIXME not implmeneted yet.
1123 // Need to add to database later
1124 for (auto &oneTrain : m_OpticalTrains)
1125 {
1126 if (oneTrain["name"].toString() == name)
1127 {
1129 if (INDIListener::findDevice(oneTrain["adaptiveoptics"].toString(), generic))
1130 return generic->getAdaptiveOptics();
1131 }
1132 }
1133
1134 return nullptr;
1135}
1136
1137////////////////////////////////////////////////////////////////////////////
1138///
1139////////////////////////////////////////////////////////////////////////////
1140const QVariantMap OpticalTrainManager::getOpticalTrain(uint8_t id) const
1141{
1142 for (auto &oneTrain : m_OpticalTrains)
1143 {
1144 if (oneTrain["id"].toInt() == id)
1145 return oneTrain;
1146 }
1147
1148 return QVariantMap();
1149}
1150
1151////////////////////////////////////////////////////////////////////////////
1152///
1153////////////////////////////////////////////////////////////////////////////
1154bool OpticalTrainManager::exists(uint8_t id) const
1155{
1156 for (auto &oneTrain : m_OpticalTrains)
1157 {
1158 if (oneTrain["id"].toInt() == id)
1159 return true;
1160 }
1161
1162 return false;
1163}
1164
1165////////////////////////////////////////////////////////////////////////////
1166///
1167////////////////////////////////////////////////////////////////////////////
1168const QVariantMap OpticalTrainManager::getOpticalTrain(const QString &name) const
1169{
1170 for (auto &oneTrain : m_OpticalTrains)
1171 {
1172 if (oneTrain["name"].toString() == name)
1173 return oneTrain;
1174 }
1175
1176 return QVariantMap();
1177}
1178
1179////////////////////////////////////////////////////////////////////////////
1180///
1181////////////////////////////////////////////////////////////////////////////
1182void OpticalTrainManager::refreshTrains()
1183{
1184 refreshModel();
1185 emit updated();
1186}
1187
1188////////////////////////////////////////////////////////////////////////////
1189///
1190////////////////////////////////////////////////////////////////////////////
1191void OpticalTrainManager::refreshOpticalElements()
1192{
1193 m_ScopeNames = KStars::Instance()->data()->userdb()->getOpticalElementNames();
1194 syncDelegatesToDevices();
1195}
1196
1197////////////////////////////////////////////////////////////////////////////
1198///
1199////////////////////////////////////////////////////////////////////////////
1200int OpticalTrainManager::id(const QString &name) const
1201{
1202 for (auto &oneTrain : m_OpticalTrains)
1203 {
1204 if (oneTrain["name"].toString() == name)
1205 return oneTrain["id"].toUInt();
1206 }
1207
1208 return -1;
1209}
1210
1211////////////////////////////////////////////////////////////////////////////
1212///
1213////////////////////////////////////////////////////////////////////////////
1214QString OpticalTrainManager::name(int id) const
1215{
1216 for (auto &oneTrain : m_OpticalTrains)
1217 {
1218 if (oneTrain["id"].toInt() == id)
1219 return oneTrain["name"].toString();
1220 }
1221
1222 return QString();
1223}
1224
1225////////////////////////////////////////////////////////////////////////////
1226///
1227////////////////////////////////////////////////////////////////////////////
1228void OpticalTrainManager::checkMissingDevices()
1229{
1230 // Double check the sanity of the train. If devices are added or missing, then we need to show it to alert the user.
1231 auto devices = getMissingDevices();
1232 if (!devices.empty())
1233 {
1234 if (devices.count() == 1)
1235 {
1236 KSNotification::event(QLatin1String("IndiServerMessage"),
1237 i18n("Missing device detected (%1). Please reconfigure the optical trains before proceeding any further.",
1238 devices.first()),
1239 KSNotification::General, KSNotification::Warn);
1240 }
1241 else
1242 {
1243 KSNotification::event(QLatin1String("IndiServerMessage"),
1244 i18n("Missing devices detected (%1). Please reconfigure the optical trains before proceeding any further.",
1245 devices.join(", ")),
1246 KSNotification::General, KSNotification::Warn);
1247 }
1248 show();
1249 raise();
1250 emit configurationRequested(true);
1251 }
1252}
1253
1254////////////////////////////////////////////////////////////////////////////
1255///
1256////////////////////////////////////////////////////////////////////////////
1257QStringList OpticalTrainManager::getMissingDevices() const
1258{
1259 auto missing = QStringList();
1260 for (auto &oneTrain : m_OpticalTrains)
1261 {
1262 auto mount = oneTrain["mount"].toString();
1263 if (mount != "--" && m_MountNames.contains(mount) == false)
1264 missing << mount;
1265
1266 auto camera = oneTrain["camera"].toString();
1267 if (camera != "--" && m_CameraNames.contains(camera) == false)
1268 missing << camera;
1269
1270 auto dustcap = oneTrain["dustcap"].toString();
1271 if (dustcap != "--" && m_DustCapNames.contains(dustcap) == false)
1272 missing << dustcap;
1273
1274 auto lightbox = oneTrain["lightbox"].toString();
1275 if (lightbox != "--" && m_LightBoxNames.contains(lightbox) == false)
1276 missing << lightbox;
1277
1278 auto focuser = oneTrain["focuser"].toString();
1279 if (focuser != "--" && m_FocuserNames.contains(focuser) == false)
1280 missing << focuser;
1281
1282 auto filterwheel = oneTrain["filterwheel"].toString();
1283 if (filterwheel != "--" && m_FilterWheelNames.contains(filterwheel) == false)
1284 missing << filterwheel;
1285
1286 auto guider = oneTrain["guider"].toString();
1287 if (guider != "--" && m_GuiderNames.contains(guider) == false)
1288 missing << guider;
1289
1290 }
1291
1292 return missing;
1293}
1294
1295////////////////////////////////////////////////////////////////////////////
1296///
1297////////////////////////////////////////////////////////////////////////////
1298void Ekos::OpticalTrainManager::updateOpticalTrainValue(QComboBox *cb, const QString &element)
1299{
1300 if (trainNamesList->currentItem() != nullptr && m_Persistent == true)
1301 setOpticalTrainValue(trainNamesList->currentItem()->text(), element, cb->currentText());
1302}
1303
1304////////////////////////////////////////////////////////////////////////////
1305///
1306////////////////////////////////////////////////////////////////////////////
1307void OpticalTrainManager::updateOpticalTrainValue(double value, const QString &element)
1308{
1309 if (trainNamesList->currentItem() != nullptr && m_Persistent == true)
1310 setOpticalTrainValue(trainNamesList->currentItem()->text(), element, value);
1311
1312}
1313
1314////////////////////////////////////////////////////////////////////////////
1315/// Reset optical train to default values.
1316////////////////////////////////////////////////////////////////////////////
1317void OpticalTrainManager::reset()
1318{
1319 if (m_CurrentOpticalTrain != nullptr)
1320 {
1321 auto id = m_CurrentOpticalTrain->value("id");
1322 auto name = m_CurrentOpticalTrain->value("name");
1323 int row = trainNamesList->currentRow();
1324 m_CurrentOpticalTrain->clear();
1325
1326 m_CurrentOpticalTrain->insert("id", id);
1327 m_CurrentOpticalTrain->insert("name", name);
1328 m_CurrentOpticalTrain->insert("mount", "--");
1329 m_CurrentOpticalTrain->insert("camera", "--");
1330 m_CurrentOpticalTrain->insert("rotator", "--");
1331 m_CurrentOpticalTrain->insert("guider", "--");
1332 m_CurrentOpticalTrain->insert("dustcap", "--");
1333 m_CurrentOpticalTrain->insert("scope", "--");
1334 m_CurrentOpticalTrain->insert("filterwheel", "--");
1335 m_CurrentOpticalTrain->insert("focuser", "--");
1336 m_CurrentOpticalTrain->insert("reducer", 1);
1337 m_CurrentOpticalTrain->insert("lightbox", "--");
1338
1339 KStarsData::Instance()->userdb()->UpdateOpticalTrain(*m_CurrentOpticalTrain, id.toInt());
1340 refreshTrains();
1341 selectOpticalTrain(name.toString());
1342 trainNamesList->setCurrentRow(row);
1343 }
1344}
1345
1346}
AdaptiveOptics class handles control of INDI AdaptiveOptics devices.
Camera class controls an INDI Camera device.
Definition indicamera.h:44
Handles operation of a remotely controlled dust cover cap.
Definition indidustcap.h:25
Focuser class handles control of INDI focuser devices.
Definition indifocuser.h:21
Handles operation of a remotely controlled light box.
device handle controlling Mounts.
Definition indimount.h:29
Rotator class handles control of INDI Rotator devices.
Definition indirotator.h:20
bool AddOpticalTrain(const QVariantMap &oneTrain)
Add a new optical train to the database.
Definition ksuserdb.cpp:947
bool GetOpticalTrains(uint32_t profileID, QList< QVariantMap > &opticalTrains)
Populate the reference passed with all optical trains.
bool UpdateOpticalTrain(const QVariantMap &oneTrain, int id)
Update an existing optical train.
Definition ksuserdb.cpp:982
KSUserDB * userdb()
Definition kstarsdata.h:215
This is the main window for KStars.
Definition kstars.h:91
static KStars * Instance()
Definition kstars.h:123
KStarsData * data() const
Definition kstars.h:135
Information on telescope used in observation.
Definition scope.h:18
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:79
QString name(GameStandardAction id)
KIOCORE_EXPORT SimpleJob * mount(bool ro, const QByteArray &fstype, const QString &dev, const QString &point, JobFlags flags=DefaultFlags)
void clicked(bool checked)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void currentIndexChanged(int index)
void finished(int result)
void valueChanged(double d)
QVariantMap toVariantMap() const const
void append(QList< T > &&value)
iterator begin()
void clear()
qsizetype count() const const
bool empty() const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
T & first()
bool isEmpty() const const
void currentRowChanged(int currentRow)
void itemChanged(QListWidgetItem *item)
void itemClicked(QListWidgetItem *item)
Qt::ItemFlags flags() const const
void setFlags(Qt::ItemFlags flags)
QString text() const const
QSqlDatabase database(const QString &connectionName, bool open)
int count() const const
QString fieldName(int index) const const
QVariant value(const QString &name) const const
QString arg(Args &&... args) const const
void clear()
bool isEmpty() const const
ItemIsEditable
MatchExactly
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:59:51 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.