File indexing completed on 2024-04-21 14:46:58

0001 /*
0002     SPDX-FileCopyrightText: 2005 Jason Harris <jharris@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "obslistwizard.h"
0008 #include "Options.h"
0009 
0010 #include "geolocation.h"
0011 #include "kstarsdata.h"
0012 #include "dialogs/locationdialog.h"
0013 #include "skycomponents/constellationboundarylines.h"
0014 #include "skycomponents/catalogscomponent.h"
0015 #include "skycomponents/skymapcomposite.h"
0016 #include "catalogobject.h"
0017 #include "catalogsdb.h"
0018 
0019 ObsListWizardUI::ObsListWizardUI(QWidget *p) : QFrame(p)
0020 {
0021     setupUi(this);
0022 }
0023 
0024 ObsListWizard::ObsListWizard(QWidget *ksparent) : QDialog(ksparent)
0025 {
0026 #ifdef Q_OS_OSX
0027     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0028 #endif
0029     olw                     = new ObsListWizardUI(this);
0030     QVBoxLayout *mainLayout = new QVBoxLayout;
0031     mainLayout->addWidget(olw);
0032     setLayout(mainLayout);
0033 
0034     setWindowTitle(i18nc("@title:window", "Observing List Wizard"));
0035 
0036     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
0037     nextB                       = new QPushButton(i18n("&Next >"));
0038     nextB->setDefault(true);
0039     backB = new QPushButton(i18n("< &Back"));
0040     backB->setEnabled(false);
0041 
0042     buttonBox->addButton(backB, QDialogButtonBox::ActionRole);
0043     buttonBox->addButton(nextB, QDialogButtonBox::ActionRole);
0044     mainLayout->addWidget(buttonBox);
0045 
0046     connect(nextB, SIGNAL(clicked()), this, SLOT(slotNextPage()));
0047     connect(backB, SIGNAL(clicked()), this, SLOT(slotPrevPage()));
0048     connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotApplyFilters()));
0049     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
0050     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0051 
0052     connect(olw->AllButton, SIGNAL(clicked()), this, SLOT(slotAllButton()));
0053     connect(olw->NoneButton, SIGNAL(clicked()), this, SLOT(slotNoneButton()));
0054     connect(olw->DeepSkyButton, SIGNAL(clicked()), this, SLOT(slotDeepSkyButton()));
0055     connect(olw->SolarSystemButton, SIGNAL(clicked()), this, SLOT(slotSolarSystemButton()));
0056     connect(olw->LocationButton, SIGNAL(clicked()), this, SLOT(slotChangeLocation()));
0057 
0058     //Update the count of objects when the user asks for it
0059     connect(olw->updateButton, SIGNAL(clicked()), this, SLOT(slotUpdateObjectCount()));
0060 
0061     // Enable the update count button when certain elements are changed
0062     // -1- ObjectType
0063     connect(olw->TypeList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty);
0064     // -A- By constellation
0065     connect(olw->ConstellationList, &QListWidget::itemSelectionChanged, this, &ObsListWizard::slotObjectCountDirty);
0066     // -B- Rectangular region
0067     connect(olw->RAMin,  &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0068     connect(olw->RAMax,  &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0069     connect(olw->DecMin, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0070     connect(olw->DecMax, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0071     // -C- Circular region
0072     connect(olw->RA,     &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0073     connect(olw->Dec,    &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0074     connect(olw->Radius, &QLineEdit::editingFinished, this, &ObsListWizard::slotParseRegion);
0075     // -2- Date
0076     connect(olw->SelectByDate, SIGNAL(clicked()), this, SLOT(slotToggleDateWidgets()));
0077     connect(olw->Date, &QDateEdit::dateChanged, this, &ObsListWizard::slotObjectCountDirty);
0078     connect(olw->timeTo, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty);
0079     connect(olw->timeFrom, &QTimeEdit::timeChanged, this, &ObsListWizard::slotObjectCountDirty);
0080     connect(olw->minAlt, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
0081             &ObsListWizard::slotObjectCountDirty);
0082     connect(olw->maxAlt, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
0083             &ObsListWizard::slotObjectCountDirty);
0084     olw->coverage->setValue(Options::obsListCoverage());
0085     connect(olw->coverage, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), [&](double value)
0086     {
0087         Options::setObsListCoverage(value);
0088         slotObjectCountDirty();
0089     });
0090     // -3- Magnitude
0091     connect(olw->SelectByMagnitude, SIGNAL(clicked()), this, SLOT(slotToggleMagWidgets()));
0092     connect(olw->Mag, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
0093             &ObsListWizard::slotObjectCountDirty);
0094     connect(olw->IncludeNoMag, &QPushButton::clicked, this, &ObsListWizard::slotObjectCountDirty);
0095 
0096 
0097 
0098     geo = KStarsData::Instance()->geo();
0099     olw->LocationButton->setText(geo->fullName());
0100     olw->Date->setDate(KStarsDateTime::currentDateTime().date());
0101     olw->timeFrom->setTime(QTime(18, 0));
0102     olw->timeTo->setTime(QTime(23, 59));
0103 
0104     initialize();
0105 }
0106 
0107 void ObsListWizard::initialize()
0108 {
0109     KStarsData *data = KStarsData::Instance();
0110     olw->olwStack->setCurrentIndex(0);
0111 
0112     //Populate the list of constellations
0113     foreach (SkyObject *p, data->skyComposite()->constellationNames())
0114         olw->ConstellationList->addItem(p->name());
0115 
0116     //unSelect all object types
0117     olw->TypeList->clearSelection();
0118 
0119     olw->Mag->setMinimum(-5.0);
0120     olw->Mag->setMaximum(20.0);
0121     olw->Mag->setValue(6.0);
0122 
0123     olw->RA->setUnits(dmsBox::HOURS);
0124     olw->RAMin->setUnits(dmsBox::HOURS);
0125     olw->RAMax->setUnits(dmsBox::HOURS);
0126 
0127     //Initialize object counts
0128     ObjectCount   = 0; //number of objects in observing list    
0129 
0130     StarCount     = data->skyComposite()->stars().size();     //total number of stars
0131     PlanetCount   = std::size(sun_moon_planets_list);         //Sun, Moon, 7 planets (excluding Earth and Pluto)
0132     AsteroidCount = data->skyComposite()->asteroids().size(); //total number of asteroids
0133     CometCount    = data->skyComposite()->comets().size();    //total number of comets
0134     //DeepSkyObjects
0135     OpenClusterCount = 0;
0136     GlobClusterCount = 0;
0137     GasNebCount      = 0;
0138     PlanNebCount     = 0;
0139     GalaxyCount      = 0;
0140 
0141     CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() };
0142 
0143     const auto &stats{ manager.get_master_statistics() };
0144     if (!stats.first)
0145         return;
0146 
0147     for (const auto &element : stats.second.object_counts)
0148     {
0149         auto cnt = element.second;
0150         switch (element.first)
0151         {
0152             case SkyObject::GALAXY:
0153                 GalaxyCount += cnt;
0154                 break;
0155             case SkyObject::STAR:// Excluded case SkyObject::CATALOG_STAR later on in filter as well.
0156                 break;
0157             case SkyObject::OPEN_CLUSTER:
0158                 OpenClusterCount += cnt;
0159                 break;
0160             case SkyObject::GLOBULAR_CLUSTER:
0161                 GlobClusterCount += cnt;
0162                 break;
0163             case SkyObject::GASEOUS_NEBULA:
0164             case SkyObject::SUPERNOVA_REMNANT:
0165                 GasNebCount += cnt;
0166                 break;
0167             case SkyObject::PLANETARY_NEBULA:
0168                 PlanNebCount += cnt;
0169                 break;
0170             default:
0171                 break;
0172         }
0173     }
0174 }
0175 
0176 bool ObsListWizard::isItemSelected(const QString &name, QListWidget *listWidget)
0177 {
0178     foreach(QListWidgetItem *item, listWidget->selectedItems())
0179     {
0180         if (item->text().compare(name, Qt::CaseInsensitive) == 0)
0181             return true;
0182     }
0183     return false;
0184 }
0185 
0186 void ObsListWizard::setItemSelected(const QString &name, QListWidget *listWidget, bool value)
0187 {
0188     QList<QListWidgetItem *> items = listWidget->findItems(name, Qt::MatchContains);
0189     if (items.size())
0190         items[0]->setSelected(value);
0191 }
0192 
0193 //Advance to the next page in the stack.  However, on page 2 the user
0194 //selects what regional filter they want to use, and this determines
0195 //what the page following page 2 should be:
0196 // + Constellation(s):   3
0197 // + Rectangular region: 4
0198 // + Circular region:    5
0199 // + No region selected (a.k.a. ALL_OVER_THE_SKY): 6
0200 //
0201 //Also, if the current page index is 3, 4 or 5, then the next page should be 6.
0202 //
0203 //NOTE: the page indexes are hard-coded here, which isn't ideal.  However,
0204 //there's no easy way to access the pointers of widgets in the stack
0205 //if you didn't save them at the start.
0206 // The order is: MAIN, OBJECT_TYPE, REGION_TYPE, CONSTELLATION, RECTANGULAR, CIRCULAR, DATE, MAGNITUDE
0207 //                  0,           1,           2,             3,           4,        5,    6,         7
0208 
0209 void ObsListWizard::slotNextPage()
0210 {
0211     int NextPage = olw->olwStack->currentIndex() + 1;
0212 
0213     if (olw->olwStack->currentIndex() == PAGE_ID_REGION_TYPE)
0214     {
0215         //On the Region select page.  Determine what the next page index should be.
0216         //No need to handle BY_CONSTELLATION, it's already currentIndex + 1.
0217         if (isItemSelected(i18n(IN_A_RECTANGULAR_REGION), olw->RegionList))
0218             NextPage = PAGE_ID_RECTANGULAR;
0219         else if (isItemSelected(i18n(IN_A_CIRCULAR_REGION), olw->RegionList))
0220             NextPage = PAGE_ID_CIRCULAR;
0221         else if (isItemSelected(i18n(ALL_OVER_THE_SKY), olw->RegionList))
0222             NextPage = PAGE_ID_DATE;
0223     }
0224 
0225     if ( olw->olwStack->currentIndex() == PAGE_ID_CONSTELLATION ||
0226          olw->olwStack->currentIndex() == PAGE_ID_RECTANGULAR)
0227         NextPage = PAGE_ID_DATE;
0228 
0229     olw->olwStack->setCurrentIndex(NextPage);
0230 
0231     if (olw->olwStack->currentIndex() == olw->olwStack->count() - 1)
0232         nextB->setEnabled(false);
0233 
0234     backB->setEnabled(true);
0235 }
0236 
0237 //Advance to the previous page in the stack.  However, because the
0238 //path through the wizard branches depending on the user's choice of
0239 //Region filter, the previous page is not always currentPage-1.
0240 //Specifically, if the current page index is 4, 5, or 6, then the Previous
0241 //page index should be 2 rather than currentIndex-1.
0242 void ObsListWizard::slotPrevPage()
0243 {
0244     int PrevPage = olw->olwStack->currentIndex() - 1;
0245 
0246     if ( olw->olwStack->currentIndex() == PAGE_ID_RECTANGULAR ||
0247          olw->olwStack->currentIndex() == PAGE_ID_CIRCULAR ||
0248          olw->olwStack->currentIndex() == PAGE_ID_DATE)
0249         PrevPage = PAGE_ID_REGION_TYPE;
0250 
0251     olw->olwStack->setCurrentIndex(PrevPage);
0252 
0253     if (olw->olwStack->currentIndex() == 0)
0254         backB->setEnabled(false);
0255 
0256     nextB->setEnabled(true);
0257 }
0258 
0259 void ObsListWizard::slotAllButton()
0260 {
0261     for (int i = 0; i < olw->TypeList->count(); ++i)
0262         olw->TypeList->item(i)->setSelected(true);
0263 }
0264 
0265 void ObsListWizard::slotNoneButton()
0266 {
0267     olw->TypeList->clearSelection();
0268 }
0269 
0270 void ObsListWizard::slotDeepSkyButton()
0271 {
0272     olw->TypeList->clearSelection();
0273     setItemSelected(i18n("Open clusters"), olw->TypeList, true);
0274     setItemSelected(i18n("Globular clusters"), olw->TypeList, true);
0275     setItemSelected(i18n("Gaseous nebulae"), olw->TypeList, true);
0276     setItemSelected(i18n("Planetary nebulae"), olw->TypeList, true);
0277     setItemSelected(i18n("Galaxies"), olw->TypeList, true);
0278 }
0279 
0280 void ObsListWizard::slotSolarSystemButton()
0281 {
0282     olw->TypeList->clearSelection();
0283     setItemSelected(i18n("Sun, moon, planets"), olw->TypeList, true);
0284     setItemSelected(i18n("Comets"), olw->TypeList, true);
0285     setItemSelected(i18n("Asteroids"), olw->TypeList, true);
0286 }
0287 
0288 void ObsListWizard::slotChangeLocation() {
0289     QPointer<LocationDialog> ld = new LocationDialog(this);
0290 
0291     if (ld->exec() == QDialog::Accepted)
0292     {
0293         //set geographic location
0294         if (ld->selectedCity())
0295         {
0296             geo = ld->selectedCity();
0297             olw->LocationButton->setText(geo->fullName());
0298         }
0299     }
0300     delete ld;
0301 }
0302 
0303 void ObsListWizard::slotToggleDateWidgets()
0304 {
0305     bool needDate = olw->SelectByDate->isChecked();
0306     olw->Date->setEnabled(needDate);
0307     olw->LocationButton->setEnabled(needDate);
0308     olw->timeTo->setEnabled(needDate);
0309     olw->timeFrom->setEnabled(needDate);
0310     olw->minAlt->setEnabled(needDate);
0311     olw->maxAlt->setEnabled(needDate);
0312 
0313     slotObjectCountDirty();
0314 }
0315 
0316 void ObsListWizard::slotToggleMagWidgets()
0317 {
0318     bool needMagnitude = olw->SelectByMagnitude->isChecked();
0319     olw->Mag->setEnabled(needMagnitude);
0320     olw->IncludeNoMag->setEnabled(needMagnitude);
0321 
0322     slotObjectCountDirty();
0323 }
0324 
0325 /** Parse the user-entered info to a complete set of parameters if possible. */
0326 void ObsListWizard::slotParseRegion()
0327 {
0328     if ( isItemSelected(i18n(IN_A_RECTANGULAR_REGION), olw->RegionList) &&
0329             (sender()->objectName() == "RAMin" || sender()->objectName() == "RAMax" ||
0330             sender()->objectName() == "DecMin" || sender()->objectName() == "DecMax"))
0331     {
0332         if (!olw->RAMin->isEmpty() && !olw->RAMax->isEmpty() &&
0333             !olw->DecMin->isEmpty() && !olw->DecMax->isEmpty())
0334         {
0335             bool rectOk = false;
0336             xRect1      = 0.0;
0337             xRect2      = 0.0;
0338             yRect1      = 0.0;
0339             yRect2      = 0.0;
0340 
0341             xRect1 = olw->RAMin->createDms(&rectOk).Hours();
0342             if (rectOk)
0343                 xRect2 = olw->RAMax->createDms(&rectOk).Hours();
0344             if (rectOk)
0345                 yRect1 = olw->DecMin->createDms(&rectOk).Degrees();
0346             if (rectOk)
0347                 yRect2 = olw->DecMax->createDms(&rectOk).Degrees();
0348             if (xRect2 == 0.0)
0349                 xRect2 = 24.0;
0350 
0351             if (!rectOk)
0352             {
0353                 qWarning() << i18n( "Illegal rectangle specified, no region selection possible." ) ;
0354                 return;
0355             }
0356 
0357             if (yRect1 > yRect2)
0358                 std::swap(yRect1, yRect2);
0359 
0360             //If xRect1 > xRect2, we may need to swap the two values, or subtract 24h from xRect1.
0361             if (xRect1 > xRect2)
0362             {
0363                 if (xRect1 - xRect2 > 12.0) //the user probably wants a region that straddles 0h
0364                 {
0365                     xRect1 -= 24.0;
0366                 }
0367                 else //the user probably wants xRect2 to be the lower limit
0368                 {
0369                     double temp = xRect2;
0370                     xRect2      = xRect1;
0371                     xRect1      = temp;
0372                 }
0373             }
0374             slotObjectCountDirty();
0375         }
0376         return; // only one selection possible.
0377     }    
0378 
0379     if ( isItemSelected(i18n(IN_A_CIRCULAR_REGION), olw->RegionList) &&
0380               (!olw->RA->isEmpty() && !olw->Dec->isEmpty() && !olw->Radius->isEmpty()))
0381     {
0382         bool circOk1;
0383         bool circOk2;
0384         bool circOk3;
0385         dms ra = olw->RA->createDms(&circOk1);
0386         dms dc = olw->Dec->createDms(&circOk2);
0387         pCirc.set(ra, dc);
0388         rCirc = olw->Radius->createDms(&circOk3).Degrees();
0389         if (circOk1  && circOk2 && circOk3)
0390             slotObjectCountDirty();
0391         else
0392             qWarning() << i18n("Illegal circle specified, no region selection possible.");
0393     }
0394 }
0395 
0396 void ObsListWizard::slotObjectCountDirty()
0397 {
0398     olw->updateButton->setDisabled(false);
0399 }
0400 
0401 void ObsListWizard::slotUpdateObjectCount()
0402 {
0403     QApplication::setOverrideCursor(Qt::WaitCursor);
0404     ObjectCount = 0;
0405     if (isItemSelected(i18n("Stars"), olw->TypeList))
0406         ObjectCount += StarCount;
0407     if (isItemSelected(i18n("Sun, moon, planets"), olw->TypeList))
0408         ObjectCount += PlanetCount;
0409     if (isItemSelected(i18n("Comets"), olw->TypeList))
0410         ObjectCount += CometCount;
0411     if (isItemSelected(i18n("Asteroids"), olw->TypeList))
0412         ObjectCount += AsteroidCount;
0413     if (isItemSelected(i18n("Galaxies"), olw->TypeList))
0414         ObjectCount += GalaxyCount;
0415     if (isItemSelected(i18n("Open clusters"), olw->TypeList))
0416         ObjectCount += OpenClusterCount;
0417     if (isItemSelected(i18n("Globular clusters"), olw->TypeList))
0418         ObjectCount += GlobClusterCount;
0419     if (isItemSelected(i18n("Gaseous nebulae"), olw->TypeList))
0420         ObjectCount += GasNebCount;
0421     if (isItemSelected(i18n("Planetary nebulae"), olw->TypeList))
0422         ObjectCount += PlanNebCount;
0423 
0424     applyFilters(false); //false = only adjust counts, do not build list
0425     QApplication::restoreOverrideCursor();
0426     olw->updateButton->setDisabled(true);
0427 }
0428 
0429 void ObsListWizard::applyFilters(bool doBuildList)
0430 {
0431     KStarsData *data = KStarsData::Instance();
0432     if (doBuildList)
0433         obsList().clear();
0434 
0435     //We don't need to call applyRegionFilter() if no region filter is selected.
0436     bool needRegion = !isItemSelected(i18n(ALL_OVER_THE_SKY), olw->RegionList);
0437 
0438     double maglimit = 100.;
0439     bool needMagnitude = olw->SelectByMagnitude->isChecked();
0440     bool needNoMagnitude = true;
0441     if (needMagnitude)
0442     {
0443         maglimit        = olw->Mag->value();
0444         needNoMagnitude = olw->IncludeNoMag->isChecked();
0445     }
0446     bool needDate = olw->SelectByDate->isChecked();
0447     FilterParameters filterParameters = { maglimit, needMagnitude, needNoMagnitude, needRegion, needDate, doBuildList};
0448 
0449     if (isItemSelected(i18n("Stars"), olw->TypeList))
0450     {
0451         const QList<SkyObject *> &starList = data->skyComposite()->stars();
0452         int starIndex(starList.size());
0453         qDebug() << Q_FUNC_INFO << QString("starIndex: [%1] and maglimit: [%2]").arg(starIndex).arg(maglimit);
0454         for (int i = 0; i < starIndex; ++i)
0455         {
0456             SkyObject *obj = (SkyObject *)(starList[i]);
0457             applyMagnitudeAndRegionAndObservableFilter(obj, filterParameters);
0458         }
0459     }
0460 
0461     if (isItemSelected(i18n("Sun, moon, planets"), olw->TypeList))
0462     {
0463         for (auto name : sun_moon_planets_list)
0464         {
0465             QString qStringName = i18n(name);
0466             SkyObject *obj      = data->skyComposite()->findByName(qStringName);
0467             if (obj == nullptr)
0468             {
0469                 qWarning() << Q_FUNC_INFO
0470                            << QString("Failed to find element by name: [%1]").arg(name);
0471                 ObjectCount--;
0472                 continue;
0473             }
0474             applyMagnitudeAndRegionAndObservableFilter(obj, filterParameters);
0475         }
0476     }
0477 
0478     bool dso = (isItemSelected(i18n("Open clusters"), olw->TypeList) ||
0479                 isItemSelected(i18n("Globular clusters"), olw->TypeList) ||
0480                 isItemSelected(i18n("Gaseous nebulae"), olw->TypeList) ||
0481                 isItemSelected(i18n("Planetary nebulae"), olw->TypeList) ||
0482                 isItemSelected(i18n("Galaxies"), olw->TypeList));
0483 
0484     if (dso)
0485     {
0486         CatalogsDB::DBManager manager{ CatalogsDB::dso_db_path() };
0487         CatalogsDB::CatalogObjectList cObjectList = manager.get_objects_all(); // JFD: Can't skip faint objects because counting down
0488 
0489         for (auto &o : cObjectList)
0490         {
0491             //Skip unselected object types
0492             bool typeSelected = false;
0493             switch (o.type())
0494             {
0495                 case SkyObject::OPEN_CLUSTER:
0496                     if (isItemSelected(i18n("Open clusters"), olw->TypeList))
0497                         typeSelected = true;
0498                     break;
0499 
0500                 case SkyObject::GLOBULAR_CLUSTER:
0501                     if (isItemSelected(i18n("Globular clusters"), olw->TypeList))
0502                         typeSelected = true;
0503                     break;
0504 
0505                 case SkyObject::GASEOUS_NEBULA:
0506                 case SkyObject::SUPERNOVA_REMNANT:
0507                     if (isItemSelected(i18n("Gaseous nebulae"), olw->TypeList))
0508                         typeSelected = true;
0509                     break;
0510 
0511                 case SkyObject::PLANETARY_NEBULA:
0512                     if (isItemSelected(i18n("Planetary nebulae"), olw->TypeList))
0513                         typeSelected = true;
0514                     break;
0515                 case SkyObject::GALAXY:
0516                     if (isItemSelected(i18n("Galaxies"), olw->TypeList))
0517                         typeSelected = true;
0518                     break;
0519             }
0520             if (!typeSelected)
0521                 continue;
0522 
0523             auto *obj = &o;
0524             obj       = &data->skyComposite()->catalogsComponent()->insertStaticObject(o);
0525             applyMagnitudeAndRegionAndObservableFilter(obj, filterParameters);
0526         } // end for objects
0527     }
0528 
0529     if (isItemSelected(i18n("Comets"), olw->TypeList))
0530     {
0531         foreach (SkyObject *o, data->skyComposite()->comets())
0532             applyMagnitudeAndRegionAndObservableFilter(o, filterParameters);
0533     }
0534 
0535     if (isItemSelected(i18n("Asteroids"), olw->TypeList))
0536     {
0537         foreach (SkyObject *o, data->skyComposite()->asteroids())
0538             applyMagnitudeAndRegionAndObservableFilter(o, filterParameters);
0539     }
0540 
0541     //Update the object count label
0542     if (doBuildList)
0543         ObjectCount = obsList().size();
0544 
0545     olw->CountLabel->setText(i18np("Your observing list currently has 1 object",
0546                                    "Your observing list currently has %1 objects", ObjectCount));
0547 }
0548 
0549 bool ObsListWizard::applyMagnitudeAndRegionAndObservableFilter(SkyObject *o, FilterParameters filterParameters )
0550 {
0551     bool needMagnitude = filterParameters.needMagnitude;
0552     bool needRegion = filterParameters.needRegion;
0553     bool needDate = filterParameters.needDate;
0554     bool doBuildList = filterParameters.doBuildList;
0555 
0556     bool filterPass = true;
0557 
0558     if (filterPass && needMagnitude)
0559         filterPass = applyMagnitudeFilter(o, filterParameters);
0560     if (filterPass && (needRegion || doBuildList))
0561         // Call for adding to obsList even if region filtering is not needed.
0562         filterPass = applyRegionFilter(o, doBuildList);
0563     if (filterPass && needDate )
0564         filterPass = applyObservableFilter(o, doBuildList);
0565     return filterPass;
0566 }
0567 
0568 bool ObsListWizard::applyMagnitudeFilter(SkyObject *o, FilterParameters filterParameters)
0569 {
0570     bool needMagnitude   = filterParameters.needMagnitude;
0571     bool needNoMagnitude = filterParameters.needNoMagnitude;
0572     if (needMagnitude && ((std::isnan(o->mag()) && (!needNoMagnitude)) ||
0573                           (o->mag() > filterParameters.maglimit)))
0574     {
0575         ObjectCount--;
0576         return false;
0577     }
0578     return true;
0579 }
0580 
0581 /** This routine will add the object to the obsList if doBuildList.
0582  *  NB: Call this routine even if you don't need region filtering!*/
0583 bool ObsListWizard::applyRegionFilter(SkyObject *o, bool doBuildList)
0584 {
0585     //select by constellation
0586     if (isItemSelected(i18n(BY_CONSTELLATION), olw->RegionList))
0587     {
0588         QString constellationName = KStarsData::Instance()
0589                                         ->skyComposite()
0590                                         ->constellationBoundary()
0591                                         ->constellationName(o);
0592 
0593         if (isItemSelected(constellationName, olw->ConstellationList))
0594         {
0595             if (doBuildList)
0596                 obsList().append(o);
0597             return true;
0598         }
0599         ObjectCount--;
0600         return false;
0601     }
0602 
0603     //select by rectangular region
0604     else if (isItemSelected(i18n(IN_A_RECTANGULAR_REGION), olw->RegionList))
0605     {
0606         double ra      = o->ra().Hours();
0607         double dec     = o->dec().Degrees();
0608         bool addObject = false;
0609         if (dec >= yRect1 && dec <= yRect2)
0610         {
0611             if (xRect1 < 0.0)
0612             {
0613                 addObject = ra >= xRect1 + 24.0 || ra <= xRect2;
0614             }
0615             else
0616             {
0617                 addObject = ra >= xRect1 && ra <= xRect2;
0618             }
0619         }
0620 
0621         if (addObject)
0622         {
0623             if (doBuildList)
0624                 obsList().append(o);
0625             return true;
0626         }
0627         ObjectCount--;
0628         return false;
0629     }
0630 
0631     //select by circular region
0632     //make sure circ region data are valid
0633     else if (isItemSelected(i18n(IN_A_CIRCULAR_REGION), olw->RegionList))
0634     {
0635         if (o->angularDistanceTo(&pCirc).Degrees() < rCirc)
0636         {
0637             if (doBuildList)
0638                 obsList().append(o);
0639             return true;
0640         }
0641         ObjectCount--;
0642         return false;
0643     }
0644 
0645     //No region filter, just add the object
0646     else if (doBuildList)
0647     {
0648         obsList().append(o);
0649     }
0650 
0651     return true;
0652 }
0653 
0654 /** This routine will remove any item from the obsList if doBuildList is set which means
0655  *  it was added before in the previous filter.
0656  */
0657 bool ObsListWizard::applyObservableFilter(SkyObject *o, bool doBuildList)
0658 {
0659     SkyPoint p = *o;
0660 
0661     //Check altitude of object every hour from 18:00 to midnight
0662     //If it's ever above 15 degrees, flag it as visible
0663     KStarsDateTime Evening(olw->Date->date(), QTime(18, 0, 0), Qt::LocalTime);
0664     KStarsDateTime Midnight(olw->Date->date().addDays(1), QTime(0, 0, 0), Qt::LocalTime);
0665     double minAlt = 15, maxAlt = 90;
0666 
0667     // Or use user-selected values, if they're valid
0668     if (olw->timeFrom->time().isValid() && olw->timeTo->time().isValid())
0669     {
0670         Evening.setTime(olw->timeFrom->time());
0671         Midnight.setTime(olw->timeTo->time());
0672 
0673         // If time from < timeTo (e.g. 06:00 PM to 9:00 PM)
0674         // then we stay on the same day.
0675         if (olw->timeFrom->time() < olw->timeTo->time())
0676         {
0677             Midnight.setDate(olw->Date->date());
0678         }
0679         // Otherwise we advance by one day
0680         else
0681         {
0682             Midnight.setDate(olw->Date->date().addDays(1));
0683         }
0684     }
0685 
0686     minAlt = olw->minAlt->value();
0687     maxAlt = olw->maxAlt->value();
0688 
0689     // This is the "relaxed" search mode
0690     // where if the object obeys the restrictions in 50% of the time of the range
0691     // then it qualifies as "visible"
0692     double totalCount = 0, visibleCount = 0;
0693     for (KStarsDateTime t = Evening; t < Midnight; t = t.addSecs(3600.0))
0694     {
0695         dms LST = geo->GSTtoLST(t.gst());
0696         p.EquatorialToHorizontal(&LST, geo->lat());
0697         totalCount++;
0698         if (p.alt().Degrees() >= minAlt && p.alt().Degrees() <= maxAlt)
0699             visibleCount++;
0700     }
0701 
0702     // If the object is within the min/max alt at least coverage % of the time range
0703     // then consider it visible
0704     if (visibleCount / totalCount >= olw->coverage->value() / 100.0)
0705         return true;
0706 
0707     ObjectCount--;
0708     if (doBuildList)
0709         obsList().takeAt(obsList().indexOf(o));
0710 
0711     return false;
0712 }