1 /*
2     SPDX-FileCopyrightText: 2004 Jason Harris <kstars@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "kswizard.h"
8 
9 #include "geolocation.h"
10 #include "kspaths.h"
11 #include "kstars.h"
12 #include "kstarsdata.h"
13 #include "ksnotification.h"
14 #include "ksutils.h"
15 #include "Options.h"
16 #include "widgets/dmsbox.h"
17 
18 #include <kns3/downloaddialog.h>
19 
20 #include <QDesktopServices>
21 #include <QFile>
22 #include <QLineEdit>
23 #include <QPixmap>
24 #include <QPushButton>
25 #include <QStackedWidget>
26 #include <QStandardPaths>
27 
28 namespace
29 {
hasPrefix(QString str,QString prefix)30 bool hasPrefix(QString str, QString prefix)
31 {
32     if (prefix.isEmpty())
33         return true;
34     return str.startsWith(prefix, Qt::CaseInsensitive);
35 }
36 }
37 
WizWelcomeUI(QWidget * parent)38 WizWelcomeUI::WizWelcomeUI(QWidget *parent) : QFrame(parent)
39 {
40     setupUi(this);
41 }
42 
WizLocationUI(QWidget * parent)43 WizLocationUI::WizLocationUI(QWidget *parent) : QFrame(parent)
44 {
45     setupUi(this);
46 }
47 
WizDownloadUI(QWidget * parent)48 WizDownloadUI::WizDownloadUI(QWidget *parent) : QFrame(parent)
49 {
50     setupUi(this);
51 }
52 
53 #ifdef Q_OS_OSX
WizDataUI(QWidget * parent)54 WizDataUI::WizDataUI(QWidget *parent) : QFrame(parent)
55 {
56     setupUi(this);
57 }
58 #endif
59 
KSWizard(QWidget * parent)60 KSWizard::KSWizard(QWidget *parent) : QDialog(parent)
61 {
62     wizardStack = new QStackedWidget(this);
63     adjustSize();
64 
65     setWindowTitle(i18nc("@title:window", "Startup Wizard"));
66 
67     QVBoxLayout *mainLayout = new QVBoxLayout;
68     mainLayout->addWidget(wizardStack);
69     setLayout(mainLayout);
70 
71     buttonBox = new QDialogButtonBox(Qt::Horizontal);
72     nextB     = new QPushButton(i18n("&Next >"));
73     nextB->setDefault(true);
74     backB = new QPushButton(i18n("< &Back"));
75     backB->setEnabled(false);
76     completeB = new QPushButton(i18n("Done"));
77 
78     buttonBox->addButton(backB, QDialogButtonBox::ActionRole);
79     buttonBox->addButton(nextB, QDialogButtonBox::ActionRole);
80     buttonBox->addButton(completeB, QDialogButtonBox::AcceptRole);
81     completeB->setVisible(false);
82 
83     mainLayout->addWidget(buttonBox);
84 
85     welcome = new WizWelcomeUI(wizardStack);
86 #ifdef Q_OS_OSX
87     data       = new WizDataUI(wizardStack);
88 #endif
89     location                = new WizLocationUI(wizardStack);
90     WizDownloadUI *download = new WizDownloadUI(wizardStack);
91 
92     wizardStack->addWidget(welcome);
93 #ifdef Q_OS_OSX
94     wizardStack->addWidget(data);
95 #endif
96     wizardStack->addWidget(location);
97     wizardStack->addWidget(download);
98     wizardStack->setCurrentWidget(welcome);
99 
100     //Load images into banner frames.
101     QPixmap im;
102     if (im.load(KSPaths::locate(QStandardPaths::AppDataLocation, "wzstars.png")))
103         welcome->Banner->setPixmap(im);
104     else if (im.load(QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars").absolutePath() +
105                      "/wzstars.png"))
106         welcome->Banner->setPixmap(im);
107     if (im.load(KSPaths::locate(QStandardPaths::AppDataLocation, "wzgeo.png")))
108         location->Banner->setPixmap(im);
109     else if (im.load(QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars").absolutePath() + "/wzgeo.png"))
110         location->Banner->setPixmap(im);
111     if (im.load(KSPaths::locate(QStandardPaths::AppDataLocation, "wzdownload.png")))
112         download->Banner->setPixmap(im);
113     else if (im.load(QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars").absolutePath() +
114                      "/wzdownload.png"))
115         download->Banner->setPixmap(im);
116 
117 #ifdef Q_OS_OSX
118     if (im.load(KSPaths::locate(QStandardPaths::AppDataLocation, "wzdownload.png")))
119         data->Banner->setPixmap(im);
120     else if (im.load(QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars").absolutePath() +
121                      "/wzdownload.png"))
122         data->Banner->setPixmap(im);
123 
124     data->dataPath->setText(
125         QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString(), QStandardPaths::LocateDirectory) +
126         "kstars");
127     slotUpdateDataButtons();
128 
129     connect(data->copyKStarsData, SIGNAL(clicked()), this, SLOT(slotOpenOrCopyKStarsDataDirectory()));
130     connect(data->installGSC, SIGNAL(clicked()), this, SLOT(slotInstallGSC()));
131 
132     gscMonitor = new QProgressIndicator(data);
133     data->GSCLayout->addWidget(gscMonitor);
134     data->downloadProgress->setValue(0);
135 
136     data->downloadProgress->setEnabled(false);
137     data->downloadProgress->setVisible(false);
138 
139     data->gscInstallCancel->setVisible(false);
140     data->gscInstallCancel->setEnabled(false);
141 
142 #endif
143 
144     //connect signals/slots
145     connect(nextB, SIGNAL(clicked()), this, SLOT(slotNextPage()));
146     connect(backB, SIGNAL(clicked()), this, SLOT(slotPrevPage()));
147     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
148     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
149 
150     connect(location->CityListBox, SIGNAL(itemSelectionChanged()), this, SLOT(slotChangeCity()));
151     connect(location->CityFilter, SIGNAL(textChanged(QString)), this, SLOT(slotFilterCities()));
152     connect(location->ProvinceFilter, SIGNAL(textChanged(QString)), this, SLOT(slotFilterCities()));
153     connect(location->CountryFilter, SIGNAL(textChanged(QString)), this, SLOT(slotFilterCities()));
154     connect(download->DownloadButton, SIGNAL(clicked()), this, SLOT(slotDownload()));
155 
156     //Initialize Geographic Location page
157     if (KStars::Instance())
158         initGeoPage();
159 }
160 
setButtonsEnabled()161 void KSWizard::setButtonsEnabled()
162 {
163     nextB->setEnabled(wizardStack->currentIndex() < wizardStack->count() - 1);
164     backB->setEnabled(wizardStack->currentIndex() > 0);
165     completeB->setVisible(wizardStack->currentIndex() == wizardStack->count() - 1);
166 
167 #ifdef Q_OS_OSX
168     if ((wizardStack->currentWidget() == data) && (!dataDirExists()))
169     {
170         nextB->setEnabled(false);
171     }
172 #endif
173 }
174 
slotNextPage()175 void KSWizard::slotNextPage()
176 {
177     wizardStack->setCurrentIndex(wizardStack->currentIndex() + 1);
178     setButtonsEnabled();
179 }
180 
slotPrevPage()181 void KSWizard::slotPrevPage()
182 {
183     wizardStack->setCurrentIndex(wizardStack->currentIndex() - 1);
184     setButtonsEnabled();
185 }
186 
initGeoPage()187 void KSWizard::initGeoPage()
188 {
189     KStarsData *data = KStarsData::Instance();
190     location->LongBox->setReadOnly(true);
191     location->LatBox->setReadOnly(true);
192 
193     //Populate the CityListBox
194     //flag the ID of the current City
195     foreach (GeoLocation *loc, data->getGeoList())
196     {
197         location->CityListBox->addItem(loc->fullName());
198         filteredCityList.append(loc);
199         if (loc->fullName() == data->geo()->fullName())
200         {
201             Geo = loc;
202         }
203     }
204 
205     //Sort alphabetically
206     location->CityListBox->sortItems();
207     //preset to current city
208     QList<QListWidgetItem*> locations = location->CityListBox->findItems(QString(data->geo()->fullName()), Qt::MatchExactly);
209     if (locations.isEmpty() == false)
210         location->CityListBox->setCurrentItem(locations[0]);
211 }
212 
slotChangeCity()213 void KSWizard::slotChangeCity()
214 {
215     if (location->CityListBox->currentItem())
216     {
217         for (auto &city : filteredCityList)
218         {
219             if (city->fullName() == location->CityListBox->currentItem()->text())
220             {
221                 Geo = city;
222                 break;
223             }
224         }
225         location->LongBox->showInDegrees(Geo->lng());
226         location->LatBox->showInDegrees(Geo->lat());
227     }
228 }
229 
slotFilterCities()230 void KSWizard::slotFilterCities()
231 {
232     location->CityListBox->clear();
233     //Do NOT delete members of filteredCityList!
234     filteredCityList.clear();
235 
236     foreach (GeoLocation *loc, KStarsData::Instance()->getGeoList())
237     {
238         if (hasPrefix(loc->translatedName(), location->CityFilter->text()) &&
239                 hasPrefix(loc->translatedCountry(), location->CountryFilter->text()) &&
240                 hasPrefix(loc->translatedProvince(), location->ProvinceFilter->text()))
241         {
242             location->CityListBox->addItem(loc->fullName());
243             filteredCityList.append(loc);
244         }
245     }
246     location->CityListBox->sortItems();
247 
248     if (location->CityListBox->count() > 0) // set first item in list as selected
249         location->CityListBox->setCurrentItem(location->CityListBox->item(0));
250 }
251 
slotDownload()252 void KSWizard::slotDownload()
253 {
254     KNS3::DownloadDialog dlg;
255     dlg.exec();
256 }
257 
slotOpenOrCopyKStarsDataDirectory()258 void KSWizard::slotOpenOrCopyKStarsDataDirectory()
259 {
260 #ifdef Q_OS_OSX
261     QString dataLocation =
262         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
263     if (dataLocation.isEmpty())
264     {
265         QDir dataSourceDir = QDir(QCoreApplication::applicationDirPath() + "/../Resources/kstars").absolutePath();
266         if (! dataSourceDir.exists()) //If there is no default data directory in the app bundle
267         {
268             KSNotification::sorry(i18n("There was no default data directory found in the app bundle."));
269             return;
270         }
271         QDir writableDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation));
272         writableDir.mkpath(".");
273         dataLocation =
274             QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
275         if (dataLocation.isEmpty()) //If there *still* is not a kstars data directory
276         {
277             KSNotification::sorry(i18n("There was a problem creating the data directory ~/Library/Application Support/."));
278             return;
279         }
280         KSUtils::copyRecursively(dataSourceDir.absolutePath(), dataLocation);
281         //This will update the next, ok, and copy kstars dir buttons.
282         slotUpdateDataButtons();
283     }
284     else
285     {
286         QUrl path = QUrl::fromLocalFile(
287                         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory));
288         QDesktopServices::openUrl(path);
289     }
290 
291 #endif
292 }
293 
slotInstallGSC()294 void KSWizard::slotInstallGSC()
295 {
296 #ifdef Q_OS_OSX
297 
298     QNetworkAccessManager *manager = new QNetworkAccessManager();
299 
300     QString location =
301         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
302     gscZipPath            = location + "/gsc.zip";
303 
304     data->downloadProgress->setVisible(true);
305     data->downloadProgress->setEnabled(true);
306 
307     data->gscInstallCancel->setVisible(true);
308     data->gscInstallCancel->setEnabled(true);
309 
310     QString gscURL = "http://www.indilib.org/jdownloads/Mac/gsc.zip";
311 
312     QNetworkReply *response = manager->get(QNetworkRequest(QUrl(gscURL)));
313 
314     QMetaObject::Connection *cancelConnection = new QMetaObject::Connection();
315     QMetaObject::Connection *replyConnection = new QMetaObject::Connection();
316     QMetaObject::Connection *percentConnection = new QMetaObject::Connection();
317 
318     *percentConnection = connect(response, &QNetworkReply::downloadProgress,
319                                  [ = ](qint64 bytesReceived, qint64 bytesTotal)
320     {
321         data->downloadProgress->setValue(bytesReceived);
322         data->downloadProgress->setMaximum(bytesTotal);
323     });
324 
325     *cancelConnection = connect(data->gscInstallCancel, &QPushButton::clicked,
326                                 [ = ]()
327     {
328         qDebug() << "Download Cancelled.";
329 
330         if(cancelConnection)
331             disconnect(*cancelConnection);
332         if(replyConnection)
333             disconnect(*replyConnection);
334 
335         if(response)
336         {
337             response->abort();
338             response->deleteLater();
339         }
340 
341         data->downloadProgress->setVisible(false);
342         data->downloadProgress->setEnabled(false);
343 
344         data->gscInstallCancel->setVisible(false);
345         data->gscInstallCancel->setEnabled(false);
346 
347         if(manager)
348             manager->deleteLater();
349 
350     });
351 
352     *replyConnection = connect(response, &QNetworkReply::finished, this,
353                                [ = ]()
354     {
355         if(response)
356         {
357 
358             if(cancelConnection)
359                 disconnect(*cancelConnection);
360             if(replyConnection)
361                 disconnect(*replyConnection);
362 
363             data->downloadProgress->setVisible(false);
364             data->downloadProgress->setEnabled(false);
365 
366             data->gscInstallCancel->setVisible(false);
367             data->gscInstallCancel->setEnabled(false);
368 
369 
370             response->deleteLater();
371             if(manager)
372                 manager->deleteLater();
373             if (response->error() != QNetworkReply::NoError)
374                 return;
375 
376             QByteArray responseData = response->readAll();
377 
378             QFile file(gscZipPath);
379             if (QFileInfo(QFileInfo(file).path()).isWritable())
380             {
381                 if (!file.open(QIODevice::WriteOnly))
382                 {
383                     KSNotification::error( i18n("File write error."));
384                     return;
385                 }
386                 else
387                 {
388                     file.write(responseData.data(), responseData.size());
389                     file.close();
390                     slotExtractGSC();
391                 }
392             }
393             else
394             {
395                 KSNotification::error( i18n("Data folder permissions error."));
396             }
397         }
398     });
399 
400 #endif
401 }
402 
slotExtractGSC()403 void KSWizard::slotExtractGSC()
404 {
405 #ifdef Q_OS_OSX
406     QString location =
407         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
408     QProcess *gscExtractor = new QProcess();
409     connect(gscExtractor, SIGNAL(finished(int)), this, SLOT(slotGSCInstallerFinished()));
410     connect(gscExtractor, SIGNAL(finished(int)), this, SLOT(gscExtractor.deleteLater()));
411     gscExtractor->setWorkingDirectory(location);
412     gscExtractor->start("unzip", QStringList() << "-ao"
413                         << "gsc.zip");
414     gscMonitor->startAnimation();
415 #endif
416 }
417 
slotGSCInstallerFinished()418 void KSWizard::slotGSCInstallerFinished()
419 {
420 #ifdef Q_OS_OSX
421     if (downloadMonitor)
422     {
423         downloadMonitor->stop();
424         delete downloadMonitor;
425     }
426     data->downloadProgress->setEnabled(false);
427     data->downloadProgress->setValue(0);
428     data->downloadProgress->setVisible(false);
429     gscMonitor->stopAnimation();
430     slotUpdateDataButtons();
431     if (QFile(gscZipPath).exists())
432         QFile(gscZipPath).remove();
433 #endif
434 }
435 
436 #ifdef Q_OS_OSX
dataDirExists()437 bool KSWizard::dataDirExists()
438 {
439     QString dataLocation =
440         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars", QStandardPaths::LocateDirectory);
441     return !dataLocation.isEmpty();
442 }
443 
GSCExists()444 bool KSWizard::GSCExists()
445 {
446     QString GSCLocation =
447         QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kstars/gsc", QStandardPaths::LocateDirectory);
448     return !GSCLocation.isEmpty();
449 }
450 
451 #endif
452 
slotUpdateDataButtons()453 void KSWizard::slotUpdateDataButtons()
454 {
455 #ifdef Q_OS_OSX
456     data->dataDirFound->setChecked(dataDirExists());
457     if (dataDirExists())
458     {
459         data->copyKStarsData->setText("Open KStars Data Directory");
460         data->foundFeedback1->setText("The KStars Data Directory called kstars is located at:");
461         data->foundFeedback2->setText("Your data directory was found.  If you have any problems with it, you can "
462                                       "always delete this data directory and KStars will give you a new data "
463                                       "directory.  You can click this button to open the data directory, just be "
464                                       "careful not to delete any important files.");
465     }
466     else
467     {
468         data->foundFeedback1->setText("The KStars Data Directory called kstars should be located at:");
469         data->foundFeedback2->setText("<html><head/><body><p>Your data directory was not found. You can click the "
470                                       "button below to copy a default KStars data directory to the correct location, "
471                                       "or if you have a KStars directory already some place else, you can exit KStars "
472                                       "and copy it to that location yourself.</p></body></html>");
473     }
474     bool ifGSCExists = GSCExists();
475     data->GSCFound->setChecked(ifGSCExists);
476     data->installGSC->setDisabled(ifGSCExists || !dataDirExists());
477     if (ifGSCExists)
478         data->GSCFeedback->setText("GSC was found on your system.  To use it, just take an image in the CCD simulator. "
479                                    "To uninstall, just delete the gsc folder from your data directory above.");
480     setButtonsEnabled();
481 #endif
482 }
483