1 /*
2     SPDX-FileCopyrightText: 2017 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "profilewizard.h"
8 
9 #include <QDesktopServices>
10 #include <QUrl>
11 #include <QTcpSocket>
12 #include <QTimer>
13 
14 #include "kstars.h"
15 #include "auxiliary/kspaths.h"
16 #include "ksnotification.h"
17 #include "qMDNS.h"
18 
ProfileWizard()19 ProfileWizard::ProfileWizard() : QDialog(KStars::Instance())
20 {
21     setupUi(this);
22 
23 #ifdef Q_OS_OSX
24     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
25 #endif
26 
27     QPixmap im;
28     if (im.load(KSPaths::locate(QStandardPaths::AppDataLocation, "wzekos.png")))
29         wizardPix->setPixmap(im);
30 
31     remoteEquipmentSVG->load(QString(":/icons/pi.svg"));
32 
33     connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
34     connect(buttonBox, &QDialogButtonBox::helpRequested, this, []()
35     {
36         KStars::Instance()->appHelpActivated();
37     });
38     connect(buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton * button)
39     {
40         if (button == buttonBox->button(QDialogButtonBox::Reset))
41             reset();
42     });
43 
44     connect(discoverEkosB, &QPushButton::clicked, this,
45             []()
46     {
47         QDesktopServices::openUrl(QUrl("https://www.indilib.org/about/ekos.html"));
48     });
49     connect(videoTutorialsB, &QPushButton::clicked, this,
50             []()
51     {
52         QDesktopServices::openUrl(QUrl("https://www.youtube.com/user/QAstronomy"));
53     });
54     connect(INDIInfoB, &QPushButton::clicked, this,
55             []()
56     {
57         QDesktopServices::openUrl(QUrl("https://indilib.org/about/discover-indi.html"));
58     });
59 
60     // Intro actions
61     connect(introNextB, &QPushButton::clicked, this,
62             [this]()
63     {
64         wizardContainer->setCurrentIndex(EQUIPMENT_LOCATION);
65     });
66 
67     // Equipment Location actions
68     connect(localEquipmentB, SIGNAL(clicked()), this, SLOT(processLocalEquipment()));
69     connect(remoteEquipmentB, &QPushButton::clicked, this,
70             [this]()
71     {
72         wizardContainer->setCurrentIndex(REMOTE_EQUIPMENT_SELECTION);
73     });
74     equipmentStellarmateB->setIcon(QIcon(":/icons/stellarmate.svg"));
75     connect(equipmentStellarmateB, &QPushButton::clicked, this, &ProfileWizard::processRemoteEquipmentSelection);
76 
77     equipmentAtikbaseB->setIcon(QIcon(":/icons/atikbase.svg"));
78     connect(equipmentAtikbaseB, &QPushButton::clicked, this, &ProfileWizard::processRemoteEquipmentSelection);
79 
80     connect(equipmentOtherB, &QPushButton::clicked, this, &ProfileWizard::processRemoteEquipmentSelection);
81 
82     // Remote Equipment Action
83     connect(remoteEquipmentNextB, SIGNAL(clicked()), this, SLOT(processRemoteEquipment()));
84 
85     // StellarMate Equipment Action
86     connect(stellarMateEquipmentNextB, SIGNAL(clicked()), this, SLOT(processPiEquipment()));
87 #ifdef Q_OS_WIN
88     // Auto Detect does not work on Windows yet for some reason.
89     // Packet is sent correctly, but no replies are received from anything on the LAN outside of PC.
90     PiAutoDetectB->setEnabled(false);
91 #else
92     connect(PiAutoDetectB, SIGNAL(clicked()), this, SLOT(detectStellarMate()));
93 #endif
94 
95     // Local Mac
96     connect(useExternalINDIB, SIGNAL(clicked()), this, SLOT(processLocalMac()));
97     connect(useInternalINDIB, SIGNAL(clicked()), this, SLOT(processLocalMac()));
98 
99     // Create Profile
100     connect(createProfileB, SIGNAL(clicked()), this, SLOT(createProfile()));
101 }
102 
reset()103 void ProfileWizard::reset()
104 {
105     useInternalServer   = true;
106     useWebManager       = false;
107     useJoystick         = false;
108     useRemoteAstrometry = false;
109     useSkySafari        = false;
110     useWatchDog         = false;
111     useGuiderType       = INTERNAL_GUIDER;
112 
113     host.clear();
114     port = "7624";
115 
116     wizardContainer->setCurrentIndex(INTRO);
117 }
118 
processLocalEquipment()119 void ProfileWizard::processLocalEquipment()
120 {
121 #if defined(Q_OS_OSX)
122     wizardContainer->setCurrentIndex(MAC_LOCAL);
123 #elif defined(Q_OS_WIN)
124     wizardContainer->setCurrentIndex(WINDOWS_LOCAL);
125 #else
126     useInternalServer = true;
127     useJoystickCheck->setEnabled(true);
128     useRemoteAstrometryCheck->setEnabled(false);
129     useWatchDogCheck->setEnabled(false);
130     useSkySafariCheck->setEnabled(true);
131     wizardContainer->setCurrentIndex(CREATE_PROFILE);
132 #endif
133 }
134 
processRemoteEquipment()135 void ProfileWizard::processRemoteEquipment()
136 {
137     bool portOK = false;
138     remotePortEdit->text().toInt(&portOK);
139 
140     if (portOK == false)
141     {
142         KSNotification::error(i18n("Invalid port."));
143         return;
144     }
145 
146     if (remoteHostEdit->text().isEmpty())
147     {
148         KSNotification::error(i18n("Host name cannot be empty."));
149         return;
150     }
151 
152     useInternalServer = false;
153 
154     host = remoteHostEdit->text();
155     port = remotePortEdit->text();
156 
157     if (webManagerNotSureB->isChecked())
158     {
159         QTcpSocket socket;
160         // Should probably make 8624 configurable too?
161         socket.connectToHost(host, 8624);
162         useWebManager = socket.waitForConnected();
163     }
164     else
165         useWebManager = webManagerYesR->isChecked();
166 
167     useJoystickCheck->setEnabled(true);
168     useRemoteAstrometryCheck->setEnabled(true);
169     useWatchDogCheck->setEnabled(true);
170     useSkySafariCheck->setEnabled(true);
171 
172     wizardContainer->setCurrentIndex(CREATE_PROFILE);
173 }
174 
processPiEquipment()175 void ProfileWizard::processPiEquipment()
176 {
177     if (PiHost->text().isEmpty())
178     {
179         KSNotification::error(i18n("Host name cannot be empty."));
180         return;
181     }
182 
183     host = PiHost->text();
184     port = PiPort->text();
185 
186     useInternalServer = false;
187     useWebManager = true;
188 
189     useJoystickCheck->setEnabled(true);
190     useRemoteAstrometryCheck->setEnabled(true);
191     useWatchDogCheck->setEnabled(true);
192     useSkySafariCheck->setEnabled(true);
193 
194     wizardContainer->setCurrentIndex(CREATE_PROFILE);
195 }
196 
processLocalMac()197 void ProfileWizard::processLocalMac()
198 {
199     QPushButton *button = qobject_cast<QPushButton *>(sender());
200 
201     if (button == nullptr)
202         return;
203 
204     host = "localhost";
205     port = "7624";
206 
207     useInternalServer = (button == useInternalINDIB);
208 
209     useJoystickCheck->setEnabled(false);
210     useRemoteAstrometryCheck->setEnabled(false);
211     useWatchDogCheck->setEnabled(false);
212     useSkySafariCheck->setEnabled(true);
213 
214     wizardContainer->setCurrentIndex(CREATE_PROFILE);
215 }
216 
createProfile()217 void ProfileWizard::createProfile()
218 {
219     if (profileNameEdit->text().isEmpty())
220     {
221         KSNotification::error(i18n("Profile name cannot be empty."));
222         return;
223     }
224 
225     useJoystick         = useJoystickCheck->isEnabled() && useJoystickCheck->isChecked();
226     useWatchDog         = useWatchDogCheck->isEnabled() && useWatchDogCheck->isChecked();
227     useSkySafari        = useSkySafariCheck->isEnabled() && useSkySafariCheck->isChecked();
228     useRemoteAstrometry = useRemoteAstrometryCheck->isEnabled() && useRemoteAstrometryCheck->isChecked();
229     if (useInternalGuiderR->isChecked())
230         useGuiderType = INTERNAL_GUIDER;
231     else if (usePHD2R->isChecked())
232         useGuiderType = PHD2_GUIDER;
233     else
234         useGuiderType = LIN_GUIDER;
235 
236     profileName = profileNameEdit->text();
237 
238     if (useInternalServer)
239     {
240         host.clear();
241         port.clear();
242     }
243 
244     accept();
245 }
246 
selectedAuxDrivers()247 QStringList ProfileWizard::selectedAuxDrivers()
248 {
249     QStringList auxList;
250 
251     if (useJoystick)
252         auxList << "Joystick";
253     if (useWatchDog)
254         auxList << "WatchDog";
255     if (useSkySafari)
256         auxList << "SkySafari";
257     if (useRemoteAstrometry)
258         auxList << "Astrometry";
259 
260     return auxList;
261 }
262 
selectedExternalGuider()263 int ProfileWizard::selectedExternalGuider()
264 {
265     return useGuiderType;
266 }
267 
detectStellarMate()268 void ProfileWizard::detectStellarMate()
269 {
270     stellarMateDetectDialog = new QProgressDialog(this);
271     stellarMateDetectDialog->setMinimum(0);
272     stellarMateDetectDialog->setMaximum(0);
273     stellarMateDetectDialog->setWindowTitle(i18nc("@title:window", "Detecting StellarMate..."));
274     stellarMateDetectDialog->setLabelText(i18n("Please wait while searching for StellarMate..."));
275 
276     stellarMateDetectDialog->show();
277 
278     connect(stellarMateDetectDialog.data(), &QProgressDialog::canceled, [&]()
279     {
280         qMDNS::getInstance()->disconnect();
281     });
282     connect(qMDNS::getInstance(), SIGNAL(hostFound(QHostInfo)), this, SLOT(processHostInfo(QHostInfo)));
283     QTimer::singleShot(120 * 1000, this, SLOT(detectStellarMateTimeout()));
284 
285     qMDNS::getInstance()->lookup("stellarmate");
286 }
287 
processHostInfo(QHostInfo info)288 void ProfileWizard::processHostInfo(QHostInfo info)
289 {
290     PiHost->setText(info.hostName());
291     qMDNS::getInstance()->disconnect();
292     stellarMateDetectDialog->close();
293 }
294 
detectStellarMateTimeout()295 void ProfileWizard::detectStellarMateTimeout()
296 {
297     if (stellarMateDetectDialog->isHidden() == false)
298     {
299         KSNotification::error(i18n("Failed to detect any StellarMate gadget. Make sure it is powered and on the same network."));
300         stellarMateDetectDialog->close();
301     }
302 }
303 
processRemoteEquipmentSelection()304 void ProfileWizard::processRemoteEquipmentSelection()
305 {
306     QToolButton *button = qobject_cast<QToolButton*>(sender());
307     if (!button)
308         wizardContainer->setCurrentIndex(GENERIC_EQUIPMENT);
309     else if (button == equipmentStellarmateB)
310     {
311         PiHost->setText("stellarmate.local");
312         wizardContainer->setCurrentIndex(PI_EQUIPMENT);
313     }
314     else if (button == equipmentAtikbaseB)
315     {
316         PiHost->setText("atikbase.local");
317         wizardContainer->setCurrentIndex(PI_EQUIPMENT);
318     }
319 }
320