Kstars

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

KDE's Doxygen guidelines are available online.