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