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