1 /*
2     SPDX-FileCopyrightText: 2005 Jason Harris <jharris@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "obslistwizard.h"
8 #include "Options.h"
9 
10 #include "geolocation.h"
11 #include "kstarsdata.h"
12 #include "dialogs/locationdialog.h"
13 #include "skycomponents/constellationboundarylines.h"
14 #include "skycomponents/catalogscomponent.h"
15 #include "skycomponents/skymapcomposite.h"
16 #include "catalogobject.h"
17 #include "catalogsdb.h"
18 
ObsListWizardUI(QWidget * p)19 ObsListWizardUI::ObsListWizardUI(QWidget *p) : QFrame(p)
20 {
21     setupUi(this);
22 }
23 
ObsListWizard(QWidget * ksparent)24 ObsListWizard::ObsListWizard(QWidget *ksparent) : QDialog(ksparent)
25 {
26 #ifdef Q_OS_OSX
27     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
28 #endif
29     olw                     = new ObsListWizardUI(this);
30     QVBoxLayout *mainLayout = new QVBoxLayout;
31     mainLayout->addWidget(olw);
32     setLayout(mainLayout);
33 
34     setWindowTitle(i18nc("@title:window", "Observing List Wizard"));
35 
36     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
37     nextB                       = new QPushButton(i18n("&Next >"));
38     nextB->setDefault(true);
39     backB = new QPushButton(i18n("< &Back"));
40     backB->setEnabled(false);
41 
42     buttonBox->addButton(backB, QDialogButtonBox::ActionRole);
43     buttonBox->addButton(nextB, QDialogButtonBox::ActionRole);
44     mainLayout->addWidget(buttonBox);
45 
46     connect(nextB, SIGNAL(clicked()), this, SLOT(slotNextPage()));
47     connect(backB, SIGNAL(clicked()), this, SLOT(slotPrevPage()));
48     connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotApplyFilters()));
49     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
50     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
51 
52     connect(olw->AllButton, SIGNAL(clicked()), this, SLOT(slotAllButton()));
53     connect(olw->NoneButton, SIGNAL(clicked()), this, SLOT(slotNoneButton()));
54     connect(olw->DeepSkyButton, SIGNAL(clicked()), this, SLOT(slotDeepSkyButton()));
55     connect(olw->SolarSystemButton, SIGNAL(clicked()), this, SLOT(slotSolarSystemButton()));
56     connect(olw->LocationButton, SIGNAL(clicked()), this, SLOT(slotChangeLocation()));
57 
58     //Update the count of objects when the user asks for it
59     connect(olw->updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateObjectCount()));
60 
61     // Enable the update count button when certain elements are changed
62     connect(olw->TypeList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty);
63     connect(olw->ConstellationList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty);
64     connect(olw->RAMin, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
65     connect(olw->RAMax, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
66     connect(olw->DecMin, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
67     connect(olw->DecMax, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
68     connect(olw->RA, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
69     connect(olw->Dec, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
70     connect(olw->Radius, &QLineEdit::editingFinished, this, &ObsListWizard::slotObjectCountDirty);
71     connect(olw->Date, &QDateEdit::dateChanged, this, &ObsListWizard::slotObjectCountDirty);
72     connect(olw->Mag, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
73             &ObsListWizard::slotObjectCountDirty);
74     connect(olw->IncludeNoMag, &QPushButton::clicked, this, &ObsListWizard::slotObjectCountDirty);
75     connect(olw->timeTo, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty);
76     connect(olw->timeFrom, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty);
77     connect(olw->minAlt, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
78             &ObsListWizard::slotObjectCountDirty);
79     connect(olw->maxAlt, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
80             &ObsListWizard::slotObjectCountDirty);
81 
82     olw->coverage->setValue(Options::obsListCoverage());
83     connect(olw->coverage, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [&](double value)
84     {
85         Options::setObsListCoverage(value);
86         slotObjectCountDirty();
87     });
88 
89     connect(olw->SelectByDate, SIGNAL(clicked()), this, SLOT(slotToggleDateWidgets()));
90     connect(olw->SelectByMagnitude, SIGNAL(clicked()), this, SLOT(slotToggleMagWidgets()));
91 
92     geo = KStarsData::Instance()->geo();
93     olw->LocationButton->setText(geo->fullName());
94     olw->Date->setDate(KStarsDateTime::currentDateTime().date());
95     olw->timeFrom->setTime(QTime(18, 0));
96     olw->timeTo->setTime(QTime(23, 59));
97 
98     initialize();
99 }
100 
initialize()101 void ObsListWizard::initialize()
102 {
103     KStarsData *data = KStarsData::Instance();
104     olw->olwStack->setCurrentIndex(0);
105 
106     //Populate the list of constellations
107     foreach (SkyObject *p, data->skyComposite()->constellationNames())
108         olw->ConstellationList->addItem(p->name());
109 
110     //unSelect all object types
111     olw->TypeList->clearSelection();
112 
113     olw->Mag->setMinimum(-5.0);
114     olw->Mag->setMaximum(20.0);
115     olw->Mag->setValue(6.0);
116 
117     olw->RA->setDegType(false);
118     olw->RAMin->setDegType(false);
119     olw->RAMax->setDegType(false);
120 
121     //Initialize object counts
122     ObjectCount   = 0; //number of objects in observing list
123     StarCount     = data->skyComposite()->stars().size();     //total number of stars
124     PlanetCount   = 10;                                       //Sun, Moon, 8 planets
125     AsteroidCount = data->skyComposite()->asteroids().size(); //total number of asteroids
126     CometCount    = data->skyComposite()->comets().size();    //total number of comets
127     //DeepSkyObjects
128     OpenClusterCount = 0;
129     GlobClusterCount = 0;
130     GasNebCount      = 0;
131     PlanNebCount     = 0;
132     GalaxyCount      = 0;
133 
134     CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() };
135 
136     const auto &stats{ manager.get_master_statistics() };
137     if (!stats.first)
138         return;
139 
140     for (const auto &element : stats.second.object_counts)
141     {
142         auto cnt = element.second;
143         switch (element.first)
144         {
145             case SkyObject::GALAXY:
146                 GalaxyCount += cnt;
147                 break;
148             case SkyObject::STAR:
149             case SkyObject::CATALOG_STAR:
150                 StarCount += cnt;
151                 break;
152             case SkyObject::OPEN_CLUSTER:
153                 OpenClusterCount += cnt;
154                 break;
155             case SkyObject::GLOBULAR_CLUSTER:
156                 GlobClusterCount += cnt;
157                 break;
158             case SkyObject::GASEOUS_NEBULA:
159             case SkyObject::SUPERNOVA_REMNANT:
160                 GasNebCount += cnt;
161                 break;
162             case SkyObject::PLANETARY_NEBULA:
163                 PlanNebCount += cnt;
164                 break;
165             default:
166                 break;
167         }
168     }
169 }
170 
isItemSelected(const QString & name,QListWidget * listWidget,bool * ok)171 bool ObsListWizard::isItemSelected(const QString &name, QListWidget *listWidget, bool *ok)
172 {
173     /*QList<QListWidgetItem *> items = listWidget->findItems(name, Qt::MatchContains);
174     if (ok)
175         *ok = items.size();
176     return items.size() && listWidget->isItemSelected(items[0]);
177     ;*/
178     foreach(QListWidgetItem *item, listWidget->selectedItems())
179     {
180         if (item->text().compare(name, Qt::CaseInsensitive) == 0)
181         {
182             if (ok)
183                 *ok = true;
184             return true;
185         }
186     }
187 
188     if (ok)
189         *ok = false;
190     return false;
191 }
192 
setItemSelected(const QString & name,QListWidget * listWidget,bool value,bool * ok)193 void ObsListWizard::setItemSelected(const QString &name, QListWidget *listWidget, bool value, bool *ok)
194 {
195     QList<QListWidgetItem *> items = listWidget->findItems(name, Qt::MatchContains);
196     if (ok)
197         *ok = items.size();
198     if (items.size())
199         items[0]->setSelected(value);
200 }
201 
202 //Advance to the next page in the stack.  However, on page 2 the user
203 //selects what regional filter they want to use, and this determines
204 //what the page following page 2 should be:
205 // + Constellation(s): the next page index is 3
206 // + Rectangular region: the next page index is 4
207 // + Circular region: the next page index is 5
208 // + No region selected (a.k.a. "all over the sky"): the next page index is 6
209 //
210 //Also, if the current page index is 3 or 4, then the next page should be 6.
211 //
212 //NOTE: the page indexes are hard-coded here, which isn't ideal.  However,
213 //There's no easy way to access the pointers of widgets in the stack
214 //if you didn't save them at the start.
slotNextPage()215 void ObsListWizard::slotNextPage()
216 {
217     int NextPage = olw->olwStack->currentIndex() + 1;
218 
219     if (olw->olwStack->currentIndex() == 2)
220     {
221         //On the Region select page.  Determine what
222         //the next page index should be.
223         //No need to handle "by constellation, it's already currentIndex + 1.
224         if (isItemSelected(i18n("in a rectangular region"), olw->RegionList))
225             NextPage = 4;
226         if (isItemSelected(i18n("in a circular region"), olw->RegionList))
227             NextPage = 5;
228         if (isItemSelected(i18n("all over the sky"), olw->RegionList))
229             NextPage = 6;
230     }
231 
232     if (olw->olwStack->currentIndex() == 3 || olw->olwStack->currentIndex() == 4)
233         NextPage = 6;
234 
235     olw->olwStack->setCurrentIndex(NextPage);
236 
237     if (olw->olwStack->currentIndex() == olw->olwStack->count() - 1)
238         nextB->setEnabled(false);
239 
240     backB->setEnabled(true);
241 }
242 
243 //Advance to the previous page in the stack.  However, because the
244 //path through the wizard branches depending on the user's choice of
245 //Region filter, the previous page is not always currentPage-1.
246 //Specifically, if the current page index is 4, 5, or 6, then the Previous
247 //page index should be 2 rather than currentIndex-1.
slotPrevPage()248 void ObsListWizard::slotPrevPage()
249 {
250     int PrevPage = olw->olwStack->currentIndex() - 1;
251 
252     if (olw->olwStack->currentIndex() == 4 || olw->olwStack->currentIndex() == 5 || olw->olwStack->currentIndex() == 6)
253         PrevPage = 2;
254 
255     olw->olwStack->setCurrentIndex(PrevPage);
256 
257     if (olw->olwStack->currentIndex() == 0)
258         backB->setEnabled(false);
259 
260     nextB->setEnabled(true);
261 }
262 
slotAllButton()263 void ObsListWizard::slotAllButton()
264 {
265     for (int i = 0; i < olw->TypeList->count(); ++i)
266         olw->TypeList->item(i)->setSelected(true);
267 }
268 
slotNoneButton()269 void ObsListWizard::slotNoneButton()
270 {
271     olw->TypeList->clearSelection();
272 }
273 
slotDeepSkyButton()274 void ObsListWizard::slotDeepSkyButton()
275 {
276     olw->TypeList->clearSelection();
277     setItemSelected(i18n("Open clusters"), olw->TypeList, true);
278     setItemSelected(i18n("Globular clusters"), olw->TypeList, true);
279     setItemSelected(i18n("Gaseous nebulae"), olw->TypeList, true);
280     setItemSelected(i18n("Planetary nebulae"), olw->TypeList, true);
281     setItemSelected(i18n("Galaxies"), olw->TypeList, true);
282 }
283 
slotSolarSystemButton()284 void ObsListWizard::slotSolarSystemButton()
285 {
286     olw->TypeList->clearSelection();
287     setItemSelected(i18n("Sun, moon, planets"), olw->TypeList, true);
288     setItemSelected(i18n("Comets"), olw->TypeList, true);
289     setItemSelected(i18n("Asteroids"), olw->TypeList, true);
290 }
291 
slotChangeLocation()292 void ObsListWizard::slotChangeLocation()
293 {
294     QPointer<LocationDialog> ld = new LocationDialog(this);
295 
296     if (ld->exec() == QDialog::Accepted)
297     {
298         //set geographic location
299         if (ld->selectedCity())
300         {
301             geo = ld->selectedCity();
302             olw->LocationButton->setText(geo->fullName());
303         }
304     }
305     delete ld;
306 }
307 
slotToggleDateWidgets()308 void ObsListWizard::slotToggleDateWidgets()
309 {
310     olw->Date->setEnabled(olw->SelectByDate->isChecked());
311     olw->LocationButton->setEnabled(olw->SelectByDate->isChecked());
312     olw->timeTo->setEnabled(olw->SelectByDate->isChecked());
313     olw->timeFrom->setEnabled(olw->SelectByDate->isChecked());
314     olw->minAlt->setEnabled(olw->SelectByDate->isChecked());
315     olw->maxAlt->setEnabled(olw->SelectByDate->isChecked());
316 
317     //    slotUpdateObjectCount();
318     slotObjectCountDirty();
319 }
320 
slotToggleMagWidgets()321 void ObsListWizard::slotToggleMagWidgets()
322 {
323     olw->Mag->setEnabled(olw->SelectByMagnitude->isChecked());
324     olw->IncludeNoMag->setEnabled(olw->SelectByMagnitude->isChecked());
325     slotObjectCountDirty();
326     //    slotUpdateObjectCount();
327 }
328 
slotParseRegion()329 void ObsListWizard::slotParseRegion()
330 {
331     if (sender()->objectName() == "RAMin" || sender()->objectName() == "RAMax" || sender()->objectName() == "DecMin" ||
332             sender()->objectName() == "DecMax")
333     {
334         if (!olw->RAMin->isEmpty() && !olw->RAMax->isEmpty() && !olw->DecMin->isEmpty() && !olw->DecMax->isEmpty())
335         {
336             bool rectOk = false;
337             xRect1      = 0.0;
338             xRect2      = 0.0;
339             yRect1      = 0.0;
340             yRect2      = 0.0;
341 
342             xRect1 = olw->RAMin->createDms(false, &rectOk).Hours();
343             if (rectOk)
344                 xRect2 = olw->RAMax->createDms(false, &rectOk).Hours();
345             if (rectOk)
346                 yRect1 = olw->DecMin->createDms(true, &rectOk).Degrees();
347             if (rectOk)
348                 yRect2 = olw->DecMax->createDms(true, &rectOk).Degrees();
349             if (xRect2 == 0.0)
350                 xRect2 = 24.0;
351 
352             if (!rectOk)
353             {
354                 //			qWarning() << i18n( "Illegal rectangle specified, no region selection possible." ) ;
355                 return;
356             }
357 
358             //Make sure yRect1 < yRect2.
359             if (yRect1 > yRect2)
360             {
361                 double temp = yRect2;
362                 yRect2      = yRect1;
363                 yRect1      = temp;
364             }
365 
366             //If xRect1 > xRect2, we may need to swap the two values, or subtract 24h from xRect1.
367             if (xRect1 > xRect2)
368             {
369                 if (xRect1 - xRect2 > 12.0) //the user probably wants a region that straddles 0h
370                 {
371                     xRect1 -= 24.0;
372                 }
373                 else //the user probably wants xRect2 to be the lower limit
374                 {
375                     double temp = xRect2;
376                     xRect2      = xRect1;
377                     xRect1      = temp;
378                 }
379             }
380 
381             //            slotUpdateObjectCount();
382             slotObjectCountDirty();
383         }
384     }
385     else if (!olw->RA->isEmpty() && !olw->Dec->isEmpty() && !olw->Radius->isEmpty())
386     {
387         bool circOk;
388         dms ra = olw->RA->createDms(false, &circOk);
389         dms dc;
390         if (circOk)
391             dc = olw->Dec->createDms(true, &circOk);
392         if (circOk)
393         {
394             pCirc.set(ra, dc);
395             rCirc = olw->Radius->createDms(true, &circOk).Degrees();
396         }
397         else
398         {
399             qWarning() << i18n("Illegal circle specified, no region selection possible.");
400             return;
401         }
402         //            slotUpdateObjectCount();
403         slotObjectCountDirty();
404     }
405 }
406 
slotObjectCountDirty()407 void ObsListWizard::slotObjectCountDirty()
408 {
409     olw->updateButton->setDisabled(false);
410 }
411 
slotUpdateObjectCount()412 void ObsListWizard::slotUpdateObjectCount()
413 {
414     QApplication::setOverrideCursor(Qt::WaitCursor);
415     ObjectCount = 0;
416     if (isItemSelected(i18n("Stars"), olw->TypeList))
417         ObjectCount += StarCount;
418     if (isItemSelected(i18n("Sun, moon, planets"), olw->TypeList))
419         ObjectCount += PlanetCount;
420     if (isItemSelected(i18n("Comets"), olw->TypeList))
421         ObjectCount += CometCount;
422     if (isItemSelected(i18n("Asteroids"), olw->TypeList))
423         ObjectCount += AsteroidCount;
424     if (isItemSelected(i18n("Galaxies"), olw->TypeList))
425         ObjectCount += GalaxyCount;
426     if (isItemSelected(i18n("Open clusters"), olw->TypeList))
427         ObjectCount += OpenClusterCount;
428     if (isItemSelected(i18n("Globular clusters"), olw->TypeList))
429         ObjectCount += GlobClusterCount;
430     if (isItemSelected(i18n("Gaseous nebulae"), olw->TypeList))
431         ObjectCount += GasNebCount;
432     if (isItemSelected(i18n("Planetary nebulae"), olw->TypeList))
433         ObjectCount += PlanNebCount;
434 
435     applyFilters(false); //false = only adjust counts, do not build list
436     QApplication::restoreOverrideCursor();
437     olw->updateButton->setDisabled(true);
438 }
439 
applyFilters(bool doBuildList)440 void ObsListWizard::applyFilters(bool doBuildList)
441 {
442     bool filterPass  = true;
443     KStarsData *data = KStarsData::Instance();
444     if (doBuildList)
445         obsList().clear();
446 
447     //We don't need to call applyRegionFilter() if no region filter is selected, *and*
448     //we are just counting items (i.e., doBuildList is false)
449     bool needRegion = true;
450     if (!doBuildList && isItemSelected(i18n("all over the sky"), olw->RegionList))
451         needRegion = false;
452 
453     double maglimit = 100.;
454     if (olw->SelectByMagnitude->isChecked())
455         maglimit = olw->Mag->value();
456 
457     //Stars
458     if (isItemSelected(i18n("Stars"), olw->TypeList))
459     {
460         const QList<SkyObject *> &starList = data->skyComposite()->stars();
461         int starIndex(starList.size());
462         if (maglimit < 100.)
463         {
464             //Stars are sorted by mag, so use binary search algo to find index of faintest mag
465             int low(0), high(starList.size() - 1), middle(high);
466             while (low < high)
467             {
468                 middle = (low + high) / 2;
469                 if (maglimit == starList.at(middle)->mag())
470                     break;
471                 if (maglimit < starList.at(middle)->mag())
472                     high = middle - 1;
473                 if (maglimit > starList.at(middle)->mag())
474                     low = middle + 1;
475             }
476             //now, the star at "middle" has the right mag, but we want the *last* star that has this mag.
477             for (starIndex = middle + 1; starIndex < starList.size(); ++starIndex)
478             {
479                 if (starList.at(starIndex)->mag() > maglimit)
480                     break;
481             }
482         }
483 
484         //DEBUG
485         qDebug() << QString("starIndex for mag %1: %2").arg(maglimit).arg(starIndex);
486 
487         if (!doBuildList)
488         {
489             //reduce StarCount by appropriate amount
490             ObjectCount -= StarCount;
491             ObjectCount += starIndex;
492         }
493         for (int i = 0; i < starIndex; ++i)
494         {
495             SkyObject *o = (SkyObject *)(starList[i]);
496 
497             // JM 2012-10-22: Skip unnamed stars
498             if (o->name() == "star")
499             {
500                 if (!doBuildList)
501                     --ObjectCount;
502                 continue;
503             }
504 
505             if (needRegion)
506                 filterPass = applyRegionFilter(o, doBuildList, !doBuildList);
507             //Filter objects visible from geo at Date if region filter passes
508             if (olw->SelectByDate->isChecked() && filterPass)
509                 applyObservableFilter(o, doBuildList, !doBuildList);
510         }
511     }
512 
513     //Sun, Moon, Planets
514     if (isItemSelected(i18n("Sun, moon, planets"), olw->TypeList))
515     {
516         if (maglimit < data->skyComposite()->findByName(i18n("Sun"))->mag())
517         {
518             if (!doBuildList)
519                 --ObjectCount;
520             filterPass = false;
521         }
522         else
523             filterPass = true;
524 
525         if (needRegion && filterPass)
526             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Sun")), doBuildList);
527         if (olw->SelectByDate->isChecked() && filterPass)
528             applyObservableFilter(data->skyComposite()->findByName(i18n("Sun")), doBuildList);
529 
530         if (maglimit < data->skyComposite()->findByName(i18n("Moon"))->mag())
531         {
532             if (!doBuildList)
533                 --ObjectCount;
534             filterPass = false;
535         }
536         else
537             filterPass = true;
538 
539         if (needRegion && filterPass)
540             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Moon")), doBuildList);
541         if (olw->SelectByDate->isChecked() && filterPass)
542             applyObservableFilter(data->skyComposite()->findByName(i18n("Moon")), doBuildList);
543 
544         if (maglimit < data->skyComposite()->findByName(i18n("Mercury"))->mag())
545         {
546             if (!doBuildList)
547                 --ObjectCount;
548             filterPass = false;
549         }
550         else
551             filterPass = true;
552         if (needRegion && filterPass)
553             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Mercury")), doBuildList);
554         if (olw->SelectByDate->isChecked() && filterPass)
555             applyObservableFilter(data->skyComposite()->findByName(i18n("Mercury")), doBuildList);
556 
557         if (maglimit < data->skyComposite()->findByName(i18n("Venus"))->mag())
558         {
559             if (!doBuildList)
560                 --ObjectCount;
561             filterPass = false;
562         }
563         else
564             filterPass = true;
565 
566         if (needRegion && filterPass)
567             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Venus")), doBuildList);
568         if (olw->SelectByDate->isChecked() && filterPass)
569             applyObservableFilter(data->skyComposite()->findByName(i18n("Venus")), doBuildList);
570 
571         if (maglimit < data->skyComposite()->findByName(i18n("Mars"))->mag())
572         {
573             if (!doBuildList)
574                 --ObjectCount;
575             filterPass = false;
576         }
577         else
578             filterPass = true;
579         if (needRegion && filterPass)
580             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Mars")), doBuildList);
581         if (olw->SelectByDate->isChecked() && filterPass)
582             applyObservableFilter(data->skyComposite()->findByName(i18n("Mars")), doBuildList);
583 
584         if (maglimit < data->skyComposite()->findByName(i18n("Jupiter"))->mag())
585         {
586             if (!doBuildList)
587                 --ObjectCount;
588             filterPass = false;
589         }
590         else
591             filterPass = true;
592         if (needRegion && filterPass)
593             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Jupiter")), doBuildList);
594         if (olw->SelectByDate->isChecked() && filterPass)
595             applyObservableFilter(data->skyComposite()->findByName(i18n("Jupiter")), doBuildList);
596 
597         if (maglimit < data->skyComposite()->findByName(i18n("Saturn"))->mag())
598         {
599             if (!doBuildList)
600                 --ObjectCount;
601             filterPass = false;
602         }
603         else
604             filterPass = true;
605 
606         if (needRegion && filterPass)
607             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Saturn")), doBuildList);
608         if (olw->SelectByDate->isChecked() && filterPass)
609             applyObservableFilter(data->skyComposite()->findByName(i18n("Saturn")), doBuildList);
610 
611         if (maglimit < data->skyComposite()->findByName(i18n("Uranus"))->mag())
612         {
613             if (!doBuildList)
614                 --ObjectCount;
615             filterPass = false;
616         }
617         else
618             filterPass = true;
619 
620         if (needRegion && filterPass)
621             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Uranus")), doBuildList);
622         if (olw->SelectByDate->isChecked() && filterPass)
623             applyObservableFilter(data->skyComposite()->findByName(i18n("Uranus")), doBuildList);
624 
625         if (maglimit < data->skyComposite()->findByName(i18n("Neptune"))->mag())
626         {
627             if (!doBuildList)
628                 --ObjectCount;
629             filterPass = false;
630         }
631         else
632             filterPass = true;
633 
634         if (needRegion && filterPass)
635             filterPass = applyRegionFilter(data->skyComposite()->findByName(i18n("Neptune")), doBuildList);
636         if (olw->SelectByDate->isChecked() && filterPass)
637             applyObservableFilter(data->skyComposite()->findByName(i18n("Neptune")), doBuildList);
638 
639         //        if (maglimit < data->skyComposite()->findByName(i18nc("Asteroid name (optional)", "Pluto"))->mag())
640         //        {
641         //            if (!doBuildList)
642         //                --ObjectCount;
643         //            filterPass = false;
644         //        }
645         //        else
646         //            filterPass = true;
647 
648         //        if (needRegion && filterPass)
649         //            filterPass = applyRegionFilter(data->skyComposite()->findByName(i18nc("Asteroid name (optional)", "Pluto")), doBuildList);
650         //        if (olw->SelectByDate->isChecked() && filterPass)
651         //            applyObservableFilter(data->skyComposite()->findByName(i18nc("Asteroid name (optional)", "Pluto")), doBuildList);
652     }
653 
654     //Deep sky objects
655     bool dso =
656         (isItemSelected(i18n("Open clusters"), olw->TypeList) ||
657          isItemSelected(i18n("Globular clusters"), olw->TypeList) ||
658          isItemSelected(i18n("Gaseous nebulae"), olw->TypeList) ||
659          isItemSelected(i18n("Planetary nebulae"), olw->TypeList) || isItemSelected(i18n("Galaxies"), olw->TypeList));
660 
661     if (dso)
662     {
663         //Don't need to do anything if we are just counting objects and not
664         //filtering by region or magnitude
665         if (needRegion || olw->SelectByMagnitude->isChecked() || olw->SelectByDate->isChecked())
666         {
667 
668             CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() };
669 
670             for(auto& o : manager.get_objects(olw->SelectByMagnitude->isChecked() ? maglimit : 99))
671             {
672                 //Skip unselected object types
673                 bool typeSelected = false;
674                 // if ( (o->type() == SkyObject::STAR || o->type() == SkyObject::CATALOG_STAR) &&
675                 //       isItemSelected( i18n( "Stars" ), olw->TypeList ) )
676     //                         typeSelected = true;
677                 switch (o.type())
678                 {
679                     case SkyObject::OPEN_CLUSTER:
680                         if (isItemSelected(i18n("Open clusters"), olw->TypeList))
681                             typeSelected = true;
682                         break;
683 
684                     case SkyObject::GLOBULAR_CLUSTER:
685                         if (isItemSelected(i18n("Globular clusters"), olw->TypeList))
686                             typeSelected = true;
687                         break;
688 
689                     case SkyObject::GASEOUS_NEBULA:
690                     case SkyObject::SUPERNOVA_REMNANT:
691                         if (isItemSelected(i18n("Gaseous nebulae"), olw->TypeList))
692                             typeSelected = true;
693                         break;
694 
695                     case SkyObject::PLANETARY_NEBULA:
696                         if (isItemSelected(i18n("Planetary nebulae"), olw->TypeList))
697                             typeSelected = true;
698                         break;
699                     case SkyObject::GALAXY:
700                         if (isItemSelected(i18n("Galaxies"), olw->TypeList))
701                             typeSelected = true;
702                         break;
703                 }
704 
705                 if (!typeSelected)
706                     continue;
707 
708                 if (olw->SelectByMagnitude->isChecked())
709                 {
710                     if (o.mag() > 90.)
711                     {
712                         if (olw->IncludeNoMag->isChecked())
713                         {
714                             auto *obj = &o;
715                             if(doBuildList)
716                                 obj = &data->skyComposite()->catalogsComponent()
717                                            ->insertStaticObject(o);
718 
719                             if (needRegion)
720                                 filterPass = applyRegionFilter(obj, doBuildList);
721                             if (olw->SelectByDate->isChecked() && filterPass)
722                                 applyObservableFilter(obj, doBuildList);
723                         }
724                         else if (!doBuildList)
725                             --ObjectCount;
726                     }
727                     else
728                     {
729                         if (o.mag() <= maglimit)
730                         {
731                             auto *obj = &o;
732                             if(doBuildList)
733                                 obj = &data->skyComposite()->catalogsComponent()
734                                            ->insertStaticObject(o);
735 
736                             if (needRegion)
737                                 filterPass = applyRegionFilter(obj, doBuildList);
738                             if (olw->SelectByDate->isChecked() && filterPass)
739                                 applyObservableFilter(obj, doBuildList);
740                         }
741                         else if (!doBuildList)
742                             --ObjectCount;
743                     }
744                 }
745                 else
746                 {
747                     auto *obj = &o;
748                     if(doBuildList)
749                         obj = &data->skyComposite()->catalogsComponent()
750                                    ->insertStaticObject(o);
751 
752                     if (needRegion)
753                         filterPass = applyRegionFilter(obj, doBuildList);
754                     if (olw->SelectByDate->isChecked() && filterPass)
755                         applyObservableFilter(obj, doBuildList);
756                 }
757             }
758         }
759     }
760 
761     //Comets
762     if (isItemSelected(i18n("Comets"), olw->TypeList))
763     {
764         foreach (SkyObject *o, data->skyComposite()->comets())
765         {
766             if (olw->SelectByMagnitude->isChecked())
767             {
768                 if (o->mag() > 90.)
769                 {
770                     if (olw->IncludeNoMag->isChecked())
771                     {
772                         if (needRegion)
773                             filterPass = applyRegionFilter(o, doBuildList);
774                         if (olw->SelectByDate->isChecked() && filterPass)
775                             applyObservableFilter(o, doBuildList);
776                     }
777                     else if (!doBuildList)
778                         --ObjectCount;
779                 }
780                 else
781                 {
782                     if (o->mag() <= maglimit)
783                     {
784                         if (needRegion)
785                             filterPass = applyRegionFilter(o, doBuildList);
786                         if (olw->SelectByDate->isChecked() && filterPass)
787                             applyObservableFilter(o, doBuildList);
788                     }
789                     else if (!doBuildList)
790                         --ObjectCount;
791                 }
792             }
793             else
794             {
795                 if (needRegion)
796                     filterPass = applyRegionFilter(o, doBuildList);
797                 if (olw->SelectByDate->isChecked() && filterPass)
798                     applyObservableFilter(o, doBuildList);
799             }
800         }
801     }
802 
803     //Asteroids
804     if (isItemSelected(i18n("Asteroids"), olw->TypeList))
805     {
806         foreach (SkyObject *o, data->skyComposite()->asteroids())
807         {
808             if (olw->SelectByMagnitude->isChecked())
809             {
810                 if (o->mag() > 90.)
811                 {
812                     if (olw->IncludeNoMag->isChecked())
813                     {
814                         if (needRegion)
815                             filterPass = applyRegionFilter(o, doBuildList);
816                         if (olw->SelectByDate->isChecked() && filterPass)
817                             applyObservableFilter(o, doBuildList);
818                     }
819                     else if (!doBuildList)
820                         --ObjectCount;
821                 }
822                 else
823                 {
824                     if (o->mag() <= maglimit)
825                     {
826                         if (needRegion)
827                             filterPass = applyRegionFilter(o, doBuildList);
828                         if (olw->SelectByDate->isChecked() && filterPass)
829                             applyObservableFilter(o, doBuildList);
830                     }
831                     else if (!doBuildList)
832                         --ObjectCount;
833                 }
834             }
835             else
836             {
837                 if (needRegion)
838                     filterPass = applyRegionFilter(o, doBuildList);
839                 if (olw->SelectByDate->isChecked() && filterPass)
840                     applyObservableFilter(o, doBuildList);
841             }
842         }
843     }
844 
845     //Update the object count label
846     if (doBuildList)
847         ObjectCount = obsList().size();
848 
849     olw->CountLabel->setText(i18np("Your observing list currently has 1 object",
850                                    "Your observing list currently has %1 objects", ObjectCount));
851 }
852 
applyRegionFilter(SkyObject * o,bool doBuildList,bool doAdjustCount)853 bool ObsListWizard::applyRegionFilter(SkyObject *o, bool doBuildList, bool doAdjustCount)
854 {
855     //select by constellation
856     if (isItemSelected(i18n("by constellation"), olw->RegionList))
857     {
858         QString c = KStarsData::Instance()->skyComposite()->constellationBoundary()->constellationName(o);
859 
860         if (isItemSelected(c, olw->ConstellationList))
861         {
862             if (doBuildList)
863                 obsList().append(o);
864 
865             return true;
866         }
867         else if (doAdjustCount)
868         {
869             --ObjectCount;
870             return false;
871         }
872         else
873             return false;
874     }
875 
876     //select by rectangular region
877     else if (isItemSelected(i18n("in a rectangular region"), olw->RegionList))
878     {
879         double ra      = o->ra().Hours();
880         double dec     = o->dec().Degrees();
881         bool addObject = false;
882         if (dec >= yRect1 && dec <= yRect2)
883         {
884             if (xRect1 < 0.0)
885             {
886                 addObject = ra >= xRect1 + 24.0 || ra <= xRect2;
887             }
888             else
889             {
890                 addObject = ra >= xRect1 && ra <= xRect2;
891             }
892         }
893 
894         if (addObject)
895         {
896             if (doBuildList)
897                 obsList().append(o);
898 
899             return true;
900         }
901 
902         else
903         {
904             if (doAdjustCount)
905                 --ObjectCount;
906 
907             return false;
908         }
909     }
910 
911     //select by circular region
912     //make sure circ region data are valid
913     else if (isItemSelected(i18n("in a circular region"), olw->RegionList))
914     {
915         if (o->angularDistanceTo(&pCirc).Degrees() < rCirc)
916         {
917             if (doBuildList)
918                 obsList().append(o);
919 
920             return true;
921         }
922         else if (doAdjustCount)
923         {
924             --ObjectCount;
925             return false;
926         }
927         else
928             return false;
929     }
930 
931     //No region filter, just add the object
932     else if (doBuildList)
933     {
934         obsList().append(o);
935     }
936 
937     return true;
938 }
939 
applyObservableFilter(SkyObject * o,bool doBuildList,bool doAdjustCount)940 bool ObsListWizard::applyObservableFilter(SkyObject *o, bool doBuildList, bool doAdjustCount)
941 {
942     SkyPoint p = *o;
943 
944     //Check altitude of object every hour from 18:00 to midnight
945     //If it's ever above 15 degrees, flag it as visible
946     KStarsDateTime Evening(olw->Date->date(), QTime(18, 0, 0), Qt::LocalTime);
947     KStarsDateTime Midnight(olw->Date->date().addDays(1), QTime(0, 0, 0), Qt::LocalTime);
948     double minAlt = 15, maxAlt = 90;
949 
950     // Or use user-selected values, if they're valid
951     if (olw->timeFrom->time().isValid() && olw->timeTo->time().isValid())
952     {
953         Evening.setTime(olw->timeFrom->time());
954         Midnight.setTime(olw->timeTo->time());
955 
956         // If time from < timeTo (e.g. 06:00 PM to 9:00 PM)
957         // then we stay on the same day.
958         if (olw->timeFrom->time() < olw->timeTo->time())
959         {
960             Midnight.setDate(olw->Date->date());
961         }
962         // Otherwise we advance by one day
963         else
964         {
965             Midnight.setDate(olw->Date->date().addDays(1));
966         }
967     }
968 
969     minAlt = olw->minAlt->value();
970     maxAlt = olw->maxAlt->value();
971 
972     // This is the "relaxed" search mode
973     // where if the object obeys the restrictions in 50% of the time of the range
974     // then it qualifies as "visible"
975     double totalCount = 0, visibleCount = 0;
976     for (KStarsDateTime t = Evening; t < Midnight; t = t.addSecs(3600.0))
977     {
978         dms LST = geo->GSTtoLST(t.gst());
979         p.EquatorialToHorizontal(&LST, geo->lat());
980         totalCount++;
981         if (p.alt().Degrees() >= minAlt && p.alt().Degrees() <= maxAlt)
982             visibleCount++;
983     }
984 
985     // If the object is within the min/max alt at least coverage % of the time range
986     // then consider it visible
987     if (visibleCount / totalCount >= olw->coverage->value() / 100.0)
988         return true;
989 
990     if (doAdjustCount)
991         --ObjectCount;
992     if (doBuildList)
993         obsList().takeAt(obsList().indexOf(o));
994 
995     return false;
996 
997     // This is the strict mode where ANY object that does not meet the min & max
998     // altitude at ANY time would be removed from the list.
999 #if 0
1000     for (KStarsDateTime t = Evening; t < Midnight; t = t.addSecs(3600.0))
1001     {
1002         dms LST = geo->GSTtoLST(t.gst());
1003         p.EquatorialToHorizontal(&LST, geo->lat());
1004         if (p.alt().Degrees() < minAlt || p.alt().Degrees() > maxAlt)
1005         {
1006             if (doAdjustCount)
1007                 --ObjectCount;
1008             if (doBuildList)
1009                 obsList().takeAt(obsList().indexOf(o));
1010             return false;
1011         }
1012     }
1013     return true;
1014 #endif
1015 
1016 }
1017