File indexing completed on 2024-04-28 15:11:25

0001 /*
0002     SPDX-FileCopyrightText: 2012 Samikshan Bairagya <samikshan@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "wiview.h"
0008 
0009 #include "kspaths.h"
0010 #include "kstars.h"
0011 #include "ksnotification.h"
0012 #include "modelmanager.h"
0013 #include "obsconditions.h"
0014 #include "Options.h"
0015 #include "skymap.h"
0016 #include "skymapcomposite.h"
0017 #include "skyobjitem.h"
0018 #include "skyobjlistmodel.h"
0019 #include "starobject.h"
0020 #include "wiequipsettings.h"
0021 #include "dialogs/detaildialog.h"
0022 
0023 #include <klocalizedcontext.h>
0024 
0025 #include <QGraphicsObject>
0026 #include <QNetworkAccessManager>
0027 #include <QNetworkReply>
0028 #include <QQmlContext>
0029 #include <QQuickItem>
0030 #include <QQuickView>
0031 #include <QStandardPaths>
0032 #include <QtConcurrent>
0033 
0034 #ifdef HAVE_INDI
0035 #include <basedevice.h>
0036 #include "indi/indilistener.h"
0037 #include "indi/indimount.h"
0038 #endif
0039 
0040 WIView::WIView(QWidget *parent) : QWidget(parent)
0041 {
0042     //These settings are like this just to get it started.
0043     int bortle                           = Options::bortleClass();
0044     int aperture                         = 100;
0045     ObsConditions::Equipment equip       = ObsConditions::Telescope;
0046     ObsConditions::TelescopeType telType = ObsConditions::Reflector;
0047 
0048     m_Obs = new ObsConditions(bortle, aperture, equip, telType);
0049 
0050     m_ModManager.reset(new ModelManager(m_Obs));
0051 
0052     m_BaseView = new QQuickView();
0053 
0054     ///To use i18n() instead of qsTr() in qml/wiview.qml for translation
0055     //KDeclarative kd;
0056     // kd.setDeclarativeEngine(m_BaseView->engine());
0057     //kd.initialize();
0058     //kd.setupBindings();
0059 
0060     m_Ctxt = m_BaseView->rootContext();
0061 
0062     m_Ctxt->setContextProperty(
0063         "soListModel",
0064         m_ModManager
0065         ->getTempModel()); // This is to avoid an error saying it doesn't exist.
0066 
0067     ///Use instead of KDeclarative
0068     m_Ctxt->setContextObject(new KLocalizedContext(m_BaseView));
0069 
0070 #if 0
0071     QString WI_Location;
0072 #if defined(Q_OS_OSX)
0073     WI_Location = QCoreApplication::applicationDirPath() + "/../Resources/kstars/tools/whatsinteresting/qml/wiview.qml";
0074     if (!QFileInfo(WI_Location).exists())
0075         WI_Location = KSPaths::locate(QStandardPaths::AppLocalDataLocation, "tools/whatsinteresting/qml/wiview.qml");
0076 #elif defined(Q_OS_WIN)
0077     WI_Location = KSPaths::locate(QStandardPaths::GenericDataLocation, "tools/whatsinteresting/qml/wiview.qml");
0078 #else
0079     WI_Location = KSPaths::locate(QStandardPaths::AppLocalDataLocation, "tools/whatsinteresting/qml/wiview.qml");
0080 #endif
0081 
0082     m_BaseView->setSource(QUrl::fromLocalFile(WI_Location));
0083 #endif
0084 
0085     m_BaseView->setSource(QUrl("qrc:/qml/whatisinteresting/wiview.qml"));
0086 
0087     m_BaseObj = m_BaseView->rootObject();
0088 
0089     m_ProgressBar = m_BaseObj->findChild<QQuickItem *>("progressBar");
0090 
0091     m_loadingMessage = m_BaseObj->findChild<QQuickItem *>("loadingMessage");
0092 
0093     m_CategoryTitle = m_BaseObj->findChild<QQuickItem *>(QString("categoryTitle"));
0094 
0095     m_ViewsRowObj = m_BaseObj->findChild<QQuickItem *>(QString("viewsRowObj"));
0096     connect(m_ViewsRowObj, SIGNAL(categorySelected(QString)), this,
0097             SLOT(onCategorySelected(QString)));
0098     connect(m_ViewsRowObj, SIGNAL(inspectSkyObject(QString)), this,
0099             SLOT(inspectSkyObject(QString)));
0100 
0101     m_SoListObj = m_BaseObj->findChild<QQuickItem *>("soListObj");
0102     connect(m_SoListObj, SIGNAL(soListItemClicked(int)), this,
0103             SLOT(onSoListItemClicked(int)));
0104 
0105     m_DetailsViewObj = m_BaseObj->findChild<QQuickItem *>("detailsViewObj");
0106 
0107     descTextObj = m_DetailsViewObj->findChild<QObject *>("descTextObj");
0108     infoBoxText = m_DetailsViewObj->findChild<QObject *>("infoBoxText");
0109 
0110     m_NextObj = m_BaseObj->findChild<QQuickItem *>("nextObj");
0111     connect(m_NextObj, SIGNAL(nextObjClicked()), this, SLOT(onNextObjClicked()));
0112     m_PrevObj = m_BaseObj->findChild<QQuickItem *>("prevObj");
0113     connect(m_PrevObj, SIGNAL(prevObjClicked()), this, SLOT(onPrevObjClicked()));
0114 
0115     m_CenterButtonObj = m_BaseObj->findChild<QQuickItem *>("centerButtonObj");
0116     connect(m_CenterButtonObj, SIGNAL(centerButtonClicked()), this,
0117             SLOT(onCenterButtonClicked()));
0118 
0119     autoCenterCheckbox = m_DetailsViewObj->findChild<QObject *>("autoCenterCheckbox");
0120     autoTrackCheckbox  = m_DetailsViewObj->findChild<QObject *>("autoTrackCheckbox");
0121 
0122     m_SlewTelescopeButtonObj =
0123         m_BaseObj->findChild<QQuickItem *>("slewTelescopeButtonObj");
0124     connect(m_SlewTelescopeButtonObj, SIGNAL(slewTelescopeButtonClicked()), this,
0125             SLOT(onSlewTelescopeButtonClicked()));
0126 
0127     m_DetailsButtonObj = m_BaseObj->findChild<QQuickItem *>("detailsButtonObj");
0128     connect(m_DetailsButtonObj, SIGNAL(detailsButtonClicked()), this,
0129             SLOT(onDetailsButtonClicked()));
0130 
0131     QObject *settingsIconObj = m_BaseObj->findChild<QQuickItem *>("settingsIconObj");
0132     connect(settingsIconObj, SIGNAL(settingsIconClicked()), this,
0133             SLOT(onSettingsIconClicked()));
0134 
0135     inspectIconObj = m_BaseObj->findChild<QQuickItem *>("inspectIconObj");
0136     connect(inspectIconObj, SIGNAL(inspectIconClicked(bool)), this,
0137             SLOT(onInspectIconClicked(bool)));
0138 
0139     visibleIconObj = m_BaseObj->findChild<QQuickItem *>("visibleIconObj");
0140     connect(visibleIconObj, SIGNAL(visibleIconClicked(bool)), this,
0141             SLOT(onVisibleIconClicked(bool)));
0142 
0143     favoriteIconObj = m_BaseObj->findChild<QQuickItem *>("favoriteIconObj");
0144     connect(favoriteIconObj, SIGNAL(favoriteIconClicked(bool)), this,
0145             SLOT(onFavoriteIconClicked(bool)));
0146 
0147     QObject *reloadIconObj = m_BaseObj->findChild<QQuickItem *>("reloadIconObj");
0148     connect(reloadIconObj, SIGNAL(reloadIconClicked()), this,
0149             SLOT(onReloadIconClicked()));
0150 
0151     QObject *downloadIconObj = m_BaseObj->findChild<QQuickItem *>("downloadIconObj");
0152     connect(downloadIconObj, SIGNAL(downloadIconClicked()), this,
0153             SLOT(onUpdateIconClicked()));
0154 
0155     m_BaseView->setResizeMode(QQuickView::SizeRootObjectToView);
0156     m_BaseView->show();
0157 
0158     // Fix some weird issue with what's interesting panel view under Windows
0159     // In Qt 5.9 it content is messed up and there is no way to close the panel
0160 #ifdef Q_OS_WIN
0161     m_BaseView->setFlags(Qt::WindowCloseButtonHint);
0162 #endif
0163 
0164     connect(KStars::Instance()->map(), SIGNAL(objectClicked(SkyObject *)), this,
0165             SLOT(inspectSkyObjectOnClick(SkyObject *)));
0166 
0167     manager.reset(new QNetworkAccessManager());
0168 
0169     setProgressBarVisible(true);
0170     connect(m_ModManager.get(), SIGNAL(loadProgressUpdated(double)), this,
0171             SLOT(updateProgress(double)));
0172     connect(m_ModManager.get(), SIGNAL(modelUpdated()), this, SLOT(refreshListView()));
0173     m_ViewsRowObj->setProperty("enabled", false);
0174 
0175     inspectOnClick = false;
0176 
0177     nightVision = m_BaseObj->findChild<QObject *>("nightVision");
0178     //if (Options::darkAppColors())
0179     //    nightVision->setProperty("state", "active");
0180 }
0181 
0182 void WIView::setNightVisionOn(bool on)
0183 {
0184     if (on)
0185         nightVision->setProperty("state", "active");
0186     else
0187         nightVision->setProperty("state", "");
0188 
0189     if (m_CurSoItem != nullptr)
0190         loadDetailsView(m_CurSoItem, m_CurIndex);
0191 }
0192 
0193 void WIView::setProgressBarVisible(bool visible)
0194 {
0195     m_ProgressBar->setProperty("visible", visible);
0196 }
0197 
0198 void WIView::updateProgress(double value)
0199 {
0200     m_ProgressBar->setProperty("value", value);
0201 
0202     if (value == 1)
0203     {
0204         setProgressBarVisible(false);
0205         m_ViewsRowObj->setProperty("enabled", true);
0206         m_loadingMessage->setProperty("state", "");
0207     }
0208     else
0209     {
0210         setProgressBarVisible(true);
0211         m_loadingMessage->setProperty("state", "loading");
0212     }
0213 }
0214 
0215 void WIView::updateObservingConditions()
0216 {
0217     int bortle = Options::bortleClass();
0218 
0219     /**
0220     NOTE This part of the code dealing with equipment type is presently not required
0221     as WI does not differentiate between Telescope and Binoculars. It only needs the
0222      aperture of the equipment whichever available. However this is kept as a part of
0223     the code as support to be utilised in the future.
0224     **/
0225     ObsConditions::Equipment equip = ObsConditions::None;
0226 
0227     if (Options::telescopeCheck() && Options::binocularsCheck())
0228         equip = ObsConditions::Both;
0229     else if (Options::telescopeCheck())
0230         equip = ObsConditions::Telescope;
0231     else if (Options::binocularsCheck())
0232         equip = ObsConditions::Binoculars;
0233 
0234     ObsConditions::TelescopeType telType;
0235 
0236     if (KStars::Instance()->getWIEquipSettings())
0237         telType = (equip == ObsConditions::Telescope) ? KStars::Instance()->getWIEquipSettings()->getTelType() :
0238                   ObsConditions::Invalid;
0239     else
0240         telType = ObsConditions::Invalid;
0241 
0242     int aperture = 100;
0243 
0244     //This doesn't work correctly, FIXME!!
0245     // if(KStars::Instance()->getWIEquipSettings())
0246     //    aperture = KStars::Instance()->getWIEquipSettings()->getAperture();
0247 
0248     if (!m_Obs)
0249         m_Obs = new ObsConditions(bortle, aperture, equip, telType);
0250     else
0251         m_Obs->setObsConditions(bortle, aperture, equip, telType);
0252 }
0253 
0254 void WIView::onCategorySelected(QString model)
0255 {
0256     m_CurrentObjectListName = model;
0257     m_Ctxt->setContextProperty("soListModel",
0258                                m_ModManager->returnModel(m_CurrentObjectListName));
0259     m_CurIndex = -2;
0260     if (!m_ModManager->showOnlyVisibleObjects())
0261         visibleIconObj->setProperty("state", "unchecked");
0262     if (!m_ModManager->showOnlyFavoriteObjects())
0263         favoriteIconObj->setProperty("state", "unchecked");
0264 
0265     if ((QStringList() << "ngc"
0266             << "ic"
0267             << "messier"
0268             << "sharpless")
0269             .contains(model))
0270     {
0271         QtConcurrent::run(m_ModManager.get(), &ModelManager::loadCatalog, model);
0272         return;
0273     }
0274 
0275     updateModel(*m_Obs);
0276 }
0277 
0278 void WIView::onSoListItemClicked(int index)
0279 {
0280     SkyObjItem *soitem = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem(index);
0281     if (soitem)
0282         loadDetailsView(soitem, index);
0283 }
0284 
0285 void WIView::onNextObjClicked()
0286 {
0287     if (!m_CurrentObjectListName.isEmpty())
0288     {
0289         int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
0290         if (modelSize > 0)
0291         {
0292             SkyObjItem *nextItem =
0293                 m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize);
0294             loadDetailsView(nextItem, (m_CurIndex + 1) % modelSize);
0295         }
0296     }
0297 }
0298 
0299 void WIView::onPrevObjClicked()
0300 {
0301     if (!m_CurrentObjectListName.isEmpty())
0302     {
0303         int modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
0304         if (modelSize > 0)
0305         {
0306             SkyObjItem *prevItem =
0307                 m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize);
0308             loadDetailsView(prevItem, (m_CurIndex - 1 + modelSize) % modelSize);
0309         }
0310     }
0311 }
0312 
0313 void WIView::onCenterButtonClicked()
0314 {
0315     ///Center map on selected sky-object
0316     SkyObject *so  = m_CurSoItem->getSkyObject();
0317     KStars *kstars = KStars::Instance();
0318 
0319     if (so)
0320     {
0321         kstars->map()->setFocusPoint(so);
0322         kstars->map()->setFocusObject(so);
0323         kstars->map()->setDestination(*kstars->map()->focusPoint());
0324         Options::setIsTracking(autoTrackCheckbox->property("checked") == true);
0325     }
0326 }
0327 
0328 void WIView::onSlewTelescopeButtonClicked()
0329 {
0330     if (KMessageBox::Continue ==
0331             KMessageBox::warningContinueCancel(nullptr, "Are you sure you want your telescope to slew to this object?",
0332                     i18n("Continue Slew"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
0333                     "continue_wi_slew_warning"))
0334     {
0335 #ifdef HAVE_INDI
0336 
0337         if (INDIListener::Instance()->size() == 0)
0338         {
0339             KSNotification::sorry(i18n("No connected mounts found."));
0340             return;
0341         }
0342 
0343         for (auto &oneDevice : INDIListener::devices())
0344         {
0345             if (!(oneDevice->getDriverInterface() & INDI::BaseDevice::TELESCOPE_INTERFACE))
0346                 continue;
0347 
0348             if (oneDevice->isConnected() == false)
0349             {
0350                 KSNotification::error(i18n("Mount %1 is offline. Please connect and retry again.", oneDevice->getDeviceName()));
0351                 return;
0352             }
0353 
0354             auto mount = oneDevice->getMount();
0355             if (!mount)
0356                 continue;
0357             mount->Slew(m_CurSoItem->getSkyObject());
0358 
0359             /// Slew map to selected sky-object
0360             onCenterButtonClicked();
0361             return;
0362         }
0363 
0364         KSNotification::sorry(i18n("No connected mounts found."));
0365 
0366 #endif
0367     }
0368 }
0369 
0370 void WIView::onDetailsButtonClicked()
0371 {
0372     ///Code taken from WUTDialog::slotDetails()
0373     KStars *kstars = KStars::Instance();
0374     SkyObject *so  = m_CurSoItem->getSkyObject();
0375     if (so)
0376     {
0377         DetailDialog *detail = new DetailDialog(so, kstars->data()->lt(), kstars->data()->geo(), kstars);
0378         detail->exec();
0379         delete detail;
0380     }
0381 }
0382 
0383 void WIView::onSettingsIconClicked()
0384 {
0385     KStars *kstars = KStars::Instance();
0386     kstars->showWISettingsUI();
0387 }
0388 
0389 void WIView::onReloadIconClicked()
0390 {
0391     if (!m_CurrentObjectListName.isEmpty())
0392     {
0393         updateModel(*m_Obs);
0394         m_CurIndex = m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjIndex(m_CurSoItem);
0395     }
0396     loadDetailsView(m_CurSoItem, m_CurIndex);
0397 }
0398 
0399 void WIView::onVisibleIconClicked(bool visible)
0400 {
0401     m_ModManager->setShowOnlyVisibleObjects(visible);
0402     onReloadIconClicked();
0403 }
0404 
0405 void WIView::onFavoriteIconClicked(bool favorites)
0406 {
0407     m_ModManager->setShowOnlyFavoriteObjects(favorites);
0408     onReloadIconClicked();
0409 }
0410 
0411 void WIView::onUpdateIconClicked()
0412 {
0413     QMessageBox mbox;
0414     QPushButton *currentObject = mbox.addButton("Current Object", QMessageBox::AcceptRole);
0415     QPushButton *missingObjects = nullptr;
0416     QPushButton *allObjects = nullptr;
0417 
0418     mbox.setText("Please choose which object(s) to try to update with Wikipedia data.");
0419     if (!m_CurrentObjectListName.isEmpty())
0420     {
0421         missingObjects = mbox.addButton("Objects with no data", QMessageBox::AcceptRole);
0422         allObjects     = mbox.addButton("Entire List", QMessageBox::AcceptRole);
0423     }
0424     QPushButton *cancel = mbox.addButton("Cancel", QMessageBox::AcceptRole);
0425     mbox.setDefaultButton(cancel);
0426 
0427     mbox.exec();
0428     if (mbox.clickedButton() == currentObject)
0429     {
0430         if (m_CurSoItem != nullptr)
0431         {
0432             tryToUpdateWikipediaInfo(m_CurSoItem, getWikipediaName(m_CurSoItem));
0433         }
0434     }
0435     else if (mbox.clickedButton() == allObjects || mbox.clickedButton() == missingObjects)
0436     {
0437         SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName);
0438         if (model->rowCount() > 0)
0439         {
0440             tryToUpdateWikipediaInfoInModel(mbox.clickedButton() == missingObjects);
0441         }
0442         else
0443         {
0444             qDebug() << Q_FUNC_INFO << "No Objects in List!";
0445         }
0446     }
0447 }
0448 
0449 void WIView::refreshListView()
0450 {
0451     m_Ctxt->setContextProperty("soListModel", nullptr);
0452     if (!m_CurrentObjectListName.isEmpty())
0453         m_Ctxt->setContextProperty("soListModel", m_ModManager->returnModel(m_CurrentObjectListName));
0454     if (m_CurIndex == -2)
0455         onSoListItemClicked(0);
0456     if (m_CurIndex != -1)
0457         m_SoListObj->setProperty("currentIndex", m_CurIndex);
0458 }
0459 
0460 void WIView::updateModel(ObsConditions &obs)
0461 {
0462     if (!m_CurrentObjectListName.isEmpty())
0463     {
0464         m_Obs = &obs;
0465         m_ModManager->updateModel(m_Obs, m_CurrentObjectListName);
0466     }
0467 }
0468 
0469 void WIView::inspectSkyObject(const QString &name)
0470 {
0471     if (!name.isEmpty() && name != "star")
0472     {
0473         SkyObject *obj = KStarsData::Instance()->skyComposite()->findByName(name);
0474 
0475         if (obj)
0476             inspectSkyObject(obj);
0477     }
0478 }
0479 
0480 void WIView::inspectSkyObjectOnClick(SkyObject *obj)
0481 {
0482     if (inspectOnClick && KStars::Instance()->isWIVisible())
0483         inspectSkyObject(obj);
0484 }
0485 
0486 void WIView::inspectSkyObject(SkyObject *obj)
0487 {
0488     if (!obj)
0489         return;
0490 
0491     if (obj->name() != "star")
0492     {
0493         m_CurrentObjectListName = "";
0494         trackedItem.reset(new SkyObjItem(obj));
0495         loadDetailsView(trackedItem.get(), -1);
0496         m_BaseObj->setProperty("state", "singleItemSelected");
0497         m_CategoryTitle->setProperty("text", "Selected Object");
0498     }
0499 }
0500 
0501 void WIView::loadDetailsView(SkyObjItem *soitem, int index)
0502 {
0503     if (soitem == nullptr)
0504         return;
0505 
0506     int modelSize = -1;
0507 
0508     if (index != -1)
0509         modelSize = m_ModManager->returnModel(m_CurrentObjectListName)->rowCount();
0510 
0511     if (soitem != m_CurSoItem)
0512         m_CurSoItem = soitem;
0513 
0514     m_CurIndex  = index;
0515     if (modelSize <= 1)
0516     {
0517         m_NextObj->setProperty("visible", "false");
0518         m_PrevObj->setProperty("visible", "false");
0519     }
0520     else
0521     {
0522         SkyObjItem *nextItem =
0523             m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex + 1) % modelSize);
0524         SkyObjItem *prevItem =
0525             m_ModManager->returnModel(m_CurrentObjectListName)->getSkyObjItem((m_CurIndex - 1 + modelSize) % modelSize);
0526 
0527         m_NextObj->setProperty("visible", "true");
0528         m_PrevObj->setProperty("visible", "true");
0529         QObject *nextTextObj = m_NextObj->findChild<QObject *>("nextTextObj");
0530 
0531         nextTextObj->setProperty("text", nextItem->getName());
0532         QObject *prevTextObj = m_PrevObj->findChild<QObject *>("prevTextObj");
0533 
0534         prevTextObj->setProperty("text", prevItem->getName());
0535     }
0536 
0537     QObject *sonameObj      = m_DetailsViewObj->findChild<QObject *>("sonameObj");
0538     QObject *posTextObj     = m_DetailsViewObj->findChild<QObject *>("posTextObj");
0539     QObject *detailImage    = m_DetailsViewObj->findChild<QObject *>("detailImage");
0540     QObject *detailsTextObj = m_DetailsViewObj->findChild<QObject *>("detailsTextObj");
0541 
0542     sonameObj->setProperty("text", soitem->getDescName());
0543     posTextObj->setProperty("text", soitem->getPosition());
0544     detailImage->setProperty("refreshableSource", soitem->getImageURL(false));
0545 
0546     loadObjectDescription(soitem);
0547 
0548     infoBoxText->setProperty(
0549         "text",
0550         "<BR><BR>No Wikipedia information. <BR>  Please try to download it using the orange download button below.");
0551     loadObjectInfoBox(soitem);
0552 
0553     QString summary = soitem->getSummary(false);
0554 
0555     QString magText;
0556     if (soitem->getType() == SkyObjItem::Constellation)
0557         magText = xi18n("Magnitude:  --");
0558     else
0559         magText = xi18n("Magnitude: %1", QLocale().toString(soitem->getMagnitude(), 'f', 2));
0560 
0561     QString sbText = xi18n("Surface Brightness: %1", soitem->getSurfaceBrightness());
0562 
0563     QString sizeText = xi18n("Size: %1", soitem->getSize());
0564 
0565     QString details = summary + "<BR>" + sbText + "<BR>" + magText + "<BR>" + sizeText;
0566     detailsTextObj->setProperty("text", details);
0567 
0568     if (autoCenterCheckbox->property("checked") == true)
0569     {
0570         QTimer::singleShot(500, this, SLOT(onCenterButtonClicked()));
0571     }
0572 
0573     if (m_CurIndex != -1)
0574         m_SoListObj->setProperty("currentIndex", m_CurIndex);
0575 }
0576 
0577 QString WIView::getWikipediaName(SkyObjItem *soitem)
0578 {
0579     if (!soitem)
0580         return "";
0581 
0582     QString name;
0583 
0584     if (soitem->getName().toLower().startsWith(QLatin1String("m ")))
0585         name = soitem->getName().replace("M ", "Messier_").remove(' ');
0586     else if (soitem->getName().toLower().startsWith(QLatin1String("ngc")))
0587         name = soitem->getName().toLower().replace("ngc", "NGC_").remove(' ');
0588     else if (soitem->getName().toLower().startsWith(QLatin1String("ic")))
0589         name = soitem->getName().toLower().replace("ic", "IC_").remove(' ');
0590     else if (soitem->getType() == SkyObjItem::Constellation)
0591     {
0592         QStringList words = soitem->getName().split(' ');
0593 
0594         for (int i = 0; i < words.size(); i++)
0595         {
0596             QString temp = words.at(i).toLower();
0597             temp[0]      = temp[0].toUpper();
0598             words.replace(i, temp);
0599         }
0600         name = words.join("_") + "_(constellation)";
0601         if (name.contains("Serpens"))
0602             name = "Serpens_(constellation)";
0603     }
0604     else if (soitem->getTypeName() == i18n("Asteroid"))
0605         name = soitem->getName().remove(' ') + "_(asteroid)";
0606     else if (soitem->getTypeName() == i18n("Comet"))
0607         name = soitem->getLongName();
0608     else if (soitem->getType() == SkyObjItem::Planet && soitem->getName() != i18n("Sun") && soitem->getName() != i18n("Moon"))
0609         name = soitem->getName().remove(' ') + "_(planet)";
0610     else if (soitem->getType() == SkyObjItem::Star)
0611     {
0612         StarObject *star = dynamic_cast<StarObject *>(soitem->getSkyObject());
0613 
0614         // The greek name seems to give the most consistent search results for opensearch.
0615         name             = star->gname(false).replace(' ', '_');
0616         if (name.isEmpty())
0617             name = soitem->getName().replace(' ', '_') + "_(star)";
0618         name.remove('[').remove(']');
0619     }
0620     else
0621         name = soitem->getName().remove(' ');
0622 
0623     return name;
0624 }
0625 
0626 void WIView::updateWikipediaDescription(SkyObjItem *soitem)
0627 {
0628     if (!soitem)
0629         return;
0630 
0631     QString name = getWikipediaName(soitem);
0632 
0633     QUrl url("https://en.wikipedia.org/w/api.php?format=xml&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=" + name);
0634 
0635     QNetworkReply *response = manager->get(QNetworkRequest(url));
0636     QTimer::singleShot(30000, response, [response]   //Shut it down after 30 sec.
0637     {
0638         response->abort();
0639         response->deleteLater();
0640         qDebug() << Q_FUNC_INFO << "Wikipedia Download Timed out.";
0641     });
0642     connect(response, &QNetworkReply::finished, this, [soitem, this, response, name]
0643     {
0644         response->deleteLater();
0645         if (response->error() != QNetworkReply::NoError)
0646             return;
0647         QString contentType = response->header(QNetworkRequest::ContentTypeHeader).toString();
0648         if (!contentType.contains("charset=utf-8"))
0649         {
0650             qWarning() << "Content charsets other than utf-8 are not implemented yet.";
0651             return;
0652         }
0653         QString result = QString::fromUtf8(response->readAll());
0654         int leftPos    = result.indexOf("<extract xml:space=\"preserve\">") + 30;
0655         if (leftPos < 30)
0656             return;
0657         int rightPos = result.indexOf("</extract>") - leftPos;
0658 
0659         QString srchtml =
0660         "\n<p style=text-align:right>Source: (<a href='" + QString("https://en.wikipedia.org/wiki/") + name + "'>" +
0661         "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.
0662         QString html = "<HTML>" + result.mid(leftPos, rightPos) + srchtml + "</HTML>";
0663 
0664         saveObjectInfoBoxText(soitem, "description", html);
0665 
0666         //TODO is this explicitly needed now with themes?
0667 #if 0
0668         QString color     = (Options::darkAppColors()) ? "red" : "white";
0669         QString linkColor = (Options::darkAppColors()) ? "red" : "yellow";
0670         html              = "<HTML><HEAD><style type=text/css>body {color:" + color +
0671         ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" + html + "</BODY></HTML>";
0672 #endif
0673 
0674         if (soitem == m_CurSoItem)
0675             descTextObj->setProperty("text", html);
0676         refreshListView();
0677     });
0678 }
0679 
0680 void WIView::loadObjectDescription(SkyObjItem *soitem)
0681 {
0682     QFile file;
0683     QString fname = "description-" + soitem->getName().toLower().remove(' ') + ".html";
0684     //determine filename in local user KDE directory tree.
0685     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("descriptions/" + fname));
0686 
0687     if (file.exists())
0688     {
0689         if (file.open(QIODevice::ReadOnly))
0690         {
0691             QTextStream in(&file);
0692             bool isDarkTheme = (Options::currentTheme() == "Night Vision");
0693             QString color     = (isDarkTheme) ? "red" : "white";
0694             QString linkColor = (isDarkTheme) ? "red" : "yellow";
0695 
0696             QString line      = "<HTML><HEAD><style type=text/css>body {color:" + color +
0697                                 ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY><BR>" +
0698                                 in.readAll() + "</BODY></HTML>";
0699             descTextObj->setProperty("text", line);
0700             file.close();
0701         }
0702     }
0703     else
0704     {
0705         descTextObj->setProperty("text", soitem->getTypeName());
0706     }
0707 }
0708 
0709 void WIView::loadObjectInfoBox(SkyObjItem *soitem)
0710 {
0711     if (!soitem)
0712         return;
0713     QFile file;
0714     QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html";
0715     //determine filename in local user KDE directory tree.
0716     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("descriptions/" + fname));
0717 
0718     if (file.exists())
0719     {
0720         if (file.open(QIODevice::ReadOnly))
0721         {
0722             QTextStream in(&file);
0723             QString infoBoxHTML;
0724             while (!in.atEnd())
0725             {
0726                 infoBoxHTML = in.readAll();
0727                 QString wikiImageName =
0728                     QUrl::fromLocalFile(
0729                         KSPaths::locate(QStandardPaths::AppLocalDataLocation,
0730                                         "descriptions/wikiImage-" + soitem->getName().toLower().remove(' ') + ".png"))
0731                     .url();
0732                 if (!wikiImageName.isEmpty())
0733                 {
0734                     int captionEnd = infoBoxHTML.indexOf(
0735                                          "</caption>"); //Start looking for the image AFTER the caption.  Planets have images in their caption.
0736                     if (captionEnd == -1)
0737                         captionEnd = 0;
0738                     int leftImg    = infoBoxHTML.indexOf("src=\"", captionEnd) + 5;
0739                     int rightImg   = infoBoxHTML.indexOf("\"", leftImg) - leftImg;
0740                     QString imgURL = infoBoxHTML.mid(leftImg, rightImg);
0741                     infoBoxHTML.replace(imgURL, wikiImageName);
0742                 }
0743                 bool isDarkTheme = (Options::currentTheme() == "Night Vision");
0744                 QString color     = (isDarkTheme) ? "red" : "white";
0745                 QString linkColor = (isDarkTheme) ? "red" : "yellow";
0746                 if (isDarkTheme)
0747                     infoBoxHTML.replace("color: white", "color: " + color);
0748                 infoBoxHTML = "<HTML><HEAD><style type=text/css>body {color:" + color +
0749                               ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" +
0750                               infoBoxHTML + "</BODY></HTML>";
0751 
0752                 infoBoxText->setProperty("text", infoBoxHTML);
0753             }
0754             file.close();
0755         }
0756     }
0757 }
0758 
0759 void WIView::tryToUpdateWikipediaInfoInModel(bool onlyMissing)
0760 {
0761     SkyObjListModel *model = m_ModManager->returnModel(m_CurrentObjectListName);
0762     int objectNum          = model->rowCount();
0763     for (int i = 0; i < objectNum; i++)
0764     {
0765         SkyObjItem *soitem = model->getSkyObjItem(i);
0766         QFile file;
0767         QString fname = "infoText-" + soitem->getName().toLower().remove(' ') + ".html";
0768         //determine filename in local user KDE directory tree.
0769         file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("descriptions/" + fname));
0770 
0771         if (file.exists() && onlyMissing)
0772             continue;
0773 
0774         tryToUpdateWikipediaInfo(soitem, getWikipediaName(soitem));
0775     }
0776 }
0777 
0778 void WIView::tryToUpdateWikipediaInfo(SkyObjItem *soitem, QString name)
0779 {
0780     if (name.isEmpty() || !soitem)
0781         return;
0782 
0783     QUrl url("https://en.wikipedia.org/w/index.php?action=render&title=" + name + "&redirects");
0784     QNetworkReply *response = manager->get(QNetworkRequest(url));
0785 
0786     QTimer::singleShot(30000, response, [response]   //Shut it down after 30 sec.
0787     {
0788         response->abort();
0789         response->deleteLater();
0790         qDebug() << Q_FUNC_INFO << "Wikipedia Download Timed out.";
0791     });
0792     connect(response, &QNetworkReply::finished, this, [name, response, soitem, this]
0793     {
0794         response->deleteLater();
0795         if (response->error() == QNetworkReply::ContentNotFoundError)
0796         {
0797             QString html = "<BR>Sorry, No Wikipedia article with this object name seems to exist.  It is possible that "
0798             "one does exist but does not match the namimg scheme.";
0799             saveObjectInfoBoxText(soitem, "infoText", html);
0800             infoBoxText->setProperty("text", html);
0801             return;
0802         }
0803         if (response->error() != QNetworkReply::NoError)
0804             return;
0805         QString result = QString::fromUtf8(response->readAll());
0806         int leftPos    = result.indexOf("<table class=\"infobox");
0807         int rightPos   = result.indexOf("</table>", leftPos) - leftPos;
0808 
0809         if (leftPos == -1)
0810         {
0811             //No InfoBox is Found
0812             if (soitem->getType() == SkyObjItem::Star &&
0813                     name != soitem->getName().replace(' ', '_')) //For stars, the regular name rather than gname
0814             {
0815                 tryToUpdateWikipediaInfo(soitem, soitem->getName().replace(' ', '_'));
0816                 return;
0817             }
0818             QString html = "<BR>Sorry, no Information Box in the object's Wikipedia article was found.";
0819             saveObjectInfoBoxText(soitem, "infoText", html);
0820             infoBoxText->setProperty("text", html);
0821             return;
0822         }
0823 
0824         updateWikipediaDescription(soitem);
0825 
0826         QString infoText = result.mid(leftPos, rightPos);
0827 
0828         //This if statement should correct for a situation like for the planets where there is a single internal table inside the infoText Box.
0829         if (infoText.indexOf("<table", leftPos + 6) != -1)
0830         {
0831             rightPos = result.indexOf("</table>", leftPos + rightPos + 6) - leftPos;
0832             infoText = result.mid(leftPos, rightPos);
0833         }
0834 
0835         //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.
0836         infoText.replace("background: #", "color:black;background: #")
0837         .replace("background-color: #", "color:black;background: #")
0838         .replace("background:#", "color:black;background:#")
0839         .replace("background-color:#", "color:black;background:#")
0840         .replace("background: pink", "color:black;background: pink");
0841         infoText.replace("//", "http://"); //This is to fix links on wikipedia which are missing http from the url
0842         infoText.replace("https:http:", "https:")
0843         .replace("http:http:", "http:"); //Just in case it was done to an actual complete url
0844 
0845         //This section is intended to remove links from the object name header at the top.  The links break up the header.
0846         int thLeft = infoText.indexOf("<th ");
0847         if (thLeft != -1)
0848         {
0849             int thRight = infoText.indexOf("</th>", thLeft);
0850             int firstA  = infoText.indexOf("<a ", thLeft);
0851             if (firstA != -1 && firstA < thRight)
0852             {
0853                 int rightA = infoText.indexOf(">", firstA) - firstA + 1;
0854                 infoText.remove(firstA, rightA);
0855                 int endA = infoText.indexOf("</a>", firstA);
0856                 infoText.remove(endA, 4);
0857             }
0858         }
0859 
0860         int annotationLeft  = infoText.indexOf("<annotation");
0861         int annotationRight = infoText.indexOf("</annotation>", annotationLeft) + 13 - annotationLeft;
0862         infoText.remove(annotationLeft,
0863                         annotationRight); //This removes the annotation that does not render correctly for some DSOs.
0864 
0865         int mathLeft  = infoText.indexOf("<img src=\"https://wikimedia.org/api/rest_v1/media/math");
0866         int mathRight = infoText.indexOf(">", mathLeft) + 1 - mathLeft;
0867         infoText.remove(mathLeft, mathRight); //This removes an image that doesn't render properly for some DSOs.
0868 
0869         infoText.replace("style=\"width:22em\"", "style=\"width:100%;background-color: black;color: white;\"");
0870         infoText = infoText + "<BR>(Source: <a href='" + "https://en.wikipedia.org/w/index.php?title=" + name +
0871                    "&redirects" + "'>Wikipedia</a>)";
0872         saveInfoURL(soitem, "https://en.wikipedia.org/w/index.php?title=" + name + "&redirects");
0873 
0874         int captionEnd = infoText.indexOf(
0875                              "</caption>"); //Start looking for the image AFTER the caption.  Planets have images in their caption.
0876         if (captionEnd == -1)
0877             captionEnd = 0;
0878         int leftImg = infoText.indexOf("src=\"", captionEnd) + 5;
0879         if (leftImg > captionEnd + 5)
0880         {
0881             int rightImg   = infoText.indexOf("\"", leftImg) - leftImg;
0882             QString imgURL = infoText.mid(leftImg, rightImg);
0883             imgURL.replace(
0884                 "http://upload.wikimedia.org",
0885                 "https://upload.wikimedia.org"); //Although they will display, the images apparently don't download properly unless they are https.
0886             saveImageURL(soitem, imgURL);
0887             downloadWikipediaImage(soitem, imgURL);
0888         }
0889 
0890         QString html = "<CENTER>" + infoText + "</table></CENTER>";
0891 
0892         saveObjectInfoBoxText(soitem, "infoText", html);
0893         bool isDarkTheme = (Options::currentTheme() == "Night Vision");
0894         QString color     = (isDarkTheme) ? "red" : "white";
0895         QString linkColor = (isDarkTheme) ? "red" : "yellow";
0896         if (isDarkTheme)
0897             html.replace("color: white", "color: " + color);
0898         html = "<HTML><HEAD><style type=text/css>body {color:" + color +
0899                ";} a {text-decoration: none;color:" + linkColor + ";}</style></HEAD><BODY>" + html + "</BODY></HTML>";
0900         if (soitem == m_CurSoItem)
0901             infoBoxText->setProperty("text", html);
0902     });
0903 }
0904 
0905 void WIView::saveObjectInfoBoxText(SkyObjItem *soitem, QString type, QString text)
0906 {
0907     QFile file;
0908     QString fname = type + '-' + soitem->getName().toLower().remove(' ') + ".html";
0909 
0910     QDir filePath(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/descriptions");
0911     filePath.mkpath(".");
0912 
0913     //determine filename in local user KDE directory tree.
0914     file.setFileName(filePath.filePath(fname));
0915 
0916     if (file.open(QIODevice::WriteOnly) == false)
0917     {
0918         qDebug() << Q_FUNC_INFO << "Image text cannot be saved for later.  file save error";
0919         return;
0920     }
0921     else
0922     {
0923         QTextStream stream(&file);
0924         stream << text;
0925         file.close();
0926     }
0927 }
0928 
0929 void WIView::saveImageURL(SkyObjItem *soitem, QString imageURL)
0930 {
0931     QFile file;
0932     //determine filename in local user KDE directory tree.
0933     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("image_url.dat"));
0934     QString entry = soitem->getName() + ':' + "Show Wikipedia Image" + ':' + imageURL;
0935 
0936     if (file.open(QIODevice::ReadOnly))
0937     {
0938         QTextStream in(&file);
0939         QString line;
0940         while (!in.atEnd())
0941         {
0942             line = in.readLine();
0943             if (line == entry)
0944             {
0945                 file.close();
0946                 return;
0947             }
0948         }
0949         file.close();
0950     }
0951 
0952     if (!file.open(QIODevice::ReadWrite | QIODevice::Append))
0953     {
0954         qDebug() << Q_FUNC_INFO << "Image URL cannot be saved for later.  image_url.dat error";
0955         return;
0956     }
0957     else
0958     {
0959         QTextStream stream(&file);
0960         stream << entry << '\n';
0961         file.close();
0962     }
0963 }
0964 
0965 void WIView::saveInfoURL(SkyObjItem *soitem, QString infoURL)
0966 {
0967     QFile file;
0968     //determine filename in local user KDE directory tree.
0969     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("info_url.dat"));
0970     QString entry = soitem->getName() + ':' + "Wikipedia Page" + ':' + infoURL;
0971 
0972     if (file.open(QIODevice::ReadOnly))
0973     {
0974         QTextStream in(&file);
0975         QString line;
0976         while (!in.atEnd())
0977         {
0978             line = in.readLine();
0979             if (line == entry)
0980             {
0981                 file.close();
0982                 return;
0983             }
0984         }
0985         file.close();
0986     }
0987 
0988     if (!file.open(QIODevice::ReadWrite | QIODevice::Append))
0989     {
0990         qDebug() << Q_FUNC_INFO << "Info URL cannot be saved for later.  info_url.dat error";
0991         return;
0992     }
0993     else
0994     {
0995         QTextStream stream(&file);
0996         stream << entry << '\n';
0997         file.close();
0998     }
0999 }
1000 
1001 void WIView::downloadWikipediaImage(SkyObjItem *soitem, QString imageURL)
1002 {
1003     QString fname = "wikiImage-" + soitem->getName().toLower().remove(' ') + ".png";
1004 
1005     QDir filePath(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/descriptions");
1006     filePath.mkpath(".");
1007 
1008     QString fileN = filePath.filePath(fname);
1009 
1010     QNetworkReply *response = manager->get(QNetworkRequest(QUrl(imageURL)));
1011     QTimer::singleShot(60000, response, [response]   //Shut it down after 60 sec.
1012     {
1013         response->abort();
1014         response->deleteLater();
1015         qDebug() << Q_FUNC_INFO << "Image Download Timed out.";
1016     });
1017     connect(response, &QNetworkReply::finished, this, [fileN, response, this]
1018     {
1019         response->deleteLater();
1020         if (response->error() != QNetworkReply::NoError)
1021             return;
1022         QImage *image           = new QImage();
1023         QByteArray responseData = response->readAll();
1024         if (image->loadFromData(responseData))
1025         {
1026             image->save(fileN);
1027             refreshListView(); //This is to update the images displayed with the new image.
1028         }
1029         else
1030             qDebug() << Q_FUNC_INFO << "image not downloaded";
1031     });
1032 }