1 /*
2     SPDX-FileCopyrightText: 2012 Samikshan Bairagya <samikshan@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "wiview.h"
8 
9 #include "kspaths.h"
10 #include "kstars.h"
11 #include "ksnotification.h"
12 #include "modelmanager.h"
13 #include "obsconditions.h"
14 #include "Options.h"
15 #include "skymap.h"
16 #include "skymapcomposite.h"
17 #include "skyobjitem.h"
18 #include "skyobjlistmodel.h"
19 #include "starobject.h"
20 #include "wiequipsettings.h"
21 #include "dialogs/detaildialog.h"
22 
23 #include <klocalizedcontext.h>
24 
25 #include <QGraphicsObject>
26 #include <QNetworkAccessManager>
27 #include <QNetworkReply>
28 #include <QQmlContext>
29 #include <QQuickItem>
30 #include <QQuickView>
31 #include <QStandardPaths>
32 #include <QtConcurrent>
33 
34 #ifdef HAVE_INDI
35 #include <basedevice.h>
36 #include "indi/indilistener.h"
37 #endif
38 
WIView(QWidget * parent)39 WIView::WIView(QWidget *parent) : QWidget(parent)
40 {
41     //These settings are like this just to get it started.
42     int bortle                           = Options::bortleClass();
43     int aperture                         = 100;
44     ObsConditions::Equipment equip       = ObsConditions::Telescope;
45     ObsConditions::TelescopeType telType = ObsConditions::Reflector;
46 
47     m_Obs = new ObsConditions(bortle, aperture, equip, telType);
48 
49     m_ModManager.reset(new ModelManager(m_Obs));
50 
51     m_BaseView = new QQuickView();
52 
53     ///To use i18n() instead of qsTr() in qml/wiview.qml for translation
54     //KDeclarative kd;
55     // kd.setDeclarativeEngine(m_BaseView->engine());
56     //kd.initialize();
57     //kd.setupBindings();
58 
59     m_Ctxt = m_BaseView->rootContext();
60 
61     m_Ctxt->setContextProperty(
62         "soListModel",
63         m_ModManager
64         ->getTempModel()); // This is to avoid an error saying it doesn't exist.
65 
66     ///Use instead of KDeclarative
67     m_Ctxt->setContextObject(new KLocalizedContext(m_BaseView));
68 
69 #if 0
70     QString WI_Location;
71 #if defined(Q_OS_OSX)
72     WI_Location = QCoreApplication::applicationDirPath() + "/../Resources/kstars/tools/whatsinteresting/qml/wiview.qml";
73     if (!QFileInfo(WI_Location).exists())
74         WI_Location = KSPaths::locate(QStandardPaths::AppDataLocation, "tools/whatsinteresting/qml/wiview.qml");
75 #elif defined(Q_OS_WIN)
76     WI_Location = KSPaths::locate(QStandardPaths::GenericDataLocation, "tools/whatsinteresting/qml/wiview.qml");
77 #else
78     WI_Location = KSPaths::locate(QStandardPaths::AppDataLocation, "tools/whatsinteresting/qml/wiview.qml");
79 #endif
80 
81     m_BaseView->setSource(QUrl::fromLocalFile(WI_Location));
82 #endif
83 
84     m_BaseView->setSource(QUrl("qrc:/qml/whatisinteresting/wiview.qml"));
85 
86     m_BaseObj = m_BaseView->rootObject();
87 
88     m_ProgressBar = m_BaseObj->findChild<QQuickItem *>("progressBar");
89 
90     m_loadingMessage = m_BaseObj->findChild<QQuickItem *>("loadingMessage");
91 
92     m_CategoryTitle = m_BaseObj->findChild<QQuickItem *>(QString("categoryTitle"));
93 
94     m_ViewsRowObj = m_BaseObj->findChild<QQuickItem *>(QString("viewsRowObj"));
95     connect(m_ViewsRowObj, SIGNAL(categorySelected(QString)), this,
96             SLOT(onCategorySelected(QString)));
97     connect(m_ViewsRowObj, SIGNAL(inspectSkyObject(QString)), this,
98             SLOT(inspectSkyObject(QString)));
99 
100     m_SoListObj = m_BaseObj->findChild<QQuickItem *>("soListObj");
101     connect(m_SoListObj, SIGNAL(soListItemClicked(int)), this,
102             SLOT(onSoListItemClicked(int)));
103 
104     m_DetailsViewObj = m_BaseObj->findChild<QQuickItem *>("detailsViewObj");
105 
106     descTextObj = m_DetailsViewObj->findChild<QObject *>("descTextObj");
107     infoBoxText = m_DetailsViewObj->findChild<QObject *>("infoBoxText");
108 
109     m_NextObj = m_BaseObj->findChild<QQuickItem *>("nextObj");
110     connect(m_NextObj, SIGNAL(nextObjClicked()), this, SLOT(onNextObjClicked()));
111     m_PrevObj = m_BaseObj->findChild<QQuickItem *>("prevObj");
112     connect(m_PrevObj, SIGNAL(prevObjClicked()), this, SLOT(onPrevObjClicked()));
113 
114     m_CenterButtonObj = m_BaseObj->findChild<QQuickItem *>("centerButtonObj");
115     connect(m_CenterButtonObj, SIGNAL(centerButtonClicked()), this,
116             SLOT(onCenterButtonClicked()));
117 
118     autoCenterCheckbox = m_DetailsViewObj->findChild<QObject *>("autoCenterCheckbox");
119     autoTrackCheckbox  = m_DetailsViewObj->findChild<QObject *>("autoTrackCheckbox");
120 
121     m_SlewTelescopeButtonObj =
122         m_BaseObj->findChild<QQuickItem *>("slewTelescopeButtonObj");
123     connect(m_SlewTelescopeButtonObj, SIGNAL(slewTelescopeButtonClicked()), this,
124             SLOT(onSlewTelescopeButtonClicked()));
125 
126     m_DetailsButtonObj = m_BaseObj->findChild<QQuickItem *>("detailsButtonObj");
127     connect(m_DetailsButtonObj, SIGNAL(detailsButtonClicked()), this,
128             SLOT(onDetailsButtonClicked()));
129 
130     QObject *settingsIconObj = m_BaseObj->findChild<QQuickItem *>("settingsIconObj");
131     connect(settingsIconObj, SIGNAL(settingsIconClicked()), this,
132             SLOT(onSettingsIconClicked()));
133 
134     inspectIconObj = m_BaseObj->findChild<QQuickItem *>("inspectIconObj");
135     connect(inspectIconObj, SIGNAL(inspectIconClicked(bool)), this,
136             SLOT(onInspectIconClicked(bool)));
137 
138     visibleIconObj = m_BaseObj->findChild<QQuickItem *>("visibleIconObj");
139     connect(visibleIconObj, SIGNAL(visibleIconClicked(bool)), this,
140             SLOT(onVisibleIconClicked(bool)));
141 
142     favoriteIconObj = m_BaseObj->findChild<QQuickItem *>("favoriteIconObj");
143     connect(favoriteIconObj, SIGNAL(favoriteIconClicked(bool)), this,
144             SLOT(onFavoriteIconClicked(bool)));
145 
146     QObject *reloadIconObj = m_BaseObj->findChild<QQuickItem *>("reloadIconObj");
147     connect(reloadIconObj, SIGNAL(reloadIconClicked()), this,
148             SLOT(onReloadIconClicked()));
149 
150     QObject *downloadIconObj = m_BaseObj->findChild<QQuickItem *>("downloadIconObj");
151     connect(downloadIconObj, SIGNAL(downloadIconClicked()), this,
152             SLOT(onUpdateIconClicked()));
153 
154     m_BaseView->setResizeMode(QQuickView::SizeRootObjectToView);
155     m_BaseView->show();
156 
157     // Fix some weird issue with what's interesting panel view under Windows
158     // In Qt 5.9 it content is messed up and there is no way to close the panel
159 #ifdef Q_OS_WIN
160     m_BaseView->setFlags(Qt::WindowCloseButtonHint);
161 #endif
162 
163     connect(KStars::Instance()->map(), SIGNAL(objectClicked(SkyObject *)), this,
164             SLOT(inspectSkyObjectOnClick(SkyObject *)));
165 
166     manager.reset(new QNetworkAccessManager());
167 
168     setProgressBarVisible(true);
169     connect(m_ModManager.get(), SIGNAL(loadProgressUpdated(double)), this,
170             SLOT(updateProgress(double)));
171     connect(m_ModManager.get(), SIGNAL(modelUpdated()), this, SLOT(refreshListView()));
172     m_ViewsRowObj->setProperty("enabled", false);
173 
174     inspectOnClick = false;
175 
176     nightVision = m_BaseObj->findChild<QObject *>("nightVision");
177     //if (Options::darkAppColors())
178     //    nightVision->setProperty("state", "active");
179 }
180 
setNightVisionOn(bool on)181 void WIView::setNightVisionOn(bool on)
182 {
183     if (on)
184         nightVision->setProperty("state", "active");
185     else
186         nightVision->setProperty("state", "");
187 
188     if (m_CurSoItem != nullptr)
189         loadDetailsView(m_CurSoItem, m_CurIndex);
190 }
191 
setProgressBarVisible(bool visible)192 void WIView::setProgressBarVisible(bool visible)
193 {
194     m_ProgressBar->setProperty("visible", visible);
195 }
196 
updateProgress(double value)197 void WIView::updateProgress(double value)
198 {
199     m_ProgressBar->setProperty("value", value);
200 
201     if (value == 1)
202     {
203         setProgressBarVisible(false);
204         m_ViewsRowObj->setProperty("enabled", true);
205         m_loadingMessage->setProperty("state", "");
206     }
207     else
208     {
209         setProgressBarVisible(true);
210         m_loadingMessage->setProperty("state", "loading");
211     }
212 }
213 
updateObservingConditions()214 void WIView::updateObservingConditions()
215 {
216     int bortle = Options::bortleClass();
217 
218     /**
219     NOTE This part of the code dealing with equipment type is presently not required
220     as WI does not differentiate between Telescope and Binoculars. It only needs the
221      aperture of the equipment whichever available. However this is kept as a part of
222     the code as support to be utilised in the future.
223     **/
224     ObsConditions::Equipment equip = ObsConditions::None;
225 
226     if (Options::telescopeCheck() && Options::binocularsCheck())
227         equip = ObsConditions::Both;
228     else if (Options::telescopeCheck())
229         equip = ObsConditions::Telescope;
230     else if (Options::binocularsCheck())
231         equip = ObsConditions::Binoculars;
232 
233     ObsConditions::TelescopeType telType;
234 
235     if (KStars::Instance()->getWIEquipSettings())
236         telType = (equip == ObsConditions::Telescope) ? KStars::Instance()->getWIEquipSettings()->getTelType() :
237                   ObsConditions::Invalid;
238     else
239         telType = ObsConditions::Invalid;
240 
241     int aperture = 100;
242 
243     //This doesn't work correctly, FIXME!!
244     // if(KStars::Instance()->getWIEquipSettings())
245     //    aperture = KStars::Instance()->getWIEquipSettings()->getAperture();
246 
247     if (!m_Obs)
248         m_Obs = new ObsConditions(bortle, aperture, equip, telType);
249     else
250         m_Obs->setObsConditions(bortle, aperture, equip, telType);
251 }
252 
onCategorySelected(QString model)253 void WIView::onCategorySelected(QString model)
254 {
255     m_CurrentObjectListName = model;
256     m_Ctxt->setContextProperty("soListModel",
257                                m_ModManager->returnModel(m_CurrentObjectListName));
258     m_CurIndex = -2;
259     if (!m_ModManager->showOnlyVisibleObjects())
260         visibleIconObj->setProperty("state", "unchecked");
261     if (!m_ModManager->showOnlyFavoriteObjects())
262         favoriteIconObj->setProperty("state", "unchecked");
263 
264     if ((QStringList() << "ngc"
265             << "ic"
266             << "messier"
267             << "sharpless")
268             .contains(model))
269     {
270         QtConcurrent::run(m_ModManager.get(), &ModelManager::loadCatalog, model);
271         return;
272     }
273 
274     updateModel(*m_Obs);
275 }
276 
onSoListItemClicked(int index)277 void WIView::onSoListItemClicked(int index)
278 {
279     SkyObjItem *soitem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem(index);
280     if (soitem)
281         loadDetailsView(soitem, index);
282 }
283 
onNextObjClicked()284 void WIView::onNextObjClicked()
285 {
286     if (!m_CurrentObjectListName.isEmpty())
287     {
288         int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
289         if (modelSize > 0)
290         {
291             SkyObjItem *nextItem =
292                 m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize);
293             loadDetailsView(nextItem, (m_CurIndex + 1) % modelSize);
294         }
295     }
296 }
297 
onPrevObjClicked()298 void WIView::onPrevObjClicked()
299 {
300     if (!m_CurrentObjectListName.isEmpty())
301     {
302         int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
303         if (modelSize > 0)
304         {
305             SkyObjItem *prevItem =
306                 m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize);
307             loadDetailsView(prevItem, (m_CurIndex - 1 + modelSize) % modelSize);
308         }
309     }
310 }
311 
onCenterButtonClicked()312 void WIView::onCenterButtonClicked()
313 {
314     ///Center map on selected sky-object
315     SkyObject *so  = m_CurSoItem->getSkyObject();
316     KStars *kstars = KStars::Instance();
317 
318     if (so)
319     {
320         kstars->map()->setFocusPoint(so);
321         kstars->map()->setFocusObject(so);
322         kstars->map()->setDestination(*kstars->map()->focusPoint());
323         Options::setIsTracking(autoTrackCheckbox->property("checked") == true);
324     }
325 }
326 
onSlewTelescopeButtonClicked()327 void WIView::onSlewTelescopeButtonClicked()
328 {
329     if (KMessageBox::Continue ==
330             KMessageBox::warningContinueCancel(nullptr, "Are you sure you want your telescope to slew to this object?",
331                     i18n("Continue Slew"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
332                     "continue_wi_slew_warning"))
333     {
334 #ifdef HAVE_INDI
335 
336         if (INDIListener::Instance()->size() == 0)
337         {
338             KSNotification::sorry(i18n("KStars did not find any active telescopes."));
339             return;
340         }
341 
342         foreach (ISD::GDInterface *gd, INDIListener::Instance()->getDevices())
343         {
344             INDI::BaseDevice *bd = gd->getBaseDevice();
345 
346             if (gd->getType() != KSTARS_TELESCOPE)
347                 continue;
348 
349             if (bd == nullptr)
350                 continue;
351 
352             if (bd->isConnected() == false)
353             {
354                 KSNotification::error(i18n("Telescope %1 is offline. Please connect and retry again.", gd->getDeviceName()));
355                 return;
356             }
357 
358             ISD::GDSetCommand SlewCMD(INDI_SWITCH, "ON_COORD_SET", "TRACK", ISS_ON, this);
359 
360             gd->setProperty(&SlewCMD);
361             gd->runCommand(INDI_SEND_COORDS, m_CurSoItem->getSkyObject());
362 
363             /// Slew map to selected sky-object
364             onCenterButtonClicked();
365 
366             return;
367         }
368 
369         KSNotification::sorry(i18n("KStars did not find any active telescopes."));
370 
371 #endif
372     }
373 }
374 
onDetailsButtonClicked()375 void WIView::onDetailsButtonClicked()
376 {
377     ///Code taken from WUTDialog::slotDetails()
378     KStars *kstars = KStars::Instance();
379     SkyObject *so  = m_CurSoItem->getSkyObject();
380     if (so)
381     {
382         DetailDialog *detail = new DetailDialog(so, kstars->data()->lt(), kstars->data()->geo(), kstars);
383         detail->exec();
384         delete detail;
385     }
386 }
387 
onSettingsIconClicked()388 void WIView::onSettingsIconClicked()
389 {
390     KStars *kstars = KStars::Instance();
391     kstars->showWISettingsUI();
392 }
393 
onReloadIconClicked()394 void WIView::onReloadIconClicked()
395 {
396     if (!m_CurrentObjectListName.isEmpty())
397     {
398         updateModel(*m_Obs);
399         m_CurIndex = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjIndex(m_CurSoItem);
400     }
401     loadDetailsView(m_CurSoItem, m_CurIndex);
402 }
403 
onVisibleIconClicked(bool visible)404 void WIView::onVisibleIconClicked(bool visible)
405 {
406     m_ModManager->setShowOnlyVisibleObjects(visible);
407     onReloadIconClicked();
408 }
409 
onFavoriteIconClicked(bool favorites)410 void WIView::onFavoriteIconClicked(bool favorites)
411 {
412     m_ModManager->setShowOnlyFavoriteObjects(favorites);
413     onReloadIconClicked();
414 }
415 
onUpdateIconClicked()416 void WIView::onUpdateIconClicked()
417 {
418     QMessageBox mbox;
419     QPushButton *currentObject = mbox.addButton("Current Object", QMessageBox::AcceptRole);
420     QPushButton *missingObjects = nullptr;
421     QPushButton *allObjects = nullptr;
422 
423     mbox.setText("Please choose which object(s) to try to update with Wikipedia data.");
424     if (!m_CurrentObjectListName.isEmpty())
425     {
426         missingObjects = mbox.addButton("Objects with no data", QMessageBox::AcceptRole);
427         allObjects     = mbox.addButton("Entire List", QMessageBox::AcceptRole);
428     }
429     QPushButton *cancel = mbox.addButton("Cancel", QMessageBox::AcceptRole);
430     mbox.setDefaultButton(cancel);
431 
432     mbox.exec();
433     if (mbox.clickedButton() == currentObject)
434     {
435         if (m_CurSoItem != nullptr)
436         {
437             tryToUpdateWikipediaInfo(m_CurSoItem, getWikipediaName(m_CurSoItem));
438         }
439     }
440     else if (mbox.clickedButton() == allObjects || mbox.clickedButton() == missingObjects)
441     {
442         SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName);
443         if (model->rowCount() > 0)
444         {
445             tryToUpdateWikipediaInfoInModel(mbox.clickedButton() == missingObjects);
446         }
447         else
448         {
449             qDebug() << "No Objects in List!";
450         }
451     }
452 }
453 
refreshListView()454 void WIView::refreshListView()
455 {
456     m_Ctxt->setContextProperty("soListModel", nullptr);
457     if (!m_CurrentObjectListName.isEmpty())
458         m_Ctxt->setContextProperty("soListModel", m_ModManager->returnModel(m_CurrentObjectListName));
459     if (m_CurIndex == -2)
460         onSoListItemClicked(0);
461     if (m_CurIndex != -1)
462         m_SoListObj->setProperty("currentIndex", m_CurIndex);
463 }
464 
updateModel(ObsConditions & obs)465 void WIView::updateModel(ObsConditions &obs)
466 {
467     if (!m_CurrentObjectListName.isEmpty())
468     {
469         m_Obs = &obs;
470         m_ModManager->updateModel(m_Obs, m_CurrentObjectListName);
471     }
472 }
473 
inspectSkyObject(const QString & name)474 void WIView::inspectSkyObject(const QString &name)
475 {
476     if (!name.isEmpty() && name != "star")
477     {
478         SkyObject *obj = KStarsData::Instance()->skyComposite()->findByName(name);
479 
480         if (obj)
481             inspectSkyObject(obj);
482     }
483 }
484 
inspectSkyObjectOnClick(SkyObject * obj)485 void WIView::inspectSkyObjectOnClick(SkyObject *obj)
486 {
487     if (inspectOnClick && KStars::Instance()->isWIVisible())
488         inspectSkyObject(obj);
489 }
490 
inspectSkyObject(SkyObject * obj)491 void WIView::inspectSkyObject(SkyObject *obj)
492 {
493     if (!obj)
494         return;
495 
496     if (obj->name() != "star")
497     {
498         m_CurrentObjectListName = "";
499         trackedItem.reset(new SkyObjItem(obj));
500         loadDetailsView(trackedItem.get(), -1);
501         m_BaseObj->setProperty("state", "singleItemSelected");
502         m_CategoryTitle->setProperty("text", "Selected Object");
503     }
504 }
505 
loadDetailsView(SkyObjItem * soitem,int index)506 void WIView::loadDetailsView(SkyObjItem *soitem, int index)
507 {
508     if (soitem == nullptr)
509         return;
510 
511     int modelSize = -1;
512 
513     if (index != -1)
514         modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
515 
516     if (soitem != m_CurSoItem)
517         m_CurSoItem = soitem;
518 
519     m_CurIndex  = index;
520     if (modelSize <= 1)
521     {
522         m_NextObj->setProperty("visible", "false");
523         m_PrevObj->setProperty("visible", "false");
524     }
525     else
526     {
527         SkyObjItem *nextItem =
528             m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize);
529         SkyObjItem *prevItem =
530             m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize);
531 
532         m_NextObj->setProperty("visible", "true");
533         m_PrevObj->setProperty("visible", "true");
534         QObject *nextTextObj = m_NextObj->findChild<QObject *>("nextTextObj");
535 
536         nextTextObj->setProperty("text", nextItem->getName());
537         QObject *prevTextObj = m_PrevObj->findChild<QObject *>("prevTextObj");
538 
539         prevTextObj->setProperty("text", prevItem->getName());
540     }
541 
542     QObject *sonameObj      = m_DetailsViewObj->findChild<QObject *>("sonameObj");
543     QObject *posTextObj     = m_DetailsViewObj->findChild<QObject *>("posTextObj");
544     QObject *detailImage    = m_DetailsViewObj->findChild<QObject *>("detailImage");
545     QObject *detailsTextObj = m_DetailsViewObj->findChild<QObject *>("detailsTextObj");
546 
547     sonameObj->setProperty("text", soitem->getDescName());
548     posTextObj->setProperty("text", soitem->getPosition());
549     detailImage->setProperty("refreshableSource", soitem->getImageURL(false));
550 
551     loadObjectDescription(soitem);
552 
553     infoBoxText->setProperty(
554         "text",
555         "<BR><BR>No Wikipedia information. <BR>  Please try to download it using the orange download button below.");
556     loadObjectInfoBox(soitem);
557 
558     QString summary = soitem->getSummary(false);
559 
560     QString magText;
561     if (soitem->getType() == SkyObjItem::Constellation)
562         magText = xi18n("Magnitude:  --");
563     else
564         magText = xi18n("Magnitude: %1", QLocale().toString(soitem->getMagnitude(), 'f', 2));
565 
566     QString sbText = xi18n("Surface Brightness: %1", soitem->getSurfaceBrightness());
567 
568     QString sizeText = xi18n("Size: %1", soitem->getSize());
569 
570     QString details = summary + "<BR>" + sbText + "<BR>" + magText + "<BR>" + sizeText;
571     detailsTextObj->setProperty("text", details);
572 
573     if (autoCenterCheckbox->property("checked") == true)
574     {
575         QTimer::singleShot(500, this, SLOT(onCenterButtonClicked()));
576     }
577 
578     if (m_CurIndex != -1)
579         m_SoListObj->setProperty("currentIndex", m_CurIndex);
580 }
581 
getWikipediaName(SkyObjItem * soitem)582 QString WIView::getWikipediaName(SkyObjItem *soitem)
583 {
584     if (!soitem)
585         return "";
586 
587     QString name;
588 
589     if (soitem->getName().toLower().startsWith(QLatin1String("m ")))
590         name = soitem->getName().replace("M ", "Messier_").remove(' ');
591     else if (soitem->getName().toLower().startsWith(QLatin1String("ngc")))
592         name = soitem->getName().toLower().replace("ngc", "NGC_").remove(' ');
593     else if (soitem->getName().toLower().startsWith(QLatin1String("ic")))
594         name = soitem->getName().toLower().replace("ic", "IC_").remove(' ');
595     else if (soitem->getType() == SkyObjItem::Constellation)
596     {
597         QStringList words = soitem->getName().split(' ');
598 
599         for (int i = 0; i < words.size(); i++)
600         {
601             QString temp = words.at(i).toLower();
602             temp[0]      = temp[0].toUpper();
603             words.replace(i, temp);
604         }
605         name = words.join("_") + "_(constellation)";
606         if (name.contains("Serpens"))
607             name = "Serpens_(constellation)";
608     }
609     else if (soitem->getTypeName() == i18n("Asteroid"))
610         name = soitem->getName().remove(' ') + "_(asteroid)";
611     else if (soitem->getTypeName() == i18n("Comet"))
612         name = soitem->getLongName();
613     else if (soitem->getType() == SkyObjItem::Planet && soitem->getName() != i18n("Sun") && soitem->getName() != i18n("Moon"))
614         name = soitem->getName().remove(' ') + "_(planet)";
615     else if (soitem->getType() == SkyObjItem::Star)
616     {
617         StarObject *star = dynamic_cast<StarObject *>(soitem->getSkyObject());
618 
619         // The greek name seems to give the most consistent search results for opensearch.
620         name             = star->gname(false).replace(' ', '_');
621         if (name.isEmpty())
622             name = soitem->getName().replace(' ', '_') + "_(star)";
623         name.remove('[').remove(']');
624     }
625     else
626         name = soitem->getName().remove(' ');
627 
628     return name;
629 }
630 
updateWikipediaDescription(SkyObjItem * soitem)631 void WIView::updateWikipediaDescription(SkyObjItem *soitem)
632 {
633     if (!soitem)
634         return;
635 
636     QString name = getWikipediaName(soitem);
637 
638     QUrl url("https://en.wikipedia.org/w/api.php?format=xml&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=" + name);
639 
640     QNetworkReply *response = manager->get(QNetworkRequest(url));
641     QTimer::singleShot(30000, response, [response]   //Shut it down after 30 sec.
642     {
643         response->abort();
644         response->deleteLater();
645         qDebug() << "Wikipedia Download Timed out.";
646     });
647     connect(response, &QNetworkReply::finished, this, [soitem, this, response, name]
648     {
649         response->deleteLater();
650         if (response->error() != QNetworkReply::NoError)
651             return;
652         QString contentType = response->header(QNetworkRequest::ContentTypeHeader).toString();
653         if (!contentType.contains("charset=utf-8"))
654         {
655             qWarning() << "Content charsets other than utf-8 are not implemented yet.";
656             return;
657         }
658         QString result = QString::fromUtf8(response->readAll());
659         int leftPos    = result.indexOf("<extract xml:space=\"preserve\">") + 30;
660         if (leftPos < 30)
661             return;
662         int rightPos = result.indexOf("</extract>") - leftPos;
663 
664         QString srchtml =
665         "\n<p style=text-align:right>Source: (<a href='" + QString("https://en.wikipedia.org/wiki/") + name + "'>" +
666         "Wikipedia</a>)"; //Note the \n is so that the description is put on another line in the file.  Doesn't affect the display but allows the source to be loaded in the details but not the list.
667         QString html = "<HTML>" + result.mid(leftPos, rightPos) + srchtml + "</HTML>";
668 
669         saveObjectInfoBoxText(soitem, "description", html);
670 
671         //TODO is this explicitly needed now with themes?
672 #if 0
673         QString color     = (Options::darkAppColors()) ? "red" : "white";
674         QString linkColor = (Options::darkAppColors()) ? "red" : "yellow";
675         html              = "<HTML><HEAD><style type=text/css>body {color:" + color +
676         ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" + html + "</BODY></HTML>";
677 #endif
678 
679         if (soitem == m_CurSoItem)
680             descTextObj->setProperty("text", html);
681         refreshListView();
682     });
683 }
684 
loadObjectDescription(SkyObjItem * soitem)685 void WIView::loadObjectDescription(SkyObjItem *soitem)
686 {
687     QFile file;
688     QString fname = "description-" + soitem->getName().toLower().remove(' ') + ".html";
689     //determine filename in local user KDE directory tree.
690     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("descriptions/" + fname));
691 
692     if (file.exists())
693     {
694         if (file.open(QIODevice::ReadOnly))
695         {
696             QTextStream in(&file);
697             bool isDarkTheme = (Options::currentTheme() == "Night Vision");
698             QString color     = (isDarkTheme) ? "red" : "white";
699             QString linkColor = (isDarkTheme) ? "red" : "yellow";
700 
701             QString line      = "<HTML><HEAD><style type=text/css>body {color:" + color +
702                                 ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY><BR>" +
703                                 in.readAll() + "</BODY></HTML>";
704             descTextObj->setProperty("text", line);
705             file.close();
706         }
707     }
708     else
709     {
710         descTextObj->setProperty("text", soitem->getTypeName());
711     }
712 }
713 
loadObjectInfoBox(SkyObjItem * soitem)714 void WIView::loadObjectInfoBox(SkyObjItem *soitem)
715 {
716     if (!soitem)
717         return;
718     QFile file;
719     QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html";
720     //determine filename in local user KDE directory tree.
721     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("descriptions/" + fname));
722 
723     if (file.exists())
724     {
725         if (file.open(QIODevice::ReadOnly))
726         {
727             QTextStream in(&file);
728             QString infoBoxHTML;
729             while (!in.atEnd())
730             {
731                 infoBoxHTML = in.readAll();
732                 QString wikiImageName =
733                     QUrl::fromLocalFile(
734                         KSPaths::locate(QStandardPaths::AppDataLocation,
735                                         "descriptions/wikiImage-" + soitem->getName().toLower().remove(' ') + ".png"))
736                     .url();
737                 if (!wikiImageName.isEmpty())
738                 {
739                     int captionEnd = infoBoxHTML.indexOf(
740                                          "</caption>"); //Start looking for the image AFTER the caption.  Planets have images in their caption.
741                     if (captionEnd == -1)
742                         captionEnd = 0;
743                     int leftImg    = infoBoxHTML.indexOf("src=\"", captionEnd) + 5;
744                     int rightImg   = infoBoxHTML.indexOf("\"", leftImg) - leftImg;
745                     QString imgURL = infoBoxHTML.mid(leftImg, rightImg);
746                     infoBoxHTML.replace(imgURL, wikiImageName);
747                 }
748                 bool isDarkTheme = (Options::currentTheme() == "Night Vision");
749                 QString color     = (isDarkTheme) ? "red" : "white";
750                 QString linkColor = (isDarkTheme) ? "red" : "yellow";
751                 if (isDarkTheme)
752                     infoBoxHTML.replace("color: white", "color: " + color);
753                 infoBoxHTML = "<HTML><HEAD><style type=text/css>body {color:" + color +
754                               ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" +
755                               infoBoxHTML + "</BODY></HTML>";
756 
757                 infoBoxText->setProperty("text", infoBoxHTML);
758             }
759             file.close();
760         }
761     }
762 }
763 
tryToUpdateWikipediaInfoInModel(bool onlyMissing)764 void WIView::tryToUpdateWikipediaInfoInModel(bool onlyMissing)
765 {
766     SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName);
767     int objectNum          = model->rowCount();
768     for (int i = 0; i < objectNum; i++)
769     {
770         SkyObjItem *soitem = model->getSkyObjItem(i);
771         QFile file;
772         QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html";
773         //determine filename in local user KDE directory tree.
774         file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("descriptions/" + fname));
775 
776         if (file.exists() && onlyMissing)
777             continue;
778 
779         tryToUpdateWikipediaInfo(soitem, getWikipediaName(soitem));
780     }
781 }
782 
tryToUpdateWikipediaInfo(SkyObjItem * soitem,QString name)783 void WIView::tryToUpdateWikipediaInfo(SkyObjItem *soitem, QString name)
784 {
785     if (name.isEmpty() || !soitem)
786         return;
787 
788     QUrl url("https://en.wikipedia.org/w/index.php?action=render&title=" + name + "&redirects");
789     QNetworkReply *response = manager->get(QNetworkRequest(url));
790 
791     QTimer::singleShot(30000, response, [response]   //Shut it down after 30 sec.
792     {
793         response->abort();
794         response->deleteLater();
795         qDebug() << "Wikipedia Download Timed out.";
796     });
797     connect(response, &QNetworkReply::finished, this, [name, response, soitem, this]
798     {
799         response->deleteLater();
800         if (response->error() == QNetworkReply::ContentNotFoundError)
801         {
802             QString html = "<BR>Sorry, No Wikipedia article with this object name seems to exist.  It is possible that "
803             "one does exist but does not match the namimg scheme.";
804             saveObjectInfoBoxText(soitem, "infoText", html);
805             infoBoxText->setProperty("text", html);
806             return;
807         }
808         if (response->error() != QNetworkReply::NoError)
809             return;
810         QString result = QString::fromUtf8(response->readAll());
811         int leftPos    = result.indexOf("<table class=\"infobox");
812         int rightPos   = result.indexOf("</table>", leftPos) - leftPos;
813 
814         if (leftPos == -1)
815         {
816             //No InfoBox is Found
817             if (soitem->getType() == SkyObjItem::Star &&
818                     name != soitem->getName().replace(' ', '_')) //For stars, the regular name rather than gname
819             {
820                 tryToUpdateWikipediaInfo(soitem, soitem->getName().replace(' ', '_'));
821                 return;
822             }
823             QString html = "<BR>Sorry, no Information Box in the object's Wikipedia article was found.";
824             saveObjectInfoBoxText(soitem, "infoText", html);
825             infoBoxText->setProperty("text", html);
826             return;
827         }
828 
829         updateWikipediaDescription(soitem);
830 
831         QString infoText = result.mid(leftPos, rightPos);
832 
833         //This if statement should correct for a situation like for the planets where there is a single internal table inside the infoText Box.
834         if (infoText.indexOf("<table", leftPos + 6) != -1)
835         {
836             rightPos = result.indexOf("</table>", leftPos + rightPos + 6) - leftPos;
837             infoText = result.mid(leftPos, rightPos);
838         }
839 
840         //This next section is for the headers in the colored boxes. It turns them black instead of white because they are more visible that way.
841         infoText.replace("background: #", "color:black;background: #")
842         .replace("background-color: #", "color:black;background: #")
843         .replace("background:#", "color:black;background:#")
844         .replace("background-color:#", "color:black;background:#")
845         .replace("background: pink", "color:black;background: pink");
846         infoText.replace("//", "http://"); //This is to fix links on wikipedia which are missing http from the url
847         infoText.replace("https:http:", "https:")
848         .replace("http:http:", "http:"); //Just in case it was done to an actual complete url
849 
850         //This section is intended to remove links from the object name header at the top.  The links break up the header.
851         int thLeft = infoText.indexOf("<th ");
852         if (thLeft != -1)
853         {
854             int thRight = infoText.indexOf("</th>", thLeft);
855             int firstA  = infoText.indexOf("<a ", thLeft);
856             if (firstA != -1 && firstA < thRight)
857             {
858                 int rightA = infoText.indexOf(">", firstA) - firstA + 1;
859                 infoText.remove(firstA, rightA);
860                 int endA = infoText.indexOf("</a>", firstA);
861                 infoText.remove(endA, 4);
862             }
863         }
864 
865         int annotationLeft  = infoText.indexOf("<annotation");
866         int annotationRight = infoText.indexOf("</annotation>", annotationLeft) + 13 - annotationLeft;
867         infoText.remove(annotationLeft,
868                         annotationRight); //This removes the annotation that does not render correctly for some DSOs.
869 
870         int mathLeft  = infoText.indexOf("<img src=\"https://wikimedia.org/api/rest_v1/media/math");
871         int mathRight = infoText.indexOf(">", mathLeft) + 1 - mathLeft;
872         infoText.remove(mathLeft, mathRight); //This removes an image that doesn't render properly for some DSOs.
873 
874         infoText.replace("style=\"width:22em\"", "style=\"width:100%;background-color: black;color: white;\"");
875         infoText = infoText + "<BR>(Source: <a href='" + "https://en.wikipedia.org/w/index.php?title=" + name +
876                    "&redirects" + "'>Wikipedia</a>)";
877         saveInfoURL(soitem, "https://en.wikipedia.org/w/index.php?title=" + name + "&redirects");
878 
879         int captionEnd = infoText.indexOf(
880                              "</caption>"); //Start looking for the image AFTER the caption.  Planets have images in their caption.
881         if (captionEnd == -1)
882             captionEnd = 0;
883         int leftImg = infoText.indexOf("src=\"", captionEnd) + 5;
884         if (leftImg > captionEnd + 5)
885         {
886             int rightImg   = infoText.indexOf("\"", leftImg) - leftImg;
887             QString imgURL = infoText.mid(leftImg, rightImg);
888             imgURL.replace(
889                 "http://upload.wikimedia.org",
890                 "https://upload.wikimedia.org"); //Although they will display, the images apparently don't download properly unless they are https.
891             saveImageURL(soitem, imgURL);
892             downloadWikipediaImage(soitem, imgURL);
893         }
894 
895         QString html = "<CENTER>" + infoText + "</table></CENTER>";
896 
897         saveObjectInfoBoxText(soitem, "infoText", html);
898         bool isDarkTheme = (Options::currentTheme() == "Night Vision");
899         QString color     = (isDarkTheme) ? "red" : "white";
900         QString linkColor = (isDarkTheme) ? "red" : "yellow";
901         if (isDarkTheme)
902             html.replace("color: white", "color: " + color);
903         html = "<HTML><HEAD><style type=text/css>body {color:" + color +
904                ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" + html + "</BODY></HTML>";
905         if (soitem == m_CurSoItem)
906             infoBoxText->setProperty("text", html);
907     });
908 }
909 
saveObjectInfoBoxText(SkyObjItem * soitem,QString type,QString text)910 void WIView::saveObjectInfoBoxText(SkyObjItem *soitem, QString type, QString text)
911 {
912     QFile file;
913     QString fname = type + '-' + soitem->getName().toLower().remove(' ') + ".html";
914 
915     QDir filePath(KSPaths::writableLocation(QStandardPaths::AppDataLocation) + "/descriptions");
916     filePath.mkpath(".");
917 
918     //determine filename in local user KDE directory tree.
919     file.setFileName(filePath.filePath(fname));
920 
921     if (file.open(QIODevice::WriteOnly) == false)
922     {
923         qDebug() << "Image text cannot be saved for later.  file save error";
924         return;
925     }
926     else
927     {
928         QTextStream stream(&file);
929         stream << text;
930         file.close();
931     }
932 }
933 
saveImageURL(SkyObjItem * soitem,QString imageURL)934 void WIView::saveImageURL(SkyObjItem *soitem, QString imageURL)
935 {
936     QFile file;
937     //determine filename in local user KDE directory tree.
938     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("image_url.dat"));
939     QString entry = soitem->getName() + ':' + "Show Wikipedia Image" + ':' + imageURL;
940 
941     if (file.open(QIODevice::ReadOnly))
942     {
943         QTextStream in(&file);
944         QString line;
945         while (!in.atEnd())
946         {
947             line = in.readLine();
948             if (line == entry)
949             {
950                 file.close();
951                 return;
952             }
953         }
954         file.close();
955     }
956 
957     if (!file.open(QIODevice::ReadWrite | QIODevice::Append))
958     {
959         qDebug() << "Image URL cannot be saved for later.  image_url.dat error";
960         return;
961     }
962     else
963     {
964         QTextStream stream(&file);
965         stream << entry << '\n';
966         file.close();
967     }
968 }
969 
saveInfoURL(SkyObjItem * soitem,QString infoURL)970 void WIView::saveInfoURL(SkyObjItem *soitem, QString infoURL)
971 {
972     QFile file;
973     //determine filename in local user KDE directory tree.
974     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("info_url.dat"));
975     QString entry = soitem->getName() + ':' + "Wikipedia Page" + ':' + infoURL;
976 
977     if (file.open(QIODevice::ReadOnly))
978     {
979         QTextStream in(&file);
980         QString line;
981         while (!in.atEnd())
982         {
983             line = in.readLine();
984             if (line == entry)
985             {
986                 file.close();
987                 return;
988             }
989         }
990         file.close();
991     }
992 
993     if (!file.open(QIODevice::ReadWrite | QIODevice::Append))
994     {
995         qDebug() << "Info URL cannot be saved for later.  info_url.dat error";
996         return;
997     }
998     else
999     {
1000         QTextStream stream(&file);
1001         stream << entry << '\n';
1002         file.close();
1003     }
1004 }
1005 
downloadWikipediaImage(SkyObjItem * soitem,QString imageURL)1006 void WIView::downloadWikipediaImage(SkyObjItem *soitem, QString imageURL)
1007 {
1008     QString fname = "wikiImage-" + soitem->getName().toLower().remove(' ') + ".png";
1009 
1010     QDir filePath(KSPaths::writableLocation(QStandardPaths::AppDataLocation) + "/descriptions");
1011     filePath.mkpath(".");
1012 
1013     QString fileN = filePath.filePath(fname);
1014 
1015     QNetworkReply *response = manager->get(QNetworkRequest(QUrl(imageURL)));
1016     QTimer::singleShot(60000, response, [response]   //Shut it down after 60 sec.
1017     {
1018         response->abort();
1019         response->deleteLater();
1020         qDebug() << "Image Download Timed out.";
1021     });
1022     connect(response, &QNetworkReply::finished, this, [fileN, response, this]
1023     {
1024         response->deleteLater();
1025         if (response->error() != QNetworkReply::NoError)
1026             return;
1027         QImage *image           = new QImage();
1028         QByteArray responseData = response->readAll();
1029         if (image->loadFromData(responseData))
1030         {
1031             image->save(fileN);
1032             refreshListView(); //This is to update the images displayed with the new image.
1033         }
1034         else
1035             qDebug() << "image not downloaded";
1036     });
1037 }
1038